Skip to main content
The admin system uses a permission hierarchy to control access to admin features. All admin actions are logged for accountability.

Overview

Permission Levels

Admin and Staff tiers

Permission Checks

How to verify permissions

Logging

Audit trail for admin actions

Permission Hierarchy

LevelMethodAccess
Adminplayer.Permissions:IsAdmin()Full admin panel, all commands, all callbacks
Staffplayer.Permissions:IsStaff()Limited panel, staff commands, read-only callbacks
PlayerNeitherNo admin access

Checking Permissions

In Server Callbacks

All admin callbacks follow this pattern:
Callbacks:RegisterServerCallback('Admin:MyAction', function(source, data, cb)
    local player = Fetch:Source(source)

    if not player then
        return cb(false)
    end

    -- Admin-only action
    if player.Permissions:IsAdmin() then
        -- Perform admin action
        cb({ success = true })
    else
        cb(false)
    end
end)

Tiered Access

Some callbacks support both admin and staff with different capabilities:
Callbacks:RegisterServerCallback('Admin:GetPlayerInfo', function(source, data, cb)
    local player = Fetch:Source(source)

    if not player then
        return cb(false)
    end

    if player.Permissions:IsAdmin() then
        -- Full player data (admin)
        local targetPlayer = Fetch:Source(data.targetSource)
        local char = targetPlayer:GetData('Character')

        cb({
            name = GetPlayerName(data.targetSource),
            character = char:GetData('First') .. ' ' .. char:GetData('Last'),
            stateId = char:GetData('SID'),
            jobs = char:GetData('Jobs'),
            cash = char:GetData('Cash'),  -- Admin-only
            bankBalance = Banking.Balance:Get(account)        -- Admin-only
        })
    elseif player.Permissions:IsStaff() then
        -- Limited data (staff)
        cb({
            name = GetPlayerName(data.targetSource),
            character = 'Hidden',
            stateId = 'Hidden'
        })
    else
        cb(false)
    end
end)

In Chat Commands

Use the Chat component’s built-in admin/staff registration:
-- Automatically checks IsAdmin()
Chat:RegisterAdminCommand('mycommand', function(source, args, rawCommand)
    -- Only runs if player is admin
end, { help = 'Description' })

-- Automatically checks IsStaff()
Chat:RegisterStaffCommand('mycommand', function(source, args, rawCommand)
    -- Only runs if player is staff
end, { help = 'Description' })

In Event Handlers

AddEventHandler('myResource:server:AdminAction', function(data)
    local src = source
    local player = Fetch:Source(src)

    if not player or not player.Permissions:IsAdmin() then
        Logger:Warn('Security', 'Unauthorized admin action attempt', {
            console = true,
            file = true,
            discord = true
        }, {
            source = src,
            action = data.action
        })
        return
    end

    -- Process admin action
end)

Middleware Initialization

Admin permissions are initialized when a character spawns:
-- Registered at priority 5 on Characters:Spawning
Middleware:Add('Characters:Spawning', function(source, character)
    local player = Fetch:Source(source)

    if player.Permissions:IsStaff() or player.Permissions:IsAdmin() then
        -- Send permission data to client for admin panel UI
        TriggerClientEvent('Admin:Client:Menu:RecievePermissionData', source, permissionData)
    end
end, 5)

Logging Patterns

All significant admin actions should be logged to multiple outputs:

Standard Admin Action Log

Logger:Warn('Admin', string.format(
    '%s (%s) performed %s on %s (%s)',
    adminName, adminSID,
    action,
    targetName, targetSID
), {
    console = true,
    file = true,
    database = true,
    discord = {
        embed = true,
        type = 'warning',
        title = 'Admin Action',
        description = string.format('**Admin:** %s\n**Action:** %s\n**Target:** %s',
            adminName, action, targetName
        )
    }
}, {
    adminSource = source,
    adminSID = adminSID,
    action = action,
    targetSource = targetSource,
    targetSID = targetSID,
    timestamp = os.time()
})

Ban Logging

Logger:Warn('Admin', string.format(
    '%s banned %s for: %s (Duration: %d days)',
    adminName, targetName, reason, duration
), {
    console = true,
    file = true,
    database = true,
    discord = {
        embed = true,
        type = 'error',
        title = 'Player Banned',
        content = '@here Player banned',
        description = string.format(
            '**Admin:** %s\n**Player:** %s\n**Reason:** %s\n**Duration:** %d days',
            adminName, targetName, reason, duration
        )
    }
}, {
    adminSID = adminSID,
    targetSID = targetSID,
    reason = reason,
    duration = duration
})

Best Practices

-- ✅ Good: Server-side check
if not player.Permissions:IsAdmin() then
    return cb(false)
end

-- ❌ Bad: Trusting client data
if data.isAdmin then  -- Client can spoof this!
    -- NEVER trust client claims
end
-- Always log bans, kicks, item gives, teleports
Logger:Warn('Admin', actionDescription, {
    console = true,
    file = true,
    database = true,
    discord = true
}, actionData)
-- Admin-only: destructive actions (ban, kick, give items, modify data)
-- Staff-only: read-only actions (lookup, view info, spectate)

-- ✅ Good: Staff can only view
Chat:RegisterStaffCommand('lookup', viewOnlyHandler)

-- ✅ Good: Admin required for modification
Chat:RegisterAdminCommand('giveitem', modifyHandler)

Next Steps

Admin - Commands

Chat commands reference

Admin - Callbacks

Admin panel callbacks

Logger API

Logging system

Chat API

Command registration