Metafields
The store's own typed fields on products and orders, the schema that explains them, and how to filter by one.
A merchant can extend Queek's product and order shapes with fields of their own — fabric, warranty months, a size-chart image, an ERP reference. Two primitives carry them, and they are deliberately different things:
| Metafields | Metadata | |
|---|---|---|
| What | Typed, defined fields. The merchant declares custom.fabric is single_line_text once; every value written after that is validated against the definition. | Free-form string key/values an integrator writes for its own bookkeeping — an ERP id, a channel reference. |
| Who reads it | Themes, site builders, AI store builders, filters. | Only the integrator that wrote it. Queek never interprets or renders it. |
| On the public API | Yes — metafields on every product, keyed namespace.key. | No. Metadata is private to the merchant API and to webhooks. |
| On orders | Written on the merchant API, read back in webhook payloads. | Same. |
| Filterable | Yes, exact match. | Never. |
{} on either means the store has defined or written none.
Reading metafields on a product
Every product a listing or detail call returns carries metafields:
"metafields": {
"custom.fabric": "cotton",
"custom.warranty_months": 24,
"custom.size_chart": "https://cdn.usequeek.com/…/size-chart.jpg"
}Keys are handles, namespace.key, sorted. Values arrive in the definition's type — a
boolean is a JSON boolean, an integer a number, a list.* a JSON array. A media_id
field arrives already resolved to an image URL, never as an id, so render it directly.
Free-form metadata is not on this payload. It is an integrator's private bookkeeping and stays
on the merchant API.
Reading the schema
A handle alone tells you nothing about what it means, what type it holds or what values are legal. The store's definitions are public exactly because their values already are:
curl https://client.usequeek.com/v1/store/metafield-definitions \
-H "X-Client-Key: pk_live_your_key_here" \
-H "Accept: application/json"data is the list of definitions, ordered by owner_type, namespace, key:
| Field | Type | Notes |
|---|---|---|
p_id | integer | The definition's short id. |
handle | string | namespace.key — the key you will see on products. |
owner_type | string | product. Only product definitions are public (see below). |
namespace, key | string | The two halves of the handle. |
type | string | One of the types in the next table. |
name | string | Human label. Use it for the field's label. |
description | string | null | Help text. Use it for tooltips; never guess what a handle means. |
validations | object | The rules values must satisfy — see Validations. |
created_at, updated_at | ISO 8601 |
The response is not paginated. A store may hold at most 200 definitions across every owner type, so the whole schema always fits in one call.
Order definitions stay private
GET /store/metafield-definitions returns product definitions only. Order metafields are a
description of a merchant's back office — ERP ids, wholesale account codes — and no order
metafield value is ever public, so neither is its schema. You meet order metafields in
webhook payloads and nowhere else on this API.
Types
Nine scalar types, and a list. form of every scalar except json:
| Type | Value shape | Also as a list |
|---|---|---|
single_line_text | string | list.single_line_text |
multi_line_text | string | list.multi_line_text |
integer | number | list.integer |
decimal | number | list.decimal |
boolean | true / false | list.boolean |
date | YYYY-MM-DD string | list.date |
url | string | list.url |
json | any JSON | — |
media_id | an image URL on read | list.media_id — an array of URLs |
Validations
validations is honoured per type. A rule the type cannot support is rejected when the
definition is created, so a rule you read here always applies.
| Rule | Applies to | Meaning |
|---|---|---|
min, max | numbers | Inclusive bounds on the value. |
min, max | text | Bounds on the character length. |
min, max | date | Earliest / latest date, YYYY-MM-DD. |
min, max | list.* | Bounds on the list length. |
regex | text | A pattern the value must match. |
choices | scalars | The only values allowed. Render it as a select. |
Limits
These are part of the contract, so you can design around them. They are also machine-readable
in the OpenAPI document under x-queek-metafields.
| Limit | Value |
|---|---|
| Definitions per store (all owner types) | 200 |
| Metafield values per product or order | 50 |
| Bytes per serialised value | 65,536 |
| Metadata keys per resource | 50 |
| Metadata key length | 64 characters |
| Metadata value length | 500 characters |
Filtering products by a metafield
GET /store/products accepts metafield[handle]=value. The category, collection and
promotion listings do not take it in v1 — filter the full catalogue and narrow with
category_slug instead:
curl "https://client.usequeek.com/v1/store/products?metafield\[custom.fabric\]=cotton&metafield\[custom.fit\]=slim" \
-H "X-Client-Key: pk_live_your_key_here"The rules, all of them:
- Exact match only. No ranges, no
IN, no partial or case-insensitive match in v1. - Up to 5 handles per request, ANDed. A product must match every one.
- Scalar types only.
jsonand everylist.*type cannot be filtered; asking is ametafield_value_invalid. - The value is typed by the definition.
metafield[custom.warranty_months]=24compares as the integer 24;metafield[custom.on_sale]=1,yesandonall compare astrue. Text is compared as given. - Each value is at most 255 characters.
- Combine freely with
keyword,category_slug,sort,pageandper_page.
An undefined handle is refused, not answered with an empty page — an empty list would be indistinguishable from "no product has that value":
{
"status": "failed",
"error_code": "metafield_definition_missing",
"message": "No metafield definition 'custom.colour' exists for product. Create it with POST /api/v1/biz/vendor/metafield-definitions before sending a value.",
"data": null,
"error": {
"code": "metafield_definition_missing",
"message": "No metafield definition 'custom.colour' exists for product. …",
"field": "metafield.custom.colour",
"handle": "custom.colour",
"owner_type": "product",
"doc_url": "https://docs.usequeek.com/docs/versioning-and-errors#metafield_definition_missing",
"request_id": "…"
}
}How a merchant defines and writes them
Definitions and values are written on the merchant side, never through this API. The
dashboard's own API (/api/v1/biz/vendor/…, vendor-authenticated) is where a merchant or their
integration:
- creates a definition —
owner_type,namespace,key,type,name, optionaldescriptionandvalidations; - writes values on a product or an order as
metafields: [{ namespace, key, value }], plusmetadata: { … }for free-form bookkeeping. Writes are a partial upsert: an omitted handle keeps its value and"value": nulldeletes one; - reads its own
metadataback — a merchant's ERP id round-trips byte for byte.
Once a definition exists its namespace, key, owner_type and type are immutable;
only name, description and validations change. Deleting a definition deletes every value
written against it.
Order metafields and metadata are written only by the merchant, never by a shopper: a value a visitor could set would be a forged record, not an extension point.
Because a write is validated against the definition, a value that fails its type or its
validations is refused with a 422 naming the handle and the rule.
Reserved namespaces
The namespace queek — and any namespace starting queek_ — is reserved for Queek's own
first-party fields. A definition there is refused with metafield_namespace_reserved, and a
first-party field can never collide with one you already ship. Use a namespace of your own;
custom is the conventional one.
Error codes
error.code | Status | When | Extra keys in error |
|---|---|---|---|
metafield_definition_missing | 422 | A filter names a handle this store has not defined. | handle, owner_type, field |
metafield_value_invalid | 422 | A filter value the definition cannot hold, or a filter on a json / list.* field. | handle, owner_type, expected_type, field |
metafield_namespace_reserved | 422 | A definition was requested in a reserved namespace. | namespace |
expected_type is the definition's type, so the repair — send a value of that type — is
derivable from the response without parsing the message.
In webhooks
Every order webhook payload carries the order's metafields and metadata under data, so a
receiver can match the order to its own record without a second call. A change to an order's
metafields on the merchant API emits orders/updated on its own — see
Webhooks.