WordPress’s own exporter produces a single XML file called a WXR, not a zip. If you have a zip, a migration plugin or a host made it, and what is inside depends entirely on which one.
This confusion is common and has a specific cause: WordPress’s built-in importer only accepts WXR, so people with a zip from somewhere else are told their file is invalid.
The fix is knowing which kind of archive you have, because the three common kinds need three different procedures.
Table of contents
- What the built-in export actually gives you
- Identifying the zip you have
- Where each thing lives
- Restoring each kind
- Getting an export that is actually complete
- How this fits the rest of the stack
- FAQ
What the built-in export actually gives you
Tools, Export, All Content produces one XML file — WordPress eXtended RSS, a WXR file, named something like sitename.wordpress.2026-08-30.xml.
It contains posts, pages, custom post types, comments, categories, tags, custom fields, users and the URLs of media files.
It does not contain: the media files themselves, themes, plugins, settings, widgets, menu assignments, or anything else stored in options.
The media point is the one that catches people. The XML references https://oldsite.com/wp-content/uploads/2026/08/photo.jpg, and the importer downloads each file from that URL if you tick “Download and import file attachments”. That works only while the old site is still online. Migrate after taking the old site down and you have imported posts with broken images and no way to recover them from the export.
So the built-in export is a content export, not a backup. It is the right tool for moving posts between sites and the wrong tool for moving a site.
Identifying the zip you have
Open it and look at the top level.
A migration plugin archive. One proprietary file, or a folder containing a .sql dump plus wp-content. Some use custom extensions rather than .zip, and renaming the extension to open it is a bad idea — these formats often carry a checksum, and the plugin is the only thing that reads them properly. Restore with the same plugin on the destination.
A full-site backup. A recognisable WordPress installation: wp-admin/, wp-includes/, wp-content/, wp-config.php, plus a .sql file somewhere. This is everything, and restoring means uploading the files and importing the database — not using WordPress’s importer.
A zipped WXR. Just an .xml file inside. Some browsers zip downloads automatically. Unzip it and import the XML normally.
A theme or plugin zip. style.css and template files at the root, no content at all. This is an installable theme, not an export.
The quick test:
unzip -l export.zip | head -30
An .xml at the top means content export. A .sql plus wp-content means a full backup. wp-admin and wp-includes mean a complete site.
Where each thing lives
For a full backup, the map is:
- Post and page text — in the SQL dump, in
wp_posts. Not in any file on disk. - Media files —
wp-content/uploads/, in year/month folders. Includes generated thumbnail sizes. - Themes —
wp-content/themes/. Your customisations are in the child theme folder. - Plugins —
wp-content/plugins/. - Settings, widgets, menus, plugin configuration — in the SQL dump, mostly in
wp_options. - Users and passwords —
wp_usersandwp_usermeta. Passwords are hashed. - Database credentials and salts —
wp-config.php.
The recurring surprise is that content is in the database, not in files. People search wp-content for their posts and find nothing, because posts have never been files in WordPress.
To read post content out of a SQL dump without importing it:
grep -o "INSERT INTO \`wp_posts\`[^;]*;" backup.sql | head -1
# Better: import into a local database and query it
mysql -u root -p temp_db < backup.sql
mysql -u root -p temp_db -e \
"SELECT post_title, post_date FROM wp_posts WHERE post_status='publish' AND post_type='post';"
Restoring each kind
A WXR — Tools, Import, WordPress, install the importer, upload the XML, map authors, tick “Download and import file attachments” while the source site is still reachable. Note that the default upload limit often rejects large WXR files; either raise it or split the export by content type on the source site.
A migration plugin archive — install the same plugin on the destination and use its restore. Do not try to extract it by hand.
A full backup:
- Create an empty database and a user for it.
- Import the
.sqldump. - Upload
wp-content— or the whole installation — to the web root. - Edit
wp-config.phpwith the new database credentials. - Run a search-and-replace for the old domain across the database.
- Log in and re-save permalinks to regenerate rewrite rules.
Step 5 needs care. WordPress stores some options as PHP serialized data, which embeds string lengths — so a plain SQL REPLACE corrupts any serialized value containing the URL, and the symptom is settings that silently revert or a broken widget area. Use WP-CLI’s search-replace, which handles serialization properly:
wp search-replace 'https://oldsite.com' 'https://newsite.com' --skip-columns=guid
--skip-columns=guid is deliberate: GUIDs are permanent identifiers for feed readers rather than URLs to follow, and changing them makes every post appear new.
Getting an export that is actually complete
If you are creating the export rather than receiving one, decide what you need:
- Moving content into an existing site — WXR is right, plus copying
wp-content/uploadsseparately if the old site is going offline. - Moving the whole site — a full backup: database dump plus the complete
wp-content. - Keeping a backup — both the database and the files, on a schedule, stored somewhere other than the server they came from.
And test the restore. An untested backup is a hypothesis, and the moment you discover a .sql dump was truncated is not the moment you want to discover it.
Managed WordPress on RunxBuild includes a database browser in the dashboard — tables, rows, SQL, export and import — alongside a file manager for browsing, uploading, unzipping and downloading. That means taking a database export and pulling down wp-content are both browser tasks, and inspecting what is inside an archive someone sent you does not require setting up a local MySQL first.
How this fits the rest of the stack
WordPress exports XML, not zip — so a zip came from a plugin or a host, and the top-level contents tell you which. Content lives in the database rather than in files, media is referenced by URL rather than embedded in a WXR, and any full restore needs a serialization-aware search-and-replace rather than a raw SQL one. The RunxBuild hosting calculator covers managed WordPress with a dashboard database browser and file manager for exactly this work.
Useful related references:
- WordPress vs Drupal: Ease of Use Against Structured Content
- There Has Been a Critical Error on This Website: Finding What WordPress Is Hiding
- What WordPress Is Actually Used For, and Where It Stops Making Sense
- Services on RunxBuild
FAQ
Does WordPress export a zip file?
No. The built-in exporter at Tools, Export produces a single WXR XML file containing posts, pages, comments, taxonomies and custom fields. A zip came from a migration plugin or a host backup tool, and needs a different restore procedure.
Where are my posts in a WordPress backup?
In the SQL dump, in the wp_posts table. Post content has never been stored as files, so searching wp-content for your articles will find nothing. Only media, themes and plugins are on disk.
Does the WordPress export include images?
No. The WXR file contains the URLs of media files, not the files. The importer downloads them from the original site if you tick “Download and import file attachments”, which only works while the source site is still online.
Why does WordPress say my zip is not a valid WXR file?
Because the importer only accepts the XML format WordPress produces. If your zip came from a migration plugin, restore it with that same plugin. If it is a full backup, import the SQL dump and upload the files rather than using the importer.
How do I change the site URL after restoring a backup?
Use WP-CLI’s wp search-replace, which handles PHP serialized data correctly. A plain SQL REPLACE corrupts serialized options because it does not update the embedded string lengths, which silently breaks settings and widgets.