Stream Deck plugin for sending commands to the FiveM or RedM client console.
Works with a physical Stream Deck, Stream Deck + or Stream Deck Mobile connected to the same PC running the game.
josh.tf/fxcommands | Wiki & Docs
| Feature | Description |
|---|---|
| Single commands | Send any FiveM/RedM console command with one button press |
| Chained commands | Run multiple commands sequentially with ; separator |
| Delayed commands | Add timed pauses between commands with ;; or {NNNms} |
| Staged buttons | Cycle through up to 5 different commands per button |
| Press and release | Separate commands for key down and key up events |
| Dials (Stream Deck +) | Separate commands for push, rotate left/right, and screen tap |
| Command responses | Show a value returned by your server on the button or dial |
| FiveM and RedM | Works with both games out of the box |
FXCommands on the Stream Deck Store
- Download the latest
.streamDeckPluginfrom the Releases page - Double-click the file and accept the Stream Deck installation prompt
Drag the FXCommands Action onto your Stream Deck and enter the command to execute. You can run a command on press, on release, or both.
# Single command
e wave
# Chained commands (no delay)
e sit;me relaxes on the ground
# Delayed commands
e think;me thinking;{2000ms};e cFor a toggle button, set Stages to 2 and fill in each stage separately rather than writing
one command, for example e sit on stage 0 and e c on stage 1. The button advances a stage on each
press.
Drop the action onto the dial row of a Stream Deck + and the Property Inspector gains a Dial section with three extra commands. Pushing the dial uses the same Command / On Release fields as a key, so a dial can also cycle through stages.
| Trigger | Field | Runs when |
|---|---|---|
| Push | Command / On Release | The dial is pressed and released |
| Rotate left | Rotate Left | The dial turns anticlockwise |
| Rotate right | Rotate Right | The dial turns clockwise |
| Touch | Touch | The touch strip above the dial is tapped |
Rotation commands support placeholders that are substituted before the command is sent:
| Placeholder | Value |
|---|---|
{ticks} |
Detents moved this event, negative left, positive right |
{rotationPercent} |
Running position as 0 to 100 |
{rotationAbsolute} |
Running position as 0 to 255 |
# Send the raw detent delta, letting the server apply it
radio_volume_adjust {ticks}
# Send an absolute target the server can set directly
radio_volume_set {rotationPercent}Note
{rotationPercent} and {rotationAbsolute} track a counter held by the plugin, not the game's
real value. It starts at 0, is clamped to 0-255, and will drift if something changes the
value in-game. Prefer {ticks} unless your server echoes its state back.
A button can display a value your server sends back: current radio volume, fuel level, on-duty
count, anything you can print. It needs a change on the server, so it stays out of the way until
you ask for it: tick Enable command responses in Advanced Settings, and the per-command
Show response checkboxes appear.
When Show response is ticked, FXCommands appends a short correlation token to the command:
radio_voldown @fxid:ab12cd34
It then watches the console for a line containing that same token, strips the token out, and shows what's left. The token exists so that pressing several buttons at once doesn't cross wires. If nothing matches before the response timeout, the button is left unchanged.
Your command needs to pull the token off the end of its arguments and echo it back with the value. Client-side Lua:
RegisterCommand('radio_voldown', function(source, args)
local sdToken = nil
if args[#args] and args[#args]:match("^@fxid:") then
sdToken = table.remove(args)
end
-- Regular command logic here
if sdToken then
print(("%s %s"):format(sdToken, "60.0"))
end
end)Important
Print the token on the same line as the value. Its position doesn't matter, it's stripped out and whatever remains is displayed. Print nothing else on that line.
Caution
Only tick Show response for commands whose handler strips the token, as shown above. Any
other command will receive @fxid:... as an unexpected extra argument.
| Control | Where the value appears |
|---|---|
| Key | The button title |
| Dial | The touch-strip value field |
For keys, Response Label controls the formatting. Use {value} for the response, press Enter
for a line break, and leave it blank to replace the title with the raw response.
Volume
{value}
On a dial, a response formatted as a percentage additionally draws a progress bar. Print a
trailing % to opt in. 60% shows the bar, 60 shows the value alone.
print(("%s %d%%"):format(sdToken, volume))Get Value runs whenever the button appears (plugin start, profile load, page switch) so the button shows the current value instead of a stale one. It captures a response the same way, so its handler needs the same token handling.
Tick Also run after commands without "Show response" to re-read the value after any command on that button, which is useful when the command that changes a value isn't the one that reports it.
Response Timeout in Advanced Settings controls how long to wait, defaulting to 1500ms. The ceiling is 5000ms because the FiveM client drops the connection after five seconds of inactivity.
For full syntax reference, examples, and advanced setups see the Wiki.
- Node.js 20+
- Stream Deck 6.9+
npm install
npm run buildOutput:
dist/tf.josh.fxcommands.sdPlugin/unpacked plugindist/tf.josh.fxcommands.streamDeckPlugininstallable package
npm run watchnpm run check # typecheck + lint + format + spell + circular depsscripts/fivem-emulator.cjs stands in for the game so the plugin can be exercised without FiveM
running. It decodes the commands the plugin sends, answers the handshake, and replies to any
command carrying an @fxid: token so command responses can be tested end to end.
node scripts/fivem-emulator.cjs # echo commands, reply with a simulated value
node scripts/fivem-emulator.cjs --percent # reply as "60%" to exercise the dial bar layout
node scripts/fivem-emulator.cjs --drop # never reply, to exercise the capture timeout
node scripts/fivem-emulator.cjs --garbage # prefix replies with junk, to exercise frame resync
node scripts/fivem-emulator.cjs --split # split replies across TCP chunks
node scripts/fivem-emulator.cjs --delay=2000 --value=75To exercise the Server IP and Server Port overrides, move the emulator off the default
127.0.0.1:29200 and point a button at it.
node scripts/fivem-emulator.cjs --cl2 # listen on 29300, the port a -cl2 client uses
node scripts/fivem-emulator.cjs --port=29300 # the same thing, spelled out
node scripts/fivem-emulator.cjs --host=0.0.0.0 # accept connections from another machineTwo emulators on different ports can run side by side, which is the quickest way to check that each button keeps its own address.
See the Contributing guide for full development setup.
- Wiki - Full documentation, examples, and guides
- Troubleshooting - Common issues and fixes
- Issues - Report a bug or request a feature

