> For the complete documentation index, see [llms.txt](https://docs.noctrastore.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.noctrastore.net/noctra-garage/custom-framework.md).

# Custom framework

Connect Noctra Garage to a framework that is not included by default.

Use this page when your server does not use ESX, QBCore, or Qbox, or when its vehicle and player systems differ from their standard versions.

The bridge is intentionally kept in the `custom` folder so it can be changed without editing the main garage code.

## 1. Select the custom bridge

Set the framework in `config.lua`:

```lua
Config.Framework = 'custom'
```

Import `sql/custom.sql` once. If you already have a vehicle table, compare the SQL with your schema before running it and keep your existing vehicle data.

## 2. Map the vehicle table

Open `custom/server/framework/custom.lua` and map your table and columns:

```lua
FW.VehiclesTable = 'player_vehicles'
FW.VehicleOwnerColumn = 'citizenid'
FW.VehiclePropertiesColumn = 'mods'
FW.VehicleStoredColumn = 'state'
FW.VehicleImpoundedColumn = 'impounded'
FW.VehicleImpoundDataColumn = 'impound'
```

Your vehicle table must provide these values:

| Value               | Purpose                                                           |
| ------------------- | ----------------------------------------------------------------- |
| Unique row ID       | Keeps each owned vehicle record independently identifiable.       |
| `plate`             | Unique, indexed vehicle plate.                                    |
| Owner column        | Stable character identifier used by `FW.GetPlayerIdentifier`.     |
| Properties column   | JSON vehicle properties. It must include the vehicle model.       |
| Stored column       | `1` while stored and `0` while outside.                           |
| `garage`            | ID of the current garage, or your outside value while retrieved.  |
| `tag`               | Optional player-created vehicle label.                            |
| `favorite`          | Favorite state, normally `0` or `1`.                              |
| Impounded column    | Impound state, normally `0` or `1`.                               |
| Impound-data column | JSON containing the reason, fine, account, time, and player name. |

The plate inside the properties JSON is kept aligned with the database `plate` value. The database plate remains the source of truth.

{% hint style="warning" %}
The identifier returned by your bridge must match the value already stored in the owner column. A license identifier will not match a character ID unless your vehicle table also uses that license.
{% endhint %}

## 3. Implement the server functions

Complete the functions in `custom/server/framework/custom.lua`.

| Function                                                         | Expected result                                                         |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `FW.GetPlayerIdentifier(src)`                                    | Stable character identifier matching the vehicle owner column.          |
| `FW.GetPlayerName(src)`                                          | Player or character display name.                                       |
| `FW.Notify(src, text, type, duration)`                           | Sends a notification to the player.                                     |
| `FW.GetPlayerJob(src)`                                           | `{ name = 'police', grade = 2 }`, or `nil` when unavailable.            |
| `FW.GetPlayerGang(src)`                                          | `{ name = 'gang_name', grade = 1 }`, or `nil` when unavailable.         |
| `FW.GetAccountMoney(src, account)`                               | Current balance as a number.                                            |
| `FW.RemoveAccountMoney(src, account, amount)`                    | `true` only after the payment succeeds.                                 |
| `FW.AddAccountMoney(src, account, amount)`                       | `true` only after the balance is updated.                               |
| `FW.AddAccountMoneyByIdentifier(identifier, account, amount)`    | Updates an offline or online character and returns `true` after saving. |
| `FW.RemoveAccountMoneyByIdentifier(identifier, account, amount)` | Removes money by identifier and returns `true` after saving.            |
| `FW.HasCreatorPermission(src)`                                   | Boolean permission for the garage creator.                              |
| `FW.IsPlayerAdmin(src)`                                          | Boolean permission for the admin impound command.                       |

Money removal must check the balance before changing it. Never return `true` when the framework rejected or failed to save the transaction.

The by-identifier functions are important for garage purchases and owner payments because the affected owner may be offline.

## 4. Implement client updates and text UI

Open `custom/client/framework/custom.lua` and configure:

```lua
FW.JobUpdateEvent = 'your_framework:client:jobUpdated'
FW.GangUpdateEvent = 'your_framework:client:gangUpdated'
```

The event names can remain empty if your framework does not provide them, but garage access will then refresh only through the normal sync or a manual call:

```lua
exports.noctra_garage:RefreshGarageInteractions()
```

Implement `FW.ShowTextUI(id, text, key)` and `FW.HideTextUI(id)` using your text UI resource. Return the created text UI ID when your hide function needs it.

## 5. Check fuel and vehicle keys

`custom/client/vehicle.lua` already supports several common fuel and vehicle-key resources. If your server uses another resource, adapt:

```lua
FW.GetVehicleFuel(vehicle)
FW.SetVehicleFuel(vehicle, fuelLevel)
FW.GiveVehicleKeys(plate, vehicle)
```

`FW.GetVehicleFuel` should return a value from `0` to `100`. `FW.SetVehicleFuel` should restore that value. `FW.GiveVehicleKeys` should grant keys to the local player after retrieval.

## 6. Validate the bridge

Test the following with two different characters:

1. Both characters see only the vehicles they should see.
2. Storing and retrieving keeps the plate, model, upgrades, fuel, and damage.
3. Storage and retrieval fees fail cleanly when the balance is too low.
4. Job and gang access updates after a role change.
5. Impound and release update the correct owned-vehicle row.
6. Garage purchases pay an owner who is offline.
7. Creator and admin commands reject players without permission.

If an owned vehicle cannot be found, compare the exact output of `FW.GetPlayerIdentifier` with the owner value stored on that vehicle before checking anything else.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.noctrastore.net/noctra-garage/custom-framework.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
