Skip to content
RustPluginGenerator

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.

Signature
void OnAddVendingOffer(VendingMachine vendingMachine, ProtoBuf.VendingMachine.SellOrder sellOrder)

Parameters

Hook parameters
ParameterTypeDescription
vendingMachineVendingMachineThe 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.
sellOrderProtoBuf.VendingMachine.SellOrderThe 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

VendingSkinGuard.cs
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.Count against 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.sellOrders yourself is the only way to reject it, and that removal must happen after AddSellOrder finishes.

  • 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 NextTick to strip it.

  • Duplicate network updates

    The game calls RefreshSellOrderStockLevel, UpdateMapMarker and SendNetworkUpdate after 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.

Prompt: Enforce a skin whitelist using the OnAddVendingOffer hook, with a config file and a permission
Generate this plugin

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