Documentation Index
Fetch the complete documentation index at: https://spreecommerce.org/docs/llms.txt
Use this file to discover all available pages before exploring further.
The Admin API uses Ransack for filtering and sorting on collection endpoints. All filter conditions go through the q parameter; sorting and pagination are top-level params.
Filtering
Pass filter conditions via q:
const orders = await client.orders.list({
status_eq: 'complete',
total_gteq: 100,
email_cont: '@example.com',
})
The SDK automatically wraps filter keys in q[...] and appends [] for array values — pass flat params.
Common predicates
| Predicate | Description | SDK | cURL |
|---|
eq | Equals | status_eq: 'complete' | q[status_eq]=complete |
not_eq | Not equals | status_not_eq: 'canceled' | q[status_not_eq]=canceled |
cont | Contains (case-insensitive) | email_cont: '@acme' | q[email_cont]=@acme |
start | Starts with | number_start: 'R10' | q[number_start]=R10 |
end | Ends with | slug_end: 'sale' | q[slug_end]=sale |
lt / lteq | Less than / less than or equal | total_lteq: 50 | q[total_lteq]=50 |
gt / gteq | Greater than / greater than or equal | total_gteq: 100 | q[total_gteq]=100 |
in | In a list | status_in: ['complete', 'canceled'] | q[status_in][]=complete&q[status_in][]=canceled |
null / not_null | Is null / not null | completed_at_not_null: true | q[completed_at_not_null]=true |
true / false | Boolean | accepts_email_marketing_true: 1 | q[accepts_email_marketing_true]=1 |
Prefixed IDs in filters
Resource ID filters accept Stripe-style prefixed IDs directly. The server decodes them before querying:
// All orders for a specific customer
const orders = await client.orders.list({
user_id_eq: 'cus_UkLWZg9DAJ',
})
The same applies to _id_in, _id_not_eq, and other ID predicates.
Combining filters
Multiple filters combine with AND:
// Completed orders over $100 from the last 7 days
const orders = await client.orders.list({
status_eq: 'complete',
total_gteq: 100,
completed_at_gteq: new Date(Date.now() - 7 * 86_400_000).toISOString(),
})
Filtering by association
Use underscore notation to filter on associated model attributes:
// Customers tagged as "wholesale"
const customers = await client.customers.list({
tags_name_eq: 'wholesale',
})
// Products in a specific category
const products = await client.products.list({
taxons_id_eq: 'ctg_xxx',
})
Custom search scopes
Some resources expose convenience search scopes:
| Resource | Scope | Example |
|---|
| Customers | search | Full-text over email + first/last name |
| Customers | with_min_total_spent | Filter by lifetime spend |
| Orders | multi_search | Number + email full-text |
const customers = await client.customers.list({
search: 'jane',
})
const vipCustomers = await client.customers.list({
with_min_total_spent: 1000,
})
Sorting
Use the top-level sort parameter on any list endpoint. Prefix with - for descending. Follows the JSON:API sorting convention.
// Most recent orders first
const orders = await client.orders.list({
sort: '-completed_at',
})
// Multiple sort keys (priority left to right)
const customers = await client.customers.list({
sort: '-total_spent,email',
})
Sortable columns are limited to those whitelisted on the model (Ransack’s whitelisted_ransackable_attributes). Sorting on a virtual column (e.g., a serializer-computed field like display_total_spent) is not supported.
All collection endpoints return paginated results. Control with page and limit:
const { data: orders, meta } = await client.orders.list({
page: 2,
limit: 50,
})
// meta.count, meta.pages, meta.previous, meta.next ...
| Parameter | Default | Max | Description |
|---|
page | 1 | — | Page number (1-indexed) |
limit | 25 | 100 | Records per page |
Responses include a meta object:
{
"data": [...],
"meta": {
"page": 2,
"limit": 50,
"count": 327,
"pages": 7,
"from": 51,
"to": 100,
"in": 50,
"previous": 1,
"next": 3
}
}
| Field | Description |
|---|
page | Current page number |
limit | Records per page |
count | Total number of matching records |
pages | Total number of pages |
from / to / in | Position range / count of this page |
previous / next | Previous/next page number, or null |
Expanding associations
Most admin endpoints return slim payloads by default — associations are returned as IDs. Use the expand parameter to include related resources inline:
const order = await client.orders.get('or_xxx', {
expand: ['items', 'fulfillments', 'payments', 'customer'],
})
// Nested expand
const customer = await client.customers.get('cus_xxx', {
expand: ['addresses', 'store_credits'],
})
Nested expand
Use dot notation up to 4 levels deep:
const order = await client.orders.get('or_xxx', {
expand: ['items.variant.product', 'fulfillments.tracking'],
})
A nested expand implicitly includes its parent — expand: ['items.variant'] returns both items and their variant data.
What can I expand?
Each endpoint documents its supported expand keys in the OpenAPI reference. Common admin expansions:
| Resource | Common expands |
|---|
| Orders | items, fulfillments, payments, customer, discounts, adjustments, billing_address, shipping_address |
| Customers | addresses, store_credits, orders |
| Products | variants, media, option_types, categories |
Field selection
Use the fields parameter to request only specific fields on a resource. Reduces payload size for bandwidth-sensitive integrations:
const orders = await client.orders.list({
fields: ['number', 'total', 'status', 'completed_at'],
})
Rules:
id is always included
- Expanded associations return their full payload regardless of
fields
- Field selection applies to the top-level resource only
TypeScript types in @spree/admin-sdk remain fully typed regardless of fields. At runtime, only the requested fields are present — the rest are undefined.