Skip to main content
The Middleware component provides a middleware layer for intercepting events, enabling logging, monitoring, data collection, and side-effects when events fire.

Overview

Middleware handlers are registered for specific events and execute in priority order when that event is triggered. They are used for:

Intercept Events

Run code when events fire

Log Activity

Log all event activity automatically

Collect Data

Gather data from multiple handlers

Side Effects

Trigger additional actions on events
Important: Middleware handlers in TriggerEvent run all registered handlers regardless of return values. Middleware does NOT block or prevent event execution — it runs side-effects alongside events.

Methods

Add

Register middleware for a specific event.
Middleware:Add(eventName, handler, priority)
eventName
string
required
Name of the event to intercept
handler
function
required
Middleware function to execute.Parameters:
  • source (number) - Player who triggered event (if applicable)
  • ... - Additional event parameters
priority
number
default:"1"
Execution priority (lower numbers execute first)
Example:
-- Log all player connections
Middleware:Add("playerConnecting", function(source, name, setKickReason, deferrals)
    Logger:Info("Connections", string.format("Player connecting: %s", name), {
        console = true,
        file = true
    })
end, 50)

TriggerEvent

Trigger all middleware handlers for an event. All handlers run in priority order — errors in one handler do not stop others.
Middleware:TriggerEvent(eventName, source, ...)
eventName
string
required
Event name to trigger
source
number
required
Player source (or 0 if not player-related)
...
any
Additional parameters passed to all handlers
Behavior:
  1. Handlers are sorted by priority (lowest first)
  2. Each handler runs via pcall — errors are caught and printed, never crash the server
  3. All handlers run regardless — return values are ignored
  4. Errors print to console: [Middleware] ERROR in 'eventName' handler (priority X): error message
Example:
-- Framework internally triggers middleware
Middleware:TriggerEvent("Characters:Spawning", source, characterData)

TriggerEventWithData

Trigger middleware and collect return values from all handlers into a combined table. This is used when you need handlers to contribute data.
local data = Middleware:TriggerEventWithData(eventName, source, ...)
eventName
string
required
Event name to trigger
source
number
required
Player source
...
any
Additional parameters
Returns: A combined table of all handler return values. Each handler should return an array of tables. Each returned item gets an ID field assigned for ordering. Handler return format:
-- Handler should return an array of tables
Middleware:Add("myEvent", function(source, ...)
    return {
        { name = "Item 1", value = 100 },
        { name = "Item 2", value = 200 }
    }
end)
Example — collecting menu items from multiple resources:
-- Resource A registers middleware
Middleware:Add("interaction:getOptions", function(source, targetId)
    return {
        { label = "Search", action = "search" },
        { label = "Frisk", action = "frisk" }
    }
end, 10)

-- Resource B registers middleware
Middleware:Add("interaction:getOptions", function(source, targetId)
    return {
        { label = "Give Keys", action = "givekeys" }
    }
end, 20)

-- Triggering code collects all options
local options = Middleware:TriggerEventWithData("interaction:getOptions", source, targetId)
-- options = {
--   { label = "Search", action = "search", ID = 1 },
--   { label = "Frisk", action = "frisk", ID = 2 },
--   { label = "Give Keys", action = "givekeys", ID = 3 }
-- }

Priority System

Lower priority numbers execute first:
-- Priority 1 - Executes FIRST (default)
Middleware:Add("event", handler1)

-- Priority 10 - Executes SECOND
Middleware:Add("event", handler2, 10)

-- Priority 100 - Executes THIRD
Middleware:Add("event", handler3, 100)
The default priority is 1 if not specified.
Recommended Priorities:
PriorityUse CaseExample
1-10Critical handlers, securityAnti-cheat logging
11-50Core system handlersPermission logging
51-100General logging, monitoringActivity logs
101+Low-priority processingAnalytics

Common Use Cases

Logging Player Actions

-- Log all admin actions
Middleware:Add("Admin:Action", function(source, action, target, details)
    Logger:Info("Admin", string.format(
        "%s performed %s on %s",
        GetPlayerName(source),
        action,
        tostring(target)
    ), {
        console = true,
        file = true,
        discord = true
    })
end, 50)

Tracking Connections

-- Log player connections with identifiers
Middleware:Add("playerConnecting", function(source, name, setKickReason, deferrals)
    Logger:Info("Connections", string.format("Player connecting: %s (source: %s)", name, source), {
        console = true,
        file = true,
        database = true
    }, {
        name = name,
        source = source,
        timestamp = os.time()
    })
end, 50)

Economy Monitoring

-- Log money transactions
Middleware:Add("Finance:Transaction", function(source, amount, type, target)
    Logger:Info("Economy", string.format(
        "Transaction: $%d (%s) from %s to %s",
        amount, type, tostring(source), tostring(target)
    ), {
        console = true,
        file = true
    })
end, 100)

Anti-Cheat Detection Logging

-- Log suspicious events
Middleware:Add("AntiCheat:Detection", function(source, cheatType, details)
    Logger:Critical("AntiCheat", string.format(
        "Detection: %s by source %s",
        cheatType, tostring(source)
    ), {
        console = true,
        file = true,
        discord = {
            embed = true,
            type = "critical",
            title = "Anti-Cheat Detection",
            content = "@here Cheater detected"
        },
        database = true
    }, {
        source = source,
        cheatType = cheatType,
        details = details,
        timestamp = os.time()
    })
end, 1)

Collecting Data from Multiple Resources

-- Each job resource contributes its duty status
Middleware:Add("Jobs:GetDutyStatus", function(source, ...)
    local policeOnDuty = GetPoliceOnDuty()
    return {
        { job = "police", onDuty = #policeOnDuty, players = policeOnDuty }
    }
end, 10)

Middleware:Add("Jobs:GetDutyStatus", function(source, ...)
    local emsOnDuty = GetEMSOnDuty()
    return {
        { job = "ems", onDuty = #emsOnDuty, players = emsOnDuty }
    }
end, 20)

-- Collect all duty info
local dutyInfo = Middleware:TriggerEventWithData("Jobs:GetDutyStatus", 0)
-- dutyInfo contains entries from all registered handlers

Error Handling

Middleware handlers are wrapped in pcall. If a handler errors:
  1. The error is printed to console with the event name and priority
  2. Other handlers continue to execute — one error does not stop the chain
  3. For TriggerEventWithData, errored handlers contribute nothing to the result
^1[Middleware] ERROR in 'playerConnecting' handler (priority 50): attempt to index a nil value^7

Resource Restart Behavior

Middleware handlers are cleared when any resource starts (except the current resource). This means:
  • Handlers are re-registered when resources restart
  • Stale handlers from stopped resources are cleaned up automatically

Best Practices

-- ✅ Good: Critical logging first
Middleware:Add("event", securityLog, 1)
Middleware:Add("event", generalLog, 50)
Middleware:Add("event", analytics, 200)
Middleware runs on every event trigger. Keep handlers fast:
-- ✅ Good: Quick logging
Middleware:Add("event", function(source, data)
    Logger:Info("Monitor", "Event fired", { file = true })
end, 100)

-- ❌ Bad: Heavy database query in middleware
Middleware:Add("event", function(source, data)
    Database.Game:find({ collection = "all_data", query = {} }, function(success, results)
        -- This is too slow for middleware
    end)
end, 100)
When you need handlers to contribute data, use TriggerEventWithData:
-- ✅ Good: Return data arrays for collection
Middleware:Add("getOptions", function(source, target)
    return {
        { label = "Option A", callback = "doA" },
        { label = "Option B", callback = "doB" }
    }
end)

local options = Middleware:TriggerEventWithData("getOptions", source, target)
For plain side-effects (logging, notifications), use TriggerEvent instead.
Unlike some frameworks, Mythic middleware does NOT support blocking:
-- ❌ This does NOT prevent the event from firing
Middleware:Add("event", function(source, data)
    return false  -- Return value is IGNORED by TriggerEvent
end)
If you need to prevent an action, implement the check in the event handler itself, not in middleware.

Next Steps

Event System

Understanding the event system

Base API

Core framework exports

Logger API

Logging component

Callbacks

Request-response communication