Fix FormatException: Index (zero based) must be greater than or equal to zero (Oxide plugin)
A lang message contains a placeholder like {1} but the code passes fewer arguments to string.Format than the highest index used, so the lookup of that argument throws FormatException. The fix is to keep placeholders and argument counts in sync and to validate slots before calling format.
Failed to call hook 'OnPlayerConnected' on plugin 'WelcomeMessages v1.0.0' (FormatException: Index (zero based) must be greater than or equal to zero and less than the size of the argument list.) at System.Text.StringBuilder.AppendFormatHelper (System.IFormatProvider provider, System.String format, System.ParamsArray args) [0x000f3] in <b0e1ad7573a24fd5a9f2af9595e677e7>:0
Why it happens
string.Format reads placeholders by number: {0} is the first argument, {1} the second, and so on. If the format string mentions {1} while only one argument was supplied, formatting fails and Oxide prints Failed to call hook '...' with FormatException: Index (zero based) must be greater than or equal to zero....
In plugins this usually happens in two places. First, the code passes too few values: a message was written for two values but the call site still passes one. Second, the format string is not what the developer wrote. Lang messages are edited by server owners in oxide/lang/en/<PluginName>.json (or carbon/lang/...), and someone can insert {1}, {2} or a broken { into a translation. Arguments themselves can be the second trap: passing a value that is null does not throw this exception, but forgetting to add it does.
Because the throw happens inside the hook, Oxide logs the error and keeps calling the hook, so the player still fails to get their message on every connect.
How to fix it
- Read the console line. The hook name and plugin name tell you where to look; paste the stack trace into
oxide/logssearch if you need the source line. - Open the plugin and find every
string.Formatand everylang.GetMessagehelper. Compare each message's placeholders with the argument list in the same call. - Open
oxide/lang/en/<PluginName>.json(orcarbon/lang/en/...) and do the same comparison there, since the file overrides the defaults. Fix or delete the edited entry; the plugin rewrites defaults only whenLoadDefaultMessagesruns on a fresh language file. - Make the code defensive: count the highest
{n}used, or pad the argument array so every slot exists before formatting. - Reload with
oxide.reload WelcomeMessages, reconnect a test client, and confirm the translated message prints with noFailed to call hookline in the console.
Before and after
using System.Collections.Generic;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("WelcomeMessages", "RustPluginGenerator", "1.0.0")]
[Description("Sends a welcome message with the player name and server name")]
public class WelcomeMessages : RustPlugin
{
private const string MsgWelcome = "Welcome";
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
// Two placeholders: player name and server name
["Welcome"] = "Welcome {0} to {1}!"
}, this);
}
private string Msg(string key, string userId = null, params object[] args)
=> string.Format(lang.GetMessage(key, this, userId), args);
private void OnPlayerConnected(BasePlayer player)
{
if (player == null)
return;
// Only one argument is passed, but the message uses {1}
PrintToChat(player, Msg(MsgWelcome, player.UserIDString, player.displayName));
}
}
}using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("WelcomeMessages", "RustPluginGenerator", "1.0.0")]
[Description("Sends a welcome message with the player name and server name")]
public class WelcomeMessages : RustPlugin
{
private const string MsgWelcome = "Welcome";
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
// One placeholder per argument the code actually supplies
["Welcome"] = "Welcome {0}!"
}, this);
}
private string Msg(string key, string userId = null, params object[] args)
{
string format = lang.GetMessage(key, this, userId);
if (string.IsNullOrEmpty(format))
return key;
// Drop any placeholder the code cannot fill, so edited lang files cannot throw
format = Regex.Replace(format, @"\{(\d+)\}", m =>
{
int index;
if (!int.TryParse(m.Groups[1].Value, out index) || index < 0 || index >= (args == null ? 0 : args.Length))
return string.Empty;
return m.Value;
});
try
{
return string.Format(format, args ?? new object[0]);
}
catch (FormatException)
{
return format;
}
}
private void OnPlayerConnected(BasePlayer player)
{
if (player == null)
return;
PrintToChat(player, Msg(MsgWelcome, player.UserIDString, player.displayName));
}
}
}FAQ
Why does the error appear only for some players?
The throw happens on every call that reaches the broken format string, but if the message is only sent on first connection, only new joins trigger it. Reconnecting the same player should reproduce it every time until the arguments and placeholders match.
The lang file looks fine to me. Where else can {1} come from?
Check the plugin source for a hard-coded format string, and for a helper method that calls string.Format with a fixed argument list. If the helper is reused by several messages and one message has extra placeholders, only that message throws.
Does Carbon show the same error?
Yes. Carbon loads the same Oxide plugin and uses the same lang API, so a mismatched argument list fails the same way. The log line format differs, but the FormatException text and the fix are identical.
Different error?
Paste the full console output and get the cause and a fix in plain English.
Open the error explainerSkip the debugging
Generated plugins are compiled before you get them, and compiler errors are fixed automatically.
Generate a plugin2 free plugins a month, no card.
Last updated Oct 26, 2026.
Get plugins that compile first time
Generate