# Developer Ecosystem SDK

# Developer Ecosystem SDK

Provider-aware runtime items, actions, schemas, images, hooks and health reporting.

**Applies to:** `Nord Inventory v2.0.0`
# Developer Ecosystem

Nord Inventory exposes a provider-aware server SDK so another resource can own its items and behavior without editing `shared/items.lua` or the Nord core.

## Core rules

- Runtime registrations are transient and should be repeated when the provider resource starts.
- The calling FiveM resource is detected with `GetInvokingResource()`; integrations do not need to hard-code their own provider name for bare export handlers.
- A runtime definition cannot replace a database-authoritative item. `RegisterItem` / `RegisterIntegration` return `database_authoritative` for that definition.
- A provider may still attach an external use handler to an already-authoritative item.
- Runtime ownership, use handlers, item actions, metadata schemas and hooks are cleaned up by provider ownership when the resource stops or calls `UnregisterIntegration()`.
- One provider cannot silently steal a runtime item, external use handler, metadata schema or an action ID owned by another provider.

## Capability detection

```lua
if not exports.nord_inventory:HasFeature('integration_manifest') then
    return
end

local capabilities = exports.nord_inventory:GetCapabilities()
```

Useful capability names include `runtime_items`, `external_use`, `provider_registry`, `integration_manifest`, `item_actions`, `metadata_schema_api`, `durability_api`, `containers`, `image_registration`, `health_check` and `backups`.

## RegisterIntegration

Use one server-side manifest when your resource owns several pieces of an integration:

```lua
local ok, result = exports.nord_inventory:RegisterIntegration({
    name = 'Example Medical',
    version = '1.0.0',
    author = 'Example Studio',
    description = 'Native Nord Inventory integration',
    dependencies = { 'nord_inventory' },

    items = {
        medical_scanner = {
            label = 'Medical Scanner',
            description = 'Portable diagnostic device',
            weight = 1200,
            stack = false,
            close = true,

            -- Optional asset copied from this provider resource.
            imageSource = 'install/nord_inventory/medical_scanner.png',

            client = {
                export = 'UseMedicalScanner',
                remove = 0
            },

            metadataDefaults = {
                serial = '',
                owner = ''
            },

            metadataSchema = {
                serial = { type = 'text', label = 'Serial' },
                owner = { type = 'text', label = 'Owner' }
            },

            actions = {
                {
                    id = 'inspect',
                    label = 'Inspect Scanner',
                    icon = 'fa-magnifying-glass',
                    side = 'client',
                    export = 'InspectMedicalScanner',
                    close = false
                }
            }
        }
    },

    hooks = {
        itemUsed = function(payload)
            -- Provider-scoped hook; removed automatically with the integration.
        end
    }
})
```

Client exports in the provider resource:

```lua
exports('UseMedicalScanner', function(item, slot)
    StartMedicalScanner(item, slot)
    return true
end)

exports('InspectMedicalScanner', function(item, slot, actionId)
    InspectScanner(item, slot)
    return true
end)
```

## RegisterItem / RegisterItems

```lua
local ok, def = exports.nord_inventory:RegisterItem('service_token', {
    label = 'Service Token',
    weight = 5,
    stack = true,
    close = false,
    metadataDefaults = { issuer = '', issuedAt = 0 },
    client = { export = 'UseServiceToken', remove = 0 }
})
```

Batch form:

```lua
exports.nord_inventory:RegisterItems({
    item_one = { label = 'Item One', weight = 100 },
    item_two = { label = 'Item Two', weight = 200 }
})
```

## Use handlers

Attach a callback/funcref:

```lua
exports.nord_inventory:RegisterUsableItem('legacy_item', function(source, item, definition, slot)
    return true
end, 'server')
```

Or attach an external export:

```lua
exports.nord_inventory:RegisterUseExport('carplay', 'carplay', 'client')
```

Full explicit form:

```lua
exports.nord_inventory:RegisterUseExport('service_token', {
    resource = 'my_resource',
    export = 'UseServiceToken',
    side = 'server'
})
```

Cross-resource FiveM funcrefs are accepted and wrapped safely; they are not indexed as ordinary Lua tables.

## Unregister ownership

```lua
exports.nord_inventory:UnregisterItem('service_token')
exports.nord_inventory:UnregisterItems({ 'item_one', 'item_two' })
exports.nord_inventory:UnregisterUseHandler('legacy_item')
exports.nord_inventory:UnregisterIntegration()
```

These calls are provider-scoped. A resource cannot unregister another provider's owned registration.

## Provider inspection

```lua
local provider = exports.nord_inventory:GetRegisteredProvider('service_token')
local providers = exports.nord_inventory:GetProviderRegistry()
local debug = exports.nord_inventory:DebugItem('service_token')
```

`DebugItem` reports the definition source, provider, registered actions, metadata extension and use-handler presence.

## Item Actions API

Add an action to an existing item context menu:

```lua
exports.nord_inventory:RegisterItemAction('id_card', {
    id = 'verify_card',
    label = 'Verify Card',
    icon = 'fa-id-card',
    side = 'server',
    export = 'VerifyCard',
    close = false
})

exports('VerifyCard', function(source, item, slot, definition, actionId)
    -- Server-side validation here.
    return true
end)
```

Remove only your own action:

```lua
exports.nord_inventory:UnregisterItemAction('id_card', 'verify_card')
```

Read public actions:

```lua
local actions = exports.nord_inventory:GetItemActions('id_card')
```

## Metadata Schema API

```lua
exports.nord_inventory:RegisterMetadataSchema('employee_card', {
    employeeName = { type = 'text', label = 'Employee' },
    company = { type = 'text', label = 'Company' },
    employeeId = { type = 'text', label = 'Employee ID' }
}, {
    employeeName = true,
    company = true,
    employeeId = true
})
```

The extension is merged into the item metadata presentation without rewriting the authoritative item definition.

```lua
local schema = exports.nord_inventory:GetMetadataSchema('employee_card')
exports.nord_inventory:UnregisterMetadataSchema('employee_card')
```

## Item Image Registration

Trusted server resources can copy a PNG/WebP shipped inside their own resource:

```lua
local ok, imageName = exports.nord_inventory:RegisterItemImage(
    'install/nord_inventory/service_token.png',
    'service_token.png'
)
```

The API rejects traversal paths, unsupported formats, oversized images and target-name ownership conflicts.

For `RegisterIntegration`, use `imageSource` directly inside an item to copy the asset and assign the resulting image name to the definition.

## Durability API

```lua
local value, maximum = exports.nord_inventory:GetDurability(source, slot)
exports.nord_inventory:SetDurability(source, slot, 50)
exports.nord_inventory:AddDurability(source, slot, 20)
exports.nord_inventory:RemoveDurability(source, slot, 10)
exports.nord_inventory:RepairItem(source, slot)      -- full repair
exports.nord_inventory:RepairItem(source, slot, 25) -- add 25
```

All mutations run through the normal Nord inventory metadata path and sync the affected inventory.

## Containers

```lua
exports.nord_inventory:OpenItemContainer(source, slot)
local container = exports.nord_inventory:GetContainerInventory(source, slot)
```

The slot must contain an item whose Nord definition has a `container` configuration.

## Hooks

```lua
exports.nord_inventory:RegisterHook('itemUsed', function(payload)
    print(payload.source, payload.item and payload.item.name)
end)
```

Provider-owned hooks are removed on `UnregisterIntegration()` or resource stop.

Hook names emitted by the current core include `beforeUseItem`, `itemUsed`, `itemAdded`, `itemRemoved`, `itemMoved`, `metadataChanged`, `itemRepaired`, `itemBroken`, `itemAction`, `inventoryOpened` and `inventoryClosed`.

## Health diagnostics

```lua
local report = exports.nord_inventory:GetHealthReport()
```

The report is also visible in Admin Studio and checks provider/resource state, dependencies, export descriptors, missing images, durability configuration, registration conflicts and orphan extensions.


## Metadata privacy from `RegisterItem`

External resources can decide whether an item's metadata detail cards are visible
to players directly when registering the item.

Use the canonical option:

```lua
exports['nord_inventory']:RegisterItem('nr_food', {
    label = 'Restaurant Food',
    weight = 250,
    stack = true,

    -- Player can still see metadata.label / metadata.image as presentation,
    -- but the remaining metadata values are hidden from the item-info modal.
    hideMetadataDetails = true
})
```

Then an instance can still use normal presentation metadata:

```lua
exports['nord_inventory']:AddItem(source, 'nr_food', 1, {
    label = 'Cute Cafe Burger',
    image = 'cute_cafe_burger.png',

    recipeId = 14,
    restaurant = 'cute_cafe',
    hunger = 25,
    basePrice = 20
})
```

The player sees `Cute Cafe Burger` and `cute_cafe_burger.png`, but does not see
`recipeId`, `restaurant`, `hunger`, `basePrice`, or other metadata detail cards.

The metadata is **not deleted or filtered from server exports**. Scripts still
receive the complete metadata.

For compatibility, these registration aliases are also accepted:

```lua
hideMetadata = true
metadataPrivate = true
metadataPlayerVisible = false
metadataVisibility = false
metadataVisibility = 'hidden'
```

Nord normalizes all of them internally to:

```lua
hideMetadataDetails = true
```

The same option works with `RegisterItems` and with each item inside
`RegisterIntegration`.


## Metadata details are hidden by default

All registered items now use a privacy-first default.

This is enough:

```lua
exports['nord_inventory']:RegisterItem('nr_food', {
    label = 'Restaurant Food',
    weight = 250
})
```

Metadata detail cards are hidden automatically.

Instance presentation still works:

```lua
exports['nord_inventory']:AddItem(source, 'nr_food', 1, {
    label = 'Cute Cafe Burger',
    image = 'cute_cafe_burger.png',
    recipeId = 14,
    restaurant = 'cute_cafe'
})
```

The player sees `Cute Cafe Burger` and its metadata image, but does not see
`recipeId`, `restaurant`, or other metadata cards.

To deliberately show metadata details for one item:

```lua
exports['nord_inventory']:RegisterItem('identity_card', {
    label = 'Identity Card',
    weight = 10,
    hideMetadataDetails = false
})
```

The equivalent opt-in alias is:

```lua
showMetadataDetails = true
```

You can still use per-field `metadataDisplay` / schema `show` settings once the
item has opted into visible metadata details.


## Register items together with their category

For integrations such as mechanics, restaurants or jobs, you can register the
category and all items in one call. Every item automatically receives the
category; you do not need to repeat `category = 'mechanic'` on each definition.

```lua
local ok, result = exports['nord_inventory']:RegisterCategoryItems({
    name = 'mechanic_materials',
    label = 'Mechanic Materials',
    icon = 'fa-screwdriver-wrench',
    order = 80
}, {
    mech_sandpaper = {
        label = 'Sandpaper',
        weight = 150,
        stack = true
    },

    mech_primer = {
        label = 'Paint Primer',
        weight = 500,
        stack = true
    },

    mech_clearcoat = {
        label = 'Clear Coat',
        weight = 500,
        stack = true
    }
})
```

The result includes the category state plus registered/failed items:

```lua
{
    category = 'mechanic_materials',
    categoryState = 'created', -- or existing
    registered = { 'mech_sandpaper', 'mech_primer', 'mech_clearcoat' },
    failed = {}
}
```

The equivalent alias is:

```lua
exports['nord_inventory']:RegisterItemsWithCategory(category, items)
```

A string category name is also supported with the category definition as the
third argument:

```lua
exports['nord_inventory']:RegisterCategoryItems(
    'mechanic_materials',
    items,
    { label = 'Mechanic Materials', icon = 'fa-screwdriver-wrench' }
)
```

### Register a complete item catalog

Use `RegisterItemCatalog` when one resource owns several categories:

```lua
exports['nord_inventory']:RegisterItemCatalog({
    mechanic_paint = {
        label = 'Paint Materials',
        icon = 'fa-spray-can-sparkles',
        order = 80,
        items = {
            mech_basecoat = { label = 'Base Coat', weight = 500 },
            mech_clearcoat = { label = 'Clear Coat', weight = 500 }
        }
    },

    mechanic_tools = {
        label = 'Mechanic Tools',
        icon = 'fa-screwdriver-wrench',
        order = 90,
        items = {
            mech_polisher = { label = 'Polisher', weight = 1800, stack = false },
            mech_wrench = { label = 'Workshop Wrench', weight = 900, stack = false }
        }
    }
})
```

Categories are created/reused first, then their items are registered. Runtime
items still follow the normal provider ownership rules and should be registered
again when the provider resource starts. Custom category folders are persistent.

## Dynamic category registration

External resources can install their own item categories before registering
items. Category registration is idempotent: if the category already exists,
Nord Inventory reuses it instead of creating a duplicate.

### Register one category

```lua
local ok, category, state = exports['nord_inventory']:RegisterCategory(
    'restaurant_food',
    {
        label = 'Restaurant Food',
        icon = 'fa-utensils',
        order = 100
    }
)
```

`state` is `created` or `existing`.

### Register several categories

```lua
exports['nord_inventory']:RegisterCategories({
    restaurant_food = {
        label = 'Restaurant Food',
        icon = 'fa-utensils',
        order = 100
    },

    restaurant_drinks = {
        label = 'Restaurant Drinks',
        icon = 'fa-glass-water',
        order = 110
    }
})
```

### Create a category directly from `RegisterItem`

An item can carry an inline category definition:

```lua
exports['nord_inventory']:RegisterItem('nr_burger', {
    label = 'Restaurant Burger',
    weight = 250,

    category = {
        name = 'restaurant_food',
        label = 'Restaurant Food',
        icon = 'fa-utensils',
        order = 100
    }
})
```

Nord creates/reuses `restaurant_food` first, then registers the item with:

```lua
category = 'restaurant_food'
```

### `RegisterIntegration` categories

This is the recommended form for complete script integrations:

```lua
exports['nord_inventory']:RegisterIntegration({
    name = 'Nord Restaurants',

    categories = {
        restaurant_food = {
            label = 'Restaurant Food',
            icon = 'fa-utensils',
            order = 100
        },

        restaurant_drinks = {
            label = 'Restaurant Drinks',
            icon = 'fa-glass-water',
            order = 110
        }
    },

    items = {
        nr_burger = {
            label = 'Burger',
            category = 'restaurant_food',
            weight = 250
        },

        nr_coffee = {
            label = 'Coffee',
            category = 'restaurant_drinks',
            weight = 300
        }
    }
})
```

Categories are registered before items in the same integration call.

### Existing categories

If a category already exists, it is reused and is not overwritten by another
resource. A resource that originally created a custom category may explicitly
update it:

```lua
exports['nord_inventory']:RegisterCategory('restaurant_food', {
    label = 'Restaurant Meals',
    icon = 'fa-utensils',
    order = 90,
    updateExisting = true
})
```

Only the owning resource can use `updateExisting = true`.

### Read categories

```lua
local category = exports['nord_inventory']:GetCategory('restaurant_food')
local categories = exports['nord_inventory']:GetCategories()
```

### Remove an owned category

```lua
local ok, reason = exports['nord_inventory']:UnregisterCategory('restaurant_food')
```

For safety, a category can only be removed by the resource that created it and
only while it contains no active items. `UnregisterIntegration` does not delete
persistent category folders automatically.
