Actions are voice commands. Register them with registerAction and the SDK handles speech recognition, matching, variable extraction, and trigger execution.
Syntaxexports['onex-voiceInteraction']:registerAction(def)
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | ✅ | — | Unique action identifier. Duplicate names are rejected with DUPLICATE. |
phrases | string[] | ✅ | — | Voice phrases to match. Use {variable} for extraction slots. Must be non-empty. |
trigger | table | ✅ | — | What executes on match. At least one trigger type required. |
description | string | — | nil | Natural language description for LLM tier matching. Max 200 chars. |
minSimilarity | number | — | 0.7 | Minimum match score (0.0–1.0). Falls back to Config.Matching.MinConfidence. |
group | string | — | nil | Group name for batch enable/disable. |
tags | string[] | — | {} | Additional tags. Can be disabled like groups with setActionEnabled. |
layer | string | — | auto | Layer assignment. Auto-assigned if omitted — see Layer Auto-Assignment below. |
condition | function | — | nil | Pre-execution gate. Receives callback data. Return false to block. |
variableAliases | table | — | nil | Per-variable synonym map { amount = "sum" }. |
variableTypes | table | — | nil | Per-variable type hints: "number", "boolean", "string". |
variableDefaults | table | — | nil | Custom word-to-value fallback mappings { size = "large" }. |
numberNormalization | boolean | — | nil | Enable automatic word-to-number parsing (e.g. "five" → 5). |
{ success = true, data = { name = "buy_item" } } { success = false, error = "...", code = "DUPLICATE" }
If layer is not specified, it is automatically assigned based on the group field:
explicit layer field → uses that layer
has group, no layer → "context"
no group, no layer → "ambient"
Actions on "ambient" are active when no custom layer suppresses ambient. Actions on "context" or custom layers require the layer to be entered with a matching group. Actions on "global" are always active.
Your Scriptexports['onex-voiceInteraction']:registerAction({ name = "buy_item", phrases = { "buy {item}", "i want {item}", "give me {item}" }, trigger = { event = { name = "myres:buyItem", type = "server", params = { shopId = "liquor_1" } } }, group = "voice_shop_liquor_1", minSimilarity = 0.7 })
Within the same resource, use RegisterAction (global) instead of the export. Lua callbacks do not survive export boundaries — only the global bypasses this:
Your Script-- Inside the same resource as onex-voiceInteraction scripts RegisterAction({ name = "internal_command", phrases = { "test voice" }, trigger = { callback = function(data) print("Matched:", data.transcript) end } })
The trigger field is a table that can contain any combination of types. All provided types fire simultaneously when the action matches.
Syntaxtrigger = { callback = function(data) end, -- direct function call event = { ... }, -- single client/server event events = { ... }, -- multiple events export = { ... }, -- another resource's export }
At least one type must be present.
Direct function call. Best for logic that lives in the same resource.
Your Scripttrigger = { callback = function(data) print("Transcript:", data.transcript) print("Confidence:", data.confidence) print("Amount:", data.extractedVariables.amount) end }
Fire one client or server event.
Your Scripttrigger = { event = { name = "myresource:giveWater", -- required type = "server", -- "client" or "server" (required) params = { itemId = "water" } -- merged into callback data(optional) } }
The receiving handler gets all standard callback data fields plus your custom params:
Server HandlerRegisterNetEvent('myresource:giveWater') AddEventHandler('myresource:giveWater', function(data) -- data.transcript, data.confidence, data.score, data.extractedVariables -- data.itemId = "water" (from params) end)
Fire multiple events simultaneously.
Your Scripttrigger = { events = { { name = "combat:attack", type = "client" }, { name = "combat:log", type = "server", params = { logAction = true } } } }
Call an export function from another resource. Executed as exports["resource"].name(mergedData).
Your Scripttrigger = { export = { resource = "ox_inventory", -- required name = "openInventory", -- required params = { type = "player" } } }
Your Scriptexports['onex-voiceInteraction']:registerAction({ name = "buy_drink", phrases = { "buy {item}" }, trigger = { event = { name = "myres:logPurchase", type = "server", params = { shopId = "liquor_1" } }, callback = function(data) PlaySoundFrontend(-1, "PURCHASE", "HUD_LIQUOR_STORE_SOUNDSET", false) end }, group = "voice_shop_liquor_1" })
Every trigger receives this data object:
| Field | Type | Description |
|---|---|---|
transcript | string | Recognized speech text |
confidence | number | STT confidence (0.0–1.0) |
command | table | Full action definition |
score | number | Match score (0.0–1.0) |
tier | number | Matching tier used (1–4) |
method | string | Tier label: "tier1", "tier2", "tier3", or "llm" |
extractedVariables | table | Variables from {placeholder} phrases |
For event, events, and export triggers, the params table is merged into this object before dispatch.
Use {variable} in phrases to capture spoken words. Extracted values arrive in data.extractedVariables.
Your Scriptexports['onex-voiceInteraction']:registerAction({ name = "give_cash", phrases = { "give me {amount} dollars", "transfer {amount}" }, trigger = { callback = function(data) local amount = data.extractedVariables.amount print("Amount:", amount) end }, variableTypes = { amount = "number" }, variableAliases = { amount = "sum" }, variableDefaults = { amount = "100" } })
Coerce extracted string values to Lua types:
| Value | Behavior |
|---|---|
"number" | tonumber() — "five" becomes 5 |
"boolean" | "yes"/"true" → true, others → false |
"string" | No conversion (default) |
Map spoken synonyms to canonical variable names. If the player says "sum" and you define { amount = "sum" }, the extracted value appears as extractedVariables.amount.
Fallback values used when the variable is not extracted from speech.
A pre-execution gate. If it returns false, the action is silently skipped — no trigger fires, no event emitted.
Your Scriptexports['onex-voiceInteraction']:registerAction({ name = "rob_npc", phrases = { "give me your money", "hands up" }, condition = function(data) -- Only allow if player has a weapon drawn return IsPedArmed(PlayerPedId(), 4) end, trigger = { callback = function(data) TriggerEvent('robbery:start') end } })
The condition function receives the same data object as triggers (transcript, confidence, etc.).
Syntaxexports['onex-voiceInteraction']:unregisterAction(target)
| Target form | Behavior |
|---|---|
"action_name" | Unregister that specific action |
"@resource_name" | Unregister all actions from that resource |
| (omitted) | Unregister all actions from the calling resource |
Static actions (registered from config.lua) cannot be unregistered — they return STATIC_ACTION.
Actions from another resource cannot be unregistered — returns PERMISSION_DENIED.
Resources are also auto-cleaned when they stop (onResourceStop).
Your Scriptexports['onex-voiceInteraction']:unregisterAction("buy_item") exports['onex-voiceInteraction']:unregisterAction("@my-resource") exports['onex-voiceInteraction']:unregisterAction()
Syntaxexports['onex-voiceInteraction']:queryActions(filter)
All filter fields are optional. Omit to return all registered actions.
| Filter field | Type | Description |
|---|---|---|
name | string | Exact action name |
group | string | Group name |
tag | string | Tag value |
enabled | boolean | Current enabled state |
resource | string | Source resource name |
Returns: { success = true, data = { actions = { ... }, count = 5 } }
Each action in the array includes: name, phrases, trigger, minSimilarity, source, isStatic, group, tags, enabled.
Your Scriptlocal r = exports['onex-voiceInteraction']:queryActions({ group = "voice_shop_1", enabled = true }) for _, action in ipairs(r.data.actions) do print(action.name, action.enabled) end
Syntaxexports['onex-voiceInteraction']:setActionEnabled(target, enabled)
| Target form | Behavior |
|---|---|
"action_name" | Enable/disable that specific action |
"#group_name" | Enable/disable all actions with that group |
"*" | Enable/disable all registered actions |
Your Scriptexports['onex-voiceInteraction']:setActionEnabled("buy_item", false) exports['onex-voiceInteraction']:setActionEnabled("#voice_shop_1", false) exports['onex-voiceInteraction']:setActionEnabled("*", true)
Syntaxexports['onex-voiceInteraction']:getActionEnabled(target)
| Target form | Returns |
|---|---|
"action_name" | { enabled = true } |
"#group_name" | { enabled = true } |
| (omitted) | { disabledActions = { ... }, disabledGroups = { ... } } |
Your Scriptlocal r = exports['onex-voiceInteraction']:getActionEnabled("#voice_shop_1") print("shop group enabled:", r.data.enabled)
| Code | When it occurs |
|---|---|
INVALID_INPUT | def is not a table, or required fields are missing/wrong type |
DUPLICATE | An action with that name is already registered |
NOT_FOUND | Target action name does not exist |
NOT_INITIALIZED | Action registry not yet ready (called too early) |
STATIC_ACTION | Attempt to unregister a config-defined action |
PERMISSION_DENIED | Attempt to unregister an action owned by another resource |
Last updated 14 days ago