> For the complete documentation index, see [llms.txt](https://visionstore.gitbook.io/visionstore-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://visionstore.gitbook.io/visionstore-docs/scripts/badges/developers/bridges.md).

# Bridges

The open-source framework, inventory and target bridges - how they work and how to add your own.

Vision Badges Creator talks to your framework, inventory and target resource **only** through the bridges in `bridge/`. They are open source: read them, change them, or add your own system.

| File                          | Side   | What it does                                                                                                                                                                                                                                                                                          |
| ----------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bridge/framework/server.lua` | Server | Detects the framework. Builds the player info used for every permission check (identifier, name, job, gang, other jobs, duty). Rank and job labels, job / gang lists for the Studio, admin rights, the "dead, downed or cuffed" check, notifications and the membership check behind INACTIVE badges. |
| `bridge/framework/client.lua` | Client | The same player info on the client, rebuilt only when the framework reports a change. It only hides desk options a player could never use; the server decides everything again.                                                                                                                       |
| `bridge/inventory/server.lua` | Server | Detects the inventory. Checks the item exists and fits, gives, finds and removes badge items, registers the "use item" handler. Contains the ox\_inventory export `useBadge`.                                                                                                                         |
| `bridge/target/client.lua`    | Client | Turns desks into ox\_target or qb-target zones, or an `[E]` prompt with ox\_lib. Re-adds the zones when ox\_target or qb-target restarts.                                                                                                                                                             |

Every call into another resource is wrapped in `pcall`: a broken framework function never breaks a badge.

## Identifiers

Each badge stores the **identifier** of its owner: the value for `issuerIds`, `RevokeCitizenBadges` and the database.

| Framework  | Identifier (stored)                         | ID printed on the card                         | Name                              |
| ---------- | ------------------------------------------- | ---------------------------------------------- | --------------------------------- |
| qbx\_core  | `citizenid`                                 | The same                                       | First + last name from `charinfo` |
| qb-core    | `citizenid`                                 | The same                                       | First + last name from `charinfo` |
| ESX        | `xPlayer.identifier` (full value)           | First 8 characters after the `:`, in capitals  | `xPlayer.getName()`               |
| ox\_core   | `charId`                                    | `stateId` (or the `charId`)                    | First + last name                 |
| Standalone | `license2:` or `license:` identifier (full) | First 8 characters of the license, in capitals | The FiveM player name             |

## Per-framework notes

| Framework  | Receives through             | Issues through               | On duty                                      |
| ---------- | ---------------------------- | ---------------------------- | -------------------------------------------- |
| qbx\_core  | Active job, gang, other jobs | Active job, gang             | `job.onduty`                                 |
| qb-core    | Active job, gang             | Active job, gang             | `job.onduty`                                 |
| ESX        | Job                          | Job                          | On duty unless the job says `onDuty = false` |
| ox\_core   | Active group, other groups   | Active group                 | Always on duty                               |
| Standalone | Ace jobs                     | Aces, admins and `issuerIds` | Always on duty                               |

{% tabs %}
{% tab title="Qbox" %}

* Job and gang lists for the Studio: `exports.qbx_core:GetJobs()` / `GetGangs()`.
* Admins: aces only (`Config.Admin.ace`, or `<group>` / `group.<group>` for each name in `Config.Admin.groups`).
* INACTIVE check: the `player_groups` table, or the job / gang in `players.job` / `players.gang`.
  {% endtab %}

{% tab title="QBCore" %}

* Job and gang lists: `QBCore.Shared.Jobs` / `QBCore.Shared.Gangs`.
* Admins: aces, plus `QBCore.Functions.HasPermission(source, <group>)`.
* INACTIVE check: online players live first, then the `players.job` / `players.gang` JSON.
  {% endtab %}

{% tab title="ESX" %}

* No gangs. Job list: `ESX.GetJobs()`.
* Admins: aces, plus `xPlayer.getGroup()` matching a name in `Config.Admin.groups`.
* INACTIVE check: online players live first, then `users.job`.
  {% endtab %}

{% tab title="ox\_core" %}

* Every group counts as a job; the active group is the active job. No gangs, no duty.
* Group lists and grade labels come from the `ox_groups` and `ox_group_grades` tables.
* Admins: aces only. INACTIVE check: the `character_groups` table.
  {% endtab %}

{% tab title="Standalone" %}
Jobs come from aces:

```properties
add_ace identifier.license:YOUR_LICENSE vision_badges.job.police allow
```

* Every grade is 0, and the rank printed is `0`.
* The server works out which badges a player may issue when they join: ace changes apply after a reconnect or any Studio save.
* `issuerIds` take the full license identifier.
* Badges never show INACTIVE. The name on the badge is the FiveM player name.
  {% endtab %}
  {% endtabs %}

## Inventories

| Inventory              | Badge data in | Label / icon per badge | Use handler                                                     |
| ---------------------- | ------------- | ---------------------- | --------------------------------------------------------------- |
| ox\_inventory          | Item metadata | Yes                    | `server.export = 'vision_badges.useBadge'` in `items.lua`       |
| qs-inventory           | Item `info`   | No                     | Registered by the script (`CreateUsableItem`)                   |
| qb / ps / lj-inventory | Item `info`   | No                     | Registered by the script (`QBCore.Functions.CreateUseableItem`) |
| None                   | Database only |                        | `/badge`                                                        |

The item is never consumed when used. Metadata of a badge item:

| Key                                                | Content                                |
| -------------------------------------------------- | -------------------------------------- |
| `serial`                                           | The badge serial (the database record) |
| `type`                                             | Badge id                               |
| `citizenid`                                        | Owner identifier                       |
| `display_id`                                       | ID printed on the card                 |
| `name`, `rank`, `badge_number`, `issued`, `issuer` | A readable copy of the record          |
| `label`, `image`, `description`                    | ox\_inventory label, icon and tooltip  |

{% hint style="info" %}
**The database record is the authority.** The badge shown is the record stored under `serial`, with its current status. When there's no such record, or its type or owner doesn't match, the badge shows **UNREGISTERED**.
{% endhint %}

## Adding a framework

Edit `bridge/framework/server.lua`:

1. `detect()`: return a new name, for example `'myfw'`, when your core resource is started (or set `Config.Framework = 'myfw'`).
2. `getters.myfw = function(src) ... end`: return the player info, or `nil` when no character is loaded:

```lua
{
    identifier = 'ABC12345',          -- stored on the badge, used in issuerIds
    displayId = 'ABC12345',           -- printed on the card (optional)
    firstname = 'Alex', lastname = 'Morgan',
    job  = { name = 'police', label = 'LSPD', grade = 3, gradeLabel = 'Sergeant', onduty = true },
    gang = { name = 'ballas', label = 'Ballas', grade = 1, gradeLabel = 'Member' },  -- or nil
    jobs = { police = 3 },            -- every job held, the active one included
}
```

3. `sharedGroup(name)`: return the job table (with `label` and `grades`) for rank labels.
4. `Bridge.GetGroups()`: add a branch that lists jobs and grades for the Studio pickers.
5. `membershipQueries.myfw = function(identifier, names) ... end`: return `true` / `false` (offline membership for INACTIVE badges). Leave it out and badges never show INACTIVE.
6. Optional: `onlineMembership`, `Bridge.IsAdmin` and `Bridge.IsIncapacitated` branches.

Then edit `bridge/framework/client.lua`: the same name in `detect()`, the same shape in `build()`, and call `changed()` in your framework's "player loaded / job changed / logout" events.

## Adding an inventory

Edit `bridge/inventory/server.lua`:

1. `detect()`: return a new name when your inventory is running (or set `Config.Inventory` to it).
2. Write a table with these functions and add it to `systems`:

| Function                  | Returns                                                                            |
| ------------------------- | ---------------------------------------------------------------------------------- |
| `exists(item)`            | `true` when the item is registered                                                 |
| `canCarry(src, item)`     | `true` when one more fits                                                          |
| `give(src, item, meta)`   | `true` when the item with this metadata was added                                  |
| `find(src, itemSet)`      | `{ { item, slot, metadata }, ... }` for every slot whose item name is in `itemSet` |
| `remove(src, item, slot)` | `true` when removed                                                                |

3. In `Inventory.OnUse`, register the usable item with your inventory and call `handlers[item](source, item, slot, metadata)` when it's used.

## Adding a target

Edit `bridge/target/client.lua`: add a branch to `detect()`, an `add...` function used by `Target.AddDesk(desk, options)`, and the matching removal in `Target.RemoveDesk(handle)`. Each option has `name`, `label`, `icon`, `canInteract()` and `onSelect()`.


---

# 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://visionstore.gitbook.io/visionstore-docs/scripts/badges/developers/bridges.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.
