OnCuiDraggableDrop
OnCuiDraggableDrop is called when a player releases a CuiDraggable CUI element onto another element on their client. It fires from CommunityEntity.Hook_DropRPC after the client sends the drop RPC to the server.
void OnCuiDraggableDrop(BasePlayer player, string draggedName, string draggedSlot, string swappedName, string swappedSlot)Parameters
| Parameter | Type | Description |
|---|---|---|
player | BasePlayer | The BasePlayer whose client sent the drop RPC. Always the player who controls the CUI that contains the draggable elements. |
draggedName | string | The CuiElement name of the draggable element that was released on top of another element. |
draggedSlot | string | The slot string authored on the dragged CuiDraggable (the Slot field or parent slot), used by the UI author to identify which item was dragged. |
swappedName | string | The CuiElement name of the element that received the drop, or an empty string when the drop target had no named element. |
swappedSlot | string | The slot string of the receiving element, or empty when the drop landed on an element without a slot. |
Return value
The return value is ignored. Hook_DropRPC is a void method and only invokes Interface.CallHook; whatever your hook method returns is discarded and the original code path continues unchanged.
How it works
This hook fires once per drop action, when the client finishes dragging a CuiDraggable element and releases it over a target element. The client sends the element names and slot strings for both the dragged element and the target it was released on; the server passes them straight into the hook. draggedName and draggedSlot identify what was moved, swappedName and swappedSlot identify where it landed. An empty target name or slot usually means the drop happened over an unnamed area of the UI rather than a registered element.
Since CUI is purely client-side authoring, this hook is your only server-side signal that a drag operation completed. Use it to move items between containers, swap equipment between slots, or reorder data arrays that back the UI.
Declaring player as BasePlayer (the only sensible type here) already matches all calls. You can narrow further with a null check; the hook will fire with player set whenever a client of an active player triggers it.
Because it relies on a client RPC, only players with the CUI visible can trigger it. It will not fire for server-side programmatic movement.
Example plugin: Drag-reorder a loadout grid backed by the player's belt
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Cui Drag Loadout", "RustPluginGenerator", "1.0.0")]
[Description("Opens a CUI grid of belt slots players can drag to reorder their belt items.")]
public class CuiDragLoadout : RustPlugin
{
private const string PermUse = "cuidragloadout.use";
private const string PanelName = "CuiDragLoadoutPanel";
private PluginConfig config;
public class PluginConfig
{
[JsonProperty("Require permission")]
public bool RequirePermission = true;
[JsonProperty("Grid columns")]
public int Columns = 6;
[JsonProperty("Cell width (fraction)")]
public float CellWidth = 0.1f;
}
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>
{
["NoPermission"] = "You do not have permission to use this.",
["Opened"] = "Drag slots to reorder your belt. Type /loadout.close to close.",
["Invalid"] = "That drop was not valid."
}, 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(PermUse, this);
}
[ChatCommand("loadout")]
private void CmdLoadout(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (config.RequirePermission && !permission.UserHasPermission(player.UserIDString, PermUse))
{
SendReply(player, Msg("NoPermission", player.UserIDString));
return;
}
Draw(player);
SendReply(player, Msg("Opened", player.UserIDString));
}
[ChatCommand("loadout.close")]
private void CmdClose(BasePlayer player, string command, string[] args)
{
if (player == null) return;
CuiHelper.DestroyUi(player, PanelName);
}
private void Draw(BasePlayer player)
{
CuiHelper.DestroyUi(player, PanelName);
var c = new CuiElementContainer();
var panel = c.Add(new CuiPanel
{
Image = { Color = "0.08 0.08 0.08 0.9" },
RectTransform = { AnchorMin = "0.3 0.4", AnchorMax = "0.7 0.6" },
CursorEnabled = true
}, "Overlay", PanelName);
var belt = player.inventory?.containerBelt;
int count = Mathf.Max(config.Columns, belt?.capacity ?? config.Columns);
float startX = 0.02f;
for (int i = 0; i < count; i++)
{
float x0 = startX + i * config.CellWidth;
float x1 = x0 + config.CellWidth * 0.9f;
string label = "empty";
var item = belt?.GetSlot(i);
if (item != null && item.info != null) label = item.info.displayName.english;
var slotName = "CuiDragLoadout.slot" + i;
c.Add(new CuiButton
{
Button = { Command = "cuidragloadout.move " + i, Color = "0.2 0.2 0.2 1" },
RectTransform = { AnchorMin = x0 + " 0.2", AnchorMax = x1 + " 0.8" },
Text = { Text = label, FontSize = 12, Align = TextAnchor.MiddleCenter, Color = "0.9 0.9 0.9 1" }
}, panel, slotName);
}
CuiHelper.AddUi(player, c);
}
[ConsoleCommand("cuidragloadout.move")]
private void CcmdMove(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
if (config.RequirePermission && !permission.UserHasPermission(player.UserIDString, PermUse)) return;
int from = arg.GetInt(0, -1);
int to = arg.GetInt(1, -1);
if (from < 0 || to < 0 || from == to) return;
SwapSlots(player, from, to);
}
private bool SwapSlots(BasePlayer player, int from, int to)
{
var belt = player.inventory?.containerBelt;
if (belt == null) return false;
if (from < 0 || to < 0 || from >= belt.capacity || to >= belt.capacity) return false;
var source = belt.GetSlot(from);
var target = belt.GetSlot(to);
if (source == null && target == null) return false;
if (source != null) source.MoveToContainer(belt, to);
if (target != null) target.MoveToContainer(belt, from);
belt.MarkDirty();
NextTick(() => { if (player != null && player.IsConnected) Draw(player); });
return true;
}
// Drag-and-drop hook: reorder belt items when a draggable is dropped on a slot.
// Returns null to keep default CUI behavior.
private object OnCuiDraggableDrop(BasePlayer player, string dragId, string dropId, Vector2 position, Vector2 delta)
{
if (player == null || string.IsNullOrEmpty(dragId) || string.IsNullOrEmpty(dropId)) return null;
if (config.RequirePermission && !permission.UserHasPermission(player.UserIDString, PermUse)) return null;
const string prefix = "CuiDragLoadout.slot";
if (!dragId.StartsWith(prefix) || !dropId.StartsWith(prefix)) return null;
if (!int.TryParse(dragId.Substring(prefix.Length), out int from)) return null;
if (!int.TryParse(dropId.Substring(prefix.Length), out int to)) return null;
if (from == to) return null;
if (!SwapSlots(player, from, to)) return null;
return true;
}
private void Unload()
{
foreach (var player in BasePlayer.activePlayerList)
{
if (player != null) CuiHelper.DestroyUi(player, PanelName);
}
}
}
}The plugin opens a CUI grid of fixed slots via /loadout. Each slot is a CuiDraggable whose element name encodes the slot index (slot:3) and whose Slot field is the same string. When the player drags one slot onto another, OnCuiDraggableDrop reads both slot indices from the parameter strings, validates they are within bounds, swaps the items in the player's belt container, and rebuilds the UI so the new order is visible. Empty target slots (default enableDragging swap behavior) still fire the hook with the destination name, so an empty-to-slot or slot-to-empty move is handled by the same code path.
Common use cases
Swap item positions in a custom container grid
Listen for drops inside a plugin-authored CUI backpack and move the corresponding items in the backing ItemContainer when the player releases a dragged cell.
Reorder a favorite items bar
Keep a saved list per player and reorder entries when the player drags slots around; persist with Oxide data files.
Move items between CUI categories
Use
swappedSlotprefixes to know which category a drop landed in and filter the source items accordingly.
Pitfalls
Hook fires only for CUI drags
This is not the native Rust inventory drag; it only fires when the client interacts with a CuiDraggable element authored by your own plugin. Do not use it to intercept normal inventory moves.
Target may be empty
Releasing on an unnamed area produces an empty
swappedName/swappedSlot. Always guard against null or empty strings before indexing into your data.Client sends what it wants
The slot strings are authored by your own CUI; treat them as untrusted input and revalidate indexes, ranges and permissions server-side before mutating containers or saving data.
Build a plugin with OnCuiDraggableDrop
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 OnCuiDraggableDrop
Generate