OnPhoneNameUpdated
OnPhoneNameUpdated is called after a telephone's name has been changed and network-synced, at the end of PhoneController.UpdatePhoneName. It fires only when the companion OnPhoneNameUpdate hook returned null.
void OnPhoneNameUpdated(PhoneController phoneController, string phoneName, BasePlayer currentPlayer)Parameters
| Parameter | Type | Description |
|---|---|---|
phoneController | PhoneController | The PhoneController entity whose display name was changed. Use it to read net.ID, OwnerID and transform.position of the telephone. |
phoneName | string | The name that was just applied to the phone. Already trimmed by the game, and empty when the player cleared the name. |
currentPlayer | BasePlayer | The BasePlayer who submitted the rename. May be null if the change did not come from a player. |
Return value
The return value is ignored. The name has already been written and the entity network update sent before the hook is invoked, so returning anything cannot cancel or alter the rename. Blocking belongs in OnPhoneNameUpdate.
How it works
The hook runs inside PhoneController.UpdatePhoneName, immediately after the game assigns the new name and calls SendNetworkUpdate() on the phone entity. It fires for every successful rename: a player typing a name into the telephone UI, or any code path that calls the same method. Because it is the post-change hook, the value in phoneName is the one already stored on the controller, so reading phoneController.PhoneName gives the same string.
phoneController gives you the entity (its net.ID, owner, position and attached IO). phoneName is the sanitized name; an empty string means the player wiped the name. currentPlayer is the calling player, and it can be null when the rename is triggered without a player context, so always null-check before touching player members.
This hook cannot change the outcome, it is pure notification. To block or rewrite a rename, implement OnPhoneNameUpdate instead, which runs just before the assignment and lets you stop it or return a replacement. Declaring currentPlayer as BasePlayer is the only sensible narrowing; a more specific type would simply skip calls where the argument does not match.
Example plugin: Log phone renames for admins
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Phone Name Audit", "RustPluginGenerator", "1.0.0")]
[Description("Tracks telephone renames and lets admins read the recent audit log")]
public class PhoneNameAudit : RustPlugin
{
private const string PermAdmin = "phonenameaudit.admin";
private PluginConfig config;
private Timer tickTimer;
private readonly List<AuditEntry> entries = new List<AuditEntry>();
private class AuditEntry
{
[JsonProperty("time")]
public DateTime Time;
[JsonProperty("playerId")]
public string PlayerId;
[JsonProperty("playerName")]
public string PlayerName;
[JsonProperty("phoneId")]
public ulong PhoneId;
[JsonProperty("name")]
public string Name;
}
private class PluginConfig
{
[JsonProperty("Maximum entries kept")]
public int MaxEntries = 200;
[JsonProperty("Mirror entries to server console")]
public bool MirrorToConsole = true;
}
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>
{
["Cleared"] = "Audit log cleared.",
["Empty"] = "No renames recorded yet.",
["Header"] = "Recent phone renames ({0} shown):",
["Line"] = "{0} | {1} ({2}) renamed phone {3} to '{4}'",
["NoPerm"] = "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(PermAdmin, this);
}
private void OnServerInitialized()
{
tickTimer = timer.Every(600f, Trim);
}
private void Unload()
{
tickTimer?.Destroy();
tickTimer = null;
entries.Clear();
}
private void OnPhoneNameUpdated(PhoneController phoneController, string phoneName, BasePlayer currentPlayer)
{
if (phoneController == null) return;
var entity = phoneController.GetComponent<BaseEntity>();
if (entity == null || entity.net == null) return;
var entry = new AuditEntry
{
Time = DateTime.UtcNow,
PhoneId = entity.net.ID.Value,
Name = phoneName ?? string.Empty,
PlayerId = currentPlayer != null ? currentPlayer.UserIDString : "server",
PlayerName = currentPlayer != null ? currentPlayer.displayName : "server"
};
entries.Add(entry);
if (entries.Count > Mathf.Max(10, config.MaxEntries)) entries.RemoveAt(0);
if (config.MirrorToConsole)
{
Puts("[PhoneAudit] " + entry.PlayerName + " renamed phone " + entry.PhoneId + " to '" + entry.Name + "'");
}
}
private void Trim()
{
int limit = Mathf.Max(10, config.MaxEntries);
if (entries.Count > limit) entries.RemoveRange(0, entries.Count - limit);
}
[ChatCommand("phonelog")]
private void CmdPhoneLog(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, PermAdmin))
{
SendReply(player, Msg("NoPerm", player.UserIDString));
return;
}
if (args != null && args.Length > 0 && args[0].ToLowerInvariant() == "clear")
{
entries.Clear();
SendReply(player, Msg("Cleared", player.UserIDString));
return;
}
if (entries.Count == 0)
{
SendReply(player, Msg("Empty", player.UserIDString));
return;
}
int show = Mathf.Min(10, entries.Count);
SendReply(player, Msg("Header", player.UserIDString, show));
for (int i = entries.Count - show; i < entries.Count; i++)
{
var e = entries[i];
SendReply(player, Msg("Line", player.UserIDString, e.Time.ToString("HH:mm:ss"), e.PlayerName, e.PlayerId, e.PhoneId, e.Name));
}
}
}
}The plugin records each rename and reports it. On every OnPhoneNameUpdated call it validates the controller and resolves the acting player (using 'server' when there is none), then appends a line to an in-memory audit list and, if configured, prints it to the server log. Server admins and players holding the phonenameaudit.admin permission can run /phonelog to read the most recent entries, or /phonelog clear to wipe them. Telephones are tracked by their entity id so an admin can match the log line to a physical phone. Config controls the buffer size (with a minimum of 10) and whether entries go to the console, and a repeating timer prunes old data so the list cannot grow forever.
Common use cases
Staff audit trail
Keep a rolling list of who renamed which telephone and when, so admins can trace abusive or misleading phone names on a community server.
Content moderation
Scan the applied name for blocked words and alert online admins. Only report here; reject the rename in OnPhoneNameUpdate for the actual block.
Server analytics
Count renames per player or per entity to see which phones are actively used, and hand that data to a reward or statistics plugin.
Pitfalls
This is not the blocking hook
Returning a value here does nothing because the name is already applied. Use OnPhoneNameUpdate when you need to deny or substitute a name.
currentPlayer can be null
Renames triggered outside a player interaction pass null. Dereferencing
currentPlayer.UserIDStringwithout a null check will throw and spam the server console.Do not trust the string blindly
Treat
phoneNameas untrusted input made up by a player. Never embed it into a console command or file path without sanitizing it.
Build a plugin with OnPhoneNameUpdated
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 OnPhoneNameUpdated
Generate