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.
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
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:
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
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
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
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
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 ( ' \n Migration 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
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
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
Request Pacing Add 200-500ms delay between requests to keep bulk operations stable and reduce monthly API usage spikes.
Idempotency Check if content exists before creating to avoid duplicates during retries.
Error Logging Log failed operations for manual review and retry.
Batch Verification After bulk operations, verify content via Read API.
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
These are recommended values to keep bulk operations stable. Free/Trial accounts can be blocked when monthly API call limits are exceeded.
Next Steps
Pages - Write Create and update pages
Collections - Write Create collection items
Blog Posts - Write Create blog posts
Rate Limits API rate limiting