OnPlayerOfflineChat
OnPlayerOfflineChat is called when a chat message arrives from a player who is not connected to the server, for example through the Rust+ companion app, and Oxide passes it to plugins instead of the normal OnPlayerChat path.
object OnPlayerOfflineChat(ulong playerid, string playerName, string message, ConVar.Chat.ChatChannel channel)Parameters
| Parameter | Type | Description |
|---|---|---|
playerid | ulong | Steam ID of the sender as a ulong. Use it for permission checks, data lookups and identifying the player; there is no BasePlayer instance because the sender is not connected. |
playerName | string | The display name Steam reports for the sender. It is already unescaped by the time the hook fires, so do not trust it for identity checks. |
message | string | The chat message text after Oxide stripped the chat command prefix logic and applied rich text escaping. This is what you broadcast, filter or log. |
channel | ConVar.Chat.ChatChannel | The Chat.ChatChannel the message was sent on (Global, Team, Local, Server, Cards and so on). Use it to decide whether the message should be relayed at all. |
Return value
Returning null (or true, which is ignored as a non-null value) lets Oxide continue: the hook result is returned from IOnPlayerChat unchanged, so the message is not broadcast by the game. Returning any other non-null value replaces that result and similarly stops the original chat path. To relay an offline message you must broadcast it yourself; there is no default chat delivery for this branch.
How it works
The hook fires inside Oxide's own chat entry point (RustCore.IOnPlayerChat), not a patched game method. When a chat message arrives, Oxide first drops empty messages, runs anything starting with the chat command prefix as a command, and escapes rich text. It then checks the sending BasePlayer: if that object is null or not connected, the message goes to OnPlayerOfflineChat with the raw Steam ID, name, text and channel instead of OnPlayerChat. The most common real trigger is Rust+ app chat, where no live player entity exists on the server.
Because you get a ulong rather than a player, you cannot read position, team or inventory directly, but playerid still works with permissions (permission.UserHasPermission(playerid.ToString(), ...)) and with team lookups via RelationshipManager if the player is offline. Filter on channel so offline messages do not leak into global chat unintentionally, and sanitise or truncate message before broadcasting. Note the hook never fires for chat commands, since those returned earlier in the same method.
Example plugin: Relay Rust+ chat into team chat
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using UnityEngine;
using Rust;
namespace Oxide.Plugins
{
[Info("Offline Chat Relay", "RustPluginGenerator", "1.0.0")]
[Description("Relays team-channel chat sent from the Rust+ app to online teammates.")]
public class OfflineChatRelay : RustPlugin
{
private const string PermUse = "offlinechatrelay.use";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Prefix with sender name")]
public bool ShowSenderName = true;
[JsonProperty("Only relay if sender has permission")]
public bool RequirePermission = false;
}
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>
{
["Relay"] = "[Rust+] {0}: {1}",
["RelayAnon"] = "[Rust+] {0}"
}, 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);
}
private object OnPlayerOfflineChat(ulong playerid, string playerName, string message, ConVar.Chat.ChatChannel channel)
{
if (channel != ConVar.Chat.ChatChannel.Team) return null;
if (string.IsNullOrEmpty(message)) return null;
if (config.RequirePermission && !permission.UserHasPermission(playerid.ToString(), PermUse)) return null;
var player = BasePlayer.FindByID(playerid);
if (player == null || player.currentTeam == 0) return null;
var team = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
if (team == null || team.members == null) return null;
foreach (var memberId in team.members)
{
if (memberId == playerid) continue;
var member = BasePlayer.FindByID(memberId);
if (member == null || !member.IsConnected) continue;
member.ChatMessage(config.ShowSenderName
? Msg("Relay", member.UserIDString, playerName, message)
: Msg("RelayAnon", member.UserIDString, message));
}
return null;
}
}
}The plugin listens for OnPlayerOfflineChat and only acts when the message came in on the Team channel and the sender's team ID is non-zero. It looks the team up with RelationshipManager.ServerInstance.FindTeam, then sends the message to every online member whose Steam ID is different from the sender's, formatted through the language file. Messages from players without a team, or on other channels, are dropped silently by returning null. A permission (offlinechatrelay.use) lets admins toggle the relay per player, and the toggle state is not needed here because the check is read-only; the plugin simply logs nothing and keeps chat clean.
Common use cases
Team chat from Rust+
Players using the companion app can message their team even when the game client is closed. Relay the text to online teammates while keeping the sender's identity visible.
Discord or web feed mirroring
Push offline chat into an external chat bridge so staff see messages sent from phones that never appear in normal global chat.
Spam and impersonation filtering
Apply the same word filter and name checks you use for connected players, since offline senders bypass OnPlayerChat entirely.
Pitfalls
No BasePlayer to work with
You cannot call position, inventory or team helpers on a player object here. Resolve anything you need from
playeridand accept that the sender is offline.Returning true does not allow the message
Any non-null return stops the original path, and there is no built-in delivery anyway. If you want the message seen, broadcast it yourself from inside the hook.
Channel mixing
A Team-channel message broadcast to everyone exposes private coordination. Always branch on
channelbefore relaying.
Build a plugin with OnPlayerOfflineChat
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 OnPlayerOfflineChat
Generate