guide

Migrate Your Drupal Site to Astro

Drupal is one of the most capable content management systems ever built. Its entity/field architecture, granular permissions, and extensibility make it a genuine powerhouse for complex publishing workflows, multilingual enterprises, and government sites. The Drupal community is rightly proud of what they have built.

But architecture should match the problem. If your Drupal site is a marketing site, a blog, or a business homepage — content that is the same for every visitor — then Drupal’s PHP runtime, relational database, caching layers, and constant security patching are infrastructure serving a purpose your site does not actually need. Astro, a modern static site generator, can serve the same content faster, cheaper, and with dramatically less maintenance.

This guide is a technical deep dive on mapping Drupal’s content architecture to Astro — content types to collections, Views to generated pages, Paragraphs to components — along with every practical migration approach available today.

Why Astro is a natural fit for Drupal content sites

Astro was designed around the content site use case. It ships zero JavaScript by default, has a built-in content layer with typed schemas, and generates static HTML at build time. For Drupal sites that are primarily serving content, the architectural mapping is remarkably clean.

Drupal ConceptAstro EquivalentNotes
Content types (Article, Page, Event)Content collections with Zod schemasEach content type becomes a collection with typed frontmatter
NodesMarkdown/MDX filesOne file per node, frontmatter holds field values
Views (page listings, archives)getStaticPaths() + page generationViews that list content become generated pages at build
Taxonomy vocabularies (Tags, Categories)Frontmatter fields + generated tag pagesVocabulary terms become filterable frontmatter values
Blocks (sidebar, footer widgets)Astro componentsReusable .astro component files
Paragraphs (complex nested content)Astro components composed in MDXEach paragraph type maps to a component
Media entitiesFiles in public/ + Astro <Image>Downloaded from sites/default/files/
MenusNavigation config or data filesJSON/YAML config consumed by nav components
URL aliasesAstro page routes or redirect rules/about-us becomes src/pages/about-us.astro
WebformsStatic form handlersFormspree, Netlify Forms, or a serverless function
Drupal theme (Twig templates)Astro layouts + components.html.twig templates map to .astro layouts

The content collection system is the key. In Drupal, you define content types with fields — a “Blog Post” type might have title, body, field_author, field_category, field_featured_image. In Astro, you define a collection schema:

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    author: z.string(),
    category: z.string(),
    featuredImage: z.string().optional(),
    publishDate: z.date(),
    draft: z.boolean().default(false),
  }),
});

export const collections = { blog };

Each Drupal node becomes a markdown file with frontmatter matching the schema. Drupal’s field system — text fields, entity references, date fields, image fields — maps directly to Zod types in Astro’s collection config.

Drupal’s content architecture: what makes extraction complex

Before diving into migration approaches, it is worth understanding what makes Drupal different from simpler CMSes. Drupal’s entity/field system is the most flexible content architecture of any mainstream CMS, which makes it powerful but also makes migration more nuanced.

Content types and fields. Every piece of content in Drupal is an entity with a bundle (content type) and fields. A “Case Study” content type might have 15 custom fields: text, entity references, paragraphs, media references, link fields, and more. Each of these needs mapping.

The Paragraphs module. Many Drupal sites use Paragraphs for flexible page building — a node’s body is not a single rich text field but a sequence of typed paragraph items (Hero, Text with Image, CTA, Testimonial Slider, etc.). Each paragraph type has its own fields. This creates deeply nested content that needs special handling. In Astro, each paragraph type maps to a component, and the content is stored either as structured MDX or as JSON data consumed by components.

Entity references. Drupal nodes can reference other nodes, taxonomy terms, users, and media. An “Event” node might reference a “Venue” node and multiple “Speaker” nodes. In a static site, these become either inline data (denormalized at build time) or cross-collection references resolved during page generation.

D7 vs D8+ field storage. Drupal 7 stores field values in separate field_data_* tables — one table per field. Drupal 8+ uses a more normalized structure. This matters for database exports.

Multilingual content. Drupal’s Content Translation module stores translations as separate database records with the same entity ID. If your site is multilingual, you need to extract all language variants and map them to Astro’s i18n routing.

Migration approaches: a complete overview

1. AI coding agents (Claude Code, Cursor, Windsurf, Cline)

This is the most powerful approach for developers. Drupal has genuinely excellent APIs for programmatic content extraction, and AI coding agents can consume these APIs, extract all content, and scaffold an Astro project in hours rather than weeks.

For Drupal 8+ sites (JSON:API):

Drupal 8 and later ship with JSON:API as a core module. It exposes every entity type as a RESTful endpoint with full field data, relationships, and pagination. This is one of the best content extraction APIs of any CMS.

# List all content types
GET /jsonapi/node/article?page[limit]=50
GET /jsonapi/node/page
GET /jsonapi/taxonomy_term/tags

# Include relationships (entity references, media)
GET /jsonapi/node/article?include=field_image,field_category

# Filter by status (published only)
GET /jsonapi/node/article?filter[status]=1

Give an AI agent like Claude Code or Cursor a prompt like:

“Fetch all content from my Drupal site at example.com using JSON:API. For each content type, create an Astro content collection with a schema matching the Drupal fields. Download all media files. Generate pages for taxonomy term listings. Preserve the URL alias structure.”

The agent will iterate through the API, build markdown files with frontmatter, download images, scaffold the Astro project, and create layouts. Expect a few hours of iteration for a typical 50-200 page site.

For Drupal 7 sites (database export):

Drupal 7 does not have JSON:API built in. Your options are:

  1. Direct SQL export — the most reliable approach. Drupal 7’s schema is well-documented:
-- Export articles with body text and custom fields
SELECT n.nid, n.title, n.created, n.changed,
       b.body_value, b.body_summary,
       ua.alias as url_alias
FROM node n
JOIN field_data_body b ON b.entity_id = n.nid AND b.entity_type = 'node'
LEFT JOIN url_alias ua ON ua.source = CONCAT('node/', n.nid)
WHERE n.type = 'article' AND n.status = 1
ORDER BY n.created DESC;
  1. REST module — install the Services module or RESTful module on D7 to create API endpoints
  2. Views Data Export — create Views that export content as CSV or JSON
  3. Drush — use drush sql-query for ad hoc exports, or write a custom Drush command

An AI agent can work with any of these export formats. Feed it the exported JSON/CSV along with instructions, and it will generate the Astro project.

Real-world examples of AI-driven migrations: Sites like cursor.com and prefect.io have documented using AI coding agents to rebuild sites from scratch. The pattern — extract content programmatically, then have the agent scaffold and populate a new project — works especially well for Drupal because of its strong API support.

2. AI app builders (Bolt.new, v0.dev, Lovable, Replit Agent)

For non-developers or teams that want a more visual approach, AI app builders can generate a frontend from your content:

  1. Export your Drupal content to JSON or CSV (using JSON:API, Views Data Export, or database queries)
  2. Upload the exported content to Bolt.new, v0.dev, or Lovable
  3. Describe the site you want: “Build an Astro marketing site with a blog using this content data”
  4. The builder generates pages, components, and layouts
  5. Iterate visually until the design matches

Alternatively, screenshot your existing Drupal pages and ask the builder to recreate them with the exported content. This works well for simpler sites with a few page types.

The limitation is that app builders are less precise than a coding agent for complex Drupal sites with many content types and custom layouts. They work best for sites with 3-5 content types and straightforward designs.

3. Hire a Drupal migration specialist

Drupal has a mature migration ecosystem. Many Drupal agencies and freelancers specialize in migrations, including moves away from Drupal to static generators.

  • Freelance Drupal developers: $2,000-$10,000 for a typical migration. They understand Drupal’s content architecture deeply and can handle edge cases (Paragraphs, entity references, multilingual content) that automated tools might miss.
  • Drupal agencies: $10,000-$50,000 for complex sites with custom modules, integrations, and large content volumes. Agencies like Acquia partners, Lullabot, and others in the Drupal ecosystem offer migration services.
  • Where to find them: Drupal.org marketplace, Toptal, Upwork (search for Drupal migration), and the Drupal Slack community.

This is often the right choice for organizations with complex Drupal 7 sites that have custom modules, Paragraphs-heavy content, or multilingual setups. A Drupal specialist understands the field storage schema, hook system, and module landscape in ways that general-purpose tools do not.

4. BrowserCat Migrate (automated)

BrowserCat Migrate is an automated migration service that uses AI agents to crawl your Drupal site, extract content and structure, and rebuild it as an Astro project with a GitHub repo and live preview. It handles content extraction, image downloading, component building, and deployment. This works well for straightforward content sites where the published HTML is a faithful representation of the content.

5. Drupal-native export tools

Drupal’s own ecosystem has excellent content export capabilities:

  • JSON:API (D8+ core): The gold standard for content extraction. Full field data, relationships, pagination, filtering.
  • Views Data Export (contrib module): Create Views that export any content type as CSV, JSON, or XML. Very flexible.
  • Drupal Migrate module (core in D8+): Originally designed for D6/D7-to-D8 migrations, but the framework can export content to any destination with a custom destination plugin.
  • Drush: Command-line tool for Drupal administration. drush sql-dump for full database exports, custom commands for targeted content extraction.
  • Default Content module: Export content entities as YAML/JSON files that can be version-controlled.

For the Astro side, once you have content in JSON/CSV/markdown format, building the Astro project is straightforward — especially with AI assistance for the scaffolding.

6. Manual DIY migration

For developers comfortable with both Drupal and JavaScript:

  1. Audit your Drupal site: list all content types, fields, taxonomy vocabularies, and Views
  2. Export content using JSON:API, database queries, or Views Data Export
  3. Create an Astro project: npm create astro@latest
  4. Define content collection schemas matching your Drupal content types
  5. Write a script to convert exported content to markdown files with frontmatter
  6. Download all files from sites/default/files/
  7. Build Astro layouts and components matching your existing design
  8. Implement getStaticPaths() for any Drupal Views (listing pages, archives)
  9. Set up redirects for any URL changes
  10. Deploy to Cloudflare Pages, Vercel, or Netlify (all free tier)

This gives you maximum control but takes weeks for a typical site with 10+ content types and hundreds of pages.

Handling Drupal’s complex content patterns

Paragraphs to Astro components

If your Drupal site uses the Paragraphs module, content is stored as typed paragraph items rather than a single body field. A “Landing Page” might contain:

  • Hero paragraph (image, title, subtitle, CTA button)
  • Text with Image paragraph (body text, image, layout direction)
  • Testimonial Carousel paragraph (referenced testimonial nodes)
  • CTA Banner paragraph (heading, text, button link)

In Astro, each paragraph type becomes a component. The page content is stored as structured data (JSON in frontmatter or a data file) and rendered by composing components:

---
// src/pages/landing.astro
import Hero from '../components/Hero.astro';
import TextWithImage from '../components/TextWithImage.astro';
import Testimonials from '../components/Testimonials.astro';
import CTABanner from '../components/CTABanner.astro';

const sections = await getPageSections('landing');
---
{sections.map(section => {
  switch(section.type) {
    case 'hero': return <Hero {...section.data} />;
    case 'text_image': return <TextWithImage {...section.data} />;
    case 'testimonials': return <Testimonials {...section.data} />;
    case 'cta': return <CTABanner {...section.data} />;
  }
})}

Views to generated pages

Drupal Views that create listing pages (blog index, news archive, category pages) become getStaticPaths() in Astro:

---
// src/pages/blog/[...page].astro
import { getCollection } from 'astro:content';

export async function getStaticPaths({ paginate }) {
  const posts = await getCollection('blog');
  const sorted = posts.sort((a, b) =>
    b.data.publishDate.valueOf() - a.data.publishDate.valueOf()
  );
  return paginate(sorted, { pageSize: 10 });
}
---

This replaces Drupal’s Views system for content listing, filtering, and pagination — all generated at build time.

Taxonomy to tag pages

Drupal taxonomy vocabularies (Tags, Categories, Topics) map to frontmatter fields with generated index pages:

---
// src/pages/tags/[tag].astro
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  const tags = [...new Set(posts.flatMap(p => p.data.tags))];
  return tags.map(tag => ({
    params: { tag },
    props: { posts: posts.filter(p => p.data.tags.includes(tag)) }
  }));
}
---

The D7 to Astro migration case

Drupal 7 reached end of life in January 2025. If you are on D7, you are facing a forced migration. The official D7 to D10 upgrade path requires rebuilding custom modules for D10’s new architecture, converting themes from PHPTemplate to Twig, migrating database schemas, and testing all contributed module replacements. Organizations regularly report this process takes 3-6 months and costs $50,000-$200,000.

For D7 sites that are primarily content sites, migrating to Astro is often a fraction of the cost and time. But be honest about the tradeoffs:

Migrate to Astro when:

  • Your site serves the same content to every visitor
  • You do not need authenticated user areas, complex permissions, or editorial workflows
  • Content changes weekly or monthly, not minute by minute
  • Your budget does not support a $50K+ D10 upgrade

Upgrade to D10 when:

  • You genuinely need Drupal’s permissions system, editorial workflows, or multilingual translation management
  • You have complex entity relationships that drive application logic
  • You have deep integrations with enterprise systems via Drupal modules
  • Your team has strong Drupal expertise and wants to stay on the platform

The Drupal community has strong opinions about this, and they are not wrong — Drupal 10 is excellent software. The question is whether your specific site needs what Drupal provides. Many D7 sites were built on Drupal not because they needed its power but because it was the right choice at the time. If your site has grown simpler over the years, a static site might be the better architecture now.

The cost and performance comparison

Drupal (managed)Astro (static)
Hosting$100-$500/mo$0 (Cloudflare Pages, Vercel, Netlify)
Annual maintenance$3,600-$24,000Near-zero
Security patchesConstant (core + contrib modules)No server-side surface
Developer rates$50-$200/hr (Drupal specialists)Any JS developer + AI agents
Core Web Vitals~59% pass rate (httparchive.org data)95-100% typical
TTFB200ms-2s (varies by caching config)<50ms (CDN edge)
PHP dependencyRequiredNone
DatabaseRequiredNone

Drupal with proper caching (Varnish, Redis, CDN) can perform well. These numbers reflect the median Drupal site, not a perfectly optimized one. But even well-cached Drupal cannot match the simplicity of pre-built HTML served from a CDN edge node.

After the migration

Once your Drupal content lives in Astro, you gain a few things:

  • Any JavaScript developer can maintain it — no Drupal expertise required
  • AI coding agents work natively with it — Claude Code, Cursor, and similar tools can read and modify Astro projects directly
  • Content editing is file-based — edit markdown, commit, deploy. Or add a headless CMS (Decap CMS, Tina, Sanity) for a browser-based editing experience
  • Deployment is instantgit push triggers a build and deploy in under a minute
  • No ongoing security obligations — no PHP patches, no Drupal security advisories, no database hardening

The tradeoff is that you lose Drupal’s content editing UI, its permissions system, and its module ecosystem. For content-focused sites, this is usually a good trade. For sites that genuinely need those features, Drupal remains the right tool.

Automate Everything.

Tired of managing a fleet of fickle browsers? Sick of skipping e2e tests and paying the piper later?

Sign up now for free access to our headless browser fleet…

Get started today!