Skip to main content

Why migrate from Contentful?

Common reasons for migration

Contentful ChallengeButterCMS Solution
Complex content modelingIntuitive visual content type builder
Steep learning curveUser-friendly interface for marketers
Difficult bulk data importsWrite API for programmatic imports
Pricing scales with contentPredictable pricing with generous limits
Developer-heavy workflowsBalance between developer and marketer needs
Complex API structureSimple, intuitive REST API

Feature comparison

FeatureContentfulButterCMS
Content ModelingContent Types with complex relationshipsPage Types, Collections, Components
Blog EngineMust build from scratchBuilt-in blog with categories, tags, authors
PreviewRequires setupBuilt-in preview functionality
LocalizationAvailableAvailable with visual locale switcher
ComponentsEmbedded entriesVisual Component Library
MigrationsCLI-based migrationsVisual migration tool (Enterprise)
MediaAsset managementCDN-hosted Media Library
APIGraphQL + RESTREST API

Migration planning

Step 1: Audit your Contentful content

Before migrating, create an inventory of your Contentful content: Content Types to identify:
  • Entry types (equivalent to ButterCMS Page Types)
  • Assets (equivalent to ButterCMS Media)
  • Content entries (equivalent to Pages or Collection Items)
  • Locales configured
  • Webhooks set up
  • API integrations

Contentful to ButterCMS mapping

Contentful ConceptButterCMS EquivalentNotes
Content TypePage TypeDefine schema for pages
EntryPage or Collection ItemDepends on use case
AssetMedia Library itemAutomatic CDN hosting
SpaceSiteOne space = one ButterCMS site
EnvironmentEnvironmentStaging/Production environments
LocaleLocaleSimilar localization approach
Embedded EntryComponent or ReferenceUse Components for reusable blocks
Rich TextWYSIWYGSimilar functionality
ReferenceReference fieldOne-to-One or One-to-Many
ArrayRepeaterFor lists of items
LinkReferenceFor content relationships

Step 2: Map content types to ButterCMS

Contentful Content Types become ButterCMS Page Types or Collections:
  • Content that represents a webpage
  • Content with SEO requirements
  • Content that needs scheduling
  • Content with complex field structures
  • Examples: Landing pages, product pages, case studies
  • Reference data used across pages
  • Lists of items without individual URLs
  • Reusable structured data
  • Examples: Categories, authors, testimonials, FAQs
  • Standard blog posts with author, categories, tags
  • Content that follows a blog publishing workflow
  • SEO-optimized article content

Step 3: Map field types

Contentful Field TypeButterCMS Field TypeMigration Notes
Short textShort TextDirect mapping
Long textLong Text or WYSIWYGChoose based on formatting needs
Rich textWYSIWYGRich text content preserved
Number (Integer)NumberDirect mapping
Number (Decimal)NumberDirect mapping
Date/TimeDateDate fields supported
LocationShort Text (coordinates)Store as formatted string or separate fields
BooleanCheckboxDirect mapping
JSONLong TextStore as JSON string
Media (Image)MediaUpload to ButterCMS CDN
Media (File)MediaUpload to ButterCMS CDN
Reference (One)Reference (One-to-One)Link to Page Type or Collection
Reference (Many)Reference (One-to-Many)Link to multiple items
ArrayRepeaterFor repeating sets of fields

Migration process

Step 1: Export content from Contentful

Use the Contentful Export tool to export your content:
# Install Contentful CLI
npm install -g contentful-cli

# Login to Contentful
contentful login

# Export space content
contentful space export --space-id YOUR_SPACE_ID --export-dir ./export
This creates JSON files containing:
  • Content types
  • Entries
  • Assets
  • Locales

Step 2: Create ButterCMS content structure

Before importing content, set up your ButterCMS content types:
  1. Create Page Types matching your Contentful content types
  2. Create Collections for reference data
  3. Create Components for reusable content blocks
  4. Configure Locales matching your Contentful setup

Step 3: Transform and import content

Use the ButterCMS Write API to import transformed content:
// Example: Transform Contentful entry to ButterCMS page
function transformContentfulEntry(entry, contentType) {
  const fields = entry.fields;
  const locale = 'en-US'; // or iterate through locales

  return {
    "page-type": contentType.toLowerCase().replace(/ /g, '_'),
    status: entry.sys.publishedAt ? "published" : "draft",
    title: fields.title?.[locale] || fields.name?.[locale],
    slug: fields.slug?.[locale] || generateSlug(fields.title?.[locale]),
    fields: {
      // Map each field from Contentful to ButterCMS schema
      title: fields.title?.[locale],
      body: fields.content?.[locale],
      summary: fields.excerpt?.[locale] || '',
      featured_image: fields.featuredImage?.[locale]?.fields?.file?.url,
      publish_date: entry.sys.publishedAt || new Date().toISOString()
    }
  };
}

// Import to ButterCMS
async function importToButterCMS(butterPage) {
  const response = await fetch('https://api.buttercms.com/v2/pages/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Token ${BUTTERCMS_WRITE_TOKEN}`
    },
    body: JSON.stringify(butterPage)
  });

  return response.json();
}

Step 4: Handle assets/media

Contentful assets need to be uploaded to ButterCMS:
// Download Contentful asset and prepare for ButterCMS
async function migrateAsset(contentfulAsset) {
  const assetUrl = `https:${contentfulAsset.fields.file['en-US'].url}`;

  // Option 1: Use the URL directly (ButterCMS can fetch external images)
  // Option 2: Download and upload to ButterCMS Media Library

  // For direct URL usage in content:
  return assetUrl;
}
ButterCMS supports external image URLs in content fields. For optimal performance and CDN delivery, upload assets to the ButterCMS Media Library.

Step 5: Handle references and relationships

Contentful references need special handling:
// For Collection references (e.g., categories)
async function createCollectionItemAndGetID(contentfulEntry, collectionKey) {
  // First, create the collection item
  const response = await fetch('https://api.buttercms.com/v2/content/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Token ${BUTTERCMS_WRITE_TOKEN}`
    },
    body: JSON.stringify({
      key: collectionKey,
      status: 'published',
      fields: [
        {
          en: {
            name: contentfulEntry.fields.name['en-US'],
            slug: contentfulEntry.fields.slug['en-US']
          }
        }
      ]
    })
  });

  const result = await response.json();
  return result.data.meta.id; // Use this ID for references
}

Step 6: Handle rich text content

Contentful rich text needs transformation to HTML for ButterCMS WYSIWYG:
import { documentToHtmlString } from '@contentful/rich-text-html-renderer';

function transformRichText(richTextField) {
  // Convert Contentful rich text to HTML
  return documentToHtmlString(richTextField);
}

Handling specific Contentful features

Embedded entries (Components)

Contentful embedded entries map to ButterCMS Components:
  1. Create a Component in ButterCMS matching the embedded entry structure
  2. When migrating, extract embedded entries from rich text
  3. Use Component Picker fields to allow dynamic component addition

Localization

ButterCMS provides field-level localization. Set up locales in Settings, then provide locale-specific content in API requests using locale keys in the fields object:
  1. Set up matching locales in ButterCMS Settings
  2. During migration, iterate through each locale
  3. Use locale keys in the fields object when creating content via Write API
// Create localized content
const localizedPage = {
  "page-type": "article",
  status: "published",
  title: "My Article",
  slug: "my-article",
  fields: {
    en: {
      title: "English Title",
      body: "English content..."
    },
    de: {
      title: "German Title",
      body: "German content..."
    }
  }
};

Webhooks

Re-create your webhook integrations in ButterCMS:
  1. Go to Settings → Webhooks
  2. Add webhook endpoints
  3. Select events to trigger webhooks
  4. Update any receiving applications with new payload format

Migration script template

// migration.js - Contentful to ButterCMS migration script
const contentful = require('contentful-management');
const fetch = require('node-fetch');

const CONTENTFUL_SPACE_ID = process.env.CONTENTFUL_SPACE_ID;
const CONTENTFUL_ACCESS_TOKEN = process.env.CONTENTFUL_ACCESS_TOKEN;
const BUTTERCMS_WRITE_TOKEN = process.env.BUTTERCMS_WRITE_TOKEN;

async function migrate() {
  // 1. Connect to Contentful
  const client = contentful.createClient({
    accessToken: CONTENTFUL_ACCESS_TOKEN
  });

  const space = await client.getSpace(CONTENTFUL_SPACE_ID);
  const environment = await space.getEnvironment('master');

  // 2. Get all content types
  const contentTypes = await environment.getContentTypes();
  console.log(`Found ${contentTypes.items.length} content types`);

  // 3. Migrate each content type
  for (const contentType of contentTypes.items) {
    console.log(`Migrating: ${contentType.name}`);

    // Get entries for this content type
    const entries = await environment.getEntries({
      content_type: contentType.sys.id
    });

    // Transform and import each entry
    for (const entry of entries.items) {
      const butterPage = transformEntry(entry, contentType);
      await importToButterCMS(butterPage);
    }
  }

  console.log('Migration complete!');
}

function transformEntry(entry, contentType) {
  // Implement your field mapping logic here
  // Return ButterCMS-compatible object
}

async function importToButterCMS(page) {
  // Implement Write API call
}

migrate().catch(console.error);

Post-migration checklist

  • All content types created in ButterCMS
  • All entries migrated successfully
  • Media assets uploaded and references updated
  • Localized content available in all locales
  • Reference relationships working correctly
  • Webhooks reconfigured
  • Frontend application updated with new API calls
  • Preview functionality tested
  • Publishing workflow tested
  • Team members trained on new interface

Differences to note

AspectContentfulButterCMS
API StyleGraphQL + RESTREST only
Content DeliveryCDN via Content Delivery APIGlobal CDN built-in
Rate LimitsPer-second limitsMonthly API call limits
PreviewPreview APIBuilt-in preview
EnvironmentsMultiple environmentsStaging + Production
CLIFull CLI toolingAPI-first approach