OnAddVendingOffer
OnAddVendingOffer is called inside VendingMachine.AddSellOrder just before the new sell order is appended to the machine's sellOrders list. It fires for every offer added, whether through the in-game vending admin UI or a plugin call.
void OnAddVendingOffer(VendingMachine vendingMachine, ProtoBuf.VendingMachine.SellOrder sellOrder)Parameters
| Parameter | Type | Description |
|---|---|---|
vendingMachine | VendingMachine | The VendingMachine entity the order is being added to. Use it to read the owner, position, shop name or existing sellOrders before the new offer is appended. |
sellOrder | ProtoBuf.VendingMachine.SellOrder | The ProtoBuf.VendingMachine.SellOrder being appended. It already holds currencyAmountPerItem, itemToSellId, currencyId, the BP flags and skin ids, so you can inspect or adjust it before the game stores it. |
Return value
The hook's return value is ignored; the injection uses the continue behavior. Returning null or any value does not prevent the sell order from being added. To undo an offer, modify or remove it after the fact through the vendingMachine reference.
How it works
The hook fires inside VendingMachine.AddSellOrder, directly after the game fills in the sell order's fields (currencyAmountPerItem, currencyIsBP, itemToSellIsBP, sellSkinId, costSkinId) and immediately before the order is pushed into sellOrders.sellOrders. After the hook returns, the machine clears the pending order, refreshes stock levels, updates its map marker and sends a network update.
Use sellOrder for validation: check itemToSellId, currencyId, currencyAmountPerItem, the BP flags or the skin ids and decide whether the shop should be allowed to list that trade. vendingMachine gives you OwnerID, shopName, transform.position and the existing sellOrders so you can enforce per-machine limits or ownership rules.
There is no object-returning overload: the hook cannot block the addition by returning a value. To stop an offer you have to remove it afterwards (for example in the same method by scheduling a removal, or via your own command that mutates the machine). Oxide dispatches by parameter type, so declaring sellOrder as a more specific type would skip calls where the argument is not that type; keeping the exact Protobuf type is the reliable choice. The hook also fires for plugin-driven AddSellOrder calls, so it covers automated shop restocking.
Example plugin: Restrict vending machine skins to a whitelist
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Vending Skin Guard", "RustPluginGenerator", "1.0.0")]
[Description("Removes vending machine offers that sell items with non-whitelisted skins")]
public class VendingSkinGuard : RustPlugin
{
private const string PermBypass = "vendingskinguard.bypass";
private PluginConfig config;
private HashSet<ulong> allowedSkins;
private class PluginConfig
{
[JsonProperty("Allowed skins (0 is always allowed)")]
public List<ulong> AllowedSkins = new List<ulong> { 0 };
}
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();
allowedSkins = new HashSet<ulong>();
foreach (ulong skin in config.AllowedSkins) allowedSkins.Add(skin);
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["NotAllowed"] = "That skin is not allowed in vending machines on this server.",
["BypassOnly"] = "Only players with the vending bypass permission may sell this skin."
}, this);
}
private void Init()
{
permission.RegisterPermission(PermBypass, this);
}
private string Msg(string key, string userId = null) => lang.GetMessage(key, this, userId);
private void OnAddVendingOffer(VendingMachine vendingMachine, ProtoBuf.VendingMachine.SellOrder sellOrder)
{
if (vendingMachine == null || sellOrder == null) return;
if (sellOrder.sellSkinId == 0 || allowedSkins.Contains(sellOrder.sellSkinId)) return;
BasePlayer owner = BasePlayer.FindByID(vendingMachine.OwnerID);
if (owner != null && permission.UserHasPermission(owner.UserIDString, PermBypass)) return;
NextTick(() => RemoveOrder(vendingMachine, sellOrder));
}
private void RemoveOrder(VendingMachine vendingMachine, ProtoBuf.VendingMachine.SellOrder sellOrder)
{
if (vendingMachine == null || vendingMachine.IsDestroyed || sellOrder == null) return;
List<ProtoBuf.VendingMachine.SellOrder> orders = vendingMachine.sellOrders?.sellOrders;
if (orders == null || !orders.Remove(sellOrder)) return;
ItemDefinition definition = ItemManager.FindItemDefinition(sellOrder.itemToSellID);
if (definition != null) vendingMachine.RefreshSellOrderStockLevel(definition);
vendingMachine.UpdateMapMarker();
vendingMachine.SendNetworkUpdate();
BasePlayer owner = BasePlayer.FindByID(vendingMachine.OwnerID);
if (owner != null && owner.IsConnected) owner.ChatMessage(Msg("NotAllowed", owner.UserIDString));
}
}
}The plugin defines a configurable set of allowed skin ids for items sold in vending machines. On OnAddVendingOffer it reads sellOrder.sellSkinId; when the skin is not whitelisted and not zero, it schedules a NextTick removal that strips the just-added order from vendingMachine.sellOrders.sellOrders, then refreshes stock, the map marker and the network state so the client sees the corrected shop. The affected owner receives a localized chat message so they know why the offer vanished. LoadConfig rebuilds the allowed-skin HashSet<ulong> from config, and everything is validated against null references.
Common use cases
Enforce a skin whitelist
Reject offers that sell items with skins you have not approved, keeping the shop list consistent with your server's trading rules.
Limit offers per machine
Compare
vendingMachine.sellOrders.sellOrders.Countagainst a configured maximum and remove the new order when the shop is already full.Log or audit shop changes
Record the owner, item, currency and price into oxide/data so admins can review what is being listed on player shops.
Pitfalls
The hook cannot cancel the offer
The return value is ignored and the order is appended right after the hook. Removing it from
sellOrders.sellOrdersyourself is the only way to reject it, and that removal must happen afterAddSellOrderfinishes.Mutating the list during the call is unsafe
The order is added after the hook returns, so removing it inside the hook does nothing. Use a deferred action such as
NextTickto strip it.Duplicate network updates
The game calls
RefreshSellOrderStockLevel,UpdateMapMarkerandSendNetworkUpdateafter the hook. When you remove an order yourself, repeat those calls once so clients do not keep showing the rejected offer.
Build a plugin with OnAddVendingOffer
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 OnAddVendingOffer
Generate