OnItemCraftCancelled
OnItemCraftCancelled is called when a queued craft task is cancelled, after the game marks the task as cancelled and before the ingredients taken for it are refunded to the player.
void OnItemCraftCancelled(ItemCraftTask itemCraftTask, ItemCrafter itemCrafter)Parameters
| Parameter | Type | Description |
|---|---|---|
itemCraftTask | ItemCraftTask | The craft task that is being cancelled. It holds taskUID, the blueprint target (blueprintTarget), the amount still being crafted (amount), the ingredients already taken (takenItems), the owner and cancelled/crafting flags. The cancelled flag is already set to true when the hook runs. |
itemCrafter | ItemCrafter | The ItemCrafter component that owned the task, normally the player's crafting queue. Use itemCrafter.owner (a BasePlayer) to identify who cancelled. |
Return value
The return value is ignored. The original method continues and returns its own bool result (true when the owner is null, otherwise the normal refund path), so returning a value from OnItemCraftCancelled changes nothing.
How it works
The hook fires from ItemCrafter.CancelTask, which runs when a player removes a task from their crafting queue (closing the craft menu on a task, clicking cancel, or the server cancelling it on their behalf). The engine sets itemCraftTask.cancelled = true first, then calls the hook, then notifies the client with note.craft_done and starts returning the ingredients stored in itemCraftTask.takenItems to the player's inventory.
Because the hook runs between the flag being set and the refund, it is the right place to observe or account for cancellations: you can read itemCraftTask.blueprintTarget, itemCraftTask.amount and itemCraftTask.takenItems to know what was queued, and itemCrafter.owner (a BasePlayer) to know who cancelled. Note that ItemCrafter has no owner field on every path, so null-check it; the engine itself returns early when the owner is null, so the hook will not fire for ownerless crafters in that branch.
Actually cancelling a task yourself is done through ItemCrafter.CancelTask(taskUID, ...), not through this hook. Declaring the parameter types more specifically does not narrow anything here because both arguments are always the same concrete types.
Example plugin: Track cancelled crafts in a per-player log
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Craft Cancel Logger", "RustPluginGenerator", "1.0.0")]
[Description("Counts cancelled craft tasks per player and reports them in chat")]
public class CraftCancelLogger : RustPlugin
{
private const string PermStats = "craftcancellogger.stats";
private const string DataFile = "CraftCancelLogger";
private PluginConfig config;
private StoredData data;
private class PluginConfig
{
[JsonProperty("Announce cancellations in chat")]
public bool AnnounceInChat { get; set; } = true;
[JsonProperty("Save counters periodically (seconds)")]
public float SaveInterval { get; set; } = 300f;
}
private class StoredData
{
[JsonProperty("Cancelled crafts per player")]
public Dictionary<string, int> Cancelled { get; set; } = new Dictionary<string, int>();
}
private Timer saveTimer;
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new Exception();
}
catch
{
PrintWarning("Configuration file is invalid, using defaults");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["Cancelled"] = "Cancelled craft: {0} x{1} (your total: {2})",
["Stats"] = "You have cancelled {0} craft task(s).",
["NoPermission"] = "You do not have permission to use this command."
}, this);
}
private string Msg(string key, string userId = null, params object[] args)
=> string.Format(lang.GetMessage(key, this, userId), args);
private void Init()
{
permission.RegisterPermission(PermStats, this);
data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(DataFile) ?? new StoredData();
}
private void OnServerInitialized()
{
saveTimer = timer.Every(Mathf.Max(30f, config.SaveInterval), SaveData);
}
private void Unload()
{
saveTimer?.Destroy();
SaveData();
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject(DataFile, data);
}
private void OnItemCraftCancelled(ItemCraftTask itemCraftTask, ItemCrafter itemCrafter)
{
if (itemCraftTask == null || itemCrafter == null) return;
BasePlayer player = itemCrafter.owner;
if (player == null || player.IsNpc) return;
string userId = player.UserIDString;
int count;
data.Cancelled.TryGetValue(userId, out count);
count++;
data.Cancelled[userId] = count;
if (!config.AnnounceInChat) return;
ItemDefinition blueprint = itemCraftTask.blueprint != null ? itemCraftTask.blueprint.targetItem : null;
string itemName = blueprint != null ? blueprint.displayName.english : "unknown";
player.ChatMessage(Msg("Cancelled", userId, itemName, itemCraftTask.amount, count));
}
[ChatCommand("craftcancels")]
private void CmdCraftCancels(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermStats))
{
player.ChatMessage(Msg("NoPermission", player.UserIDString));
return;
}
int count;
data.Cancelled.TryGetValue(player.UserIDString, out count);
player.ChatMessage(Msg("Stats", player.UserIDString, count));
}
}
}The plugin keeps a rolling count of cancelled crafts per player and reports each cancellation to the player in chat, which helps admins spot players who repeatedly queue and cancel large crafts.
OnItemCraftCancelled reads the task's blueprint, remaining amount and the player from itemCrafter.owner. It resolves the display name from itemCraftTask.blueprint.targetItem.displayName.english, increments a per-player counter in memory, and formats the message through the language file. The craftcancels chat command lets a player with the craftcancellogger.stats permission check their own total, and the counter dictionary is written to oxide/data periodically via a timer and again on unload.
Common use cases
Refund or charge a fee on cancel
Because the hook runs before the taken items are returned, you can add or remove items from
itemCraftTask.takenItemsif your server wants a cancellation fee. Keep it simple and test on a staging server.Log queue abuse
Count cancellations per player to spot patterns where a player queues expensive crafts and cancels them repeatedly, for example to exploit a fast-track or queue-slot plugin.
Show a cancellation notice
Send the player a chat line naming the item and amount they just removed from the queue, so they know the action registered when the UI is busy.
Pitfalls
Owner can be null
itemCrafter.owneris a BasePlayer component reference that can be null (the engine itself returns before the hook in that case), so guard it before touching UserIDString or calling ChatMessage.The task is already flagged cancelled
itemCraftTask.cancelledis set to true before the hook runs. Do not use it to decide whether to cancel; if you need to prevent a cancellation, this hook cannot do it, it only observes.Do not affect the refund silently
The engine iterates
itemCraftTask.takenItemsright after the hook, so mutating that list changes what the player gets back. Only do it if the server design expects it, and never remove the list itself.
Build a plugin with OnItemCraftCancelled
Describe what should happen and get a compiled plugin that uses this hook.
Edit the prompt on the next screen. 2 free plugins a month, no card.
Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.
Build a plugin with OnItemCraftCancelled
Generate