> ## 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.

# Querying

> Filter, sort, paginate, and expand Admin API list responses

The Admin API uses [Ransack](https://activerecord-hackery.github.io/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`:

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  const orders = await client.orders.list({
    status_eq: 'complete',
    total_gteq: 100,
    email_cont: '@example.com',
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -G 'https://store.example.com/api/v3/admin/orders' \
    -H 'X-Spree-Api-Key: sk_xxx' \
    --data-urlencode 'q[status_eq]=complete' \
    --data-urlencode 'q[total_gteq]=100' \
    --data-urlencode 'q[email_cont]=@example.com'
  ```
</CodeGroup>

<Note>
  The SDK automatically wraps filter keys in `q[...]` and appends `[]` for array values — pass flat params.
</Note>

### 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:

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  // All orders for a specific customer
  const orders = await client.orders.list({
    user_id_eq: 'cus_UkLWZg9DAJ',
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -G 'https://store.example.com/api/v3/admin/orders' \
    -H 'X-Spree-Api-Key: sk_xxx' \
    --data-urlencode 'q[user_id_eq]=cus_UkLWZg9DAJ'
  ```
</CodeGroup>

The same applies to `_id_in`, `_id_not_eq`, and other ID predicates.

### Combining filters

Multiple filters combine with AND:

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  // 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(),
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -G 'https://store.example.com/api/v3/admin/orders' \
    -H 'X-Spree-Api-Key: sk_xxx' \
    --data-urlencode 'q[status_eq]=complete' \
    --data-urlencode 'q[total_gteq]=100' \
    --data-urlencode 'q[completed_at_gteq]=2026-04-22T00:00:00Z'
  ```
</CodeGroup>

### Filtering by association

Use underscore notation to filter on associated model attributes:

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  // 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',
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -G 'https://store.example.com/api/v3/admin/customers' \
    -H 'X-Spree-Api-Key: sk_xxx' \
    --data-urlencode 'q[tags_name_eq]=wholesale'
  ```
</CodeGroup>

### 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               |

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  const customers = await client.customers.list({
    search: 'jane',
  })

  const vipCustomers = await client.customers.list({
    with_min_total_spent: 1000,
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -G 'https://store.example.com/api/v3/admin/customers' \
    -H 'X-Spree-Api-Key: sk_xxx' \
    --data-urlencode 'q[search]=jane'
  ```
</CodeGroup>

## Sorting

Use the top-level `sort` parameter on any list endpoint. Prefix with `-` for descending. Follows the [JSON:API sorting convention](https://jsonapi.org/format/#fetching-sorting).

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  // 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',
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -G 'https://store.example.com/api/v3/admin/orders' \
    -H 'X-Spree-Api-Key: sk_xxx' \
    -d 'sort=-completed_at'
  ```
</CodeGroup>

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.

## Pagination

All collection endpoints return paginated results. Control with `page` and `limit`:

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  const { data: orders, meta } = await client.orders.list({
    page: 2,
    limit: 50,
  })

  // meta.count, meta.pages, meta.previous, meta.next ...
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -G 'https://store.example.com/api/v3/admin/orders' \
    -H 'X-Spree-Api-Key: sk_xxx' \
    -d 'page=2' \
    -d 'limit=50'
  ```
</CodeGroup>

| Parameter | Default | Max   | Description             |
| --------- | ------- | ----- | ----------------------- |
| `page`    | `1`     | —     | Page number (1-indexed) |
| `limit`   | `25`    | `100` | Records per page        |

### Pagination metadata

Responses include a `meta` object:

```json theme={"theme":"night-owl"}
{
  "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:

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  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'],
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -G 'https://store.example.com/api/v3/admin/orders/or_xxx' \
    -H 'X-Spree-Api-Key: sk_xxx' \
    -d 'expand=items,fulfillments,payments,customer'
  ```
</CodeGroup>

### Nested expand

Use dot notation up to 4 levels deep:

```typescript theme={"theme":"night-owl"}
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:

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  const orders = await client.orders.list({
    fields: ['number', 'total', 'status', 'completed_at'],
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -G 'https://store.example.com/api/v3/admin/orders' \
    -H 'X-Spree-Api-Key: sk_xxx' \
    -d 'fields=number,total,status,completed_at'
  ```
</CodeGroup>

Rules:

* `id` is always included
* Expanded associations return their full payload regardless of `fields`
* Field selection applies to the top-level resource only

<Note>
  TypeScript types in `@spree/admin-sdk` remain fully typed regardless of `fields`. At runtime, only the requested fields are present — the rest are `undefined`.
</Note>
