> ## Documentation Index
> Fetch the complete documentation index at: https://docs.elementlabs.net/llms.txt
> Use this file to discover all available pages before exploring further.

# A provider of your own

> Make products work with a framework, inventory or interface resource Element Labs does not ship an implementation for, by declaring one from the resource itself.

Element Labs products work with the resources listed on [Supported server resources](/sdk/supported-resources).
If your server runs a framework, inventory or interface system that is not on that list, a product
that needs it reports that no supported adapter was detected and stops.

You can supply the implementation yourself, from the resource that provides it. Nothing to compile,
no file from the SDK to include, and no separate resource to keep in sync with your framework. This
page is the contract: what you write, and exactly what your resource must return.

<Note>
  You only need this page when a product stops on a missing adapter. If your resources are on the
  supported list, there is nothing to declare.
</Note>

<CardGroup cols={3}>
  <Card title="Declare it" icon="file-lines" href="#contract">
    One manifest line and one export.
  </Card>

  <Card title="Framework" icon="user" href="#what-to-return-the-framework">
    The one contract with a shape you cannot guess.
  </Card>

  <Card title="Everything else" icon="table-list" href="#what-to-return-the-other-adapters">
    Notify, target, menu, input, progress, fuel, database, inventory.
  </Card>
</CardGroup>

## Contract

<Steps>
  <Step title="Name the capability in the resource manifest">
    Add one line to the `fxmanifest.lua` of the resource that implements the capability:

    ```lua fxmanifest.lua theme={null}
    element_adapter 'framework'
    ```

    The line is repeatable: one per capability the resource covers.
  </Step>

  <Step title="Export the implementation">
    Add one export, anywhere in that resource's Lua:

    ```lua theme={null}
    exports('elementAdapter', function(domain)
      return { ... }
    end)
    ```

    `domain` is the value you wrote in the manifest, so one export can serve several capabilities.
  </Step>

  <Step title="Restart and read the console">
    The product calls the export the first time it needs the capability, and checks your table once
    against the whole contract. Every method that is still missing is printed on one line, naming
    your resource. Add them and restart until the line is gone.
  </Step>
</Steps>

<Warning>
  There is no partial implementation. Either the table satisfies the whole contract, or the product
  does not run: a missing method stops the product on the server rather than failing later at the
  call that needed it.
</Warning>

A declared implementation wins over the supported list, for that capability only. Remove the
manifest line to go back to the list.

## Inputs

The value of `element_adapter` is the capability's lowercase name, and the same value arrives as the
export's `domain` argument. A capability lives on one side of the server, or on both:

| `element_adapter` value | Capability                                                                             | Side              |
| ----------------------- | -------------------------------------------------------------------------------------- | ----------------- |
| `framework`             | The roleplay framework: the local player on the client, players and jobs on the server | Client and server |
| `database`              | The database driver                                                                    | Server            |
| `inventory`             | The inventory resource                                                                 | Server            |
| `notify`                | On-screen notifications                                                                | Client            |
| `help-text`             | On-screen help text                                                                    | Client            |
| `target`                | The targeting / interaction system                                                     | Client            |
| `menu`                  | Context menus                                                                          | Client            |
| `input`                 | Text input dialogs                                                                     | Client            |
| `progress`              | Progress bars                                                                          | Client            |
| `fuel`                  | Vehicle fuel                                                                           | Client            |

### Covering several capabilities

The manifest line is repeatable, and the export branches on `domain`:

```lua fxmanifest.lua theme={null}
element_adapter 'framework'
element_adapter 'notify'
```

```lua theme={null}
exports('elementAdapter', function(domain)
  if domain == 'framework' then
    return { getSnapshot = function() ... end }
  elseif domain == 'notify' then
    return { notify = function() ... end }
  end
end)
```

A branch that matches nothing returns `nil`, which the product reports as a returned-nothing error.
See [Errors](#errors).

### Client and server sides of one capability

`framework` is one capability with two method lists. If a product uses it on the server too, your
export is checked against the server list as well.

The two sides read the export from separate Lua states. To give each side its own methods, register
the export from that side's own file (`client/` and `server/`); registered from one shared file, the
same table has to satisfy both contracts at once.

## What to return: the framework

### `getSnapshot`

One table describing the local player, or `nil` while no character is loaded:

```lua theme={null}
getSnapshot = function()
  return {
    source     = GetPlayerServerId(PlayerId()),   -- number
    identifier = 'your player identifier',        -- string, or nil before load
    license    = 'your license identifier',       -- string, or nil
    charInfo   = {
      firstName   = 'John',                       -- string
      lastName    = 'Doe',                        -- string
      birthDate   = '1990-01-01',                 -- string, optional
      gender      = 'male',                       -- 'male', 'female' or 'unknown', optional
      nationality = 'American',                   -- string, optional
      phone       = '555-0100',                   -- string, optional
    },
    job      = nil,                               -- a job group, or nil
    gang     = nil,                               -- a gang group, or nil
    accounts = {
      cash  = 0,                                  -- number
      bank  = 0,                                  -- number
      black = 0,                                  -- number
    },
    metadata = { anything = true },               -- any keys the product may read
    dead     = false,                             -- boolean
  }
end
```

On a framework without gangs, `gang` stays `nil`.

### A job or gang group

The `job` and `gang` values share one shape, on both sides:

```lua theme={null}
{
  name    = 'police',       -- string
  label   = 'Police',       -- string
  type    = 'leo',          -- string, optional
  grade   = {
    name  = 'sergeant',     -- string
    label = 'Sergeant',     -- string
    level = 2,              -- number
    boss  = false,          -- boolean
  },
  boss    = false,          -- boolean
  onDuty  = true,           -- boolean
  payment = 100,            -- number, optional
}
```

### Client methods

Who the player is:

| Method                       | Called with                     | Return                                             |
| ---------------------------- | ------------------------------- | -------------------------------------------------- |
| `getIsPlayerLoaded()`        | None                            | `true` or `false`                                  |
| `getPlayerIdentifier()`      | None                            | the player identifier string, or `nil`             |
| `getPlayerName()`            | None                            | `{ firstName, lastName }`, or `nil`                |
| `getPlayerData(key?)`        | optional key                    | the value under that key, or the whole stored data |
| `getPlayerJob()`             | None                            | a job group, or `nil`                              |
| `getPlayerGang()`            | None                            | a gang group, or `nil`                             |
| `getPlayerMetadata(key)`     | a metadata key                  | the value stored under it                          |
| `getIsPlayerDead()`          | None                            | `true` or `false`                                  |
| `getAccountBalance(account)` | `'cash'`, `'bank'` or `'black'` | a number                                           |

What the player sees:

| Method                              | Called with                                                                    | Return  |
| ----------------------------------- | ------------------------------------------------------------------------------ | ------- |
| `notify(message, type?, duration?)` | message; optional type; optional duration in milliseconds                      | nothing |
| `showHelpText(message, position?)`  | message; optional position (`'left-center'`, `'right-center'`, `'top-center'`) | nothing |
| `hideHelpText()`                    | None                                                                           | nothing |

What changed. Each of these returns a function that unsubscribes the callback:

| Method                            | Callback receives                                                                               |
| --------------------------------- | ----------------------------------------------------------------------------------------------- |
| `onSnapshotInvalidated(callback)` | a reason string: `'loaded'`, `'unloaded'`, `'job'`, `'gang'`, `'duty'`, `'money'`, `'metadata'` |
| `onPlayerLoaded(callback)`        | nothing                                                                                         |
| `onPlayerUnloaded(callback)`      | nothing                                                                                         |
| `onPlayerSetJob(callback)`        | the new job group                                                                               |

### Server methods

Players and identity:

| Method                              | Called with         | Return                              |
| ----------------------------------- | ------------------- | ----------------------------------- |
| `getPlayers()`                      | None                | an array of source numbers          |
| `getPlayer(src)`                    | a player source     | a player record, or `nil`           |
| `getPlayerByIdentifier(identifier)` | a player identifier | a player record, or `nil`           |
| `getPlayerSource(identifier)`       | a player identifier | the source number, or `nil`         |
| `getPlayerIdentifier(src)`          | a player source     | the identifier string, or `nil`     |
| `getPlayerName(src)`                | a player source     | `{ firstName, lastName }`, or `nil` |
| `getPlayerPhone(src)`               | a player source     | the phone string, or `nil`          |
| `isAdmin(src)`                      | a player source     | `true` or `false`                   |

Jobs and duty:

| Method                           | Called with                                    | Return                                                          |
| -------------------------------- | ---------------------------------------------- | --------------------------------------------------------------- |
| `getJobs()`                      | None                                           | an array of `{ name, label, grades: [{ name, label, level }] }` |
| `getPlayerJob(src)`              | a player source                                | a job group, or `nil`                                           |
| `setPlayerJob(src, job, grade?)` | a player source; a job name; an optional grade | `true` or `false`                                               |
| `getPlayerDuty(src)`             | a player source                                | `true` or `false`                                               |
| `setPlayerDuty(src, onDuty)`     | a player source; the new state                 | `true` or `false`                                               |

State and health:

| Method                               | Called with                               | Return                    |
| ------------------------------------ | ----------------------------------------- | ------------------------- |
| `getPlayerData(src, key)`            | a player source; a key                    | the value stored under it |
| `getPlayerMetadata(src, key)`        | a player source; a key                    | the value stored under it |
| `setPlayerMetadata(src, key, value)` | a player source; a key; the value         | `true` or `false`         |
| `getStatus(src, status)`             | a player source; a status name            | a number, 0–100           |
| `addStatus(src, status, amount)`     | a player source; a status name; an amount | a number, 0–100           |
| `getIsPlayerDead(src)`               | a player source                           | `true` or `false`         |
| `revivePlayer(src)`                  | a player source                           | nothing                   |

Money:

| Method                                       | Called with                            | Return            |
| -------------------------------------------- | -------------------------------------- | ----------------- |
| `getAccountBalance(src, account)`            | a player source; an account            | a number          |
| `addAccountBalance(src, account, amount)`    | a player source; an account; an amount | `true` or `false` |
| `removeAccountBalance(src, account, amount)` | a player source; an account; an amount | `true` or `false` |

Vehicles, items and commands:

| Method                                 | Called with                                                                                          | Return                         |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------ |
| `getOwnedVehicles(src)`                | a player source                                                                                      | an array of `{ plate, model }` |
| `isVehicleOwnedByPlayer(src, plate)`   | a player source; a plate                                                                             | `true` or `false`              |
| `registerUsableItem(item, callback)`   | the item name; `callback(src, item)`                                                                 | nothing                        |
| `addCommand(name, callback, options?)` | the command name; `callback(src, args, raw)`; optional `{ help, args?, permission?, allowConsole? }` | nothing                        |

What changed. Unlike the client events, these return nothing:

| Method                 | Callback receives      |
| ---------------------- | ---------------------- |
| `onPlayerLoaded(cb)`   | `cb(src)`              |
| `onPlayerSpawned(cb)`  | `cb(src)`              |
| `onPlayerUnloaded(cb)` | `cb(src)`              |
| `onPlayerSetJob(cb)`   | `cb(src, job, oldJob)` |

A player record is:

```lua theme={null}
{
  id         = 1,                 -- number
  identifier = 'your identifier', -- string
  accounts   = { cash = 0, bank = 0, black = 0 },
  info       = { firstName = 'John', lastName = 'Doe' },
  job        = nil,               -- a job group, or nil
  gang       = nil,               -- a gang group, or nil
  metadata   = {},
  position   = vector4(0, 0, 0, 0),
}
```

## What to return: the other adapters

These contracts are short, and the values you return are the ones your resource already speaks. The
tables below give the method names and the shapes that are not obvious.

<Tip>
  Start from the error. Add the manifest line, return an empty table, restart, and the product names
  every method that side expects for your SDK version. Implement until the message is gone.
</Tip>

### Notify

| Method            | Called with                                                                                                 | Return  |
| ----------------- | ----------------------------------------------------------------------------------------------------------- | ------- |
| `notify(options)` | `{ title?, description, type? ('success'/'error'/'info'/'warning'/'inform'), duration?, icon?, position? }` | nothing |

### Help text

| Method                    | Called with                              | Return  |
| ------------------------- | ---------------------------------------- | ------- |
| `show(message, options?)` | message; optional `{ position?, icon? }` | nothing |
| `hide()`                  | None                                     | nothing |

### Progress

| Method              | Called with                                                                                                   | Return                                       |
| ------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| `progress(options)` | `{ label, duration, canCancel?, useWhileDead?, disableMovement?, disableCombat?, disableCar?, anim?, prop? }` | `true` when finished, `false` when cancelled |

### Fuel

| Method                    | Called with                   | Return          |
| ------------------------- | ----------------------------- | --------------- |
| `getFuel(vehicle)`        | the vehicle handle            | a number, 0–100 |
| `setFuel(vehicle, level)` | the vehicle handle; the level | nothing         |

### Input

| Method                | Called with                                                                                                     | Return                                                     |
| --------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `open(title, fields)` | a title; an array of fields (`{ type, label, name?, placeholder?, default?, required?, options?, min?, max? }`) | the entered values in field order, or `nil` when cancelled |

### Menu

| Method       | Called with                                                                                                | Return  |
| ------------ | ---------------------------------------------------------------------------------------------------------- | ------- |
| `open(menu)` | `{ id?, title, items }`, where each item is `{ title, description?, icon?, disabled?, arrow?, onSelect? }` | nothing |
| `close()`    | None                                                                                                       | nothing |

### Target

| Method                         | Called with                                               | Return      |
| ------------------------------ | --------------------------------------------------------- | ----------- |
| `addBoxZone(zone)`             | `{ coords, size?, rotation?, name?, distance?, options }` | the zone id |
| `addSphereZone(zone)`          | `{ coords, radius?, name?, distance?, options }`          | the zone id |
| `addEntity(netIds, options)`   | one or more net ids; an array of options                  | nothing     |
| `addModel(models, options)`    | a model name, id or list; an array of options             | nothing     |
| `addGlobalPlayer(options)`     | an array of options                                       | nothing     |
| `addGlobalVehicle(options)`    | an array of options                                       | nothing     |
| `removeZone(id)`               | the id `addBoxZone` or `addSphereZone` returned           | nothing     |
| `removeEntity(netIds, names?)` | the entity, optionally the option names to remove         | nothing     |
| `removeModel(models, names?)`  | the model, optionally the option names to remove          | nothing     |
| `disableTargeting(state)`      | `true` or `false`                                         | nothing     |

Each option in an `options` array is
`{ name?, label, icon?, distance?, items?, groups?, canInteract?, onSelect }`, and `onSelect`
receives the entity handle.

### Database

| Method                      | Called with                          | Return                                       |
| --------------------------- | ------------------------------------ | -------------------------------------------- |
| `query(sql, params?)`       | the SQL; an optional parameter array | an array of rows                             |
| `execute(sql, params?)`     | the SQL; optional parameters         | `{ affectedRows?, insertId?, changedRows? }` |
| `fetchSingle(sql, params?)` | the SQL; optional parameters         | one row, or `nil`                            |
| `fetchScalar(sql, params?)` | the SQL; optional parameters         | one value, or `nil`                          |
| `insert(sql, params?)`      | the SQL; optional parameters         | the inserted row id                          |
| `update(sql, params?)`      | the SQL; optional parameters         | the number of changed rows                   |
| `transaction(queries)`      | an array of `{ query, params? }`     | `true` when committed                        |
| `ready()`                   | None                                 | `true` when the connection is ready          |

### Inventory

| Method                                                   | Called with                                                                                      | Return                          |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------- |
| `registerInventory(id, label, slots, maxWeight, owner?)` | the id (`number`, `string`, or `{ id, owner }`), label, slot count, weight limit, optional owner | nothing                         |
| `getInventory(id)`                                       | the id                                                                                           | an array of items               |
| `addItem(id, item, count, metadata?, slot?)`             | the id; the item name; the count                                                                 | `true` or `false`               |
| `removeItem(id, item, count, metadata?, slot?)`          | as `addItem`                                                                                     | `true` or `false`               |
| `setMetadata(id, slot, metadata)`                        | the id; the slot number; the metadata table                                                      | nothing                         |
| `getItem(id, item, metadata?)`                           | the id; the item name; optional metadata                                                         | the item, or `nil`              |
| `getSlot(id, slot)`                                      | the id; the slot number                                                                          | the item in that slot, or `nil` |
| `getItemCount(id, item)`                                 | the id; the item name or a list of names                                                         | a number                        |
| `canCarryItem(id, item, count, metadata?)`               | a player source; the item; the count                                                             | `true` or `false`               |
| `registerUsableItem(item, callback)`                     | the item name; `callback(source, item)`                                                          | nothing                         |
| `onSwapItems(callback, filter)`                          | `callback(payload)`; `{ inventoryFilter? }`: return `false` to refuse the move                   | nothing                         |
| `serverOpenInventory(source, id?)`                       | a player source; an optional inventory id                                                        | nothing                         |

`serverOpenInventory` opens an inventory for a player from the server. Most inventory resources have
no way to do that, and products treat it as optional, but a declared adapter is checked against the
whole list, so define it even if it does nothing.

## Outputs

The product reads your answers on the first use of the capability and keeps a copy. It re-reads the
whole export when you trigger the change event with the capability name:

```lua theme={null}
TriggerEvent('element:adapter:changed', 'framework')
```

Without the event, the copy is used for five seconds and the next use after that re-reads the
export. If your resource stops, the product forgets the implementation and chooses again the next
time it needs the capability.

## Errors

Each of these stops the product on the server and prints once, naming your resource.

| Message                                                                                                                                                    | Cause                                                                                                             |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `The declared Framework adapter in "custom_framework" is missing: [getPlayerGang, getIsPlayerDead]. A declared adapter must implement the whole contract.` | The export returned a table that does not implement every method. The list is every method that is still missing. |
| `Resource "custom_framework" declares "framework" but does not export "elementAdapter".`                                                                   | The manifest declares the capability, but the resource never registers the export.                                |
| `Resource "custom_framework" returned nothing from "elementAdapter" for "framework".`                                                                      | The export exists but returned nothing for that capability, usually a `domain` branch that did not match.         |

A client cannot stop its own resource, so when a client-side capability fails the product also
prints that the capability is unavailable for the rest of the session, and stops on the server side
the next time it needs the same capability.

## Example

A complete starting point for a framework's client side, ready to fill in. Every method is present,
so the contract check passes and you can replace the bodies one at a time:

```lua client/adapter.lua theme={null}
exports('elementAdapter', function(domain)
  return {
    getIsPlayerLoaded     = function() return false end,
    getPlayerIdentifier   = function() return nil end,
    getPlayerName         = function() return nil end,
    getPlayerData         = function(key) return nil end,
    getPlayerJob          = function() return nil end,
    getPlayerGang         = function() return nil end,
    getPlayerMetadata     = function(key) return nil end,
    getIsPlayerDead       = function() return false end,
    getAccountBalance     = function(account) return 0 end,
    notify                = function(message, kind, duration) end,
    showHelpText          = function(message, position) end,
    hideHelpText          = function() end,
    getSnapshot           = function()
      return {
        source     = GetPlayerServerId(PlayerId()),
        identifier = nil,
        license    = nil,
        charInfo   = { firstName = '', lastName = '' },
        job        = nil,
        gang       = nil,
        accounts   = { cash = 0, bank = 0, black = 0 },
        metadata   = {},
        dead       = false,
      }
    end,
    onSnapshotInvalidated = function(callback) return function() end end,
    onPlayerLoaded        = function(callback) return function() end end,
    onPlayerUnloaded      = function(callback) return function() end end,
    onPlayerSetJob        = function(callback) return function() end end,
  }
end)
```

Fill each method from your resource's own data, restart the resource and the product, and read the
console. The message lists exactly what is still missing, so the loop converges.

## Related

* [Supported server resources](/sdk/supported-resources)
* [Server settings](/sdk/server-settings)
* [A product does not start](/support/product-will-not-start)
