PlayDeck
Home / Course / Your First Resource
Track A · Module 2

Your First Resource

Every script you ever ship on FiveM lives inside a resource. Get the folder shape and the manifest right and the server loads your code; get them wrong and you stare at a silent console wondering why nothing happened. This lesson builds the smallest real resource from scratch so the structure stops being magic and starts being muscle memory.

What a resource actually is

A resource is just a folder the server knows how to load. Inside it you put your code (Lua, usually), your assets, and one required file — fxmanifest.lua — that tells the server what's in here and how to run it. No manifest, no resource. The server literally won't see your folder.

That's the whole mental model: folder + manifest = a thing the server can turn on and off. Everything else — frameworks, inventories, targeting — is just more resources stacked on top of this same idea. When you understand one resource, you understand a thousand of them.

The folder structure

Resources live under your server's resources/ directory. You group them in bracketed folders like [local] or [qbx] purely for organization — the brackets tell the server "this is a category, not a resource." Here's our hello-world:

resources/
└── [local]/
    └── pd_hello/
        ├── fxmanifest.lua
        └── client.lua

Naming matters. Use a short, unique prefix (pd_ here) so your resource never collides with someone else's hello. Folder names should be lowercase, no spaces — the folder name is the resource name you'll type in the console.

The manifest

This file is read first, before any of your code runs. It declares the manifest version, the target game, and which scripts run where.

-- fxmanifest.lua — the server reads this FIRST. No manifest, no resource.

fx_version 'cerulean'   -- manifest feature set; 'cerulean' is current.
                        -- AI tools love suggesting stale values like 'adamant'
                        -- or the ancient __resource.lua file — verify before trusting.
game 'gtav'             -- target game. 'gtav' for FiveM.

author 'you'
description 'My first PlayDeck resource — a client hello world'
version '0.1.0'

-- Scripts that run on each player's machine (the client):
client_script 'client.lua'

-- When you add a framework, declare it ONCE here and never mix stacks:
-- dependency 'ox_lib'   -- pin a single stack (Qbox/QBCore + ox_lib),
                         -- not ESX-and-QBCore soup

A few things worth internalizing now. client_script runs on players; server_script runs on the host; shared_script runs on both. You'll meet all three soon. And notice the comment about pinning one stack — the modern path is Qbox or QBCore with ox_lib, ox_inventory, and ox_target. When you later call across the network, you'll use lib.callback from ox_lib, not the old hand-rolled callback patterns. Decide your stack once and stay there.

Official manifest reference (bookmark it, don't memorize it): <https://docs.fivem.net/docs/scripting-reference/resource-manifest/resource-manifest/>

The hello-world client script

-- client.lua — runs in every player's game client.

-- A "thread" lets us run startup code without freezing the game.
-- CreateThread is the standard pattern for anything that waits.
CreateThread(function()
    Wait(0) -- yield one tick so the resource is fully mounted
    print('[pd_hello] client resource started — hello from PlayDeck')
end)

-- This event fires when a resource finishes starting. Guard it so you only
-- react to YOUR resource, not every resource on the server.
AddEventHandler('onClientResourceStart', function(resourceName)
    if resourceName ~= GetCurrentResourceName() then return end
    print(('[pd_hello] %s is live'):format(resourceName))
end)

Two prints, two lessons. The thread shows the "do work after a tick" pattern you'll reuse constantly. The event handler shows the resource-name guard — without that if, your code fires for every resource that starts, which is a classic source of mystery bugs. This is also exactly the kind of thing AI gets subtly wrong: it'll invent natives like RegisterClientPrint that don't exist, or drop the guard. When in doubt, check the native reference and run it in the sandbox before you trust it.

First-script reference: <https://docs.fivem.net/docs/scripting-manual/introduction/creating-your-first-script/>

Starting and stopping: the lifecycle

A resource has a life. The server mounts the folder, reads the manifest, loads the scripts in order, fires the onResourceStart events, and your code is now live until something stops it. You control that from the server console (or server.cfg) with four commands:

Use ensure by default. Reach for restart while you're iterating. That's the whole workflow.

Practice (PlayDeck sandbox — no FiveM needed)

You don't need a server to feel how the resource-name guard works. Open the PlayDeck browser sandbox, paste this, and predict the output before you run it:

local resourceName = 'pd_hello'

local function announce(name)
    if name ~= resourceName then return end
    print(('%s started'):format(name))
end

announce('chat')       -- prints? or skipped?
announce('pd_hello')   -- prints? or skipped?
announce('PD_HELLO')   -- careful — Lua strings are case-sensitive

Now run it and check yourself. Then change resourceName to 'chat' and rerun. This is the exact logic guarding your real onClientResourceStart handler — proving it in the sandbox means you'll never wonder why your event "fires twice." Verifying small in the sandbox is also your best defense against AI-suggested code: paste, predict, run, then trust.

Recap

Learn it by building it

PlayDeck is an original course on building GTA roleplay scripts with AI — Lua, frameworks, NUI, debugging, and a browser sandbox to test every lesson without booting the game.

Browse the course