Skip to content
Writing your own txAdmin recipe

Writing your own txAdmin recipe

Build a txAdmin recipe step by step: an example that installs oxmysql, ox_lib, ox_target, pma-voice and a starter resource, creates a table and writes server.cfg.

Last updated

A recipe is the best way to make your server reproducible. Your dev server, your test server and a fresh live server can all come from the same file, and new developers on your team get a working setup in two minutes. This page builds a complete example recipe and explains each decision. The task reference is in Recipes explained.

Important

The recipe below is an example written for these docs. The resource URLs are real (they are the same ones the official Qbox and ESX recipes use), but pin versions you have tested before you rely on it.

What we’ll build

A lean, framework free base for a custom game mode:

  • the default Cfx resources (mapmanager, spawnmanager, basic-gamemode…),
  • oxmysql (database), ox_lib (UI and utilities), ox_target (third eye interactions),
  • pma-voice (proximity voice) and bob74_ipl (loads GTA’s online interiors),
  • one starter resource of our own, created by the recipe,
  • a database with one table,
  • a server.cfg with everything wired up.

Plan the folder layout

Text
resources/
├─ [cfx-default]/    from citizenfx/cfx-server-data
├─ [ox]/             oxmysql, ox_lib, ox_target
├─ [standalone]/     pma-voice, bob74_ipl
└─ [local]/          my_starter (our own)
server.cfg

The recipe

my-base.yaml
$engine: 3
$onesync: on
name: My Base
version: 1.0.0
author: YourName
description: |
  A lean base with oxmysql, ox_lib, ox_target, pma-voice and a starter resource.
  No framework. Example recipe from the fivemad docs.

variables:
  dbName: null   # let txAdmin create the database with a random name

tasks:
  # 1. Database first: the most likely step to fail
  - action: connect_database

  - action: query_database
    query: |
      CREATE TABLE IF NOT EXISTS `player_stats` (
        `license` VARCHAR(60) NOT NULL PRIMARY KEY,
        `playtime` INT UNSIGNED NOT NULL DEFAULT 0,
        `last_seen` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

  # 2. Default Cfx resources, minus the old chat (the artifact has a built in one)
  - action: download_github
    src: https://github.com/citizenfx/cfx-server-data
    ref: master
    subpath: resources
    dest: ./resources/[cfx-default]

  - action: remove_path
    path: ./resources/[cfx-default]/[gameplay]/chat

  # 3. Overextended libraries from their release zips
  - action: download_file
    url: https://github.com/overextended/oxmysql/releases/latest/download/oxmysql.zip
    path: ./tmp/oxmysql.zip
  - action: unzip
    src: ./tmp/oxmysql.zip
    dest: ./resources/[ox]

  - action: download_file
    url: https://github.com/overextended/ox_lib/releases/latest/download/ox_lib.zip
    path: ./tmp/ox_lib.zip
  - action: unzip
    src: ./tmp/ox_lib.zip
    dest: ./resources/[ox]

  - action: download_file
    url: https://github.com/overextended/ox_target/releases/latest/download/ox_target.zip
    path: ./tmp/ox_target.zip
  - action: unzip
    src: ./tmp/ox_target.zip
    dest: ./resources/[ox]

  # 4. Standalone resources straight from GitHub
  - action: download_github
    src: https://github.com/AvarianKnight/pma-voice
    dest: ./resources/[standalone]/pma-voice

  - action: download_github
    src: https://github.com/Bob74/bob74_ipl
    dest: ./resources/[standalone]/bob74_ipl

  # 5. Our own starter resource, written by the recipe
  - action: write_file
    file: ./resources/[local]/my_starter/fxmanifest.lua
    data: |
      fx_version 'cerulean'
      game 'gta5'

      name 'my_starter'
      description 'Starter resource created by the My Base recipe'
      version '1.0.0'

      shared_script '@ox_lib/init.lua'
      server_scripts {
          '@oxmysql/lib/MySQL.lua',
          'server.lua',
      }
      client_script 'client.lua'

      dependencies { 'oxmysql', 'ox_lib' }

  - action: write_file
    file: ./resources/[local]/my_starter/server.lua
    data: |
      AddEventHandler('playerJoining', function()
          local src = source
          local license = GetPlayerIdentifierByType(src, 'license')
          if not license then return end
          MySQL.insert('INSERT INTO player_stats (license) VALUES (?) ON DUPLICATE KEY UPDATE last_seen = NOW()', { license })
      end)

  - action: write_file
    file: ./resources/[local]/my_starter/client.lua
    data: |
      RegisterCommand('hello', function()
          lib.notify({ title = 'My Base', description = 'Hello from my_starter!', type = 'success' })
      end, false)

  # 6. server.cfg with placeholders
  - action: write_file
    file: ./server.cfg
    data: |
      ## Generated by the My Base recipe
      {{serverEndpoints}}

      sv_hostname "{{serverName}} | My Base"
      sets sv_projectName "{{serverName}}"
      sets sv_projectDesc "{{recipeDescription}}"
      sets tags "custom, base"
      sets locale "en-US"

      sv_licenseKey "{{svLicense}}"
      sv_maxclients {{maxClients}}
      sv_enforceGameBuild 3751
      set steam_webApiKey "none"
      set resources_useSystemChat true
      sv_endpointPrivacy true

      set mysql_connection_string "{{dbConnectionString}}"

      setr voice_useNativeAudio true
      setr voice_useSendingRangeOnly true

      ensure mapmanager
      ensure chat
      ensure spawnmanager
      ensure basic-gamemode
      ensure hardcap

      ensure oxmysql
      ensure ox_lib
      ensure ox_target
      ensure pma-voice
      ensure bob74_ipl

      ensure [local]

      add_ace group.admin command allow
      add_ace group.admin command.quit deny
      {{addPrincipalsMaster}}

  # 7. Fill in every {{placeholder}} in server.cfg
  - action: replace_string
    mode: all_vars
    file: ./server.cfg

  # 8. Clean up
  - action: remove_path
    path: ./tmp

Why it’s built this way

  • Database first. The official guidelines say to put database tasks at the start, because they fail most often (wrong password, no MariaDB). Better to fail in the first second than after two minutes of downloads.
  • dbName: null. The guidelines require recipes to accept txAdmin’s random database name instead of forcing one like es_extended. We never hard code a name: the connection string comes from {{dbConnectionString}}.
  • Release zips for Overextended. Their GitHub repos hold source code with a web UI that must be built. The release zip contains the built resource. Always use the release for ox_lib, ox_inventory, ox_target.
  • Placeholders in server.cfg. The guidelines require {{maxClients}}, {{addPrincipalsMaster}}, {{serverEndpoints}} and {{svLicense}}. {{svLicense}} is replaced automatically at the end, the others by our replace_string with all_vars.
  • The chat resource is removed from cfx-server-data because set resources_useSystemChat true uses the artifact’s built in chat, as the official default recipe does.
  • $onesync: on tells txAdmin to set OneSync on. ox_lib, ox_target and pma-voice all assume it.

Pin your versions

releases/latest/download/... and download_github without ref always fetch the newest version. That’s convenient, but a breaking update will break your next deploy. For a recipe you share or use for production:

YAML
- action: download_github
  src: https://github.com/AvarianKnight/pma-voice
  ref: 0123456789abcdef0123456789abcdef01234567   # a commit you tested
  dest: ./resources/[standalone]/pma-voice

- action: download_file
  url: https://github.com/overextended/ox_lib/releases/download/v3.30.0/ox_lib.zip   # a tagged release you tested
  path: ./tmp/ox_lib.zip

The version numbers above are placeholders: look up the current release on each project’s Releases page. The guidelines prefer commit hashes over tags, because tags can be moved.

Test it

  1. Push the YAML to a public GitHub repo (or a gist) and copy the raw URL.
  2. Start a throwaway txAdmin: a separate TXHOST_DATA_PATH and TXHOST_TXA_PORT so you don’t touch your main one.
  3. Choose Remote URL Template, paste the URL, deploy.
  4. Check: the server starts with no red errors, /hello shows a notification in game, player_stats gets a row when you join.

Or paste the YAML into Custom Template to test without hosting it.

Guidelines if you want your recipe listed

The txAdmin-recipes README lists the rules for recipes in the Popular list. The main ones:

  • Start from the CFX Default recipe structure.
  • It must “just work”: no editing files before the first start.
  • Only open source or sharable resources. No leaked content.
  • OneSync compatible, $onesync: on if supported.
  • Admins in the admin ACE group must be recognised as admins by your framework.
  • NUI that opens on Tab must check IsNuiFocused() so it doesn’t steal focus from the txAdmin menu.
  • txAdmin menu actions (noclip, god mode, teleport) must not trigger your anticheat, and txAdmin’s Heal should revive downed players (listen to txAdmin:events:playerHealed).
  • Good onboarding: a loading screen that explains the framework, plenty of comments in server.cfg.
  • Use the same game build as the CFX Default recipes.