---
title: Multi-Sort
description: Sort API results by multiple fields with independent sort directions in TraceRTM
---

# Multi-Sort

Multi-sort allows you to sort results by multiple fields, each with its own sort direction. This ensures deterministic ordering and enables complex sorting scenarios like "highest priority first, then by due date."

## Basic Multi-Sort

### Syntax

Use comma-separated field names and orders:

```bash
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?sort=field1,field2,field3&order=dir1,dir2,dir3"
```

### Example: Priority then Due Date

```bash
# Sort by priority (highest first), then by due date (earliest first)
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?sort=priority,due_date&order=desc,asc" \
  -H "Authorization: Bearer rtm_live_sk_..."
```

### Result

```json
{
  "data": [
    {"title": "Critical - Due Jan 20", "priority": "critical", "due_date": "2024-01-20"},
    {"title": "Critical - Due Jan 25", "priority": "critical", "due_date": "2024-01-25"},
    {"title": "High - Due Jan 15", "priority": "high", "due_date": "2024-01-15"},
    {"title": "High - Due Jan 22", "priority": "high", "due_date": "2024-01-22"},
    {"title": "Medium - Due Jan 10", "priority": "medium", "due_date": "2024-01-10"}
  ],
  "sort": [
    {"field": "priority", "order": "desc"},
    {"field": "due_date", "order": "asc"}
  ]
}
```

---

## Multi-Sort Parameters

| Parameter | Format | Example |
|-----------|--------|---------|
| `sort` | Comma-separated fields | `sort=priority,created_at,title` |
| `order` | Comma-separated directions | `order=desc,desc,asc` |
| `nulls` | Comma-separated null positions | `nulls=last,first,last` |

### Order Count Matching

If fewer orders are provided than fields, the last order is repeated:

```bash
# sort=a,b,c with order=desc means: a desc, b desc, c desc
curl "...?sort=priority,due_date,created_at&order=desc"

# sort=a,b,c with order=desc,asc means: a desc, b asc, c asc
curl "...?sort=priority,due_date,created_at&order=desc,asc"
```

---

## Common Multi-Sort Patterns

### Priority + Date

```bash
# Highest priority first, then newest within each priority
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?sort=priority,created_at&order=desc,desc"
```

### Status + Priority

```bash
# By workflow status, then by priority within each status
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?sort=status,priority&order=asc,desc"
```

### Type + Status + Updated

```bash
# Group by type, then status, then most recently updated
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?sort=type,status,updated_at&order=asc,asc,desc"
```

### Assignee + Priority + Due Date

```bash
# By assignee (grouped), then priority, then due date
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?sort=assignee,priority,due_date&order=asc,desc,asc&nulls=last,last,last"
```

---

## Multi-Sort with Filters

Combine multi-sort with filters:

```bash
# Active requirements, sorted by priority then due date
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?type=requirement&status[in]=draft,in_progress,in_review&sort=priority,due_date&order=desc,asc"

# User's assigned items, sorted by due date then priority
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?assignee=user_abc123&sort=due_date,priority&order=asc,desc&nulls=last,last"
```

---

## Multi-Sort with Pagination

### Offset Pagination

```bash
# Page 1
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?sort=priority,created_at&order=desc,desc&limit=20&offset=0"

# Page 2 (same sort parameters)
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?sort=priority,created_at&order=desc,desc&limit=20&offset=20"
```

### Cursor Pagination

Multi-sort works seamlessly with cursor pagination. The cursor encodes all sort fields:

```bash
# First page
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?sort=priority,due_date,id&order=desc,asc,asc&limit=20"
```

Response:

```json
{
  "data": [...],
  "pagination": {
    "has_next": true,
    "next_cursor": "eyJzb3J0IjpbeyJmIjoicHJpb3JpdHkiLCJ2IjoiaGlnaCJ9LHsiZiI6ImR1ZV9kYXRlIiwidiI6IjIwMjQtMDEtMjUifSx7ImYiOiJpZCIsInYiOiJpdGVtX3JlcV8wNTAifV19"
  }
}
```

```bash
# Next page
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?sort=priority,due_date,id&order=desc,asc,asc&limit=20&cursor=eyJ..."
```

---

## Stable Sort with ID

For deterministic results, always include a unique field (like `id`) as the final sort field:

```bash
# Stable sort - items with same priority and due_date will have consistent order
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?sort=priority,due_date,id&order=desc,asc,asc"
```

### Why Stable Sort Matters

Without a unique final field, items with identical values for all sort fields may appear in different orders across requests:

```bash
# Unstable - two "high" priority items due Jan 20 could swap positions
curl "...?sort=priority,due_date&order=desc,asc"

# Stable - consistent order guaranteed
curl "...?sort=priority,due_date,id&order=desc,asc,asc"
```

---

## Null Handling in Multi-Sort

Control null positioning per field:

```bash
# Different null handling per field
curl "https://api.tracertm.dev/v1/projects/proj_abc123/items?sort=assignee,due_date,priority&order=asc,asc,desc&nulls=last,first,last"
```

| Sort Field | Nulls Position | Effect |
|------------|----------------|--------|
| assignee | last | Unassigned items at end |
| due_date | first | Items without due date first |
| priority | last | Items without priority last |

---

## Sort Limits

| Limit | Value |
|-------|-------|
| Maximum sort fields | 5 |
| Maximum field name length | 64 characters |

### Error: Too Many Sort Fields

```json
{
  "error": {
    "code": "SORT_LIMIT_EXCEEDED",
    "message": "Maximum of 5 sort fields allowed",
    "details": {
      "max_fields": 5,
      "requested_fields": 7
    }
  }
}
```

---

## SDK Examples

### Python

```python
from tracertm import TraceRTMClient

client = TraceRTMClient(api_key="rtm_live_sk_...")

# Multi-sort with list syntax
items = client.items.list(
    project_id="proj_abc123",
    sort=["priority", "due_date", "created_at"],
    order=["desc", "asc", "desc"]
)

# Multi-sort with dict syntax
items = client.items.list(
    project_id="proj_abc123",
    sort=[
        {"field": "priority", "order": "desc"},
        {"field": "due_date", "order": "asc", "nulls": "last"},
        {"field": "id", "order": "asc"}
    ]
)

# Builder pattern
items = client.items.query(
    project_id="proj_abc123"
).sort(
    "priority", "desc"
).sort(
    "due_date", "asc", nulls="last"
).sort(
    "id", "asc"
).execute()

# Multi-sort with filters
items = client.items.list(
    project_id="proj_abc123",
    type="requirement",
    status__in=["draft", "in_progress"],
    sort=["priority", "updated_at"],
    order=["desc", "desc"]
)

# Iterate with multi-sort
for item in client.items.iterate(
    project_id="proj_abc123",
    sort=["priority", "created_at", "id"],
    order=["desc", "desc", "asc"]
):
    print(f"{item.priority} | {item.created_at} | {item.title}")
```

### TypeScript

```typescript
import { TraceRTMClient } from '@tracertm/sdk';

const client = new TraceRTMClient({ apiKey: 'rtm_live_sk_...' });

// Multi-sort with arrays
const items = await client.items.list({
  projectId: 'proj_abc123',
  sort: ['priority', 'due_date', 'created_at'],
  order: ['desc', 'asc', 'desc']
});

// Multi-sort with object syntax
const sorted = await client.items.list({
  projectId: 'proj_abc123',
  sort: [
    { field: 'priority', order: 'desc' },
    { field: 'due_date', order: 'asc', nulls: 'last' },
    { field: 'id', order: 'asc' }
  ]
});

// Builder pattern
const results = await client.items
  .filter({ type: 'requirement' })
  .sort('priority', 'desc')
  .sort('due_date', 'asc', { nulls: 'last' })
  .sort('id', 'asc')
  .list({ projectId: 'proj_abc123' });

// Multi-sort with filters
const filtered = await client.items.list({
  projectId: 'proj_abc123',
  type: 'requirement',
  filters: {
    'status[in]': ['draft', 'in_progress']
  },
  sort: ['priority', 'updated_at'],
  order: ['desc', 'desc']
});

// Iterate with multi-sort
for await (const item of client.items.iterate({
  projectId: 'proj_abc123',
  sort: ['priority', 'created_at', 'id'],
  order: ['desc', 'desc', 'asc']
})) {
  console.log(`${item.priority} | ${item.createdAt} | ${item.title}`);
}
```

### Go

```go
package main

import (
    "context"
    "fmt"
    "github.com/tracertm/tracertm-go"
)

func main() {
    client := tracertm.NewClient("rtm_live_sk_...")
    ctx := context.Background()

    // Multi-sort with slices
    items, _ := client.Items.List(ctx, &tracertm.ListItemsRequest{
        ProjectID: "proj_abc123",
        Sort:      []string{"priority", "due_date", "created_at"},
        Order:     []string{"desc", "asc", "desc"},
    })

    // Multi-sort with SortSpec
    sorted, _ := client.Items.List(ctx, &tracertm.ListItemsRequest{
        ProjectID: "proj_abc123",
        SortSpec: []tracertm.SortField{
            {Field: "priority", Order: "desc"},
            {Field: "due_date", Order: "asc", Nulls: "last"},
            {Field: "id", Order: "asc"},
        },
    })

    // Multi-sort with filters
    filtered, _ := client.Items.List(ctx, &tracertm.ListItemsRequest{
        ProjectID: "proj_abc123",
        Type:      "requirement",
        Filters: map[string]interface{}{
            "status[in]": []string{"draft", "in_progress"},
        },
        Sort:  []string{"priority", "updated_at"},
        Order: []string{"desc", "desc"},
    })

    // Iterate with multi-sort
    iter := client.Items.Iterate(ctx, "proj_abc123", &tracertm.IterateOptions{
        Sort:  []string{"priority", "created_at", "id"},
        Order: []string{"desc", "desc", "asc"},
    })

    for iter.Next() {
        item := iter.Item()
        fmt.Printf("%s | %s | %s\n", item.Priority, item.CreatedAt, item.Title)
    }
}
```

---

## Response Structure

Multi-sorted responses include detailed sort information:

```json
{
  "data": [
    {
      "id": "item_req_001",
      "title": "Critical Auth Feature",
      "priority": "critical",
      "due_date": "2024-01-20",
      "created_at": "2024-01-15T10:00:00Z"
    },
    {
      "id": "item_req_002",
      "title": "Critical Payment Feature",
      "priority": "critical",
      "due_date": "2024-01-25",
      "created_at": "2024-01-14T09:00:00Z"
    },
    {
      "id": "item_req_003",
      "title": "High Priority Task",
      "priority": "high",
      "due_date": "2024-01-18",
      "created_at": "2024-01-10T14:30:00Z"
    }
  ],
  "sort": [
    {"field": "priority", "order": "desc", "nulls": "last"},
    {"field": "due_date", "order": "asc", "nulls": "last"},
    {"field": "id", "order": "asc", "nulls": "last"}
  ],
  "pagination": {
    "total": 156,
    "limit": 20,
    "offset": 0,
    "has_more": true
  }
}
```

---

## Best Practices

### 1. Always Include ID for Stable Sort

```python
# Good: Deterministic order
items = client.items.list(
    project_id="proj_abc123",
    sort=["priority", "due_date", "id"],
    order=["desc", "asc", "asc"]
)

# Bad: May have inconsistent ordering
items = client.items.list(
    project_id="proj_abc123",
    sort=["priority", "due_date"],
    order=["desc", "asc"]
)
```

### 2. Put Most Important Sort First

```python
# Good: Primary sort is priority (what users care about most)
sort=["priority", "due_date", "created_at"]

# Less useful: Primary sort is created_at
sort=["created_at", "priority", "due_date"]
```

### 3. Match Sort with Use Case

| Use Case | Recommended Sort |
|----------|------------------|
| Task board | `status, priority, due_date` |
| Sprint planning | `priority, story_points, created_at` |
| Audit trail | `updated_at, id` |
| Assignment queue | `assignee, priority, created_at` |
| Compliance review | `type, status, due_date` |

### 4. Be Consistent Across Pages

```python
# Good: Same sort parameters for all pages
sort_params = {"sort": ["priority", "created_at", "id"], "order": ["desc", "desc", "asc"]}

page1 = client.items.list(project_id, limit=50, offset=0, **sort_params)
page2 = client.items.list(project_id, limit=50, offset=50, **sort_params)
```

---

## Related Documentation

- [Sorting](/docs/api-reference/rest-api/sorting) - Single-field sorting
- [Filtering](/docs/api-reference/rest-api/filtering) - Filter results
- [Cursor Pagination](/docs/api-reference/rest-api/cursor-pagination) - Cursor pagination with sort
