Skip to content

Permissions

Permissions decide who may use a command or a feature. A permission is a dotted node that your plugin declares with a default. The server ships with a file-based backend (groups and per-player grants in permissions.json), and a plugin can replace it with its own.

You declare the nodes your plugin checks, with a default and a description, so admins can discover them:

PermissionsDemo.cs
[PluginInfo("PermissionsDemo", Version = "1.0.0")]
public sealed class PermissionsDemoPlugin : Plugin
{
protected override void OnEnable()
{
Permissions.Register("homes.use", PermissionDefault.True, "Use /home");
Permissions.Register("homes.set", PermissionDefault.True, "Use /sethome");
Permissions.Register(new Permission("homes.admin", PermissionDefault.Op, "Teleport to other players' homes"));
Events.On((ref PermissionChangedEvent e) =>
Log.Info(e.AllPlayers ? "permissions changed for everybody" : $"permissions changed for {e.Player}"));
}
private void CheckHomes(Player player)
{
if (!player.HasPermission("homes.set"))
{
player.SendMessage(Component.Text("No permission."));
return;
}
// three-valued: True, False or Unset (no entry: the node's default decides)
PermissionValue admin = PermissionManager.GetExplicit(player.Uuid, "homes.admin");
}
}

plugin.Permissions.Register is removed again when the plugin unloads. A node can also be listed in the permissions array of plugin.json, so it exists before your plugin loads. A node name is dotted, has no spaces, and does not start or end with a dot.

To check a node, call player.HasPermission("homes.set"), or plugin.Permissions.Has(player, node) (also takes a Uuid, so it works for offline players). PermissionManager.GetExplicit returns the tri-state PermissionValue without applying defaults. A command can check the same node for you with .Permission("homes.use") (see commands).

The default is what a node is worth when no entry decides:

Default Meaning
PermissionDefault.True everybody
PermissionDefault.False nobody, unless granted
PermissionDefault.Op vanilla operator level 2 or higher
PermissionDefault.OpLevel(n) vanilla operator level n or higher (1 = bypass spawn protection, 2 = gamemaster, 3 = admin, 4 = owner)
PermissionDefault.NotOp only players who are not operators

An undeclared node resolves to denied. The console has every permission.

A grant of homes.* matches every node under homes., and * matches everything. The answer is found in layers, and the first layer with an opinion wins:

  1. the player’s own entries;
  2. the groups the player is in, highest weight first (see below);
  3. the implicit default group that everybody is in;
  4. the node’s default.

Inside one layer the most specific entry wins: an exact node beats the longest matching wildcard, which beats *. A false entry is a denial.

Try it with the rules of one layer:

Try:
True because moderation.* (line 4, wildcard (1 fixed segment)) is the most specific match.
EntryValueMatch
moderation.*grantwildcard (1 fixed segment)

The built-in backend stores groups in permissions.json in the server directory. A group has a weight, optional parents (inherits), a set of nodes with true or false values, and optional prefix and suffix text for chat plugins. A per-player override, keyed by UUID, adds nodes and group memberships. The file is plain JSON. Comments and trailing commas are allowed, and changes are written to disk shortly after they happen and at shutdown. PermissionManager.Reload() (or /perm reload) reads it again.

PermissionManager.BuiltIn gives the file backend’s administration methods, such as SetPlayerPermission and SetGroupPermission. GetPrefix(uuid) returns a player’s group prefix.

Operators keep working as before. The operator level of a player (from ops.json) is the default value source for Op and OpLevel. Every vanilla command also has a node, minecraft.command.<name>, whose default is that command’s operator level, so a plugin can check it with HasPermission. An explicit value for such a node overrides the level check. The server setting permissions.enforce_vanilla_nodes (off, warn or on; the default warn only logs the difference) decides whether those explicit values also gate the vanilla command, once a permissions plugin has claimed the engine’s permission table. Until then, vanilla commands are gated by their operator level alone.

plugin.Permissions.SetProvider(provider) replaces the answers for as long as your plugin is loaded. The provider implements IPermissionProvider:

StaticPermissionProvider.cs
public sealed class StaticPermissionProvider : IPermissionProvider
{
public string Name => "static example";
public event Action<Uuid?>? Changed;
public PermissionValue Get(Uuid player, string node)
=> node.StartsWith("homes.", StringComparison.Ordinal) ? PermissionValue.True : PermissionValue.Unset;
public IReadOnlyCollection<string> Groups(Uuid player) => [];
// call this when the data behind the provider changed
public void Refresh() => Changed?.Invoke(null);
}
  • Get returns Unset when the provider has no opinion, so the node’s default decides. Wildcards and groups are the provider’s business.
  • Groups returns the group names of a player, for display and admin commands.
  • Changed tells the server that answers changed: null means everything, a Uuid means one player. The manager caches answers until then, so a database-backed provider must raise it when its data changes.

The provider is removed when its plugin unloads, and the built-in backend takes over again.

  • PermissionChangedEvent carries the affected player’s Player (a Uuid) and AllPlayers, which is true when a group or the whole backend changed. The command trees of the players are refreshed after it.
  • PermissionsReloadedEvent fires when the built-in file has been read again.

The PermissionAdmin sample adds /perm (alias /permission, node permissions.admin, default operator level 3) on top of this API. It covers reload, list, user <player> set|unset|check <node>, user <player> group add|remove <group>, user <player> info, and group <name> create|delete|set|unset|parent|list. A replacement backend can reuse the same command layout.

See the homes guide for permissions in a complete plugin.