Metafields
Metafields
Metafields let your app attach custom key-value data to store resources. Each metafield is scoped to your app — other apps cannot read or modify your data, and your app cannot access metafields created by other apps.
Use metafields to store fraud scores on orders, custom attributes on products, loyalty tiers on customers, or app-wide configuration on the store itself.
Concepts
Definitions
Before you can store values, you must create a metafield definition. A definition declares the namespace, key, resource type, and value type for a metafield. Think of it as a schema declaration.
Definition: my-app.risk_score
namespace: my-app
key: risk_score
resource_type: order
value_type: integer
Definitions are app-scoped. The namespace + key combination must be unique within your app.
Values
A metafield value is a single data point attached to a specific resource instance. For example, order #1045 has a my-app.risk_score value of 85.
Values are stored per-store. If your app is installed on three stores, each store has its own independent metafield values.
Namespacing
Use your app's slug or a descriptive prefix as the namespace. This prevents collisions if you have multiple metafield groups:
my-app.risk_score — fraud-related data
my-app.review_count — product reviews
my-app.loyalty_tier — customer data
Namespace format: lowercase alphanumeric with optional hyphens (e.g., my-app, fulfillment, analytics2).
Key format: lowercase alphanumeric with underscores (e.g., risk_score, total_reviews, tier_level).
Resource Types
| Resource Type | Description | Example Usage |
|---|---|---|
order | Order records | Fraud score, shipping priority, custom status |
product | Product records | SEO metadata, vendor notes, custom tags |
customer | Customer records | Loyalty tier, lifetime value, preferences |
store | Store-level data | App configuration, feature flags, sync state |
Value Types
| Type | Description | Example |
|---|---|---|
string | Text value | "Express shipping" |
integer | Whole number | 85 |
decimal | Floating-point number | 4.99 |
boolean | True/false | true |
json | Arbitrary JSON object or array | {"tags": ["vip", "premium"]} |
date | ISO 8601 date string | "2025-06-15" |
url | Valid URL | "https://example.com/doc.pdf" |
color | Hex color code | "#ff5733" |
Validation is enforced server-side. Setting an integer metafield to "hello" returns an error.
API Reference
All endpoints require app authentication (client credentials or OAuth token) and the read:metafields or write:metafields scope.
Create Definition
POST /api/apps/v1/metafields/definitions
Scope: write:metafields
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
namespace | string | Yes | Namespace (lowercase alphanumeric + hyphens) |
key | string | Yes | Key name (lowercase alphanumeric + underscores) |
name | string | Yes | Human-readable display name |
description | string | No | Description of the metafield |
resource_type | string | Yes | One of: order, product, customer, store |
value_type | string | No | One of the valid value types. Default: string |
validation_rules | object | No | Type-specific validation rules |
Validation rules by type:
| Value Type | Available Rules |
|---|---|
string | max_length, regex, choices (array of allowed values) |
integer | min, max |
decimal | min, max |
| All others | No additional rules |
curl -X POST "https://api.selorax.io/api/apps/v1/metafields/definitions" \
-H "X-Client-Id: sx_app_..." \
-H "X-Client-Secret: sx_secret_..." \
-H "X-Store-Id: 22" \
-H "Content-Type: application/json" \
-d '{
"namespace": "my-app",
"key": "risk_score",
"name": "Fraud Risk Score",
"description": "Numerical fraud risk assessment (0-100)",
"resource_type": "order",
"value_type": "integer",
"validation_rules": { "min": 0, "max": 100 }
}'Response:
{
"message": "Definition created.",
"data": {
"id": 12,
"app_id": 5,
"namespace": "my-app",
"key": "risk_score",
"name": "Fraud Risk Score",
"resource_type": "order",
"value_type": "integer"
},
"status": 200
}List Definitions
GET /api/apps/v1/metafields/definitions
Scope: read:metafields
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
resource_type | string | Optional. Filter by resource type. |
curl -X GET "https://api.selorax.io/api/apps/v1/metafields/definitions?resource_type=order" \
-H "X-Client-Id: sx_app_..." \
-H "X-Client-Secret: sx_secret_..." \
-H "X-Store-Id: 22"Response:
{
"message": "Definitions fetched.",
"data": [
{
"id": 12,
"app_id": 5,
"namespace": "my-app",
"key": "risk_score",
"name": "Fraud Risk Score",
"resource_type": "order",
"value_type": "integer",
"validation_rules": "{\"min\":0,\"max\":100}",
"is_active": 1,
"created_at": "2025-06-10T08:00:00.000Z",
"updated_at": "2025-06-10T08:00:00.000Z"
}
],
"status": 200
}Delete Definition
DELETE /api/apps/v1/metafields/definitions/:id
Scope: write:metafields
Soft-deletes the definition and hard-deletes all associated values. This is irreversible.
curl -X DELETE "https://api.selorax.io/api/apps/v1/metafields/definitions/12" \
-H "X-Client-Id: sx_app_..." \
-H "X-Client-Secret: sx_secret_..." \
-H "X-Store-Id: 22"Set Value
POST /api/apps/v1/metafields/values
Scope: write:metafields
Set a single metafield value. If a value already exists for this definition + resource, it is updated (upsert).
Request Body (single):
| Field | Type | Required | Description |
|---|---|---|---|
namespace | string | Yes | Definition namespace |
key | string | Yes | Definition key |
resource_type | string | Yes | Resource type |
resource_id | integer | Yes | Resource ID |
value | any | Yes | The value to store |
curl -X POST "https://api.selorax.io/api/apps/v1/metafields/values" \
-H "X-Client-Id: sx_app_..." \
-H "X-Client-Secret: sx_secret_..." \
-H "X-Store-Id: 22" \
-H "Content-Type: application/json" \
-d '{
"namespace": "my-app",
"key": "risk_score",
"resource_type": "order",
"resource_id": 1045,
"value": 85
}'Batch set — pass an array of metafields:
curl -X POST "https://api.selorax.io/api/apps/v1/metafields/values" \
-H "X-Client-Id: sx_app_..." \
-H "X-Client-Secret: sx_secret_..." \
-H "X-Store-Id: 22" \
-H "Content-Type: application/json" \
-d '{
"metafields": [
{
"namespace": "my-app",
"key": "risk_score",
"resource_type": "order",
"resource_id": 1045,
"value": 85
},
{
"namespace": "my-app",
"key": "risk_score",
"resource_type": "order",
"resource_id": 1044,
"value": 12
}
]
}'Response:
{
"message": "2 metafield(s) set.",
"data": [
{
"definition_id": 12,
"resource_type": "order",
"resource_id": 1045,
"value": 85
},
{
"definition_id": 12,
"resource_type": "order",
"resource_id": 1044,
"value": 12
}
],
"status": 200
}Definition required
You must create a definition before setting values. If no definition exists
for the namespace + key combination, the API returns an error: "No
definition found. Create a definition first."
Get Values
GET /api/apps/v1/metafields/values
Scope: read:metafields
Get all metafield values for a resource (scoped to your app).
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
resource_type | string | Yes | Resource type |
resource_id | integer | Yes | Resource ID |
curl -X GET "https://api.selorax.io/api/apps/v1/metafields/values?resource_type=order&resource_id=1045" \
-H "X-Client-Id: sx_app_..." \
-H "X-Client-Secret: sx_secret_..." \
-H "X-Store-Id: 22"Response:
{
"message": "Values fetched.",
"data": [
{
"id": 201,
"namespace": "my-app",
"key": "risk_score",
"name": "Fraud Risk Score",
"value": 85,
"value_type": "integer",
"resource_type": "order",
"resource_id": 1045,
"app_id": 5,
"updated_at": "2025-06-15T10:30:00.000Z"
}
],
"status": 200
}Values are automatically cast to their declared type. An integer definition returns a JavaScript number, a boolean returns true/false, and a json definition returns a parsed object.
Get Single Value
GET /api/apps/v1/metafields/values/:namespace/:key
Scope: read:metafields
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
resource_type | string | Yes | Resource type |
resource_id | integer | Yes | Resource ID |
curl -X GET "https://api.selorax.io/api/apps/v1/metafields/values/my-app/risk_score?resource_type=order&resource_id=1045" \
-H "X-Client-Id: sx_app_..." \
-H "X-Client-Secret: sx_secret_..." \
-H "X-Store-Id: 22"Returns the single matching value, or 404 if not found.
Delete Value
DELETE /api/apps/v1/metafields/values/:namespace/:key
Scope: write:metafields
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
resource_type | string | Yes | Resource type |
resource_id | integer | Yes | Resource ID |
curl -X DELETE "https://api.selorax.io/api/apps/v1/metafields/values/my-app/risk_score?resource_type=order&resource_id=1045" \
-H "X-Client-Id: sx_app_..." \
-H "X-Client-Secret: sx_secret_..." \
-H "X-Store-Id: 22"Batch Delete Values
DELETE /api/apps/v1/metafields/values/batch
Scope: write:metafields
Delete all metafield values for a resource, optionally filtered by namespace.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
resource_type | string | Yes | Resource type |
resource_id | integer | Yes | Resource ID |
namespace | string | No | Only delete values in this namespace |
# Delete all metafields for order 1045
curl -X DELETE "https://api.selorax.io/api/apps/v1/metafields/values/batch?resource_type=order&resource_id=1045" \
-H "X-Client-Id: sx_app_..." \
-H "X-Client-Secret: sx_secret_..." \
-H "X-Store-Id: 22"
# Delete only metafields in the "my-app" namespace for order 1045
curl -X DELETE "https://api.selorax.io/api/apps/v1/metafields/values/batch?resource_type=order&resource_id=1045&namespace=my-app" \
-H "X-Client-Id: sx_app_..." \
-H "X-Client-Secret: sx_secret_..." \
-H "X-Store-Id: 22"Response:
{
"message": "3 metafield(s) deleted.",
"deleted": 3,
"status": 200
}SDK Usage
The @selorax/ui SDK provides a high-level API for metafields in sandbox extensions. These methods call the platform API through the dashboard's API proxy.
Get all values for a resource
const metafields = await selorax.metafields.get("order", 1045);
// Returns array of { id, namespace, key, name, value, value_type, ... }Get a single value
const score = await selorax.metafields.getValue(
"my-app", // namespace
"risk_score", // key
"order", // resource_type
1045, // resource_id
);
// Returns { id, namespace, key, name, value, value_type, ... } or nullSet a value
await selorax.metafields.set({
namespace: "my-app",
key: "risk_score",
resource_type: "order",
resource_id: 1045,
value: 85,
});Set multiple values
await selorax.metafields.setMany([
{
namespace: "my-app",
key: "risk_score",
resource_type: "order",
resource_id: 1045,
value: 85,
},
{
namespace: "my-app",
key: "risk_score",
resource_type: "order",
resource_id: 1044,
value: 12,
},
]);Delete a value
await selorax.metafields.remove(
"my-app", // namespace
"risk_score", // key
"order", // resource_type
1045, // resource_id
);Rate Limiting
Metafield API requests are rate-limited to 120 requests per minute per app. This limit applies across all stores. Exceeding the limit returns HTTP 429:
{
"message": "Rate limit exceeded. Max 120 requests per minute per app.",
"code": "rate_limited",
"status": 429
}To stay within limits:
- Use batch operations (
setMany, batch delete) instead of individual calls. - Cache metafield values in your extension's local state.
- Avoid polling. Load metafields once when the extension initializes.
Namespacing Best Practices
-
Use your app slug as the namespace. This prevents collisions if you acquire or merge with another app.
-
Keep keys descriptive and specific.
risk_scoreis better thanscore.review_countis better thancount. -
Group related data under one namespace. If your app manages both fraud detection and shipping optimization, use
my-appfor both rather than separate namespaces — unless you need separate cleanup. -
Use the
jsonvalue type for complex data. Instead of creating five separate metafields for an address, store it as a single JSON value. -
Define validation rules. Use
min/maxfor numbers andchoicesfor enums. This prevents invalid data from being stored.
Cleanup on Uninstall
When a merchant uninstalls your app, the platform automatically deletes all metafield values for your app in that store. Definitions are preserved (they are app-level, not store-level) so values are not orphaned if the merchant reinstalls.
What's Next
- Sandbox Extensions — Use metafields from within sandbox extensions
- Merchant Settings — Let merchants configure your extensions
- JSON Extensions — Display metafield data in JSON UI trees