How to Migrate from Drupal to Cosmic

typescript dev.to

Drupal served a generation of content teams well. But between the PHP overhead, major-version upgrade cycles, and the complete absence of native AI capabilities, many teams are now making the move to a modern headless CMS. This guide walks you through migrating from Drupal to Cosmic step by step, with real TypeScript code you can run today.

Why Teams Are Leaving Drupal Now

Drupal 7 reached end-of-life in January 2025. That forced thousands of organizations into a choice: invest heavily in upgrading to Drupal 10, or use the moment as an opportunity to rethink the architecture entirely.

The case for moving on is strong:

  • Module sprawl and complexity. Most production Drupal sites run dozens of contrib modules. Each one is a maintenance burden and a potential security surface.
  • Dev-only content changes. Even simple schema changes, like adding a field to a content type, typically require a developer, a code deployment, and a cache flush.
  • No native AI. Drupal was designed long before LLMs. Every AI integration is a custom module or a fragile third-party connector.
  • Infrastructure overhead. A production Drupal site needs a server (or managed host), a MySQL/PostgreSQL database, and caching layers like Varnish or Redis.

Cosmic removes all of that. Fully managed cloud, REST API, TypeScript SDK, built-in AI Agents, and a visual schema builder. Let's move your content.

Drupal to Cosmic Concept Mapping

Before writing a single line of code, it helps to understand how Drupal's concepts translate to Cosmic's model:

  • Content Types = Object Types (defined in Cosmic's schema builder, no code required)
  • Nodes = Objects (each piece of content is an Object)
  • Fields = Metafields (text, number, date, file, select, relationship, and more)
  • Taxonomies = Select/multi-select metafields or related Objects
  • Media = Cosmic Media (imgix CDN - upload once, serve globally)
  • Views = REST API queries with filters
  • Modules = Integrations / Cosmic SDK
  • Roles & Permissions = Team member roles in Cosmic

Step-by-Step Migration Guide

Step 1: Audit Your Drupal Content Model

Before you export anything, document what you have. In Drupal, navigate to Structure > Content Types and list every content type. For each, note:

  • All field names, types, and cardinality (single vs. multiple values)
  • Which fields are required
  • Any taxonomy references
  • Any entity references (related content)

Create a spreadsheet mapping each Drupal content type and field to its Cosmic equivalent. This is your migration blueprint.

Step 2: Create Your Cosmic Object Types

Sign up at cosmicjs.com and create a new Bucket. For each Drupal content type, create a matching Object Type in Cosmic using the visual schema builder in the dashboard.

Field type mapping:

  • text_long / text_with_summary = Markdown or HTML-textarea metafield
  • integer / decimal = Number metafield
  • boolean = Switch metafield
  • datetime = Date metafield
  • image (with file) = File metafield
  • entity_reference (single) = Object metafield
  • entity_reference (multiple) = Objects metafield
  • list_string / taxonomy term reference = Select or Multi-select metafield

Step 3: Export Your Drupal Content

Enable Drupal's JSON:API module (included in Drupal 8.7+ core) if it isn't already active. Then export your nodes:

// Fetch all nodes of a given content type from Drupal's JSON:API
async function exportDrupalNodes(contentType: string) {
  const baseUrl = process.env.DRUPAL_BASE_URL
  let url = `${baseUrl}/jsonapi/node/${contentType}?page[limit]=50`
  const allNodes = []

  while (url) {
    const res = await fetch(url)
    const json = await res.json()
    allNodes.push(...json.data)
    url = json.links?.next?.href || null
  }

  return allNodes
}
Enter fullscreen mode Exit fullscreen mode

For large datasets, paginate through all records and save them to local JSON files before importing.

Step 4: Import Content to Cosmic via the REST API

import { createBucketClient } from '@cosmicjs/sdk'

const cosmic = createBucketClient({
  bucketSlug: process.env.COSMIC_BUCKET_SLUG!,
  writeKey: process.env.COSMIC_WRITE_KEY!,
})

async function importNodes(nodes: any[], objectType: string) {
  const results = { success: 0, failed: 0, errors: [] as string[] }

  for (const node of nodes) {
    try {
      await cosmic.objects.insertOne({
        type: objectType,
        title: node.attributes.title,
        metadata: {
          body: node.attributes.body?.processed || '',
          summary: node.attributes.body?.summary || '',
          // Map additional fields here
        },
      })
      results.success++
    } catch (err: any) {
      results.failed++
      results.errors.push(`${node.attributes.title}: ${err.message}`)
    }
  }

  return results
}
Enter fullscreen mode Exit fullscreen mode

Tips:

  • Run the import in batches of 50-100 objects at a time
  • Log successes and failures separately so you can re-run failures without duplicating successful imports

Step 5: Migrate Media Files

Drupal stores media files locally or on a CDN. Cosmic uses imgix for global media delivery.

async function migrateMedia(drupalFileUrl: string, fileName: string) {
  const cosmic = createBucketClient({
    bucketSlug: process.env.COSMIC_BUCKET_SLUG!,
    writeKey: process.env.COSMIC_WRITE_KEY!,
  })

  // Fetch file from Drupal
  const fileRes = await fetch(drupalFileUrl)
  const fileBuffer = await fileRes.arrayBuffer()
  const blob = new Blob([fileBuffer])

  // Upload to Cosmic Media
  const { media } = await cosmic.media.insertOne({
    media: { data: blob, name: fileName },
  })

  return media.imgix_url // Ready to use with imgix transformations
}
Enter fullscreen mode Exit fullscreen mode

Once uploaded to Cosmic, every image is automatically served through imgix with on-the-fly resizing, format conversion (WebP, AVIF), and global CDN delivery.

Step 6: Set Up Webhooks and Rebuild Your Frontend

Cosmic's webhook system lets you trigger rebuilds on your frontend whenever content changes. For a Next.js frontend:

// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(req: NextRequest) {
  const secret = req.nextUrl.searchParams.get('secret')
  if (secret !== process.env.REVALIDATION_SECRET) {
    return NextResponse.json({ error: 'Invalid secret' }, { status: 401 })
  }

  revalidatePath('/', 'layout')
  return NextResponse.json({ revalidated: true })
}
Enter fullscreen mode Exit fullscreen mode

Cosmic works with all major frontend frameworks: Next.js, React, Vue, Nuxt, Astro, Remix, and Svelte.

Step 7: Test, Redirect, and Go Live

Before flipping DNS:

  • Compare content counts between Drupal and Cosmic to verify completeness
  • Spot-check 10-20 objects across different content types
  • Verify all media URLs resolve via imgix
  • Test your frontend against the Cosmic API in a staging environment
  • Set up 301 redirects from old Drupal URLs to new paths
  • Monitor API response times (target: under 100ms)

Migration Checklist

  • [ ] Audit all Drupal content types and fields
  • [ ] Map Drupal fields to Cosmic metafield types
  • [ ] Create all Object Types in Cosmic
  • [ ] Export all Drupal nodes via JSON:API
  • [ ] Export all Drupal taxonomy terms
  • [ ] Export all media files and metadata
  • [ ] Import content to Cosmic via TypeScript SDK
  • [ ] Migrate media files to Cosmic Media (imgix)
  • [ ] Verify object counts match
  • [ ] Spot-check content for formatting issues
  • [ ] Build or adapt frontend to use Cosmic REST API
  • [ ] Set up webhooks for incremental rebuilds
  • [ ] Configure 301 redirects for all changed URLs
  • [ ] Test full site in staging
  • [ ] Flip DNS to new frontend
  • [ ] Monitor error rates and API latency post-launch
  • [ ] Decommission or archive Drupal server

What You Get on the Other Side

After migrating to Cosmic, your team gets:

  • A clean REST API with sub-100ms response times and 99.9% uptime SLA
  • TypeScript SDK and CLI for fast local development
  • Built-in AI Agents that live in Slack, WhatsApp, and Telegram
  • MCP Server for native AI tooling integration
  • imgix CDN for all media with on-the-fly transformations
  • A visual schema builder so editors can propose content model changes without involving a developer
  • No more major version migrations. Ever.

Originally published on cosmicjs.com

Source: dev.to

arrow_back Back to Tutorials