Most "bad AI FiveM code" is not an AI problem — it is a prompt problem. A general model writes competent Lua and then guesses the framework layer, because that is the part it was never reliably trained on. Good FiveM prompting is mostly about removing that guess. Here are the patterns that do it, drawn from the published prompt-engineering guidance of Anthropic and OpenAI, adapted to FiveM specifics.
Why "write me a script" is not enough
FiveM is a small ecosystem on top of GTA V, and the public code a model learned from is thin and often outdated. Ask for a generic feature and you get generic Lua plus a hallucinated framework call. The prompt patterns below all do the same job from different angles: supply the framework layer the model is missing. A FiveM-grounded model like SwisserAI already carries that layer; if you are prompting a general model, the rest of this post is how to supply it by hand. For the background on why this happens, see how to use AI for FiveM coding.
The seven prompt ingredients for accurate FiveM Lua
1. Give the model a role
Both Anthropic and OpenAI recommend role-setting. It anchors vocabulary and assumptions.
You are a senior FiveM Lua engineer who writes idiomatic QBCore resources.2. Pin the framework AND the version
"QBCore" is not enough — say qb-core vs qbx_core (Qbox), or "ESX Legacy" vs older ESX. They expose different functions and are not interchangeable. This is the highest-value line in any FiveM prompt.
3. State the client/server split explicitly
Tell it which file each part belongs in. Server-only functions like QBCore.Functions.GetPlayer(source) do not exist on the client, and "put validation on the server" prevents the most common trust bug.
4. Provide the real exports as examples
Examples ("few-shot") are, per Anthropic, one of the most reliable ways to steer output. In FiveM, the examples are the real calls:
Use these exact APIs:
- Player (server): QBCore.Functions.GetPlayer(source) -> Player.PlayerData.citizenid
- Callback: lib.callback.register('name', fn) / lib.callback.await('name', false, ...)
- Notify: lib.notify({ description = '...', type = 'success' })
- Database: MySQL.scalar.await('SELECT ... WHERE id = ?', { id })5. Request the fxmanifest (specify the output format)
Ask for it directly: fx_version 'cerulean', lua54 'yes', the script blocks, and a dependency for every @import. Specifying the output format is standard prompting advice; here it also prevents the "resource started before its library loaded" class of error.
6. Constrain it: do not invent
Positive, explicit constraints beat hoping. A line the model can actually follow:
Only call functions that exist in the named framework, ox_lib, or oxmysql.
If you are unsure an export exists, say so instead of inventing one.This is the one rule a general model cannot reliably keep — it has no way to know which exports actually exist. A grounded tool enforces it mechanically instead of trusting the model to police itself: SwisserAI’s linter flags an unverifiable call before you run it.
7. Ask for one resource at a time, then iterate
Decompose. One feature per prompt produces reviewable code, and when something breaks you paste the real console error back rather than arguing in the abstract.
Use a rules file — and know its limits
Dropping a framework cheatsheet and ox_lib patterns into .cursor/rules/ (or Copilot custom instructions) meaningfully reduces hallucination in a general IDE assistant. The catch: it is static. When ox_lib renames an export the file is wrong, it spends context tokens on every request, and you maintain it forever. That is the difference a FiveM-aware model makes — it keeps the grounding current so you do not have to. See SwisserAI vs Cursor for the trade-off, or point your IDE at the OpenAI-compatible API to keep your editor and get grounded output.
Copy-paste prompt templates
The framework-pinned resource brief
You are a senior FiveM Lua engineer writing for QBCore (qb-core).
Build a resource: <one sentence describing the feature>.
Stack: QBCore (qb-core), ox_lib, oxmysql. Lua 5.4.
Split: server logic is authoritative; the client only sends requests and shows UI.
Use these exact APIs:
- QBCore.Functions.GetPlayer(source) -> Player.PlayerData.citizenid
- lib.callback.register / lib.callback.await
- lib.notify({ description, type })
- MySQL.query / MySQL.single / MySQL.scalar (.await)
Only call functions that exist in QBCore, ox_lib, or oxmysql. Do not invent exports.
Output: fxmanifest.lua (fx_version 'cerulean', lua54, dependencies), then server/main.lua, then client/main.lua.Convert without mixing frameworks
Convert this ESX resource to QBCore (qb-core). Rules:
- Replace ESX.GetPlayerFromId(source) with QBCore.Functions.GetPlayer(source).
- Replace ESX.RegisterServerCallback with lib.callback.register.
- Do NOT leave any ESX.* calls in the output.
- Keep behaviour identical; only the framework layer changes.The security review pass
Review this server file as a security auditor. The client is untrusted.
Flag anywhere the client can send a value the server trusts without checking
(money, items, prices, ownership, distance). Show the fix.The "fix this runtime error" iteration
This resource throws the following on a live server:
<paste the exact console output>
Here is the file:
<paste the file>
Diagnose the real cause and return only the corrected code.Before and after — same request, two prompts
"Make a QBCore command that gives the player $500." A vague prompt invites the guess:
-- From a vague prompt — invented export, wrong shape
QBCore.Functions.GiveMoney(source, 500)The framework-grounded prompt produces the real thing:
-- From a grounded prompt — real QBCore API
local Player = QBCore.Functions.GetPlayer(source)
if not Player then return end
Player.Functions.AddMoney('cash', 500, 'admin-grant')A reusable prompt checklist
- Role — "senior FiveM Lua engineer for <framework>".
- Framework + version — qb-core / qbx_core / ESX Legacy, named.
- Client/server split — server authoritative.
- Real exports as examples — paste the exact calls.
- fxmanifest requested — cerulean, lua54, paired dependencies.
- No-invention rule — "do not invent exports".
- One feature — then iterate on the real error.
Or skip the boilerplate
Every ingredient above is context a general model is missing and you supply by hand — on every prompt, while keeping your rules file in sync with every ox_lib release. A FiveM-aware tool carries that grounding for you — the framework surface, the real exports, the linter that catches an invented call before you run it. That is the idea behind the FiveM AI script generator and validated exports: every pattern in this post, built in instead of run by hand.
Frequently asked questions
How do I stop AI from mixing ESX and QBCore?
Name exactly one framework and its resource name in the prompt — "QBCore (qb-core)" or "Qbox (qbx_core)" or "ESX Legacy" — and add the line "do not use any other framework’s functions". The mixing happens because the model treats them as interchangeable; pinning one removes the ambiguity.
Which ox_lib functions should I name in my prompt?
Name the ones the feature needs: lib.callback.register / lib.callback.await for server-client data, lib.registerContext + lib.showContext for menus, lib.notify for notifications, and oxmysql’s MySQL.query / MySQL.single / MySQL.scalar (.await) for the database. Listing the exact calls turns "guess the API" into "use these".
Do I need a rules file?
A .cursor/rules file (or Copilot custom instructions) with your framework cheatsheet and ox_lib patterns helps a general IDE assistant a lot. The trade-off is that it is static: it goes stale when ox_lib renames an export, and it spends context tokens on every request. A FiveM-aware model keeps that grounding current for you instead.
How do I get a correct fxmanifest from AI?
Ask for it explicitly and specify the format: fx_version cerulean, game gta5, lua54 yes, the shared/server/client script blocks, and a dependency for every @import (ox_lib, oxmysql, your framework). If you do not request it, the model often omits it or produces an outdated one.
What is the single most important thing to put in a FiveM prompt?
The framework, named and versioned, plus the real exports the code must call. That one habit fixes more broken output than anything else, because it replaces the model’s biggest guess — the framework layer — with fact.