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

# Bulk Operations

> Guide for performing bulk create, update, and delete operations using the ButterCMS Write API.

## Overview

While ButterCMS doesn't provide dedicated bulk endpoints, you can perform efficient bulk operations by orchestrating multiple Write API calls. This guide covers patterns and best practices for bulk content operations.

<Warning>
  Write API access requires a **Write API Token**. Contact [support@buttercms.com](mailto:support@buttercms.com) to request access.
</Warning>

### Key Considerations

* **Request Pacing**: Add small delays between requests to keep bulk operations stable and reduce monthly usage spikes.
* **Error Handling**: Implement retry logic for transient failures.
* **Progress Tracking**: Track successful and failed operations for large imports.
* **Async Nature**: Write operations return `202 Accepted`—content typically appears within seconds but processing time may vary.

## Bulk Create Pattern

### Sequential Creation with Request Pacing

```javascript theme={null}
async function bulkCreate(collectionKey, items, delayMs = 200) {
  const results = {
    success: [],
    failed: []
  };

  for (const item of items) {
    try {
      const response = await axios.post(
        `https://api.buttercms.com/v2/content/${collectionKey}/`,
        {
          status: 'draft',
          fields: item
        },
        {
          headers: {
            'Authorization': `Token ${WRITE_TOKEN}`,
            'Content-Type': 'application/json'
          }
        }
      );

      results.success.push({
        item,
        id: response.data.data.id
      });
    } catch (error) {
      results.failed.push({
        item,
        error: error.response?.data || error.message
      });
    }

    // Rate limiting delay
    await new Promise(r => setTimeout(r, delayMs));
  }

  return results;
}

// Usage
const items = [
  { name: 'Item 1', category: 'A' },
  { name: 'Item 2', category: 'B' },
  { name: 'Item 3', category: 'A' }
];

const results = await bulkCreate('products', items);
console.log(`Created: ${results.success.length}, Failed: ${results.failed.length}`);
```

### Parallel Creation with Concurrency Control

For faster bulk operations, use controlled concurrency:

```javascript theme={null}
const pLimit = require('p-limit');

async function bulkCreateParallel(collectionKey, items, concurrency = 5) {
  const limit = pLimit(concurrency);
  const results = { success: [], failed: [] };

  const promises = items.map(item =>
    limit(async () => {
      try {
        const response = await axios.post(
          `https://api.buttercms.com/v2/content/${collectionKey}/`,
          { status: 'draft', fields: item },
          { headers: { 'Authorization': `Token ${WRITE_TOKEN}` } }
        );
        results.success.push({ item, id: response.data.data.id });
      } catch (error) {
        results.failed.push({ item, error: error.response?.data });
      }
    })
  );

  await Promise.all(promises);
  return results;
}
```

## Bulk Update Pattern

### Update Multiple Items by ID

```javascript theme={null}
async function bulkUpdate(collectionKey, updates, delayMs = 200) {
  const results = { success: [], failed: [] };

  for (const { id, fields } of updates) {
    try {
      await axios.patch(
        `https://api.buttercms.com/v2/content/${collectionKey}/${id}/`,
        { fields },
        {
          headers: {
            'Authorization': `Token ${WRITE_TOKEN}`,
            'Content-Type': 'application/json'
          }
        }
      );
      results.success.push({ id });
    } catch (error) {
      results.failed.push({ id, error: error.response?.data });
    }

    await new Promise(r => setTimeout(r, delayMs));
  }

  return results;
}

// Usage
const updates = [
  { id: 123, fields: { status: 'active' } },
  { id: 124, fields: { status: 'active' } },
  { id: 125, fields: { status: 'inactive' } }
];

await bulkUpdate('products', updates);
```

### Bulk Status Update

```javascript theme={null}
async function bulkPublish(collectionKey, itemIds) {
  const results = { success: [], failed: [] };

  for (const id of itemIds) {
    try {
      await axios.patch(
        `https://api.buttercms.com/v2/content/${collectionKey}/${id}/`,
        { status: 'published' },
        { headers: { 'Authorization': `Token ${WRITE_TOKEN}` } }
      );
      results.success.push(id);
    } catch (error) {
      results.failed.push({ id, error: error.response?.data });
    }

    await new Promise(r => setTimeout(r, 200));
  }

  return results;
}
```

## Bulk Delete Pattern

```javascript theme={null}
async function bulkDelete(collectionKey, itemIds, delayMs = 200) {
  const results = { success: [], failed: [] };

  for (const id of itemIds) {
    try {
      await axios.delete(
        `https://api.buttercms.com/v2/content/${collectionKey}/${id}/`,
        { headers: { 'Authorization': `Token ${WRITE_TOKEN}` } }
      );
      results.success.push(id);
    } catch (error) {
      results.failed.push({ id, error: error.response?.data });
    }

    await new Promise(r => setTimeout(r, delayMs));
  }

  return results;
}
```

## Content Migration Example

### WordPress to ButterCMS Migration

```javascript theme={null}
const axios = require('axios');

const WRITE_TOKEN = 'your-write-token';
const API_BASE = 'https://api.buttercms.com/v2';

async function migrateWordPressPosts(wpPosts) {
  console.log(`Starting migration of ${wpPosts.length} posts...`);

  const results = {
    success: [],
    failed: [],
    skipped: []
  };

  for (let i = 0; i < wpPosts.length; i++) {
    const post = wpPosts[i];
    console.log(`[${i + 1}/${wpPosts.length}] Processing: ${post.title}`);

    // Skip if slug already exists
    try {
      await axios.get(
        `${API_BASE}/posts/${post.slug}/?auth_token=${READ_TOKEN}`
      );
      results.skipped.push({ slug: post.slug, reason: 'Already exists' });
      continue;
    } catch (e) {
      // Post doesn't exist, proceed with creation
    }

    try {
      const butterPost = {
        title: post.title,
        slug: post.slug,
        body: post.content,
        summary: post.excerpt || '',
        seo_title: post.seo_title || post.title,
        meta_description: post.meta_description || post.excerpt || '',
        featured_image: post.featured_image || '',
        status: post.status === 'publish' ? 'published' : 'draft',
        category_slugs: post.categories?.map(c => slugify(c.name)) || [],
        tag_slugs: post.tags?.map(t => slugify(t.name)) || []
      };

      await axios.post(
        `${API_BASE}/posts/`,
        butterPost,
        { headers: { 'Authorization': `Token ${WRITE_TOKEN}` } }
      );

      results.success.push({ slug: post.slug });
    } catch (error) {
      results.failed.push({
        slug: post.slug,
        error: error.response?.data || error.message
      });
    }

    // Rate limiting
    await new Promise(r => setTimeout(r, 300));
  }

  console.log('\nMigration complete:');
  console.log(`  Success: ${results.success.length}`);
  console.log(`  Failed: ${results.failed.length}`);
  console.log(`  Skipped: ${results.skipped.length}`);

  return results;
}

function slugify(text) {
  return text
    .toLowerCase()
    .replace(/[^\w\s-]/g, '')
    .replace(/\s+/g, '-')
    .trim();
}
```

## Handling 429 Responses

```javascript theme={null}
async function requestWithRetryAfter(fn, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error.response?.status === 429) {
        const retryAfter = Number(error.response.headers?.['retry-after'] || 0);
        console.log(`API limit reached. Retry after ${retryAfter}s...`);
        await new Promise(r => setTimeout(r, retryAfter * 1000));
        continue;
      }
      throw error;
    }
  }
}

// Usage in bulk operations
async function createItemWithRetry(collectionKey, fields) {
  return requestWithRetryAfter(() =>
    axios.post(
      `https://api.buttercms.com/v2/content/${collectionKey}/`,
      { status: 'draft', fields },
      { headers: { 'Authorization': `Token ${WRITE_TOKEN}` } }
    )
  );
}
```

## Progress Tracking and Logging

```javascript theme={null}
class BulkOperationTracker {
  constructor(totalItems) {
    this.total = totalItems;
    this.processed = 0;
    this.success = 0;
    this.failed = 0;
    this.startTime = Date.now();
  }

  recordSuccess() {
    this.processed++;
    this.success++;
    this.logProgress();
  }

  recordFailure(error) {
    this.processed++;
    this.failed++;
    this.logProgress();
  }

  logProgress() {
    const elapsed = (Date.now() - this.startTime) / 1000;
    const rate = this.processed / elapsed;
    const remaining = (this.total - this.processed) / rate;

    console.log(
      `Progress: ${this.processed}/${this.total} ` +
      `(${this.success} success, ${this.failed} failed) ` +
      `ETA: ${Math.round(remaining)}s`
    );
  }

  getSummary() {
    return {
      total: this.total,
      success: this.success,
      failed: this.failed,
      duration: (Date.now() - this.startTime) / 1000
    };
  }
}

// Usage
const tracker = new BulkOperationTracker(items.length);

for (const item of items) {
  try {
    await createItem(item);
    tracker.recordSuccess();
  } catch (error) {
    tracker.recordFailure(error);
  }
}

console.log('Summary:', tracker.getSummary());
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Request Pacing" icon="gauge">
    Add 200-500ms delay between requests to keep bulk operations stable and reduce monthly API usage spikes.
  </Card>

  <Card title="Idempotency" icon="repeat">
    Check if content exists before creating to avoid duplicates during retries.
  </Card>

  <Card title="Error Logging" icon="file-lines">
    Log failed operations for manual review and retry.
  </Card>

  <Card title="Batch Verification" icon="check-double">
    After bulk operations, verify content via Read API.
  </Card>
</CardGroup>

## Recommended Limits

| Operation Type | Recommended Concurrency | Delay Between Requests |
| -------------- | ----------------------- | ---------------------- |
| Create         | 3-5 parallel            | 200ms                  |
| Update         | 5-10 parallel           | 100ms                  |
| Delete         | 5-10 parallel           | 100ms                  |
| Migration      | 1-3 parallel            | 300ms                  |

<Note>
  These are recommended values to keep bulk operations stable. Free/Trial accounts can be blocked when monthly API call limits are exceeded.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Pages - Write" icon="pen" href="../../api-reference/pages/create-page-via-write-api">
    Create and update pages
  </Card>

  <Card title="Collections - Write" icon="table" href="../../api-reference/collections/create-collection-item">
    Create collection items
  </Card>

  <Card title="Blog Posts - Write" icon="pen-nib" href="../../api-reference/blog-posts/create-blog-post">
    Create blog posts
  </Card>

  <Card title="Rate Limits" icon="gauge-high" href="./rate-limits-throttling">
    API rate limiting
  </Card>
</CardGroup>
