---
title: "Import Word documents to WordPress as drafts: a plugin you can build today"
description: Pasting from Word works for a post or two. For batch imports, here is a small plugin that creates WordPress drafts from .docx files.
source: https://steem.dev/blog/wordpress-import-word-doc-plugin
published: 2026-09-14
updated: 2026-09-14
alternative_to: Wordable
site: Steem — AI WordPress plugin generator
---
# Import Word documents to WordPress as drafts: a plugin you can build today
WordPress's paste-from-Word behaviour has improved a lot. Gutenberg strips most junk formatting and preserves headings, bold, and paragraphs reasonably well. For a single post written in Word, pasting is genuinely the right answer.
The problem starts when the content is not yours to paste. A freelancer sending a batch of articles. A team editor who does not have WordPress access. A client dropping a folder of .docx files from a migration project. Pasting twenty files is half a day of work, and none of it is editorial.
The plugin here adds a single admin page: upload a .docx file, get a WordPress draft with the headings, paragraphs, bold, and italic intact. You land on the draft editor ready to add a featured image and publish. No SFTP, no manual conversion step, no third-party service.
## What one build covers
- An admin menu page for uploading a .docx file
- Headings (h1 to h3), paragraphs, bold, and italic extracted from word/document.xml using ZipArchive and DOMXPath
- Post title taken from the first h1, or the filename if none is found
- Draft created with wp_insert_post, author set to the uploading user
- Redirect to the draft edit screen immediately after creation
## What it does not cover
- Images embedded in the Word document; upload and insert them after the draft is created
- Tables; add them with the follow-up prompt in the guide
- Elementor layout; the draft contains standard HTML, not Elementor's meta format
- Bulk or batch import; the plugin handles one file at a time without the follow-up
- Password-protected .docx files; remove the password in Word before uploading
## The prompt
```text
Build a WordPress plugin that imports .docx files as WordPress post drafts. Plugin Name: WP Docx Importer. Version: 1.0.0. Description: Create WordPress drafts from uploaded Word documents. Wrap all code in a function attached to plugins_loaded. Register a top-level admin menu page with add_menu_page: title 'Import Docx', menu title 'Import Docx', capability 'edit_posts', slug 'wp-docx-importer', callback 'wp_docx_importer_page', icon 'dashicons-upload'. In the page callback function: if REQUEST_METHOD is POST and wp_verify_nonce($_POST['_wpnonce'],'wp_docx_import') passes, process the upload: check that $_FILES['docx_file']['error'] === 0 and strtolower(pathinfo($_FILES['docx_file']['name'],PATHINFO_EXTENSION)) === 'docx', else set an $error string; if valid, open $_FILES['docx_file']['tmp_name'] with new ZipArchive, call open() with ZipArchive::RDONLY, read the 'word/document.xml' entry with getFromName(), close the zip; if open fails or the entry is false, set $error; otherwise parse the XML: create a new DOMDocument, call loadXML with LIBXML_NOERROR; create a DOMXPath, register namespace prefix 'w' as 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'; query '//w:p' for all paragraph nodes; for each paragraph node, query './/w:pPr/w:pStyle/@w:val' to get the style value; map 'Heading1' to 'h1', 'Heading2' to 'h2', 'Heading3' to 'h3', everything else to 'p'; for each run queried with './/w:r', check for './/w:rPr/w:b' to determine bold, './/w:rPr/w:i' for italic; concatenate text from all './/w:t' nodes; esc_html each fragment; wrap in strong if bold; wrap in em if italic; build the paragraph or heading HTML element and append to $html string; after all paragraphs, find the first h1 content for $title, fall back to sanitize_file_name(pathinfo($_FILES['docx_file']['name'],PATHINFO_FILENAME)) if none; call wp_insert_post with array('post_status'=>'draft','post_title'=>sanitize_text_field($title),'post_content'=>$html,'post_author'=>get_current_user_id()); if is_wp_error set $error else wp_redirect(admin_url('post.php?post='.$post_id.'&action=edit')) and exit. Render the page: echo '
Import Docx
'; if $error is set output '
'; render a form method=POST enctype=multipart/form-data; call wp_nonce_field('wp_docx_import'); output a p with a label 'Word document (.docx)' and a file input name=docx_file accept=.docx required; output a p with a submit button class=button-primary value='Create Draft'; close form and wrap div. No stylesheet, no script.
```
## What the plugin extracts from your Word file
A .docx file is a ZIP archive. Inside is a file called word/document.xml that holds the entire body of the document. PHP's ZipArchive reads the ZIP; DOMDocument and DOMXPath parse the XML. No library installs, no Composer, just core PHP that ships with every WordPress host.
The plugin reads three heading levels: Heading1, Heading2, and Heading3, mapped to h1, h2, and h3. Paragraphs become p elements. Bold runs become strong, italic runs become em. The text of the first h1 becomes the post title; if there is no h1, the filename is used instead.
The resulting post lands in Drafts with your user as the author. The plugin redirects you straight to the edit screen once the draft is saved.
## What it leaves out
Images embedded in a Word document live in a separate folder inside the ZIP, word/media/. The plugin reads only the document body. Images do not transfer. Upload them separately and insert them after the draft is created.
Tables are skipped. The XML structure for a Word table is different from prose paragraphs, and reliable HTML table conversion adds enough logic that it belongs in a follow-up prompt rather than the initial build. The follow-up message in the next section covers that.
Elementor stores its layout data in post meta, not in post_content. A draft created by this plugin contains standard HTML in post_content. Opening it in Elementor will show the content in the text widget, but Elementor's drag-and-drop column structure will not be populated. If your site is built entirely on Elementor, you will need to rebuild the visual layout after import.
## When to paste instead
If you are importing one or two posts a month, the plugin adds a step that paste does not. Gutenberg handles formatting from Word well enough for most documents and requires no setup.
If your Word documents use custom heading styles rather than the built-in Heading1 through Heading3, the plugin treats them as plain paragraphs. Documents authored with agency or corporate templates may lose their heading structure. Run a test file before relying on it for a full batch.
Bulk import, folder watching, and scheduled pulls from a shared drive are not included here. This plugin handles one file per upload. The follow-up message below extends it to accept multiple files in one submission.
## How to build it
Paste the prompt below into Steem. Once the plugin is active, go to Import Docx in your WordPress admin menu, upload a .docx file, and click Create Draft. You land on the edit screen for the new draft.
If you need table support after testing, the follow-up message is: 'Extend the Docx importer to handle tables. For each w:tbl element in word/document.xml, build an HTML table: iterate w:tr rows and w:tc cells, extract the text of each cell the same way paragraph runs are extracted, and output a table element with tbody, tr, and td tags. Insert the table at its correct position in the document flow, not appended at the end.'
For batch import, the follow-up is: 'Add batch import to the Docx importer. Replace the single file input with a multiple file input named docx_files[]. Process each uploaded file in a loop using the same ZipArchive and DOMXPath logic. After processing, show a summary table with the filename and a link to each created draft.'
## Questions
### My Word document uses custom heading styles. Will the structure be preserved?
Only the built-in Heading1, Heading2, and Heading3 styles map to h1, h2, and h3. Any other style name, including custom agency or corporate template styles, is treated as a plain paragraph. If your documents use custom styles, run a test file first, or ask Steem to add those style names to the mapping.
### What about files exported from Google Docs?
Google Docs exports to .docx use the same format. Go to File, then Download, then Microsoft Word (.docx). The plugin reads the export the same way it reads a native Word file.
### Why did the plugin not pick up my images?
Embedded images live in word/media/ inside the ZIP archive. The plugin reads only the document body XML. Upload images separately via the Media Library and insert them into the draft after creation.
### Does this work with password-protected Word files?
No. Password-protected documents encrypt the ZIP contents. PHP cannot read the document.xml entry and the import will fail. Open the document in Word, remove the password under File, Info, Protect Document, then save and re-upload.
### The draft content looks correct but Elementor shows it differently. What happened?
Elementor stores its visual layout in a post meta key, not in post_content. This plugin writes to post_content, which is what Gutenberg and the classic editor use. The content is there, but Elementor's column and widget structure is not. Treat the draft as a reference and rebuild the page layout in Elementor.
---
Source: https://steem.dev/blog/wordpress-import-word-doc-plugin
Build this plugin: https://steem.dev/app