How to Migrate From Strapi to WordPress in 6 Easy Steps
How to Migrate From Strapi to WordPress in 6 Technical Steps
Migrating an enterprise web application from a headless Node.js architecture like Strapi to a monolithic or decoupled WordPress environment requires precise data mapping, rigorous schema transformation, and strict handling of relational dependencies. Transitioning content types, relational fields, and asset storage pools across fundamentally different persistence layers demands a programmatic migration strategy rather than manual copy-paste operations.
Whether you are consolidating tech stacks to empower editorial teams, reducing administrative overhead, or stabilizing a high-traffic publication engine, a structured migration methodology ensures zero data loss, preserved SEO equity, and complete structural integrity.
Problem Statement & Impact
The core technical challenge in migrating from Strapi to WordPress lies in the fundamental differences between how each platform structures, stores, and serves data. Strapi stores content in relational databases (PostgreSQL, MySQL, or SQLite) using entity-attribute-value (EAV) models or collection-specific tables with strict JSON schemas for components and dynamic zones. WordPress relies on a normalized relational schema centered around the wp_posts, wp_postmeta, wp_terms, and wp_term_relationships tables.
Chat with an engineer on WhatsApp within 15 minutes.
Direct database-to-database insertion fails due to schema divergence. Strapi uses randomized UUIDs or integer IDs coupled with relational mapping tables for many-to-many and one-to-many relations, whereas WordPress uses sequential post IDs and taxonomy term IDs. Furthermore, Strapi dynamic zones and repeatable components do not have a native equivalent in WordPress core, requiring custom post types, Advanced Custom Fields (ACF) repeater fields, or Gutenberg block serialization.
Inappropriate migration planning results in orphaned media references, broken relational foreign keys, catastrophic SEO ranking drops due to URL structure alterations, and exposed database vulnerabilities during the transition phase. System engineers must execute this transformation within a secure staging perimeter, utilizing automated CLI scripts and strict validation gates to guarantee integrity before DNS cutover.
Root Cause Analysis & Architecture
To execute a flawless migration, administrators must understand the execution lifecycle of data payloads between both systems. Strapi exposes content through its native Content API (REST or GraphQL), returning deeply nested JSON objects containing media URLs, localized fields, and component arrays. WordPress ingests data either through the WordPress REST API, WP-CLI batch execution, or direct SQL insertion.
When pulling data from Strapi via REST, pagination limits (default 25 or 100 items per request) often cause incomplete extractions if scripts do not account for total record counts. Moreover, handling media assets requires fetching binary data from Strapi’s upload provider (whether local disk, AWS S3, or Google Cloud Storage) and streaming it into the WordPress uploads directory via wp_handle_sideload() to generate appropriate attachment posts and image thumbnail sizes.
Security vulnerabilities frequently emerge during migrations when temporary staging endpoints are left unauthenticated or debug logs expose database connection strings and API tokens. Establishing strict environment isolation, enforcing token-based API authorization, and sanitizing imported HTML payloads against cross-site scripting (XSS) vectors are non-negotiable engineering requirements.
Step 1: Audit and Export Your Strapi Data
Begin by cataloging all content types, single types, components, and media libraries present in your Strapi instance. Access your Strapi project codebase and review the schema definitions located in src/api/[content-type]/content-types/[content-type]/schema.json to map out exact field types, relations, and constraints.
To extract data programmatically without overwhelming memory limits, write a Node.js extraction script that queries the Strapi REST API iteratively using pagination parameters ($pagination[page] and $pagination[pageSize]).
# Create a dedicated directory for migration scripts
mkdir wp-strapi-migration && cd wp-strapi-migration
npm init -y
npm install axios dotenv
Create an extraction script named extract.js to pull all collection entries and save them as structured JSON files:
// extract.js (Node.js script for data extraction)
const axios = require('axios');
const fs = require('fs');
require('dotenv').config();
const STRAPI_URL = process.env.STRAPI_URL || 'http://localhost:1337';
const API_TOKEN = process.env.STRAPI_API_TOKEN;
async function fetchCollection(contentType) {
let page = 1;
let pageSize = 100;
let allData = [];
let hasMore = true;
while (hasMore) {
try {
const response = await axios.get(`${STRAPI_URL}/api/${contentType}?pagination[page]=${page}&pagination[pageSize]=${pageSize}&populate=*`, {
headers: { Authorization: `Bearer ${API_TOKEN}` }
});
const data = response.data.data;
allData = allData.concat(data);
const pagination = response.data.meta.pagination;
if (page >= pagination.pageCount) {
hasMore = false;
} else {
page++;
}
} catch (error) {
console.error(`Error fetching ${contentType} on page ${page}:`, error.message);
break;
}
}
fs.writeFileSync(`${contentType}.json`, JSON.stringify(allData, null, 2));
console.log(`Successfully exported ${allData.length} records for ${contentType}.`);
}
async function runMigrationAudit() {
await fetchCollection('articles');
await fetchCollection('authors');
await fetchCollection('categories');
}
runMigrationAudit();
Step 2: Set Up Your New WordPress Environment
Provision a production-grade WordPress environment on an isolated staging server running PHP 8.2+, Nginx, and MariaDB 10.6+. Utilize WP-CLI to initialize the core installation and configure essential security baselines.
# Download and configure WordPress core via WP-CLI
wp core download --path=/var/www/html/staging
cd /var/www/html/staging
wp config create \
--dbname=wordpress_staging \
--dbuser=wp_migrator \
--dbpass='SecurePass_987!' \
--dbhost=localhost \
--path=/var/www/html/staging
wp core install \
--url=https://staging.yoursite.com \
--title="Migration Staging" \
--admin_user=sysadmin \
[email protected] \
--skip-email
Install and activate required plugins programmatically to handle custom content types and API extensions before running your import scripts:
wp plugin install advanced-custom-fields-pro --activate
wp plugin install wp-rest-api-authentication --activate
wp plugin install redis-cache --activate
Step 3: Map Your Content Architecture
Strapi schemas do not translate to WordPress natively. You must construct equivalent Custom Post Types (CPTs), taxonomies, and custom fields in WordPress prior to data ingestion. Create a custom functionality plugin or add the following code to your staging theme’s functions.php file to register your CPTs and taxonomies:
<?php
/
* Register Custom Post Types and Taxonomies to mirror Strapi Schema
*/
function strapi_register_migration_schema() {
register_post_type('article', array(
'labels' => array(
'name' => __('Articles'),
'singular_name' => __('Article'),
),
'public' => true,
'has_archive' => true,
'supports' => array('title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields'),
'show_in_rest' => true,
'rest_base' => 'articles',
));
register_taxonomy('article_category', 'article', array(
'labels' => array(
'name' => __('Categories'),
'singular_name' => __('Category'),
),
'hierarchical' => true,
'show_in_rest' => true,
));
}
add_action('init', 'strapi_register_migration_schema');
Ensure Advanced Custom Fields (ACF) field groups are configured to match any repeatable components or relational fields previously handled by Strapi JSON structures.
Step 4: Import Content and Media Assets
With schemas aligned, write a custom PHP script executed via WP-CLI to read your exported JSON files, sideload media attachments, and insert posts into the WordPress database while maintaining relational integrity.
<?php
/
* WP-CLI Migration Script: import-strapi.php
* Execute via: wp eval-file import-strapi.php
*/
if (!defined('WP_CLI')) {
return;
}
$json_data = file_get_contents(__DIR__ . '/articles.json');
$articles = json_decode($json_data, true);
foreach ($articles as $item) {
$attributes = $item['attributes'];
$title = sanitize_text_field($attributes['title']);
$content = wp_kses_post($attributes['content']);
$slug = sanitize_title($attributes['slug']);
// Check if post already exists
$existing = get_page_by_path($slug, OBJECT, 'article');
if ($existing) {
WP_CLI::line("Skipping existing post: {$title}");
continue;
}
$post_data = array(
'post_title' => $title,
'post_content' => $content,
'post_name' => $slug,
'post_status' => 'publish',
'post_type' => 'article',
'post_date' => date('Y-m-d H:i:s', strtotime($attributes['publishedAt'])),
);
$post_id = wp_insert_post($post_data);
if (is_wp_error($post_id)) {
WP_CLI::warning("Failed to insert post: {$title}");
continue;
}
// Handle Featured Media Sideloading
if (!empty($attributes['cover']['data']['attributes']['url'])) {
$image_url = 'http://localhost:1337' . $attributes['cover']['data']['attributes']['url'];
require_once(ABSPATH . 'wp-admin/includes/media.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/image.php');
$tmp = download_url($image_url);
if (!is_wp_error($tmp)) {
$file_array = array(
'name' => basename($image_url),
'tmp_name' => $tmp
);
$attachment_id = media_handle_sideload($file_array, $post_id);
if (!is_wp_error($attachment_id)) {
set_post_thumbnail($post_id, $attachment_id);
}
@unlink($tmp);
}
}
WP_CLI::success("Successfully imported: {$title} (ID: {$post_id})");
}
Run the migration script directly via the command line:
wp eval-file import-strapi.php --path=/var/www/html/staging
Step 5: Handle Redirects and SEO Preservation
Strapi permalink structures typically follow patterns like /api/articles/:slug or custom paths, whereas WordPress utilizes structures such as /article/:slug/ or /%postname%/. Failing to map these changes results in 404 errors and immediate loss of search engine crawl equity.
Configure Nginx level 301 permanent redirects to map legacy API endpoints directly to new WordPress URL patterns before DNS switching occurs. Add these rules to your Nginx server block configuration:
# Nginx 301 Redirect Rules for Strapi to WordPress Migration
location ~* ^/api/articles/(.+) {
return 301 https://yoursite.com/article/$1;
}
location ~* ^/api/authors/(.+) {
return 301 https://yoursite.com/author/$1;
}
location ~* ^/api/categories/(.+) {
return 301 https://yoursite.com/category/$1;
}
Validate redirect chains using cURL to ensure correct HTTP response headers are returned:
curl -I https://staging.yoursite.com/api/articles/my-first-post
# Expected Output: HTTP/1.1 301 Moved Permanently
# Location: https://staging.yoursite.com/article/my-first-post
Step 6: Production Hardening & Edge Cases
Migrated databases often contain serialized strings, broken internal links, and lingering references to old staging or Strapi media domains. Execute a database search-and-replace using WP-CLI to update all media asset pointers and internal hyperlinks.
wp search-replace 'http://localhost:1337/uploads/' 'https://yoursite.com/wp-content/uploads/' --all-tables --path=/var/www/html/staging
wp search-replace 'https://old-strapi-domain.com' 'https://yoursite.com' --all-tables --path=/var/www/html/staging
Address performance caching conflicts by flushing object caches and warming up WordPress page caches. Configure Redis object caching to handle high-concurrency database queries:
wp redis enable --path=/var/www/html/staging
wp cache flush
Lock down file permissions across the WordPress directory to prevent unauthorized code execution:
find /var/www/html/staging -type d -exec chmod 755 {} \;
find /var/www/html/staging -type f -exec chmod 644 {} \;
chown -R www-data:www-data /var/www/html/staging
Diagnostic Verification & Checklist
Prior to updating DNS records to point your domain to the new WordPress infrastructure, execute the following verification checklist to confirm system stability, security posture, and data integrity.
| Verification Item | Diagnostic Command / Method | Expected Result |
|---|---|---|
| Post Count Validation | wp post list --post_type=article --format=count |
Matches total record count from Strapi audit. |
| Media Attachment Integrity | wp media list --format=count |
All images successfully sideloaded and linked. |
| Redirect Verification | curl -I https://yoursite.com/api/articles/test-slug |
HTTP 301 pointing to /article/test-slug. |
| REST API Endpoint Check | curl -X GET https://yoursite.com/wp-json/wp/v2/articles |
HTTP 200 returning valid JSON article payload. |
| File Permissions Audit | find /var/www/html -perm 777 |
Zero results returned (no world-writable directories). |
Monitor error logs continuously during the initial cutover window:
tail -f /var/log/nginx/yoursite.error.log
tail -f /var/www/html/staging/wp-content/debug.log
Is Your Website Fast & Secure?
Run an instant scan for security vulnerabilities, bloated plugins, and TTFB latency.
Need Help Securing or Speeding Up Your Site?
Get 24/7 proactive security WAF, daily cloud backups, and sub-second speed optimization from ₹3,470/mo.