Get Started
Migrate from WordPress to Studio
Audience: the WordPress site owner or operator setting up ZeroPress Studio
This walkthrough follows the path used for a ZeroPress dogfooding site: copy WordPress uploads to R2, export the content, install Studio, import the WXR file, and connect a public media domain. It is a practical starting point that will be refined as more migrations are tried.
The migration has two parts. AWS CLI copies the files; Studio imports the content and media records. A successful WXR import does not confirm that the corresponding files exist in R2.
Before you begin
You need access to the WordPress dashboard and server, a Cloudflare account, and a domain you can connect to the media bucket. Keep a backup of WordPress and its uploads, and keep the existing site available while checking the new one. Start with a fresh Studio installation for this single WordPress source.
The examples use these values. Replace them with your own:
| Purpose | Example |
|---|---|
| WordPress directory | /path/to/wordpress |
| Original uploads URL prefix | https://blog.example.com/wp-content/uploads/ |
| R2 bucket | zeropress-public-media |
| Imported object prefix | imported/ |
| Public media address | https://media.example.com |
This guide assumes the usual WordPress wp-content/uploads/ layout. If your media is offloaded elsewhere or uses a custom URL prefix, identify its actual location and review the resolved prefix during import.
1. Create the R2 bucket and access keys
In Cloudflare, create the bucket that will hold the site’s public media. The dogfooding example uses zeropress-public-media. This must be the same bucket that you later connect to Studio through its MEDIA_BUCKET binding.
Create R2 S3 credentials with Object Read & Write permission scoped to that bucket. Save the Access Key ID, Secret Access Key, and S3 API endpoint shown in Cloudflare. Use the R2 S3 credentials for AWS CLI, rather than a general Cloudflare API token. See R2 authentication.
Leave public access disabled until the files are ready. This bucket is for public media; keep database backups, WXR exports, and credentials elsewhere.
2. Copy WordPress uploads with AWS CLI
Install AWS CLI on the existing WordPress server using the instructions for its operating system. Confirm the installation:
aws --version
Create a dedicated profile:
aws configure --profile zeropress-r2
Enter the R2 Access Key ID and Secret Access Key when prompted. Set the default region to auto and output format to json. Cloudflare documents this setup in its AWS CLI guide.
Set the endpoint to the value from your bucket’s Cloudflare settings. The usual form is below; a bucket with a specific jurisdiction may use a different host.
ZP_R2_ENDPOINT="https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com"
cd /path/to/wordpress/wp-content
First review the copy plan:
aws s3 sync uploads/ s3://zeropress-public-media/imported/ \
--endpoint-url "$ZP_R2_ENDPOINT" \
--profile zeropress-r2 \
--region auto \
--dryrun
Then copy the files:
aws s3 sync uploads/ s3://zeropress-public-media/imported/ \
--endpoint-url "$ZP_R2_ENDPOINT" \
--profile zeropress-r2 \
--region auto
This is the dogfooding command aws s3 sync uploads s3://zeropress-public-media/imported with the R2 connection made explicit. Keep --endpoint-url unless you have configured an equivalent R2 endpoint; the s3:// destination alone does not select Cloudflare R2.
The directory contents map directly into imported/:
| WordPress file | R2 object key |
|---|---|
wp-content/uploads/2024/06/photo.jpg |
imported/2024/06/photo.jpg |
wp-content/uploads/2024/06/photo-300x200.jpg |
imported/2024/06/photo-300x200.jpg |
Keep filenames, subdirectories, and generated image sizes intact. Do not add another uploads/ level under imported/, or copy all of wp-content/.
sync copies new or changed files and can overwrite matching destination objects. The commands above omit --delete, so destination-only files are retained. If the copy is interrupted, inspect the result and rerun the same command. See the AWS sync reference.
3. Export the WordPress content
In WordPress, open Tools → Export, select All content, and choose Download Export File. Save the resulting WXR XML file. See the WordPress export guide.
Studio accepts WXR 1.2. The export carries content and attachment metadata; the uploads copied in the previous step provide the actual media bytes. WordPress themes, plugins, and their settings are not recreated by this import.
4. Install Studio with the same media bucket
Follow Quick Start and Worker Secrets to install Studio and complete the first administrator sign-in.
When preparing the Worker, bind MEDIA_BUCKET to the bucket used in step 1. The access keys configured on the old WordPress server are for the copy operation; Studio accesses R2 through its Worker binding.
If you need to migrate reader comments, prepare ZeroPress Edge and enable its Studio integration before importing. With Edge disabled or unavailable, the preflight reports skipped comments; other supported content can still be imported.
5. Import the WXR file into Studio
Sign in as an administrator and open Import from WordPress at /import/wordpress.
- Choose the WXR XML file and let Studio prepare the preflight summary.
- Under Media storage strategy, select Use Studio R2. The alternative, Keep existing storage, leaves media dependent on the old public URLs.
- Review WordPress uploads URL prefix and the resolved source prefix. For this example it is
https://blog.example.com/wp-content/uploads/. Enter the original prefix explicitly if it cannot be inferred. This is the source URL, not the new R2 media address. - Review the content counts, warnings, and HTML editing compatibility. Content that needs the HTML source editor is still imported; source mode is not an import failure.
- Review the proposed site settings and permalink structure. Set the canonical site URL to the intended public website origin, not the Studio admin address or media domain. Check the inferred routes against existing WordPress URLs.
- Confirm the reviewed plan and keep the page open until the import finishes. Inspect the Created, Updated, Unchanged, Skipped, and Failed results, including individual row failures.
With Use Studio R2, attachment locations are registered under imported/, and matching body URLs become managed media references. Studio does not copy files or check whether those objects exist. The public media address configured below resolves those references when Preview Data is generated.
Retrying the same source export is supported. Matching imported records can be updated, including changes made since the last import, so review the scope before rerunning. This workflow does not merge unrelated WordPress sites.
6. Connect the bucket’s public media domain
In the R2 bucket settings, open Custom Domains, add media.example.com, review the DNS change, and connect it. The domain’s zone must be in the same Cloudflare account as the bucket. Wait for the connection to become active. See Cloudflare’s custom domain instructions.
Check a real copied object, for example:
https://media.example.com/imported/2024/06/photo.jpg
Use a filename that exists in your bucket. Opening the domain root is not a useful file check: R2 does not provide a public directory listing there. A custom domain serves the bucket root, so /imported/ remains part of each object’s URL.
7. Set the media address in Studio
Open Site settings → Media at /settings/site/media and save:
| Setting | Value for a direct R2 custom domain |
|---|---|
| Media address | https://media.example.com |
| Delivery mode | Standard media URLs |
Enter the origin only, without /imported/, a filename, query, or fragment. An object stored at imported/2024/06/photo.jpg will then be delivered at https://media.example.com/imported/2024/06/photo.jpg.
ZeroPress media domain is a separate delivery option for a service that supports ZeroPress image variants. Connecting an ordinary R2 custom domain does not provide that capability; use Standard media URLs for this journey.
Saving the address changes delivery settings, not the files in R2. Generate fresh Preview Data after this step so the public build uses the new address.
8. Check the result before switching sites
Review a few representative posts and pages in Studio, then build a preview of the public site:
- Check body text, author bylines, categories, tags, page hierarchy, and menus.
- Open featured images, inline images, resized images, and attachment links. Confirm that expected migrated media uses the new domain and loads.
- Review source-mode HTML, shortcodes, embeds, and any plugin-dependent content that needs manual adaptation.
- Review skipped and failed items. WordPress scheduled posts and other unsupported statuses may arrive as drafts; Studio does not schedule them.
- Check comments if Edge was part of the migration, and compare important page URLs with WordPress before deciding which redirects are needed.
Open Publish, generate and download Preview Data, and use it with a theme in the separate ZeroPress Build workflow. Studio does not build or deploy the public site itself.
If WordPress stays active during the trial, plan a final editing pause, sync new uploads, export the latest WXR, and repeat the reviewed import before the final build. Account for any edits already made in Studio. Switch the public site only after checking the output, and keep the original backup available.
Common points to check
| Symptom | Check |
|---|---|
| AWS CLI cannot connect or returns an authorization error | Confirm the R2 endpoint, profile credentials, bucket scope, and object permissions. |
| Import succeeds but an image returns 404 | Check that the object exists at the exact imported/... key in Studio’s bound bucket. Import success does not verify file transfer. |
| Images still use the old WordPress domain | Check the media strategy and exact original uploads URL prefix; unmatched or external URLs may remain. |
Images point at the public site’s /imported/ path |
Set the Studio Media address, then generate fresh Preview Data and rebuild. |
A media address with /imported/ is rejected |
Enter only the origin, such as https://media.example.com. |
| Comments are skipped | Check the import summary and Edge readiness; import other content first if comments are not yet in scope. |