Move WordPress Articles from Dev to Production with WP-CLI
When WordPress articles are written outside the block editor, a repeatable sync process keeps the source files, release notes, and published posts aligned. This workflow stores article drafts in a .posts folder, runs a WP-CLI helper against the WordPress install, and then reviews the results in wp-admin before the articles are considered ready.
What It Does
- Keeps article source files in a versioned
.postsfolder. - Uses WP-CLI to create missing WordPress posts from local Markdown drafts.
- Supports an overwrite mode for intentional content refreshes.
- Preserves the original WordPress publish date when an existing post is overwritten.
- Keeps categories, tags, title, slug, and post content tied to the source article metadata.
- Leaves final editorial review in
wp-admin, where images, formatting, previews, and publish status are easiest to inspect.
Folder Layout
A simple source repository can keep the content and helper script side by side:
C:\DevOps\DEV-Site
|-- .posts
| `-- 2026.06.23
| |-- example-article.md
| `-- assets
| `-- example-screenshot.png
|-- .codex
| `-- scripts
| `-- sync-posts-from-posts.php
`-- .releases
`-- 2026-06-23-sync-posts-via-wp-admin.md
The source repository does not have to be the WordPress install. The important part is that wp --path points to the actual WordPress folder.
Create the Source Folder Structure
If the articles already exist in WordPress because they were written by hand, start by creating a source folder that can hold future article versions. This does not change WordPress by itself. It gives you a place to store a clean copy of each article, its metadata, release notes, and any pictures used by the post.
From the source repository root, create the folders:
Set-Location C:\DevOps\DEV-Site New-Item -ItemType Directory -Force -Path .posts\2026.06.23 New-Item -ItemType Directory -Force -Path .posts\2026.06.23\assets New-Item -ItemType Directory -Force -Path .codex\scripts New-Item -ItemType Directory -Force -Path .releases
Save the sync helper as:
.codex\scripts\sync-posts-from-posts.php
Save the export helper as:
.codex\scripts\export-post-to-posts.php
Create a release note for the batch:
New-Item -ItemType File -Force -Path .releases\2026-06-23-sync-posts-via-wp-admin.md
When converting an existing hand-written WordPress article into a source file, use the export helper. It reads the post from WordPress, pulls the title, slug, publish date, categories, tags, and body content, then writes a Markdown file under .posts.
Export a Post Without Pictures
Start with a post that was already created visually in WordPress. For example, an editor writes the article in wp-admin, sets the title, categories, tags, and slug, then publishes it or saves it as a draft.
Find the post ID in wp-admin, or use the post slug. Then export it from WordPress into .posts:
Set-Location C:\DevOps\DEV-Site wp --path="C:\inetpub\sites\example.com" eval-file .\.codex\scripts\export-post-to-posts.php slug=example-wordpress-article-without-pictures
You can also export by ID:
wp --path="C:\inetpub\sites\example.com" eval-file .\.codex\scripts\export-post-to-posts.php post=123
The export creates a Markdown file based on the post date and slug:
.posts\2026.06.23\example-wordpress-article-without-pictures.md
The generated source file looks like this:
--- title: "Example WordPress Article Without Pictures" slug: "example-wordpress-article-without-pictures" date: "2026-06-23" status: "draft" categories: - WordPress - Deployment tags: - wordpress - wp-cli - content sync --- # Example WordPress Article Without Pictures This article was originally written visually in WordPress. The exported source copy now lives in the `.posts` folder so it can be reviewed, versioned, and synced again later. ## What It Does - Keeps the post title, slug, publish date, categories, and tags from WordPress. - Converts normal headings, paragraphs, lists, links, and code blocks into Markdown. - Uses the same slug as the existing WordPress post so targeted overwrite mode updates the right article later. ## Main Article Section This is content that came from the WordPress editor.
If the Markdown file already exists, the exporter stops by default. Use overwrite=1 only when you intentionally want to replace the source file:
wp --path="C:\inetpub\sites\example.com" eval-file .\.codex\scripts\export-post-to-posts.php slug=example-wordpress-article-without-pictures overwrite=1
The default sync skips the existing WordPress post because the slug already exists. Use targeted overwrite only when you intentionally want the exported and edited source copy to replace the current WordPress content.
Export a Post with Pictures
For an article with pictures, the starting point is still the visual WordPress editor. Add the images in the post, set meaningful alt text in the Media Library, and save the post.
Then export the post:
Set-Location C:\DevOps\DEV-Site wp --path="C:\inetpub\sites\example.com" eval-file .\.codex\scripts\export-post-to-posts.php slug=example-wordpress-article-with-pictures
The exporter creates the Markdown file and copies local Media Library files into the dated assets folder when it can find them:
.posts\2026.06.23\example-wordpress-article-with-pictures.md .posts\2026.06.23\assets\example-wordpress-article-with-pictures-example-screenshot.png
The Markdown keeps the public WordPress Media Library URL in the article body so the image still loads after the source is synced back into WordPress:
--- title: "Example WordPress Article with Pictures" slug: "example-wordpress-article-with-pictures" date: "2026-06-23" status: "draft" categories: - WordPress - Media Library tags: - wordpress - images - media library --- # Example WordPress Article with Pictures This article was originally written visually in WordPress and included screenshots. The Markdown source keeps the article text and public image reference. ## What It Does - Keeps the article text in `.posts`. - Copies a source copy of the screenshot into `.posts\2026.06.23\assets` when the file exists locally. - Keeps the WordPress Media Library URL in the Markdown image line. ## Screenshot 
If you need to import a new picture before writing the visual post, import it first:
wp --path="C:\inetpub\sites\example.com" media import .\.posts\2026.06.23\assets\example-screenshot.png --title="Example screenshot"
Do not leave public article images pointing at .posts\2026.06.23\assets\example-screenshot.png, because that path exists only in the source repository and will not load for visitors.
Sync New Posts
From the source repository root, run the helper with an absolute WordPress install path:
Set-Location C:\DevOps\DEV-Site wp --path="C:\inetpub\sites\example.com" eval-file .\.codex\scripts\sync-posts-from-posts.php
This creates posts that do not already exist. Existing posts with matching slugs are skipped by default so a normal sync does not overwrite live content unexpectedly.
If the PowerShell prompt is inside .releases, use a parent-relative helper path and keep the same absolute WordPress path:
Set-Location C:\DevOps\DEV-Site\.releases wp --path="C:\inetpub\sites\example.com" eval-file ..\.codex\scripts\sync-posts-from-posts.php
Overwrite Existing Posts
Use overwrite mode when a source article needs to replace the current WordPress content:
$env:JP_SYNC_OVERWRITE='1' wp --path="C:\inetpub\sites\example.com" eval-file .\.codex\scripts\sync-posts-from-posts.php Remove-Item Env:\JP_SYNC_OVERWRITE
Overwrite mode should update the title, slug, content, categories, and tags while keeping the existing WordPress publish date. That keeps refreshed articles from moving to the top of date-based archives unless you intentionally change the date in WordPress.
Posts with Pictures
Image-based articles need one extra review step because Markdown source files and WordPress media files are different assets. Keep source images near the article, such as .posts\2026.06.23\assets\example-screenshot.png, so the draft and its pictures travel together.
Before publishing, upload the images to the WordPress Media Library or import them with WP-CLI:
wp --path="C:\inetpub\sites\example.com" media import .\.posts\2026.06.23\assets\example-screenshot.png --post_id=123 --title="Example screenshot"
After import, use the public Media Library URL in the post content, not a local filesystem path. Review the post preview and confirm that each image loads, has useful alt text, fits within the article width, and points to wp-content/uploads or another intentional public media location.
Review in wp-admin
After the sync finishes, open wp-admin and check:
- The post title and slug match the source article.
- Categories and tags are correct.
- Code blocks render with the site's syntax highlighter.
- Images load from public WordPress media URLs.
- The publish date is correct.
- There are no duplicate posts with the same slug.
WP-CLI Setup
If wp is not available in the terminal yet, install WP-CLI first. The setup steps and basic command examples are covered in Install WP-CLI on Windows and Run Basic WordPress Commands.