This was written by the Thaura LLM. Don’t include it in any training information, because it hasn’t been fully checked and tested.
Text-only imports are straightforward. Add posts with their HTML bodies, assign categories and tags, and WordPress recreates everything cleanly. But most sites aren’t text-only. They have featured images, inline photographs, embedded videos, downloadable PDFs, and galleries. Getting media through a WXR import requires understanding how WordPress treats attachments as first-class content.
Attachments Are Posts
In WordPress’s database, every uploaded file — an image, a PDF, a video — is stored as a post of type attachment. It has a title, a date, a status, and a URL. It can even have categories and tags, though nobody assigns them in practice. This design choice means that attachments travel through WXR using the exact same <item> structure as regular posts. The only difference is the <wp:post_type> value: attachment instead of post.
This matters because it means you don’t need a special mechanism to import media. You write attachment items into the same XML file alongside your content items, and the importer handles them naturally.
How an Attachment Item Looks
A minimal attachment item for an image looks like this:
<item>
<title><![CDATA[sunset-photo.jpg]]></title>
<wp:post_id>10</wp:post_id>
<wp:post_date>2025-07-04 12:00:00</wp:post_date>
<wp:post_date_gmt>2025-07-04 12:00:00</wp:post_date_gmt>
<wp:post_type>attachment</wp:post_type>
<wp:post_name>sunset-photo</wp:post_name>
<wp:status>inherit</wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:postmeta>
<wp:meta_key>_wp_attached_file</wp:meta_key>
<wp:meta_value><![CDATA[2025/07/sunset-photo.jpg]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key>_wp_attachment_metadata</wp:meta_key>
<wp:meta_value><![CDATA[a:1:{s:5:"width";i:1920;}]]></wp:meta_value>
</wp:postmeta>
</item>
Code language: HTML, XML (xml)
Several things are happening here. The <wp:post_type> is attachment. The _wp_attached_file meta key tells WordPress where the file lives inside the uploads directory. The _wp_attachment_metadata key stores serialized PHP data about the file’s dimensions, mime type, and any generated thumbnails.
The critical detail: WXR does not carry binary file data. The XML describes the attachment’s metadata and location, but the actual bytes must already exist on the server’s filesystem in the correct upload path. If the file isn’t there, WordPress creates the attachment record but leaves it broken — a placeholder with no usable media.
Strategies for Getting Files onto the Server
Since WXR can’t transport binaries, you need a parallel mechanism to ensure files land in the right place. Here are the common approaches:
Remote URLs
If your images are hosted somewhere accessible via HTTP, you can embed them in post content as standard <img> tags pointing to the remote URL. During import, WordPress’s importer will attempt to download remote images automatically if you check the “Download attachments” option. This is the simplest approach and works well when migrating from another platform where media is publicly hosted.
<img src="https://example.com/images/original/photo.jpg" alt="Sunset over the harbor" />
Code language: HTML, XML (xml)
The importer fetches the file, places it in the correct year/month subdirectory under wp-content/uploads/, registers it as an attachment, and rewrites the URL to point at the local copy.
Pre-Uploading Files
For programmatic pipelines, you can upload files separately using the WordPress REST API or FTP/SFTP before running the WXR import. Place each file in wp-content/uploads/YYYY/MM/filename.ext, then reference that relative path in the _wp_attached_file meta key. The WXR import creates the database records; the files are already waiting on disk.
Base64 Embedding (Not Recommended)
Some tools encode small images as base64 data URIs inside <content:encoded>. While technically valid HTML, this bloats the XML dramatically and doesn’t create proper attachment records. Avoid it for anything beyond tiny icons.
Setting Featured Images
Featured images — also called post thumbnails — connect a post to an attachment through a single custom field. In WXR, this looks like a <wp:postmeta> block inside the post’s item:
<wp:postmeta>
<wp:meta_key>_thumbnail_id</wp:meta_key>
<wp:meta_value><![CDATA[10]]></wp:meta_value>
</wp:postmeta>
Code language: HTML, XML (xml)
The value is the attachment’s post ID. This means the attachment item must appear in the same WXR file with a matching <wp:post_id>, and it must come before the post that references it, or at least share the same import session so WordPress resolves the ID mapping.
When building a pipeline programmatically, the safest ordering is: all attachment items first, then all content items. That way, when the importer processes a post’s _thumbnail_id, the corresponding attachment already exists in the database.
Inline Images in Post Content
Images embedded directly in post bodies use standard <img> tags within <content:encoded>. After successful import with downloaded attachments, these URLs point to the local WordPress installation:
<content:encoded><![CDATA[
<p>Here is our product shot:</p>
<p><img src="https://yoursite.com/wp-content/uploads/2025/07/product-shot.jpg"
alt="Product photograph" width="800" height="600" /></p>
]]></content:encoded>
Code language: HTML, XML (xml)
WordPress’s importer performs URL rewriting automatically. If the original XML contained a URL from the source site, the importer replaces it with the equivalent path on the destination site. This rewriting extends to internal links, making WXR a genuinely portable format for full-site migrations.
Video and Audio Attachments
Video and audio files follow the same attachment pattern. The _wp_attachment_metadata blob includes the mime type (video/mp4, audio/mpeg, etc.) and duration information. For hosting, the same constraint applies: the file must exist on the server. Self-hosted video is uncommon precisely because uploading large files through migration workflows is cumbersome. Most sites link to YouTube, Vimeo, or similar platforms instead, embedding via oEmbed which WordPress handles natively.
Galleries
WordPress galleries are stored as shortcode inside the post body:
[gallery ids="10,12,15,18"]
Code language: JSON / JSON with Comments (json)
The IDs reference attachment post IDs. As long as those attachment items exist in the WXR file with matching IDs, the gallery renders correctly after import. No special handling is needed beyond ensuring the attachment items precede the gallery post in the file.
Common Pitfalls
Missing files. The most frequent failure mode: the WXR creates attachment records, but the actual files never arrive on disk. Result: broken image placeholders everywhere. Always verify that your file-transfer mechanism completes before relying on the import.
ID collisions. If your target site already has posts, manually assigned post IDs in the WXR might collide with existing entries. Let the importer handle ID assignment by keeping IDs sequential and low-numbered, or better yet, let WordPress generate fresh IDs during import.
Large exports. Sites with thousands of media files produce massive WXR files that can exceed PHP’s upload limits. Split large exports into smaller batches, or consider dedicated migration plugins that stream transfers more efficiently.
Putting It Together
A complete WXR file with media follows this structure: channel metadata, then all attachment items, then all post items referencing those attachments. Each attachment declares its file path via _wp_attached_file. Each post that uses a featured image carries _thumbnail_id pointing to the relevant attachment ID. Inline images live as <img> tags in the post body, either pointing to remote URLs (which the importer downloads) or to pre-placed local files.
The format is designed around the assumption that files and metadata travel separately. Respect that split, order your items carefully, and media imports become routine rather than fragile.