Skip to main content
Components are self-contained units of functionality that encapsulate related methods and data. They register with the framework via RegisterComponent, expose a public API, and can depend on or extend other components.

Component Structure

A typical component looks like this:
local MyComponent = {
    -- Private data (underscore convention)
    _data = {},

    -- Public methods (always use self parameter)
    DoSomething = function(self, param)
        -- Method implementation
        return result
    end,

    -- Internal helper (underscore = private by convention)
    _helperMethod = function(self)
        -- Not intended for external use
    end,

    -- Component metadata
    _protected = false,  -- Prevent other resources from overriding?
    _required = {},      -- Method names that must exist
    _name = 'mycomponent' -- Name used in logging
}

-- Register with framework
exports['mythic-base']:RegisterComponent('MyComponent', MyComponent)
All public methods use function(self, ...) and are called with colon syntax: Component:Method(args). The self parameter is automatic with : calls.

Core Components

Provided by mythic-base, these are the essential components every resource depends on:

Callbacks

Client-server callback system (request-response)

Logger

Centralized logging with levels and Discord webhooks

Database

MongoDB and MySQL database operations

Middleware

Event middleware for pre/post processing

Fetch

Player and character data access

DataStore

Key-value persistent data storage
Usage:
-- Logging
Logger:Info('MyResource', 'Something happened', { data = 'value' })
Logger:Error('MyResource', 'Error occurred', { console = true })

-- Database (async with callbacks)
Database.Game:findOne({
    collection = 'characters',
    query = { SID = 1 }
}, function(success, character)
    if success and character then
        print('Found character:', character.First)
    end
end)

-- Callbacks (server-side)
Callbacks:RegisterServerCallback('myresource:GetData', function(source, data, cb)
    cb({ success = true, data = someData })
end)

Feature Components

Registered by feature resources (mythic-inventory, mythic-jobs, etc.) to expose their APIs:
-- These are accessed via COMPONENTS after RetrieveComponents()
Inventory:AddItem(owner, 'water', 5)
Jobs.Permissions:HasJob(source, 'police')
Vehicles.Owned:GetPlayerVehicles(source)
Phone.Notification:Add(source, 'Title', 'Description', os.time(), 5000, 'phone')
Components use namespacing with dots to organize related functionality:
-- Inventory sub-components
Inventory.Items:Has(source, 'lockpick', 1)
Inventory.Items:Remove(source, 'lockpick', 1)
Inventory.Items:GetCount(source, 'water')

-- Jobs sub-components
Jobs.Permissions:HasJob(source, 'police')
Jobs.Duty:On(source, 'police')

-- Vehicles sub-components
Vehicles.Owned:Spawn(source, vehicleData)
Vehicles.Keys:Has(source, vehicleNet)

Creating Components

Basic Component

-- mythic-myresource/server/component.lua

exports['mythic-base']:RegisterComponent('MyFeature', {
    _protected = true,
    _name = 'myfeature',

    DoAction = function(self, player, data)
        Logger:Info('MyFeature', 'Action performed', {
            player = player,
            data = data
        })
        return true
    end,

    GetData = function(self, id, callback)
        Database.Game:findOne({
            collection = 'myfeature',
            query = { _id = id }
        }, function(success, result)
            if callback then
                callback(success, result)
            end
        end)
    end
})

Component with Dependencies

Use RequestDependencies to ensure required components are loaded first:
-- mythic-shop/server/component.lua

AddEventHandler('Core:Shared:Ready', function()
    exports['mythic-base']:RequestDependencies('Shop', {
        'Inventory',
        'Fetch',
        'Logger'
    }, function(errors)
        if #errors > 0 then
            return
        end

        -- All dependencies loaded, safe to use
        RetrieveComponents()
        RegisterCallbacks()
        Startup()
    end)
end)

-- After RetrieveComponents(), components are available as locals
function RegisterCallbacks()
    Callbacks:RegisterServerCallback('mythic-shop:Purchase', function(source, data, cb)
        local char = Fetch:Source(source):GetData('Character')
        -- Purchase logic...
        cb({ success = true })
    end)
end

The RetrieveComponents Pattern

Most resources use a standard pattern where RetrieveComponents() pulls all needed components into local variables:
local Inventory, Callbacks, Fetch, Logger

function RetrieveComponents()
    Inventory = exports['mythic-base']:FetchComponent('Inventory')
    Callbacks = exports['mythic-base']:FetchComponent('Callbacks')
    Fetch = exports['mythic-base']:FetchComponent('Fetch')
    Logger = exports['mythic-base']:FetchComponent('Logger')
end

AddEventHandler('Core:Shared:Ready', function()
    exports['mythic-base']:RequestDependencies('MyResource', {
        'Inventory', 'Callbacks', 'Fetch', 'Logger'
    }, function(errors)
        if #errors == 0 then
            RetrieveComponents()
        end
    end)
end)
After RetrieveComponents(), you use the local variables directly instead of X everywhere.

Component Patterns

Service Pattern

Stateless components that provide services:
exports['mythic-base']:RegisterComponent('Notifications', {
    Send = function(self, player, message, type)
        TriggerClientEvent('mythic-notifications:client:Send', player, {
            message = message,
            type = type or 'info',
            duration = 5000
        })
    end,

    SendError = function(self, player, message)
        self:Send(player, message, 'error')
    end,

    SendSuccess = function(self, player, message)
        self:Send(player, message, 'success')
    end
})

Manager Pattern

Components that manage a collection of entities:
exports['mythic-base']:RegisterComponent('VehicleManager', {
    _vehicles = {},

    Register = function(self, vehicleId, data)
        self._vehicles[vehicleId] = data
    end,

    Unregister = function(self, vehicleId)
        self._vehicles[vehicleId] = nil
    end,

    Get = function(self, vehicleId)
        return self._vehicles[vehicleId]
    end,

    GetAll = function(self)
        return self._vehicles
    end
})

Extending Components

Use ExtendComponent to add functionality to an existing component without modifying the original resource:
-- Store reference to original method
local originalAddItem = Inventory.AddItem

-- Extend with additional behavior
exports['mythic-base']:ExtendComponent('Inventory', {
    AddItem = function(self, player, item, count)
        -- Log before
        Logger:Info('Inventory', 'Adding item', {
            player = player,
            item = item,
            count = count
        })

        -- Call original
        local success = originalAddItem(self, player, item, count)

        return success
    end
})
ExtendComponent cannot modify protected components (_protected = true). It will fail with a warning.

Component Communication

Direct Method Calls

-- Simple and fast
local inventory = Inventory:Get(characterId)
Notification:Send(source, 'Item received', 'success')

Event-Based Communication

-- Component triggers event for other systems to react
exports['mythic-base']:RegisterComponent('Shop', {
    Purchase = function(self, player, item, price)
        local success = self:_processPurchase(player, item, price)
        if success then
            TriggerEvent('Shop:PurchaseComplete', player, item, price)
        end
        return success
    end
})

-- Other components listen
AddEventHandler('Shop:PurchaseComplete', function(player, item, price)
    Logger:Info('Shop', 'Purchase complete', { player = player, item = item })
end)

Best Practices

Each component should have one clear purpose:
-- Good: focused component
Garage = {
    SpawnVehicle = function(self, ...) end,
    StoreVehicle = function(self, ...) end,
    GetVehicles = function(self, ...) end,
}

-- Bad: does too many unrelated things
Everything = {
    SpawnVehicle = function(self, ...) end,
    GetInventory = function(self, ...) end,
    SendEmail = function(self, ...) end,
}
Never assume components are available immediately:
-- Bad: may be nil if component hasn't loaded yet
local Inventory = Inventory

-- Good: wait for dependencies
exports['mythic-base']:RequestDependencies('MyResource', { 'Inventory' }, function(errors)
    if #errors == 0 then
        RetrieveComponents()
    end
end)
Always handle errors gracefully:
exports['mythic-base']:RegisterComponent('Payment', {
    Charge = function(self, player, amount)
        if not player or amount <= 0 then
            return false, 'Invalid parameters'
        end

        local balance = self:GetBalance(player)
        if balance < amount then
            return false, 'Insufficient funds'
        end

        local success = self:_deductBalance(player, amount)
        if not success then
            Logger:Error('Payment', 'Failed to deduct balance')
            return false, 'Transaction failed'
        end

        return true
    end
})
Prevent accidental overrides of critical components:
exports['mythic-base']:RegisterComponent('Inventory', {
    _protected = true,
    -- Methods cannot be overridden by other resources
})

Next Steps

Proxy Pattern

Deep dive into RegisterComponent, FetchComponent, etc.

Event System

Complement components with events and callbacks

Resource Structure

How to organize resources using components

API Reference

Complete API documentation