Skip to content

WordPress API Migration

Migrate your WordPress content directly using the WordPress REST API. This is the simplest and most common way to use Unpress.

Overview

When you migrate via the WordPress REST API, Unpress connects directly to your WordPress site and fetches all content (posts, pages, categories, tags, authors) programmatically.

Advantages:

  • Fast and direct—no need to export XML files
  • Real-time data—gets the latest published content
  • Handles large sites with pagination
  • Supports all WordPress versions with REST API (WordPress 4.7+)

Disadvantages:

  • Requires live WordPress site with Application Password
  • Can be rate-limited by WordPress server
  • Depends on site uptime during migration

Step 1: Create WordPress Application Password

Before migrating, you need an Application Password to connect Unpress to your WordPress site.

Create Application Password

  1. Log in to your WordPress admin dashboard
  2. Navigate to Users → Profile (or click your username in top-right)
  3. Scroll down to Application Passwords section
  4. Enter a descriptive name (e.g., "Unpress Migration Tool")
  5. Click Add New Application Password

WordPress will generate a 20-character password like:

text
abcd efgh ijkl mnop qrst

Important: Copy this password immediately and save it securely. You won't be able to see it again!

Manage Application Passwords

If you need to revoke or recreate your Application Password:

  1. Go to Users → Profile
  2. Find "Unpress Migration Tool" in Application Passwords list
  3. Click Revoke to invalidate the password
  4. Create a new one if needed

Security Best Practices

  • Use unique Application Passwords for each tool
  • Revoke passwords when migration is complete
  • Never commit passwords to Git or share publicly
  • Store passwords in environment variables or secure config files

Step 2: Run API Migration

Once you have your Application Password, run the migration:

dotenv
WP_URL=https://your-wordpress-site.com
WP_USER=your-username
WP_APP_PASSWORD=your-20-char-app-password
bash
pnpx @selfagency/unpress --generate-site
# or
npx -y @selfagency/unpress --generate-site

Unpress automatically reads .env from the directory where you run the command.

Core values

These usually live in .env:

  • WP_URL - Your WordPress site URL
  • WP_USER - Your WordPress username
  • WP_APP_PASSWORD - Your WordPress Application Password

Optional Flags

  • --generate-site - Generate complete 11ty project in site/ folder
  • --download-media - Download media files locally
  • --out-dir <dir> - Specify output directory (defaults to current directory)
  • --concurrency <num> - Number of concurrent requests (default: 2)
  • --dry-run - Validate configuration without making changes

Step 3: What Gets Migrated?

Unpress migrates all public content from your WordPress site:

Content Types

ContentMigratedLocation in Output
Posts✅ All published postssite/content/posts/
Pages✅ All published pagessite/content/ (root level files)
Categories✅ All categories with hierarchyMetadata in post frontmatter + category index
Tags✅ All tagsMetadata in post frontmatter + tag index
Authors✅ All authorssite/content/authors/ (if multi-author)

Post Data

Each post includes:

  • Title - Post title (rendered)
  • Content - Post body converted to Markdown
  • Excerpt - Post excerpt (if available)
  • Date - Publication date
  • Modified Date - Last modification date
  • Slug - URL-friendly post name
  • Status - Post status (published, draft, etc.)
  • Type - Post type (post, page, custom post type)
  • Author - Author information
  • Categories - Category list
  • Tags - Tag list
  • Custom Fields - All WordPress custom fields (postmeta)
  • Featured Image - Featured image URL (if set)

Media Handling

By default, Unpress does not download media. To handle media, choose a mode:

Mode 1: Download Locally

bash
pnpx @selfagency/unpress --download-media --generate-site

Media files are saved to site/media/ and URLs in Markdown are updated automatically.

Mode 2: Reupload to S3

Create a config file unpress.yml:

yaml
source:
  type: api
  api:
    baseUrl: https://your-site.com

media:
  mode: reupload
  reupload:
    driver: s3
    s3:
      bucket: your-bucket-name
      region: us-east-1
      endpoint: https://s3.amazonaws.com

Set AWS credentials via environment variables:

bash
export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key
export AWS_REGION=us-east-1

Run migration:

bash
pnpx @selfagency/unpress --config unpress.yml --generate-site

Mode 3: Leave URLs (Archival)

Keep original media URLs unchanged (best for archival):

yaml
media:
  mode: leave

See Media Handling Guide for detailed media configuration.

Step 4: Understand Output Structure

After successful migration, check your output:

bash
ls -la site/

You should see:

text
site/
├── content/
│   ├── posts/
│   │   ├── 2024-01-15-my-first-post.md
│   │   ├── 2024-01-16-second-post.md
│   │   └── ...
│   ├── authors/
│   │   ├── admin.md
│   │   └── jane-doe.md
│   ├── tags/
│   │   ├── travel.md
│   │   ├── food.md
│   │   └── ...
│   └── categories/
│       ├── uncategorized.md
│       └── ...
├── _includes/
│   └── layouts/
│       └── base.njk
├── assets/
│   └── styles.css
└── index.md

Post File Example

Open a post file to see the structure:

markdown
---
title: "My First Blog Post"
date: 2024-01-15T10:30:00
modified: 2024-01-16T14:20:00
slug: my-first-post
status: publish
type: post
author:
  name: Admin User
  email: admin@example.com
categories:
  - Uncategorized
tags:
  - travel
  - food
---

## My First Blog Post

This is the content of my blog post, converted from WordPress HTML to clean Markdown...

![Featured image](https://your-site.com/wp-content/uploads/2024/01/image.jpg)

Step 5: Rate Limiting and Concurrency

WordPress servers may rate-limit API requests to prevent overload. Unpress provides flags to control this:

Concurrency Control

Limit number of simultaneous requests:

bash
pnpx @selfagency/unpress --concurrency 1 --generate-site
  • Default: 2 concurrent requests
  • Recommendation: Start with 1-2 for unknown servers
  • Large sites: Reduce concurrency to avoid timeouts

Rate Limiting

Control request rate per second:

bash
pnpx @selfagency/unpress --intervalCap 5 --interval 1000 --generate-site
  • --intervalCap 5: Max 5 requests per interval
  • --interval 1000: 1000ms (1 second) interval
  • Result: Max 5 requests/second

Configuration File Example

Set these options in unpress.yml for consistency:

yaml
processing:
  concurrency: 2
  intervalCap: 10
  interval: 1000

Step 6: Troubleshooting API Migration

"Authentication Failed" Error

Cause: Wrong credentials or authentication method.

Solutions:

  • Double-check username matches your WordPress login (not email)
  • Verify Application Password was copied correctly (20 characters)
  • Check if your site uses HTTPS—use https:// in URL
  • Test credentials manually: curl -u username:password https://your-site.com/wp-json

"403 Forbidden" or "401 Unauthorized"

Cause: Permission denied or CORS issues.

Solutions:

  • Ensure Application Password has correct permissions (it should by default)
  • Check if your WordPress installation blocks REST API (some security plugins do)
  • Try accessing https://your-site.com/wp-json/wp/v2/posts in a browser
  • Contact your hosting provider if issue persists

"429 Too Many Requests"

Cause: WordPress server rate limiting.

Solutions:

  • Reduce concurrency: --concurrency 1
  • Add rate limiting: --intervalCap 2 --interval 1000
  • Wait a few minutes and run migration again
  • Contact your hosting provider to increase API limits

"Empty Content" (No Posts Migrated)

Cause: No published posts or wrong query.

Solutions:

  • Verify posts are "Published" status (not "Draft" or "Private")
  • Check if custom post types exist—configure types.yml to include them
  • Test API directly in browser: https://your-site.com/wp-json/wp/v2/posts
  • Check WordPress REST API is enabled: https://your-site.com/wp-json/

"Timeout" or "Connection Reset"

Cause: Server timeout or network issues.

Solutions:

  • Reduce concurrency and increase rate limiting
  • Check your internet connection
  • Try migrating during off-peak hours
  • Contact your hosting provider if timeout is too aggressive

"Memory Error" (Node.js)

Cause: Processing too much data at once.

Solutions:

  • Reduce concurrency to process fewer items simultaneously
  • Use pagination to fetch smaller batches
  • Increase Node.js memory limit: NODE_OPTIONS=--max-old-space-size=4096 pnpx @selfagency/unpress ...

Next Steps

After successful API migration:

  1. Generated Site Guide - Understand your output structure
  2. Customization Guide - Modify templates and styles
  3. Deployment Guide - Deploy your site to hosting platform
  4. Meilisearch Guide - Add search to your site

Comparison: API vs XML Export

FeatureAPI MigrationXML Export
SpeedFast, real-timeSlower, requires export first
ConnectionRequires live siteWorks offline with export file
ContentAll published contentAll content (including drafts)
MediaHandles media via flagsSame media options
Rate LimitsMay be rate-limitedNo rate limiting (local processing)
Best ForActive sites, small-to-medium sitesLarge sites, archival, offline work

When to use API migration:

  • Your WordPress site is live and accessible
  • You have fewer than 5,000 posts
  • You want the latest published content
  • You don't want to export XML manually

When to use XML export:

  • Your WordPress site is large (5,000+ posts)
  • You want to migrate offline
  • You need to include drafts or private posts
  • Your WordPress site has strict rate limiting

See XML Export Migration Guide for XML-based migration.

Released under MIT License.