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

# Metafields

> Store your app's own typed data on products, orders, customers and the store, and show chosen values on the storefront.

## Overview

A **metafield** is one typed value attached to one Salesive record. Use metafields to keep your
app's data next to the record it belongs to, instead of in a database of your own keyed by
Salesive ids: a size chart on a product, a loyalty tier on a customer, a gift message on an
order, a feature flag on the store.

Every metafield has an owner, a namespace, a key, a type and a value:

```json theme={null}
{
  "_id": "6730a1f2c2d4e5f6a7b8c9d0",
  "ownerType": "product",
  "ownerId": "66b1f0a3c2d4e5f6a7b8c9d0",
  "namespace": "app--app_3f9c0d1e2a4b5c6d7e8f9a0b1c2d3e4f",
  "key": "size_chart",
  "type": "json",
  "value": { "S": 50, "M": 54, "L": 58 },
  "storefrontVisible": false,
  "createdAt": "2026-11-05T09:30:00.000Z",
  "updatedAt": "2026-11-05T09:30:00.000Z"
}
```

| Field | Description |
| - | - |
| `ownerType` | `product`, `food`, `service`, `digital-product`, `order`, `customer` or `shop`. Use the catalog type the store sells: products for ecommerce, foods for restaurants, services for businesses, digital products for digital stores. |
| `ownerId` | The record's id. For `shop`, the store's own id (`store.id` from [`GET /app/context`](https://docs.salesive.com/apps/scopes-permissions)). |
| `namespace` | Who the value belongs to (see [Namespaces](#namespaces)). |
| `key` | The name of the value inside the namespace. |
| `type` | One of the [value types](#value-types). |
| `storefrontVisible` | Whether themes can read it. Set by a [definition](#definitions), never directly. |

Metafields need the `READ_METAFIELDS` and `WRITE_METAFIELDS` scopes.

## Namespaces

| Namespace | Who writes it | Who reads it |
| - | - | - |
| Your app namespace, `app--<your clientId>` | Only your app | Your app and the merchant. **Never another app.** |
| Shared namespaces, such as `custom` | The merchant, and any app with `WRITE_METAFIELDS` | The merchant, and any app with `READ_METAFIELDS` |
| `salesive` and `salesive--*` | Reserved | Reserved |

You never need to spell out your own namespace: send `"namespace": "$app"` and Salesive expands it
to `app--<your clientId>`. Responses always show the expanded name. Your namespace is the same on
every store and survives a reinstall.

Another app's namespace does not exist as far as your app can tell: it is left out of lists and
counts, a direct read is `404`, and a write is `403`. The merchant can read your namespace in the
dashboard but cannot edit it.

Namespaces are 2 to 100 characters and keys 1 to 64, using lowercase letters, digits, `_` and `-`.

## Value types

| Type | Send | Stored as | Limit |
| - | - | - | - |
| `single_line_text` | A string without line breaks | string | 1,024 characters |
| `multi_line_text` | A string | string | 16,384 characters |
| `number_integer` | A whole number, or a string holding one | number | safe integer range |
| `number_decimal` | A number, or a string holding one | number | finite |
| `boolean` | `true` or `false` (the strings work too) | boolean | |
| `json` | Any JSON value | as sent | 16 KB, 10 levels deep, no keys starting with `$` or containing `.` |
| `url` | An absolute `http` or `https` URL | string | 2,048 characters |
| `date` | `YYYY-MM-DD` | string | a real calendar date |
| `date_time` | ISO 8601 with a zone, such as `2026-11-05T10:30:00+01:00` | UTC ISO string | |
| `money` | `{ "amount": 1500, "currencyCode": "NGN" }` | same, code upper cased | 3 letter code |

A record holds at most 200 metafields. A value that fails its type is a `400` with a message
naming the problem.

## Endpoints

All paths are under `https://api.salesive.com/api/v1` with your `app_` token. The store comes from
your token; never send a shop id.

### List a record's metafields

`GET /metafields?ownerType=product&ownerId=<id>` (`READ_METAFIELDS`)

Optional `namespace` (or `$app`), `key`, `page` and `limit` (up to 250). Returns
`{ metafields, total, page, limit }`. An owner on another store, or one that does not exist, is a
`404`.

### Get one

`GET /metafields/:id` (`READ_METAFIELDS`)

### Set one

`PUT /metafields` (`WRITE_METAFIELDS`) creates or replaces the value for that owner, namespace and
key.

```bash theme={null}
curl -X PUT https://api.salesive.com/api/v1/metafields \
  -H "Authorization: Bearer $APP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ownerType": "product",
    "ownerId": "66b1f0a3c2d4e5f6a7b8c9d0",
    "namespace": "$app",
    "key": "size_chart",
    "type": "json",
    "value": { "S": 50, "M": 54, "L": 58 }
  }'
```

When a definition exists for the key you may leave `type` out; if you send it, it must match.

### Set many

`POST /metafields/set` (`WRITE_METAFIELDS`) with `{ "metafields": [ ... ] }`, up to 25 rows, on any
mix of owners. Every row is checked before any is written, so one bad row fails the whole request
and nothing is saved. The error message names the row, such as `metafields[3]: value must be a
number.` Returns the saved rows in the order sent.

### Delete one

`DELETE /metafields/:id` (`WRITE_METAFIELDS`). Returns the deleted row's identifiers.

## Definitions

A **definition** is an optional schema for one key on one owner type. It fixes the type, adds
validations, and is the only way to make a value readable by the storefront theme.

```bash theme={null}
curl -X POST https://api.salesive.com/api/v1/metafield-definitions \
  -H "Authorization: Bearer $APP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ownerType": "product",
    "namespace": "$app",
    "key": "size_chart",
    "type": "json",
    "name": "Size chart",
    "description": "Chest width per size, in cm",
    "storefrontVisible": true
  }'
```

| Field | Notes |
| - | - |
| `ownerType`, `namespace`, `key`, `type` | Fixed once created. To change them, delete and create again. |
| `name`, `description` | Shown to the merchant. |
| `validations` | Optional. `min` and `max` for numbers and money amounts, `maxLength` for text and url, `choices` (a list of allowed values) for single line text. |
| `storefrontVisible` | Lets themes read the values. Only for `product`, `food`, `service`, `digital-product` and `shop`; orders and customers are never public. |

| Method and path | Scope |
| - | - |
| `GET /metafield-definitions?ownerType=product` | `READ_METAFIELDS` |
| `GET /metafield-definitions/:id` | `READ_METAFIELDS` |
| `POST /metafield-definitions` | `WRITE_METAFIELDS` |
| `PUT /metafield-definitions/:id` | `WRITE_METAFIELDS` |
| `DELETE /metafield-definitions/:id` | `WRITE_METAFIELDS` |

Creating a definition fails with `409` when existing values for the key have a different type.
Turning `storefrontVisible` on or off applies to the existing values at once. Deleting a
definition keeps the values but makes them private again. A store can have 200 definitions per
owner type. The same namespace rules apply: your app's definitions are hidden from other apps.

## On the storefront

Themes read storefront visible values from the Store API, for the store being browsed:

* `GET /api/v1/products/:id` includes `metafields`, keyed `"<namespace>.<key>"`:

  ```json theme={null}
  "metafields": {
    "app--app_3f9c0d1e2a4b5c6d7e8f9a0b1c2d3e4f.size_chart": { "type": "json", "value": { "S": 50 } },
    "custom.care": { "type": "single_line_text", "value": "Cold wash" }
  }
  ```

* `GET /api/v1/metafields?ownerType=product&ownerIds=<id>,<id>` (up to 50 ids) returns the same,
  grouped by owner id, for list pages. `ownerType=shop` returns the store's own values.

Values without a storefront visible definition never appear there.

## Webhooks

With `READ_METAFIELDS` your [webhook URL](https://docs.salesive.com/apps-api/webhooks.md) receives `metafields/created`,
`metafields/updated` and `metafields/deleted`, and the same for `metafield-definitions`. Each
delivery covers one row, so a batch of 10 arrives as 10 deliveries, and adds which record and key
changed:

```json theme={null}
{
  "id": "8f1c2e3a-0000-4000-8000-9b0d00000000",
  "topic": "metafields/updated",
  "resource": "metafields",
  "action": "updated",
  "resourceId": "6730a1f2c2d4e5f6a7b8c9d0",
  "shopId": "6680aabbccddeeff00112200",
  "occurredAt": "2026-11-05T09:31:00.000Z",
  "metafield": {
    "ownerType": "product",
    "ownerId": "66b1f0a3c2d4e5f6a7b8c9d0",
    "namespace": "custom",
    "key": "care"
  }
}
```

The value is never in the webhook; fetch it with `GET /metafields/:id`. Changes in your own
namespace reach only your app, and you never hear about another app's namespace.

## Errors

| Status | When |
| - | - |
| `400` | Bad owner type, id, namespace, key, type or value (the message says which). |
| `403` | Missing scope, another app's namespace, a reserved namespace. |
| `404` | The owner, metafield or definition does not exist on this store, or belongs to another app. |
| `409` | A definition already exists, or existing values clash with its type. |
| `422` | The type does not match the definition, or a limit was reached. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.