> ## Documentation Index
> Fetch the complete documentation index at: https://buttercms.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Query optimization

> ButterCMS API query optimization: exclude_body, levels, field filtering, ordering, and pagination — plus patterns for parallel requests and query caching.

{/* /SOURCE */}

## Key optimization parameters

### 1. The `exclude_body` parameter

The `exclude_body=true` parameter reduces response size by **50-70%** for blog post listing operations. This is one of the most impactful optimizations you can make.

**When to Use:**

* Blog post listing pages
* Archive pages
* Search results
* RSS feed generation
* Related posts widgets

**When NOT to Use:**

* Individual post pages (you need the body)
* Full-text search implementations
* Content that displays the full article

**Example:**

```javascript theme={null}
// Good - for listing pages
const posts = await butter.post.list({
  page: 1,
  page_size: 10,
  exclude_body: true  // 50-70% smaller response
});

// Display post cards with title and summary
posts.data.data.forEach(post => {
  console.log(post.title);   // Available
  console.log(post.summary); // Available
  console.log(post.body);    // undefined (excluded)
});
```

### 2. The `levels` parameter

The `levels` parameter controls reference field depth in API responses, balancing data completeness with response size and performance.

**Configuration:**

* **Default**: `levels=2` covers most page display scenarios
* **Range**: 1-5 (values outside this range are automatically adjusted)
* **Level 1**: Reference fields return API URIs only (minimal data transfer)
* **Level 2+**: Reference fields return complete object data
* **Level 5 (max)**: Returns URIs for any remaining references beyond specified depth

**Level Comparison:**

```json theme={null}
// Level 1 - URIs only (fastest, smallest)
{
  "fields": {
    "author": ["/v2/content/?keys=authors[_id=123]"],
    "category": ["/v2/content/?keys=categories[_id=456]"]
  }
}

// Level 2 - Full objects (default)
{
  "fields": {
    "author": [{
      "meta": { "id": 123 },
      "name": "John Doe",
      "bio": "Technical writer..."
    }],
    "category": [{
      "meta": { "id": 456 },
      "name": "Technology",
      "slug": "technology"
    }]
  }
}
```

**Recommended Usage:**

| Use Case               | Recommended Level | Reason                        |
| ---------------------- | ----------------- | ----------------------------- |
| Navigation menus       | 1                 | Only need slugs/titles        |
| Page listings          | 1                 | Minimal reference data needed |
| Standard page display  | 2 (default)       | Most common scenario          |
| Pages with nested refs | 3-4               | Complex content structures    |
| Deep reference chains  | 5                 | Maximum depth needed          |

### 3. Field filtering

Filter content server-side to fetch only what you need — by author, category, tag, or any custom field value.

See [Filtering Requests](./filtering-requests) for the full reference with examples for blog posts, pages, and collections.

### 4. Ordering

Order results server-side instead of sorting client-side:

**Available Order Fields:**

* `published` - Publication date
* `updated` - Last update date
* Custom content fields

**Order Direction:**

* Ascending (default): `order=published`
* Descending: `order=-published` (prepend `-`)

**Examples:**

```bash theme={null}
# Newest first
/v2/pages/blog-post/?order=-published

# Alphabetical by title
/v2/pages/blog-post/?order=title

# Custom field ordering
/v2/pages/product/?order=-price
```

### 5. Pagination

Use appropriate page sizes and pagination methods to avoid oversized responses. The recommended range is **10-25 items per request** for optimal performance.

<CardGroup cols={2}>
  <Card title="Pagination" icon="filter" href="./pagination">
    Page-based and offset-based pagination, infinite scroll, and archive page patterns
  </Card>

  <Card title="Filtering Requests" icon="sliders" href="./filtering-requests">
    Filter by author, category, tag, and custom field values
  </Card>
</CardGroup>

## Serialization performance

When cached content is unavailable (cache miss), the API must serialize both the requested content and all content referenced at the specified depth. For list endpoints, this increases processing time significantly.

**Example Problem:**
Requesting 100 pages with `levels=2` may require serializing 100+ individual objects plus their references.

```mermaid theme={null}
flowchart TB
    subgraph Levels2["List Request with levels=2"]
        A1[100 Pages Requested] --> B1[Serialization Required]
        B1 --> C1["Page 1 -> Author, Category, Related Pages (5 refs)"]
        B1 --> D1["Page 2 -> Author, Categories, Related Pages (5 refs)"]
        B1 --> E1["... Page 100 -> ..."]
        B1 --> F1["Total objects: 400-600+<br/>Response time: 500ms-2s"]
    end

    subgraph Levels1["List Request with levels=1"]
        A2[100 Pages<br/>URIs only] --> B2[Serialization Required]
        B2 --> C2["Page 1 (fields + URI refs)"]
        B2 --> D2["Page 2 (fields + URI refs)"]
        B2 --> E2["... Page 100 (fields + URI refs)"]
        B2 --> F2["Total objects: 100<br/>Response time: 100-300ms"]
    end
```

**Recommendation:** Always use `levels=1` for list operations, then fetch full details for individual items when needed.

## Query optimization patterns

### Pattern 1: two-stage fetching

Fetch lists with minimal data, then fetch full details on demand:

```javascript theme={null}
// Stage 1: Get list with minimal data
const list = await butter.page.list('blog-post', {
  page_size: 10,
  levels: 1,
  fields: { 'fields.featured': 'true' }
});

// Stage 2: Fetch full details for a specific item
const fullPage = await butter.page.retrieve('blog-post', selectedSlug, {
  levels: 2
});
```

### Pattern 2: parallel requests

Fetch independent data in parallel:

```javascript theme={null}
// Good - parallel requests
const [posts, categories, authors] = await Promise.all([
  butter.post.list({ page_size: 10, exclude_body: true }),
  butter.category.list(),
  butter.author.list()
]);

// Avoid - sequential requests (slower)
const posts = await butter.post.list({ page_size: 10 });
const categories = await butter.category.list();
const authors = await butter.author.list();
```

### Pattern 3: selective field loading

Only request the data you need for each view:

```javascript theme={null}
// Homepage: Just need titles and images
const homepagePosts = await butter.post.list({
  page_size: 3,
  exclude_body: true
});
// Returns: title, slug, summary, featured_image

// Archive page: Need dates and categories too
const archivePosts = await butter.post.list({
  page_size: 20,
  exclude_body: true
});
// Returns: title, slug, summary, published date, categories

// Individual post: Need everything
const fullPost = await butter.post.retrieve(slug);
// Returns: All fields including body
```

### Pattern 4: caching query results

Cache optimized query results for repeated access:

```javascript theme={null}
import NodeCache from 'node-cache';

const cache = new NodeCache({ stdTTL: 300 });

async function getOptimizedPageList(pageType, filters = {}) {
  const cacheKey = `list-${pageType}-${JSON.stringify(filters)}`;

  let result = cache.get(cacheKey);
  if (result) return result;

  result = await butter.page.list(pageType, {
    levels: 1,
    page_size: 100,
    ...filters
  });

  cache.set(cacheKey, result);
  return result;
}
```

## Response size optimization

<Warning>
  **10MB Response Limit**: ButterCMS has a 10MB response size limit. If your queries approach this limit, reduce `levels` or `page_size`.
</Warning>

### Estimating response sizes

| Content Type           | Typical Size | With `exclude_body` |
| ---------------------- | ------------ | ------------------- |
| Blog post (full)       | 5-50KB       | 1-5KB               |
| Page (simple)          | 2-10KB       | N/A                 |
| Page (with components) | 10-100KB     | N/A                 |
| Collection item        | 0.5-5KB      | N/A                 |

### Size reduction strategies

1. **Use `exclude_body=true`** for all listing endpoints
2. **Use `levels=1`** for navigation and lists
3. **Limit `page_size`** to 10-25 items
4. **Filter server-side** instead of fetching everything

## Search optimization

When using the search endpoint, keep these optimizations in mind:

**Search Limitations:**

* Only the direct content of pages is searched
* References are excluded from search but appear in results
* Maximum query length: 100 characters

**Optimized Search Implementation:**

```javascript theme={null}
async function searchPages(query, pageType = '*') {
  const response = await butter.page.search(query, {
    page_type: pageType,
    page_size: 20,
    levels: 1  // Get full details separately if needed
  });

  return response.data.data;
}

// Then fetch full details for the selected result
async function getSearchResultDetails(slug) {
  return butter.page.retrieve('*', slug, { levels: 2 });
}
```

## Query optimization checklist

<AccordionGroup>
  <Accordion title="Before building features">
    * [ ] Identify the minimum data needed for each view
    * [ ] Plan which endpoints to use for each feature
    * [ ] Consider caching strategy for each query type
  </Accordion>

  <Accordion title="During development">
    * [ ] Use `exclude_body=true` for all blog listings
    * [ ] Set appropriate `levels` (default to 1 for lists)
    * [ ] Use server-side filtering instead of client-side
    * [ ] Order results server-side with `order` parameter
    * [ ] Implement parallel fetching for independent data
  </Accordion>

  <Accordion title="Performance testing">
    * [ ] Measure response times with real data volumes
    * [ ] Test with cache cold and warm states
    * [ ] Monitor response sizes stay well under 10MB
    * [ ] Verify minimal data for each view type
  </Accordion>
</AccordionGroup>
