Restart at midnight. At five past, somebody is in your Discord saying the server ate their car. Not crashed it, not impounded it. Ate it. They bought it on Tuesday, they parked it outside their house, and now the garage list is empty and they would like a word with whoever is in charge.
The car is still there. It is a row in a table, exactly where it was, and it has been there the whole time. What went missing is the small piece of state that says where the car is supposed to be, and once you know which column that is and who writes it, this entire category of panic becomes a two minute fix. So here is where ESX actually keeps vehicles, what each part of that row means, and why a restart is the moment it all goes wrong.
The row, and what is in it
Owned vehicles live in owned_vehicles. Ask your own database what shape yours is, because garage resources add columns and versions differ:
SHOW COLUMNS FROM owned_vehicles;
SELECT owner, plate, type, job, stored FROM owned_vehicles LIMIT 5;
A typical Legacy row has the owner identifier, the plate, a vehicle column holding JSON, a type, and some flavour of stored or state column. Garage scripts commonly add a garage name, a fuel value, a body and engine health, and an impound flag.
The JSON in vehicle is a property dump: model, colours, extras, every mod slot, wheels, plate text. It is what the client hands over when the car is saved, and it is what gets applied again when the car is spawned. That is why a car comes back with the same neon and the same bent wheel arch.
Two things are usually not in there. Where the car physically was, because vehicles are not persisted as entities. And fuel, unless your fuel script writes it, which some do and some do not.
The plate is the identity, and it is a string
Everything keys on the plate. Garages, impounds, insurance, keys, the lot. Not on an id, not on the owner, on the eight characters painted on the back of the car.
Which means all the classic string problems apply. GTA plates are padded to eight characters, so a plate stored as ABC 123 with a trailing space does not match a lookup for ABC 123, and the car becomes invisible to a script that trims and visible to one that does not. Two cars can also end up with the same plate if your generator does not check, and then a garage happily hands one player the other's vehicle.
Two queries worth running today, before anybody complains:
SELECT plate, COUNT(*) c FROM owned_vehicles GROUP BY plate HAVING c > 1;
SELECT plate FROM owned_vehicles WHERE plate <> TRIM(plate);
Both should return nothing. If either returns rows, fix it now, because the bug reports it produces look like haunting rather than data.
The stored flag is the thing that breaks
Here is the actual mechanism behind ninety percent of missing cars.
When a player takes a car out, the garage sets the row to "out". When they put it away, the garage sets it back to "in". The garage list only shows cars marked as in. Nothing else about the car changes, and no entity is saved anywhere.
Now restart the server, or crash it, while thirty cars are out. Every one of those entities disappears, because entities never survive a restart. The rows all still say "out". The cars are not in anyone's garage, and they are not in the world either. They exist in the worst possible place, which is nowhere with a database row.
That is the whole bug. And the fix is one statement run at server start:
UPDATE owned_vehicles SET stored = 1 WHERE stored = 0;
Your column name and value may differ, so check your schema first. Plenty of garage resources do this themselves on start. If yours does not, add it to whatever runs on boot, and the midnight Discord message stops happening forever.
What actually happens across a restart
Worth being precise, because the mental model matters more than the fix.
Vehicles in the world are entities. Entities are not saved. At shutdown they cease to exist, and nothing about that is recoverable or worth trying to recover. What persists is the row: who owns it, what it looks like, and its flag.
So the question after any restart is only ever "does the flag match reality", and reality after a restart is that every car is in the garage whether it wants to be or not. Anyone who left a car parked artfully outside a nightclub will be mildly disappointed and entirely un-robbed.
Migrating between garage resources
The day you swap garage scripts, all of this becomes load bearing at once. Different resources use different column names and different values for the same idea: some use 0 and 1, some use a string, some keep a separate garage name column, some store a state integer with three meanings.
Back up the table, write the migration as explicit SQL rather than a script that guesses, and run it against a copy first. Then check three players you can actually message before you let everyone in. The exact column set also moves between framework versions, so confirm which version your server is actually on before following anyone's migration snippet, including a good one.
A debugging checklist for "my car is gone"
Work down this list with the plate in hand and you will find it every time.
- Does a row exist for that plate at all? If not, it was sold, deleted, or never saved.
- Does the owner identifier match the player asking? People change accounts more often than you would think.
- What does the stored flag say? Out means the restart case above.
- Is there an impound or job flag set?
- Is the garage column set to a garage that exists? A typo here hides a car in a garage nobody can visit.
- Does the plate have stray whitespace?
Six checks, all read only, and you can do them while the player is typing.
Keys, insurance and who is allowed to drive it
Ownership in the table answers one question: whose garage does this car appear in. Every other question your players care about is handled by something else, and knowing which is which saves a lot of pointless debugging.
Keys are a separate resource on most servers, with their own store of who can start which plate. That is why a friend can drive your car all evening and still not see it in their garage, and why handing someone keys does not transfer ownership. When somebody says their car has been stolen, the first question is whether the thief had keys, and the answer is not in owned_vehicles.
Impound is usually a flag, sometimes its own table. Either way the row survives, which is the important part. An impounded car is being held, not deleted, and any script that deletes rows on impound is a script to be nervous about.
Insurance and recovery are almost always a roleplay convention built on top of the impound flag. There is rarely any data behind it beyond a fee and a state change, which is fine, and worth knowing before you spend an evening looking for an insurance table that does not exist.
Job vehicles are a different thing entirely. Most job garages spawn from a config list and never touch owned_vehicles at all, so a police car does not belong to the officer driving it. If a job car ever appears in someone's personal garage, a script has written a row it should not have, and that row will outlive their employment.
The practical rule: when a vehicle behaves oddly, work out which system owns the behaviour before opening the database. Half the time the table is entirely innocent and a keys resource is having a bad day.
Selling and spawning, briefly
Buying a car is an insert. Selling is a delete, and it should be a delete rather than a flag, because rows that linger as "sold" get found by scripts that do not know about your flag. Spawning applies the JSON back onto a fresh entity, which is why a car occasionally comes back without a modification that a mod menu applied but never saved.
If you are shopping for the resources that do all this, the vehicle and garage listings worth your time are the ones whose documentation tells you the schema they expect. A garage script that will not tell you its columns is a garage script that will fight your next migration.
Practical takeaway
Vehicles live as rows, not as entities. The plate is the key, the JSON is the appearance, and the stored flag is the only thing that decides whether a car shows up in a garage list. Run the reset on boot, check for duplicate and untrimmed plates, and keep your schema written down before you swap garage resources.
The car was never eaten. It spent the night in a table, mislabelled, waiting for someone to run one UPDATE and restore its dignity.


