Build a first resource with an observable result
Use a private development server where you can already connect and spawn. This guide follows the tasks in the official first Lua resource tutorial , with bounded model loading and explicit teardown. It is a learning resource, not an authorized vehicle-purchase system.
Create the resource and its manifest
Inside the server’s active data directory, create resources/[local]/doc_first_resource. The bracketed directory is a resource category; doc_first_resource is the resource name passed to the console. Ensure the editor does not silently save a .txt suffix.
Create fxmanifest.lua:
fx_version 'cerulean'
game 'gta5'
client_script 'client.lua'
server_script 'server.lua'The manifest declares which files run on which side. It is not loaded as an ordinary script that shares variables with client.lua. Client scripts are downloaded to players; do not put credentials in them or in shared files. See the complete manifest reference.
Create server.lua:
print('[doc_first_resource] server script started')
RegisterCommand('docservercheck', function(source)
if source ~= 0 then return end
print('[doc_first_resource] server-console command received')
end, true)Create client.lua initially with:
print('[doc_first_resource] client script started')
RegisterCommand('dochello', function()
print('The client command is running.')
end, false)Start, observe and restart
In the server console, enter refresh, then ensure doc_first_resource. refresh discovers new resource folders. ensure starts a stopped resource or restarts a running one. The server startup message appears in the server console; after connecting, the client startup message appears in that client’s F8 console.
Enter dochello in F8 and expect the client message. Enter docservercheck in the server console and expect the server message. A client’s call must not execute the console-only diagnostic. A slash-prefixed chat command requires a chat resource; F8 does not.
After a source change, run restart doc_first_resource or ensure doc_first_resource from the server console. Add ensure doc_first_resource to the appropriate development configuration only when startup ordering is correct. A manifest or script typo should produce a specific console error; fix the first error rather than deleting client caches.
Replace the client script with a bounded vehicle demo
This example creates at most one non-networked local vehicle. Other players do not receive a server-authoritative vehicle from it. Do not install this learning command on a production server or use it to bypass entity lockdown or permissions.
The seven steps are: validate the requested model, request it, wait with a deadline, read the current player’s location, create the vehicle, seat the player, and release the model reference. Keep an owned handle only for cleanup; do not search for and delete unrelated vehicles.
Replace client.lua with:
local vehicle = 0
local loading = false
local generation = 0
local function removeDemo()
if vehicle ~= 0 and DoesEntityExist(vehicle) then
DeleteEntity(vehicle)
end
vehicle = 0
end
RegisterCommand('docremove', removeDemo, false)
RegisterCommand('docvehicle', function(_, args)
if loading then
print('A model request is already pending.')
return
end
local name = args[1] or 'adder'
if type(name) ~= 'string' or #name > 64 then return end
local model = GetHashKey(name)
if not IsModelInCdimage(model) or not IsModelAVehicle(model) then
print('Choose a vehicle model available in this game build.')
return
end
loading = true
local requestGeneration = generation
local startedAt = GetGameTimer()
RequestModel(model)
while not HasModelLoaded(model) do
if generation ~= requestGeneration or ((GetGameTimer() - startedAt) & 0xffffffff) >= 10000 then
SetModelAsNoLongerNeeded(model)
loading = false
print('Model loading was cancelled or timed out.')
return
end
Wait(50)
end
if generation ~= requestGeneration then
SetModelAsNoLongerNeeded(model)
loading = false
return
end
local ped = PlayerPedId()
if ped == 0 or not DoesEntityExist(ped) then
SetModelAsNoLongerNeeded(model)
loading = false
return
end
local position = GetEntityCoords(ped) + GetEntityForwardVector(ped) * 5.0
removeDemo()
vehicle = CreateVehicle(model, position.x, position.y, position.z,
GetEntityHeading(ped), false, false)
SetModelAsNoLongerNeeded(model)
loading = false
if vehicle == 0 or not DoesEntityExist(vehicle) then
vehicle = 0
print('Vehicle creation failed.')
return
end
SetPedIntoVehicle(ped, vehicle, -1)
print('Local demo created. Use docremove to delete it.')
end, false)
AddEventHandler('onResourceStop', function(resource)
if resource ~= GetCurrentResourceName() then return end
generation = generation + 1
removeDemo()
end)Run docvehicle adder in F8 after restarting the resource. The model must exist in the selected build. A valid model that never loads exits after the deadline instead of waiting forever. The timer is a scheduling bound, not a real-time guarantee or an operating-system request cancellation API.
SetModelAsNoLongerNeeded receives the model hash that was requested, not the model-name string. Releasing the model reference is different from releasing or deleting the created entity. SetEntityAsNoLongerNeeded would allow the game to manage lifetime; this demo instead retains the local entity until docremove, replacement or resource stop.
Model names, hashes and native context
A model name is hashed to identify a game asset. A native function has its own API identifier and signature; the two identifiers are not interchangeable. Validate that the model exists and is a vehicle before requesting it. Read coordinates after the asynchronous wait so you do not use a player’s old position.
The native calls above run in the client game context. Moving this file to server_script does not create an equivalent server vehicle system. Server setters, ownership, routing buckets and replicated identity require a separate design. Continue with native functions, network IDs and platform migration.
Optional stock gametype integration
The official starter also demonstrates a gametype that integrates mapmanager, spawnmanager and chat. Those are real dependencies, not events guaranteed on every framework. Do not enable a second automatic-spawn owner alongside an existing character system. Follow stock resource setup for gametype metadata, compatibility and provider startup, then use the seven spawnmanager exports to choose a single spawning owner.
Acceptance and removal
Test the hello commands first, then a valid vehicle, an invalid model, repeated requests during loading, replacement, docremove, and resource stop while loading. Confirm that no unrelated entity is removed. The ground ahead may be unsuitable for a vehicle; choose an open test area rather than assuming a coordinate calculation guarantees collision-safe placement.
Stop the resource, remove its development ensure line and delete only its own folder when finished. Host-side tests can check the code’s branching with mocked natives, but actual model streaming, seating and collision still require a FiveM client. For other languages, use the JavaScript first resource and C# first resource.