Skip to main content
The Characters system manages player characters in Mythic Framework, including creation, selection, loading, and data persistence. Character data is accessed through the Fetch component using the DataStore pattern.
Critical: Do NOT use Characters:GetCharacter() - this method does not exist.Always use the Fetch component:
local player = Fetch:Source(source)
local char = player:GetData('Character')

Overview

Multi-Character

Support for multiple characters per player

DataStore Pattern

In-memory state management with GetData/SetData

Auto-Save

MongoDB persistence with periodic saves

Fetch Component

Primary API for character access

Accessing Characters

Getting Active Character

The primary way to access character data is through the Fetch component. Pattern:
local player = Fetch:Source(source)
if not player then return end

local char = player:GetData('Character')
if not char then return end

-- Access character data
local stateId = char:GetData('SID')
local firstName = char:GetData('First')
Example in Event Handler:
-- Server side
RegisterNetEvent('myresource:server:doSomething', function()
    local src = source
    local player = Fetch:Source(src)

    if not player then
        print('[ERROR] Player not found')
        return
    end

    local char = player:GetData('Character')
    if not char then
        TriggerClientEvent('mythic-notifications:client:Send', src, {
            message = 'You must be logged in as a character',
            type = 'error'
        })
        return
    end

    -- Safe to access character data
    local stateId = char:GetData('SID')
    local name = string.format('%s %s', char:GetData('First'), char:GetData('Last'))
    local cash = char:GetData('Cash')
    local jobs = char:GetData('Jobs')  -- Array of job objects

    print('Character:', name, 'SID:', stateId, 'Cash:', cash)
end)

Character Data Access Methods

Once you have a character DataStore object, use these methods:

GetData

Retrieve character data. Signature:
char:GetData(key)
Parameters:
NameTypeRequiredDescription
keystringNoSpecific field (omit for all data)
Common Character Fields:
  • SID (number) - State ID (primary key)
  • First (string) - First name
  • Last (string) - Last name
  • DOB (string) - Date of birth
  • Gender (number) - Gender code
  • Phone (string) - Phone number
  • Jobs (table) - Array of job objects (each with Id, WorkplaceId, GradeId, GradeLevel)
  • Cash (number) - Cash on hand
  • MetaData (table) - Additional character data
Examples:
-- Get specific fields
local stateId = char:GetData('SID')
local firstName = char:GetData('First')
local lastName = char:GetData('Last')
local cash = char:GetData('Cash')
local jobs = char:GetData('Jobs')  -- Array of job objects

-- Get all data
local allData = char:GetData()
print(json.encode(allData, {indent = true}))

-- Access nested data
local metadata = char:GetData('MetaData')
local hunger = metadata.hunger or 100

SetData

Update character data. Signature:
char:SetData(key, value)
Parameters:
NameTypeRequiredDescription
keystringYesField name
valueanyYesNew value
Character SetData automatically syncs to client with Characters:Client:SetData event.
Examples:
-- Update cash
local currentCash = char:GetData('Cash')
char:SetData('Cash', currentCash + 500)

-- Note: Bank balances are managed via the Banking component, not character SetData
-- Note: Jobs are managed via the Jobs component: Jobs:GiveJob(stateId, jobId, workplaceId, gradeId)

-- Update phone number
char:SetData('Phone', '555-1234')

-- Update nested metadata
local metadata = char:GetData('MetaData')
metadata.hunger = 80
metadata.thirst = 90
char:SetData('MetaData', metadata)

Finding Characters

By Server Source

Get character by player server ID (most common). Example:
-- Get character from source
local player = Fetch:Source(source)
if player then
    local char = player:GetData('Character')
    if char then
        print('Found character:', char:GetData('First'), char:GetData('Last'))
    end
end
See: Core - Fetch: Source

By State ID (SID)

Find player by character State ID. Example:
-- Find character by SID
local player = Fetch:SID(123)

if player then
    local char = player:GetData('Character')
    local source = player:GetData('Source')

    -- Send notification
    TriggerClientEvent('mythic-notifications:client:Send', source, {
        message = 'You received a message',
        type = 'info'
    })
end
See: Core - Fetch: SID

By Character Data Field

Find player by any character field (phone, name, etc). Example:
-- Find by phone number
local player = Fetch:CharacterData('Phone', '555-0123')

if player then
    local char = player:GetData('Character')
    print('Found:', char:GetData('First'), char:GetData('Last'))
end

-- Find by SID using CharacterData
local player = Fetch:CharacterData('SID', 123)
See: Core - Fetch: CharacterData

Offline Character Data

Get specific data from offline character (database query). Example:
-- Get offline character data (blocking)
local phone = Fetch:GetOfflineData(123, 'Phone')
local cash = Fetch:GetOfflineData(123, 'Cash')

print('Offline character phone:', phone)
GetOfflineData is synchronous and blocks the thread. Use sparingly and only when player is offline.
See: Core - Fetch: GetOfflineData

Common Patterns

Safe Character Access

Always validate player and character exist:
RegisterNetEvent('myresource:server:action', function()
    local src = source
    local player = Fetch:Source(src)

    if not player then
        print('[ERROR] Player not found')
        return
    end

    local char = player:GetData('Character')

    if not char then
        TriggerClientEvent('mythic-notifications:client:Send', src, {
            message = 'You must be logged in as a character',
            type = 'error'
        })
        return
    end

    -- Safe to use char now
    local stateId = char:GetData('SID')
end)

Cash Management

Adding/Removing Cash:
-- Add cash
local player = Fetch:Source(source)
local char = player:GetData('Character')

local currentCash = char:GetData('Cash')
char:SetData('Cash', currentCash + 500)

-- Notify player
Notification:Success(source, 'Received $500 cash')
Remove Cash with Validation:
local player = Fetch:Source(source)
local char = player:GetData('Character')
local price = 250

local currentCash = char:GetData('Cash')

if currentCash >= price then
    char:SetData('Cash', currentCash - price)
    Notification:Success(source, string.format('Paid $%d', price))
else
    Notification:Error(source, 'Insufficient funds')
end
Checking Cash:
function CanAfford(source, amount)
    local player = Fetch:Source(source)
    if not player then return false end

    local char = player:GetData('Character')
    if not char then return false end

    local cash = char:GetData('Cash')
    return cash >= amount
end
Bank balances are managed through the Banking component (Banking.Balance:Get/Deposit/Withdraw/Charge), not through character SetData. See the Banking API for bank operations.

Iterating All Online Characters

-- Get all online players
local players = Fetch:All()

for source, player in pairs(players) do
    local char = player:GetData('Character')

    if char then
        local name = string.format('%s %s', char:GetData('First'), char:GetData('Last'))
        local stateId = char:GetData('SID')

        print(source, name, stateId)

        -- Send server-wide event
        TriggerClientEvent('myresource:client:serverEvent', source, eventData)
    end
end

print('Total characters online:', Fetch:CountCharacters())

Getting Player Source from SID

-- You have a character SID, need the player source
function GetSourceFromSID(stateId)
    local player = Fetch:SID(stateId)

    if player then
        return player:GetData('Source')
    end

    return nil
end

-- Usage
local targetSource = GetSourceFromSID(123)
if targetSource then
    TriggerClientEvent('myresource:client:event', targetSource, data)
end

Character Management Events

These events are triggered internally by the Characters system. You can listen to them but should not trigger them directly.

Character Selection

Event: Characters:Server:PlayerLoggedIn Triggered when player selects a character and spawns. Parameters:
  • source (number) - Player server ID
Example:
AddEventHandler('Characters:Server:PlayerLoggedIn', function(source)
    local player = Fetch:Source(source)
    local char = player:GetData('Character')

    print('Character logged in:', char:GetData('First'), char:GetData('Last'))

    -- Initialize systems
    TriggerEvent('inventory:server:loadInventory', source)
    TriggerEvent('jobs:server:loadJob', source)
end)

Character Logout

Event: Characters:Server:PlayerDropped Triggered when player disconnects. Parameters:
  • source (number) - Player server ID
Example:
AddEventHandler('Characters:Server:PlayerDropped', function(source)
    local player = Fetch:Source(source)

    if player then
        local char = player:GetData('Character')

        if char then
            print('Character logged out:', char:GetData('SID'))

            -- Save data
            TriggerEvent('inventory:server:saveInventory', source)
        end
    end
end)

Best Practices

✅ Correct:
local player = Fetch:Source(source)
if not player then return end
local char = player:GetData('Character')
if not char then return end
❌ Wrong:
local char = Characters:GetCharacter(source)  -- DOESN'T EXIST!
Character SID is the primary key for database queries:
-- ✅ Good: Use SID
local char = player:GetData('Character')
local stateId = char:GetData('SID')

Database.Game:findOne({
    collection = 'vehicles',
    query = {owner = stateId}
}, callback)

-- ❌ Bad: Don't use source
Database.Game:findOne({
    collection = 'vehicles',
    query = {owner = source}  -- Wrong! Source is player ID, not character ID
}, callback)
Use GetData/SetData methods, not direct table access:
-- ✅ Good: Use DataStore methods
local firstName = char:GetData('First')
char:SetData('Cash', 1000)

-- ❌ Bad: Direct access (may not work)
local firstName = char.First
char.Cash = 1000
function IsCharacterOnline(stateId)
    return Fetch:SID(stateId) ~= nil
end

-- Usage
if IsCharacterOnline(123) then
    -- Character is online, use Fetch
    local player = Fetch:SID(123)
    local char = player:GetData('Character')
else
    -- Character is offline, use database or GetOfflineData
    local phone = Fetch:GetOfflineData(123, 'Phone')
end
function ModifyCash(source, amount, reason)
    local player = Fetch:Source(source)
    if not player then return false end

    local char = player:GetData('Character')
    if not char then return false end

    local currentCash = char:GetData('Cash')

    if amount < 0 and currentCash < math.abs(amount) then
        return false  -- Insufficient funds
    end

    char:SetData('Cash', currentCash + amount)

    -- Log transaction
    Logger:Info('Economy', 'Cash modified', {
        console = true,
        file = true,
        database = true
    }, {
        characterSID = char:GetData('SID'),
        amount = amount,
        reason = reason
    })

    return true
end
-- Note: For bank operations, use Banking.Balance:Deposit/Withdraw/Charge

Complete Examples

Job Payment System

-- Server side
AddEventHandler('jobs:server:processPaycheck', function()
    local players = Fetch:All()

    for source, player in pairs(players) do
        local char = player:GetData('Character')

        if char then
            local jobs = char:GetData('Jobs')  -- Array of job objects

            if jobs and #jobs > 0 then
                for _, job in ipairs(jobs) do
                    -- Calculate pay based on job
                    local payAmount = GetJobPayAmount(job.Id, job.GradeLevel)

                    if payAmount > 0 then
                        -- Add to cash
                        local currentCash = char:GetData('Cash')
                        char:SetData('Cash', currentCash + payAmount)

                        -- Notify player
                        Notification:Success(source,
                            string.format('Paycheck: $%d received', payAmount))
                    end
                end
            end
        end
    end
end)

Shop Purchase System

-- Server side
Callbacks:RegisterServerCallback('shop:purchaseItem', function(source, data, cb)
    local player = Fetch:Source(source)
    if not player then
        return cb(false, 'Player not found')
    end

    local char = player:GetData('Character')
    if not char then
        return cb(false, 'Not logged in')
    end

    -- Get item data
    local itemData = GetShopItem(data.shopId, data.itemName)
    if not itemData then
        return cb(false, 'Item not available')
    end

    local totalCost = itemData.price * data.count
    local currentCash = char:GetData('Cash')

    -- Check funds
    if currentCash < totalCost then
        return cb(false, 'Insufficient funds')
    end

    -- Deduct money
    char:SetData('Cash', currentCash - totalCost)

    -- Add item to inventory
    local stateId = char:GetData('SID')
    local success, slot = Inventory:AddItem(
        stateId,
        data.itemName,
        data.count
    )

    if success then
        cb(true, {
            item = data.itemName,
            count = data.count,
            cost = totalCost,
            remaining = currentCash - totalCost
        })

        -- Log purchase
        Logger:Info('Shop', 'Purchase completed', {
            console = true,
            file = true,
            database = true
        }, {
            characterSID = stateId,
            item = data.itemName,
            count = data.count,
            cost = totalCost,
            shop = data.shopId
        })
    else
        -- Refund on failure
        char:SetData('Cash', currentCash)
        cb(false, 'Inventory full')
    end
end)

Next Steps

Core - Fetch

Complete Fetch component documentation

Characters - Events

Character-related events

Characters - Data Structure

Character data schema

Inventory API

Inventory management
Critical Pattern: Always use Fetch:Source(source) to get the player, then player:GetData('Character') to get the character. This is the ONLY correct way to access character data.