<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Advanced WP-CLI Techniques Archives - WP-CLI Mastery</title>
	<atom:link href="https://wpclimastery.com/blog/category/advanced-wp-cli-techniques/feed/" rel="self" type="application/rss+xml" />
	<link>https://wpclimastery.com/blog/category/advanced-wp-cli-techniques/</link>
	<description>Automate WordPress Like a DevOps Pro.</description>
	<lastBuildDate>Mon, 24 Nov 2025 11:16:47 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://wpclimastery.com/wp-content/uploads/2025/11/cropped-favicon-32x32.webp</url>
	<title>Advanced WP-CLI Techniques Archives - WP-CLI Mastery</title>
	<link>https://wpclimastery.com/blog/category/advanced-wp-cli-techniques/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>WP-CLI Search-Replace: Safe Database Modifications for WordPress Migration</title>
		<link>https://wpclimastery.com/blog/wp-cli-search-replace-safe-database-modifications-for-wordpress-migration/</link>
		
		<dc:creator><![CDATA[Krasen]]></dc:creator>
		<pubDate>Sun, 25 Jan 2026 09:00:00 +0000</pubDate>
				<category><![CDATA[Advanced WP-CLI Techniques]]></category>
		<category><![CDATA[database search replace]]></category>
		<category><![CDATA[wordpress database migration]]></category>
		<category><![CDATA[wordpress url change]]></category>
		<category><![CDATA[wp-cli db]]></category>
		<category><![CDATA[wp-cli search-replace]]></category>
		<guid isPermaLink="false">https://wpclimastery.com/?p=138</guid>

					<description><![CDATA[<p>Changing URLs in WordPress databases manually destroys serialized data, breaks widget settings, corrupts option values, and leaves your site unusable. MySQL find-replace queries seem simple until you realize WordPress stores...</p>
<p>The post <a href="https://wpclimastery.com/blog/wp-cli-search-replace-safe-database-modifications-for-wordpress-migration/">WP-CLI Search-Replace: Safe Database Modifications for WordPress Migration</a> appeared first on <a href="https://wpclimastery.com">WP-CLI Mastery</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Changing URLs in WordPress databases manually destroys serialized data, breaks widget settings, corrupts option values, and leaves your site unusable. MySQL find-replace queries seem simple until you realize WordPress stores array lengths in serialized strings—one wrong replacement breaks everything.</p>



<p>WP-CLI’s <code>search-replace</code> command handles serialized data automatically, validates replacements before executing, and shows exactly what changed. It’s the only safe way to modify WordPress database content at scale.</p>



<p>In this guide, you’ll master WP-CLI search-replace for site migrations, domain changes, protocol updates, and any database modification requiring precision and safety.</p>



<h3 class="wp-block-heading" id="why-search-replace">Why WP-CLI Search-Replace is Critical</h3>



<p><a href="https://www.php.net/manual/en/function.serialize.php">WordPress serialized data</a> contains length values that break when modified with simple find-replace operations.</p>



<h4 class="wp-block-heading" id="problems-with-manual-database-search-replace">Problems with Manual Database Search-Replace</h4>



<p><strong>Serialized data corruption</strong>: Direct MySQL replacement breaks array and object serialization.</p>



<p><strong>Partial replacements</strong>: URLs in JSON, meta fields, and options get missed.</p>



<p><strong>No validation</strong>: Can’t preview changes before executing them.</p>



<p><strong>No reporting</strong>: Don’t know what was changed or how many replacements occurred.</p>



<p><strong>Irreversible</strong>: Once executed, can’t undo without restoring backups.</p>



<h4 class="wp-block-heading" id="wp-cli-search-replace-advantages">WP-CLI Search-Replace Advantages</h4>



<p><strong>Serialization-safe</strong>: Automatically recalculates lengths in serialized strings.</p>



<p><strong>Dry-run mode</strong>: Preview all changes before executing them.</p>



<p><strong>Comprehensive</strong>: Searches all tables, columns, and data types.</p>



<p><strong>Detailed reporting</strong>: Shows exactly what will change and where.</p>



<p><strong>Table-specific</strong>: Target specific tables or exclude sensitive data.</p>



<p>According to <a href="https://wpengine.com/">WordPress migration studies</a>, 80% of failed migrations are due to incorrect URL replacement breaking serialized data.</p>



<h3 class="wp-block-heading" id="fundamentals">Search-Replace Fundamentals</h3>



<p>Master basic search-replace operations safely.</p>



<h4 class="wp-block-heading" id="basic-search-replace-syntax">Basic Search-Replace Syntax</h4>



<div class="sourceCode" id="cb1">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="co"># Basic replacement</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old-text'</span> <span class="st">'new-text'</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true"></a></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true"></a><span class="co"># Replace in specific tables</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> wp_posts wp_postmeta</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true"></a></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true"></a><span class="co"># Replace in all tables (dangerous, use carefully)</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> --all-tables</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true"></a></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true"></a><span class="co"># Skip specific columns</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> --skip-columns=guid</span></code></pre>
</div>



<h4 class="wp-block-heading" id="always-use-dry-run-first">Always Use Dry-Run First</h4>



<div class="sourceCode" id="cb2">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true"></a><span class="co"># Preview changes without executing (CRITICAL!)</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'oldsite.com'</span> <span class="st">'newsite.com'</span> --dry-run</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true"></a><span class="co"># Dry-run with detailed report</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'http://'</span> <span class="st">'https://'</span> --dry-run --report</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true"></a></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true"></a><span class="co"># See exact replacement count</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> --dry-run --report --precise</span></code></pre>
</div>



<p><strong>Critical Rule</strong>: NEVER run search-replace without <code>--dry-run</code> first. Always preview changes.</p>



<h4 class="wp-block-heading" id="understanding-serialized-data">Understanding Serialized Data</h4>



<div class="sourceCode" id="cb3">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true"></a><span class="co"># WordPress serialized data example:</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true"></a><span class="co"># a:3:{s:4:"name";s:4:"John";s:3:"age";i:30;}</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true"></a><span class="co">#      ^ length     ^ length</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true"></a></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true"></a><span class="co"># Direct MySQL replacement breaks this:</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true"></a><span class="co"># UPDATE wp_options SET option_value =</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true"></a><span class="co"># REPLACE(option_value, 'John', 'Jonathan');</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true"></a><span class="co"># Result: a:3:{s:4:"name";s:8:"Jonathan";...}</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true"></a><span class="co">#                        ^ WRONG! Should be s:8</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true"></a></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true"></a><span class="co"># WP-CLI handles this automatically:</span></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'John'</span> <span class="st">'Jonathan'</span> wp_options</span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true"></a></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true"></a><span class="co"># WP-CLI recalculates: s:4 becomes s:8 automatically</span></span></code></pre>
</div>



<p>Learn more about <a href="https://developer.wordpress.org/cli/commands/search-replace/">WP-CLI search-replace documentation</a>.</p>



<h3 class="wp-block-heading" id="use-cases">Common Search-Replace Use Cases</h3>



<p>Handle the most frequent WordPress database modification scenarios.</p>



<h4 class="wp-block-heading" id="domain-migration">Domain Migration</h4>



<div class="sourceCode" id="cb4">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true"></a><span class="co">#!/bin/bash</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true"></a><span class="co"># migrate-domain.sh - Complete domain change</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true"></a></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true"></a><span class="va">OLD_DOMAIN=</span><span class="st">"oldsite.com"</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true"></a><span class="va">NEW_DOMAIN=</span><span class="st">"newsite.com"</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true"></a></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true"></a><span class="co"># Backup first (ALWAYS!)</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true"></a><span class="ex">wp</span> db export backup-before-domain-change.sql.gz</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true"></a></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true"></a><span class="co"># Dry-run to preview</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true"></a><span class="bu">echo</span> <span class="st">"Previewing changes..."</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">"https://</span><span class="va">$OLD_DOMAIN</span><span class="st">"</span> <span class="st">"https://</span><span class="va">$NEW_DOMAIN</span><span class="st">"</span> --dry-run --report</span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true"></a></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true"></a><span class="co"># Ask for confirmation</span></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true"></a><span class="bu">read</span> -p <span class="st">"Proceed with replacement? (y/n) "</span> -n 1 -r</span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true"></a><span class="bu">echo</span></span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true"></a><span class="kw">if [[</span> <span class="ot">!</span> <span class="va">$REPLY</span> =~ ^[Yy]$<span class="kw"> ]]</span>; <span class="kw">then</span></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true"></a>    <span class="bu">echo</span> <span class="st">"Operation cancelled"</span></span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true"></a>    <span class="bu">exit</span> 0</span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true"></a><span class="kw">fi</span></span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true"></a></span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true"></a><span class="co"># Execute replacements</span></span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true"></a><span class="bu">echo</span> <span class="st">"Executing replacements..."</span></span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true"></a></span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true"></a><span class="co"># Replace full URLs</span></span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">"https://</span><span class="va">$OLD_DOMAIN</span><span class="st">"</span> <span class="st">"https://</span><span class="va">$NEW_DOMAIN</span><span class="st">"</span></span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">"http://</span><span class="va">$OLD_DOMAIN</span><span class="st">"</span> <span class="st">"https://</span><span class="va">$NEW_DOMAIN</span><span class="st">"</span></span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true"></a></span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true"></a><span class="co"># Replace protocol-relative URLs</span></span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">"//</span><span class="va">$OLD_DOMAIN</span><span class="st">"</span> <span class="st">"//</span><span class="va">$NEW_DOMAIN</span><span class="st">"</span></span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true"></a></span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true"></a><span class="co"># Update WordPress options directly</span></span>
<span id="cb4-33"><a href="#cb4-33" aria-hidden="true"></a><span class="ex">wp</span> option update home <span class="st">"https://</span><span class="va">$NEW_DOMAIN</span><span class="st">"</span></span>
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true"></a><span class="ex">wp</span> option update siteurl <span class="st">"https://</span><span class="va">$NEW_DOMAIN</span><span class="st">"</span></span>
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true"></a></span>
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true"></a><span class="co"># Flush caches</span></span>
<span id="cb4-37"><a href="#cb4-37" aria-hidden="true"></a><span class="ex">wp</span> cache flush</span>
<span id="cb4-38"><a href="#cb4-38" aria-hidden="true"></a><span class="ex">wp</span> rewrite flush</span>
<span id="cb4-39"><a href="#cb4-39" aria-hidden="true"></a></span>
<span id="cb4-40"><a href="#cb4-40" aria-hidden="true"></a><span class="bu">echo</span> <span class="st">"✓ Domain migration complete"</span></span></code></pre>
</div>



<h4 class="wp-block-heading" id="http-to-https-migration">HTTP to HTTPS Migration</h4>



<div class="sourceCode" id="cb5">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true"></a><span class="co">#!/bin/bash</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true"></a><span class="co"># migrate-to-https.sh</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true"></a></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true"></a><span class="va">DOMAIN=</span><span class="st">"example.com"</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true"></a></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true"></a><span class="co"># Backup</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true"></a><span class="ex">wp</span> db export backup-before-https.sql.gz</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true"></a></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true"></a><span class="co"># Dry-run</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">"http://</span><span class="va">$DOMAIN</span><span class="st">"</span> <span class="st">"https://</span><span class="va">$DOMAIN</span><span class="st">"</span> --dry-run</span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true"></a></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true"></a><span class="co"># Execute</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">"http://</span><span class="va">$DOMAIN</span><span class="st">"</span> <span class="st">"https://</span><span class="va">$DOMAIN</span><span class="st">"</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true"></a></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true"></a><span class="co"># Update options</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true"></a><span class="ex">wp</span> option update home <span class="st">"https://</span><span class="va">$DOMAIN</span><span class="st">"</span></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true"></a><span class="ex">wp</span> option update siteurl <span class="st">"https://</span><span class="va">$DOMAIN</span><span class="st">"</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true"></a></span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true"></a><span class="bu">echo</span> <span class="st">"✓ Migrated to HTTPS"</span></span></code></pre>
</div>



<p><strong>Use Case</strong>: Essential for SEO and security—Google requires HTTPS for ranking.</p>



<h4 class="wp-block-heading" id="path-changes-subdirectory-moves">Path Changes (Subdirectory Moves)</h4>



<div class="sourceCode" id="cb6">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true"></a><span class="co"># Move from subdirectory to root</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'https://example.com/blog'</span> <span class="st">'https://example.com'</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true"></a></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true"></a><span class="co"># Move from root to subdirectory</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'https://example.com'</span> <span class="st">'https://example.com/wordpress'</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true"></a></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true"></a><span class="co"># Move between subdirectories</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'https://example.com/old'</span> <span class="st">'https://example.com/new'</span></span></code></pre>
</div>



<h4 class="wp-block-heading" id="content-text-replacement">Content Text Replacement</h4>



<div class="sourceCode" id="cb7">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true"></a><span class="co"># Replace company name in all content</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'Old Company LLC'</span> <span class="st">'New Company Inc'</span> wp_posts</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true"></a></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true"></a><span class="co"># Fix common typos across site</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'recieve'</span> <span class="st">'receive'</span> wp_posts wp_postmeta</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true"></a></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true"></a><span class="co"># Update email addresses</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'contact@oldcompany.com'</span> <span class="st">'contact@newcompany.com'</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true"></a></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true"></a><span class="co"># Replace shortcodes</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'[old_shortcode]'</span> <span class="st">'[new_shortcode]'</span> wp_posts</span></code></pre>
</div>



<h3 class="wp-block-heading" id="advanced-techniques">Advanced Search-Replace Techniques</h3>



<p>Handle complex scenarios with precision.</p>



<h4 class="wp-block-heading" id="regex-pattern-replacement">Regex Pattern Replacement</h4>



<div class="sourceCode" id="cb8">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true"></a><span class="co"># Enable regex mode</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'pattern'</span> <span class="st">'replacement'</span> --regex</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true"></a></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true"></a><span class="co"># Replace all HTTP/HTTPS variations</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'https?://oldsite\.com'</span> <span class="st">'https://newsite.com'</span> --regex</span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true"></a></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true"></a><span class="co"># Remove URL parameters</span></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'\?utm_[^\s"]+'</span> <span class="st">''</span> --regex wp_posts</span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true"></a></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true"></a><span class="co"># Fix malformed URLs</span></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'http://http://'</span> <span class="st">'http://'</span> --regex</span></code></pre>
</div>



<p><strong>Warning</strong>: Test regex patterns thoroughly with <code>--dry-run</code>. Incorrect patterns cause unexpected replacements.</p>



<h4 class="wp-block-heading" id="table-specific-operations">Table-Specific Operations</h4>



<div class="sourceCode" id="cb9">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true"></a><span class="co"># Replace in posts and metadata only</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> wp_posts wp_postmeta</span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true"></a></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true"></a><span class="co"># Replace in all tables except options</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> --all-tables-with-prefix --skip-tables=wp_options</span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true"></a></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true"></a><span class="co"># List all tables first</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true"></a><span class="ex">wp</span> db tables</span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true"></a></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true"></a><span class="co"># Replace in custom tables</span></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> wp_custom_table</span></code></pre>
</div>



<h4 class="wp-block-heading" id="export-replacement-report">Export Replacement Report</h4>



<div class="sourceCode" id="cb10">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true"></a><span class="co"># Generate CSV report of all changes</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> --export=changes.csv</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true"></a></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true"></a><span class="co"># Generate report with dry-run</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> --dry-run --report <span class="op">&gt;</span> replacement-report.txt</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true"></a></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true"></a><span class="co"># Count replacements</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true"></a><span class="va">REPLACEMENTS=$(</span><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> --dry-run --format=count<span class="va">)</span></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true"></a><span class="bu">echo</span> <span class="st">"Will replace </span><span class="va">$REPLACEMENTS</span><span class="st"> occurrences"</span></span></code></pre>
</div>



<p>Learn about <a href="https://wordpress.org/support/article/database-description/">WordPress database tables</a> structure.</p>



<h4 class="wp-block-heading" id="skip-sensitive-columns">Skip Sensitive Columns</h4>



<div class="sourceCode" id="cb11">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true"></a><span class="co"># Don't modify GUIDs (recommended)</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> --skip-columns=guid</span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true"></a></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true"></a><span class="co"># Skip multiple columns</span></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> --skip-columns=guid,post_date</span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true"></a></span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true"></a><span class="co"># Skip user passwords and meta</span></span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> wp_users --skip-columns=user_pass</span></code></pre>
</div>



<p><strong>Best Practice</strong>: Always skip <code>guid</code> column—changing GUIDs breaks feed readers and external references.</p>



<h3 class="wp-block-heading" id="safe-migration">Safe Migration Workflow</h3>



<p>Complete migration process with validation and rollback capability.</p>



<h4 class="wp-block-heading" id="complete-migration-script">Complete Migration Script</h4>



<div class="sourceCode" id="cb12">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true"></a><span class="co">#!/bin/bash</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true"></a><span class="kw">set</span> <span class="ex">-euo</span> pipefail</span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true"></a></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true"></a><span class="co"># Configuration</span></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true"></a><span class="va">SOURCE_URL=</span><span class="st">"https://staging.example.com"</span></span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true"></a><span class="va">TARGET_URL=</span><span class="st">"https://example.com"</span></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true"></a><span class="va">BACKUP_DIR=</span><span class="st">"/backups"</span></span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true"></a><span class="va">LOG_FILE=</span><span class="st">"/var/log/migration.log"</span></span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true"></a></span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true"></a><span class="fu">log()</span> <span class="kw">{</span></span>
<span id="cb12-11"><a href="#cb12-11" aria-hidden="true"></a>    <span class="bu">echo</span> <span class="st">"[</span><span class="va">$(</span><span class="fu">date</span> <span class="st">'+%Y-%m-%d %H:%M:%S'</span><span class="va">)</span><span class="st">] </span><span class="va">$@</span><span class="st">"</span> <span class="kw">|</span> <span class="fu">tee</span> -a <span class="st">"</span><span class="va">$LOG_FILE</span><span class="st">"</span></span>
<span id="cb12-12"><a href="#cb12-12" aria-hidden="true"></a><span class="kw">}</span></span>
<span id="cb12-13"><a href="#cb12-13" aria-hidden="true"></a></span>
<span id="cb12-14"><a href="#cb12-14" aria-hidden="true"></a><span class="ex">log</span> <span class="st">"=== WordPress Migration Started ==="</span></span>
<span id="cb12-15"><a href="#cb12-15" aria-hidden="true"></a></span>
<span id="cb12-16"><a href="#cb12-16" aria-hidden="true"></a><span class="co"># Validate WordPress</span></span>
<span id="cb12-17"><a href="#cb12-17" aria-hidden="true"></a><span class="kw">if</span> ! <span class="ex">wp</span> core is-installed<span class="kw">;</span> <span class="kw">then</span></span>
<span id="cb12-18"><a href="#cb12-18" aria-hidden="true"></a>    <span class="ex">log</span> <span class="st">"ERROR: WordPress not installed"</span></span>
<span id="cb12-19"><a href="#cb12-19" aria-hidden="true"></a>    <span class="bu">exit</span> 1</span>
<span id="cb12-20"><a href="#cb12-20" aria-hidden="true"></a><span class="kw">fi</span></span>
<span id="cb12-21"><a href="#cb12-21" aria-hidden="true"></a></span>
<span id="cb12-22"><a href="#cb12-22" aria-hidden="true"></a><span class="co"># Create backup</span></span>
<span id="cb12-23"><a href="#cb12-23" aria-hidden="true"></a><span class="ex">log</span> <span class="st">"Creating backup..."</span></span>
<span id="cb12-24"><a href="#cb12-24" aria-hidden="true"></a><span class="va">BACKUP_FILE=</span><span class="st">"</span><span class="va">$BACKUP_DIR</span><span class="st">/pre-migration-</span><span class="va">$(</span><span class="fu">date</span> +%Y%m%d-%H%M%S<span class="va">)</span><span class="st">.sql.gz"</span></span>
<span id="cb12-25"><a href="#cb12-25" aria-hidden="true"></a><span class="ex">wp</span> db export <span class="st">"</span><span class="va">$BACKUP_FILE</span><span class="st">"</span></span>
<span id="cb12-26"><a href="#cb12-26" aria-hidden="true"></a></span>
<span id="cb12-27"><a href="#cb12-27" aria-hidden="true"></a><span class="va">BACKUP_SIZE=$(</span><span class="fu">stat</span> -c%s <span class="st">"</span><span class="va">$BACKUP_FILE</span><span class="st">"</span><span class="va">)</span></span>
<span id="cb12-28"><a href="#cb12-28" aria-hidden="true"></a><span class="kw">if</span><span class="bu"> [</span> <span class="st">"</span><span class="va">$BACKUP_SIZE</span><span class="st">"</span> <span class="ot">-lt</span> 1000<span class="bu"> ]</span>; <span class="kw">then</span></span>
<span id="cb12-29"><a href="#cb12-29" aria-hidden="true"></a>    <span class="ex">log</span> <span class="st">"ERROR: Backup too small, aborting"</span></span>
<span id="cb12-30"><a href="#cb12-30" aria-hidden="true"></a>    <span class="bu">exit</span> 1</span>
<span id="cb12-31"><a href="#cb12-31" aria-hidden="true"></a><span class="kw">fi</span></span>
<span id="cb12-32"><a href="#cb12-32" aria-hidden="true"></a><span class="ex">log</span> <span class="st">"✓ Backup created: </span><span class="va">$BACKUP_FILE</span><span class="st">"</span></span>
<span id="cb12-33"><a href="#cb12-33" aria-hidden="true"></a></span>
<span id="cb12-34"><a href="#cb12-34" aria-hidden="true"></a><span class="co"># Dry-run validation</span></span>
<span id="cb12-35"><a href="#cb12-35" aria-hidden="true"></a><span class="ex">log</span> <span class="st">"Validating replacements..."</span></span>
<span id="cb12-36"><a href="#cb12-36" aria-hidden="true"></a><span class="kw">if</span> ! <span class="ex">wp</span> search-replace <span class="st">"</span><span class="va">$SOURCE_URL</span><span class="st">"</span> <span class="st">"</span><span class="va">$TARGET_URL</span><span class="st">"</span> --dry-run --report <span class="op">&amp;&gt;</span> /dev/null<span class="kw">;</span> <span class="kw">then</span></span>
<span id="cb12-37"><a href="#cb12-37" aria-hidden="true"></a>    <span class="ex">log</span> <span class="st">"ERROR: Dry-run failed"</span></span>
<span id="cb12-38"><a href="#cb12-38" aria-hidden="true"></a>    <span class="bu">exit</span> 1</span>
<span id="cb12-39"><a href="#cb12-39" aria-hidden="true"></a><span class="kw">fi</span></span>
<span id="cb12-40"><a href="#cb12-40" aria-hidden="true"></a></span>
<span id="cb12-41"><a href="#cb12-41" aria-hidden="true"></a><span class="co"># Count expected replacements</span></span>
<span id="cb12-42"><a href="#cb12-42" aria-hidden="true"></a><span class="va">REPLACEMENTS=$(</span><span class="ex">wp</span> search-replace <span class="st">"</span><span class="va">$SOURCE_URL</span><span class="st">"</span> <span class="st">"</span><span class="va">$TARGET_URL</span><span class="st">"</span> --dry-run --report <span class="kw">|</span> <span class="fu">grep</span> -c <span class="st">"</span><span class="va">$SOURCE_URL</span><span class="st">"</span> <span class="kw">||</span> <span class="fu">true</span><span class="va">)</span></span>
<span id="cb12-43"><a href="#cb12-43" aria-hidden="true"></a><span class="ex">log</span> <span class="st">"Found </span><span class="va">$REPLACEMENTS</span><span class="st"> occurrences to replace"</span></span>
<span id="cb12-44"><a href="#cb12-44" aria-hidden="true"></a></span>
<span id="cb12-45"><a href="#cb12-45" aria-hidden="true"></a><span class="kw">if</span><span class="bu"> [</span> <span class="st">"</span><span class="va">$REPLACEMENTS</span><span class="st">"</span> <span class="ot">-eq</span> 0<span class="bu"> ]</span>; <span class="kw">then</span></span>
<span id="cb12-46"><a href="#cb12-46" aria-hidden="true"></a>    <span class="ex">log</span> <span class="st">"WARNING: No replacements found, check URLs"</span></span>
<span id="cb12-47"><a href="#cb12-47" aria-hidden="true"></a><span class="kw">fi</span></span>
<span id="cb12-48"><a href="#cb12-48" aria-hidden="true"></a></span>
<span id="cb12-49"><a href="#cb12-49" aria-hidden="true"></a><span class="co"># Execute replacement</span></span>
<span id="cb12-50"><a href="#cb12-50" aria-hidden="true"></a><span class="ex">log</span> <span class="st">"Executing search-replace..."</span></span>
<span id="cb12-51"><a href="#cb12-51" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">"</span><span class="va">$SOURCE_URL</span><span class="st">"</span> <span class="st">"</span><span class="va">$TARGET_URL</span><span class="st">"</span> --report</span>
<span id="cb12-52"><a href="#cb12-52" aria-hidden="true"></a></span>
<span id="cb12-53"><a href="#cb12-53" aria-hidden="true"></a><span class="co"># Also replace without protocol</span></span>
<span id="cb12-54"><a href="#cb12-54" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">"//staging.example.com"</span> <span class="st">"//example.com"</span></span>
<span id="cb12-55"><a href="#cb12-55" aria-hidden="true"></a></span>
<span id="cb12-56"><a href="#cb12-56" aria-hidden="true"></a><span class="co"># Update options directly</span></span>
<span id="cb12-57"><a href="#cb12-57" aria-hidden="true"></a><span class="ex">log</span> <span class="st">"Updating WordPress options..."</span></span>
<span id="cb12-58"><a href="#cb12-58" aria-hidden="true"></a><span class="ex">wp</span> option update home <span class="st">"</span><span class="va">$TARGET_URL</span><span class="st">"</span></span>
<span id="cb12-59"><a href="#cb12-59" aria-hidden="true"></a><span class="ex">wp</span> option update siteurl <span class="st">"</span><span class="va">$TARGET_URL</span><span class="st">"</span></span>
<span id="cb12-60"><a href="#cb12-60" aria-hidden="true"></a></span>
<span id="cb12-61"><a href="#cb12-61" aria-hidden="true"></a><span class="co"># Verify replacements</span></span>
<span id="cb12-62"><a href="#cb12-62" aria-hidden="true"></a><span class="ex">log</span> <span class="st">"Verifying replacements..."</span></span>
<span id="cb12-63"><a href="#cb12-63" aria-hidden="true"></a><span class="va">REMAINING=$(</span><span class="ex">wp</span> search-replace <span class="st">"</span><span class="va">$SOURCE_URL</span><span class="st">"</span> <span class="st">"</span><span class="va">$TARGET_URL</span><span class="st">"</span> --dry-run --report <span class="kw">|</span> <span class="fu">grep</span> -c <span class="st">"</span><span class="va">$SOURCE_URL</span><span class="st">"</span> <span class="kw">||</span> <span class="fu">true</span><span class="va">)</span></span>
<span id="cb12-64"><a href="#cb12-64" aria-hidden="true"></a></span>
<span id="cb12-65"><a href="#cb12-65" aria-hidden="true"></a><span class="kw">if</span><span class="bu"> [</span> <span class="st">"</span><span class="va">$REMAINING</span><span class="st">"</span> <span class="ot">-gt</span> 0<span class="bu"> ]</span>; <span class="kw">then</span></span>
<span id="cb12-66"><a href="#cb12-66" aria-hidden="true"></a>    <span class="ex">log</span> <span class="st">"WARNING: </span><span class="va">$REMAINING</span><span class="st"> occurrences still remain"</span></span>
<span id="cb12-67"><a href="#cb12-67" aria-hidden="true"></a><span class="kw">else</span></span>
<span id="cb12-68"><a href="#cb12-68" aria-hidden="true"></a>    <span class="ex">log</span> <span class="st">"✓ All occurrences replaced"</span></span>
<span id="cb12-69"><a href="#cb12-69" aria-hidden="true"></a><span class="kw">fi</span></span>
<span id="cb12-70"><a href="#cb12-70" aria-hidden="true"></a></span>
<span id="cb12-71"><a href="#cb12-71" aria-hidden="true"></a><span class="co"># Clear caches</span></span>
<span id="cb12-72"><a href="#cb12-72" aria-hidden="true"></a><span class="ex">wp</span> cache flush</span>
<span id="cb12-73"><a href="#cb12-73" aria-hidden="true"></a><span class="ex">wp</span> rewrite flush</span>
<span id="cb12-74"><a href="#cb12-74" aria-hidden="true"></a></span>
<span id="cb12-75"><a href="#cb12-75" aria-hidden="true"></a><span class="ex">log</span> <span class="st">"=== Migration Complete ==="</span></span>
<span id="cb12-76"><a href="#cb12-76" aria-hidden="true"></a><span class="ex">log</span> <span class="st">"Rollback backup: </span><span class="va">$BACKUP_FILE</span><span class="st">"</span></span></code></pre>
</div>



<h4 class="wp-block-heading" id="rollback-procedure">Rollback Procedure</h4>



<div class="sourceCode" id="cb13">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true"></a><span class="co">#!/bin/bash</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true"></a><span class="co"># rollback-migration.sh</span></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true"></a></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true"></a><span class="va">BACKUP_FILE=</span><span class="st">"</span><span class="va">$1</span><span class="st">"</span></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true"></a></span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true"></a><span class="kw">if</span><span class="bu"> [</span> <span class="ot">-z</span> <span class="st">"</span><span class="va">$BACKUP_FILE</span><span class="st">"</span><span class="bu"> ]</span>; <span class="kw">then</span></span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true"></a>    <span class="bu">echo</span> <span class="st">"Usage: </span><span class="va">$0</span><span class="st"> &lt;backup-file.sql.gz&gt;"</span></span>
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true"></a>    <span class="bu">exit</span> 1</span>
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true"></a><span class="kw">fi</span></span>
<span id="cb13-10"><a href="#cb13-10" aria-hidden="true"></a></span>
<span id="cb13-11"><a href="#cb13-11" aria-hidden="true"></a><span class="kw">if</span><span class="bu"> [</span> <span class="ot">!</span> <span class="ot">-f</span> <span class="st">"</span><span class="va">$BACKUP_FILE</span><span class="st">"</span><span class="bu"> ]</span>; <span class="kw">then</span></span>
<span id="cb13-12"><a href="#cb13-12" aria-hidden="true"></a>    <span class="bu">echo</span> <span class="st">"ERROR: Backup file not found: </span><span class="va">$BACKUP_FILE</span><span class="st">"</span></span>
<span id="cb13-13"><a href="#cb13-13" aria-hidden="true"></a>    <span class="bu">exit</span> 1</span>
<span id="cb13-14"><a href="#cb13-14" aria-hidden="true"></a><span class="kw">fi</span></span>
<span id="cb13-15"><a href="#cb13-15" aria-hidden="true"></a></span>
<span id="cb13-16"><a href="#cb13-16" aria-hidden="true"></a><span class="bu">echo</span> <span class="st">"Rolling back to: </span><span class="va">$BACKUP_FILE</span><span class="st">"</span></span>
<span id="cb13-17"><a href="#cb13-17" aria-hidden="true"></a><span class="bu">read</span> -p <span class="st">"This will overwrite current database. Continue? (y/n) "</span> -n 1 -r</span>
<span id="cb13-18"><a href="#cb13-18" aria-hidden="true"></a><span class="bu">echo</span></span>
<span id="cb13-19"><a href="#cb13-19" aria-hidden="true"></a></span>
<span id="cb13-20"><a href="#cb13-20" aria-hidden="true"></a><span class="kw">if [[</span> <span class="ot">!</span> <span class="va">$REPLY</span> =~ ^[Yy]$<span class="kw"> ]]</span>; <span class="kw">then</span></span>
<span id="cb13-21"><a href="#cb13-21" aria-hidden="true"></a>    <span class="bu">echo</span> <span class="st">"Rollback cancelled"</span></span>
<span id="cb13-22"><a href="#cb13-22" aria-hidden="true"></a>    <span class="bu">exit</span> 0</span>
<span id="cb13-23"><a href="#cb13-23" aria-hidden="true"></a><span class="kw">fi</span></span>
<span id="cb13-24"><a href="#cb13-24" aria-hidden="true"></a></span>
<span id="cb13-25"><a href="#cb13-25" aria-hidden="true"></a><span class="co"># Restore database</span></span>
<span id="cb13-26"><a href="#cb13-26" aria-hidden="true"></a><span class="ex">wp</span> db import <span class="st">"</span><span class="va">$BACKUP_FILE</span><span class="st">"</span></span>
<span id="cb13-27"><a href="#cb13-27" aria-hidden="true"></a></span>
<span id="cb13-28"><a href="#cb13-28" aria-hidden="true"></a><span class="co"># Flush caches</span></span>
<span id="cb13-29"><a href="#cb13-29" aria-hidden="true"></a><span class="ex">wp</span> cache flush</span>
<span id="cb13-30"><a href="#cb13-30" aria-hidden="true"></a><span class="ex">wp</span> rewrite flush</span>
<span id="cb13-31"><a href="#cb13-31" aria-hidden="true"></a></span>
<span id="cb13-32"><a href="#cb13-32" aria-hidden="true"></a><span class="bu">echo</span> <span class="st">"✓ Rollback complete"</span></span></code></pre>
</div>



<h3 class="wp-block-heading" id="troubleshooting">Troubleshooting Search-Replace</h3>



<p>Fix common search-replace issues.</p>



<h4 class="wp-block-heading" id="urls-not-changing">URLs Not Changing</h4>



<div class="sourceCode" id="cb14">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true"></a><span class="co"># Check current URLs in database</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'oldsite.com'</span> <span class="st">'oldsite.com'</span> --dry-run --report <span class="kw">|</span> <span class="fu">head</span> -20</span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true"></a></span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true"></a><span class="co"># Try all URL variations</span></span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'http://oldsite.com'</span> <span class="st">'https://newsite.com'</span></span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'https://oldsite.com'</span> <span class="st">'https://newsite.com'</span></span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'http://www.oldsite.com'</span> <span class="st">'https://newsite.com'</span></span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'//oldsite.com'</span> <span class="st">'//newsite.com'</span></span>
<span id="cb14-9"><a href="#cb14-9" aria-hidden="true"></a></span>
<span id="cb14-10"><a href="#cb14-10" aria-hidden="true"></a><span class="co"># Force update options</span></span>
<span id="cb14-11"><a href="#cb14-11" aria-hidden="true"></a><span class="ex">wp</span> option update home <span class="st">'https://newsite.com'</span></span>
<span id="cb14-12"><a href="#cb14-12" aria-hidden="true"></a><span class="ex">wp</span> option update siteurl <span class="st">'https://newsite.com'</span></span></code></pre>
</div>



<h4 class="wp-block-heading" id="serialization-errors">Serialization Errors</h4>



<div class="sourceCode" id="cb15">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true"></a><span class="co"># Check for serialization issues</span></span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> --precise</span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true"></a></span>
<span id="cb15-4"><a href="#cb15-4" aria-hidden="true"></a><span class="co"># Skip problematic tables</span></span>
<span id="cb15-5"><a href="#cb15-5" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> --skip-tables=wp_options</span>
<span id="cb15-6"><a href="#cb15-6" aria-hidden="true"></a></span>
<span id="cb15-7"><a href="#cb15-7" aria-hidden="true"></a><span class="co"># Replace table by table</span></span>
<span id="cb15-8"><a href="#cb15-8" aria-hidden="true"></a><span class="kw">for</span> <span class="ex">TABLE</span> in <span class="va">$(</span><span class="ex">wp</span> db tables --format=csv<span class="va">)</span><span class="kw">;</span> <span class="kw">do</span></span>
<span id="cb15-9"><a href="#cb15-9" aria-hidden="true"></a>    <span class="bu">echo</span> <span class="st">"Processing: </span><span class="va">$TABLE</span><span class="st">"</span></span>
<span id="cb15-10"><a href="#cb15-10" aria-hidden="true"></a>    <span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> <span class="st">"</span><span class="va">$TABLE</span><span class="st">"</span> --dry-run</span>
<span id="cb15-11"><a href="#cb15-11" aria-hidden="true"></a><span class="kw">done</span></span></code></pre>
</div>



<h4 class="wp-block-heading" id="performance-issues">Performance Issues</h4>



<div class="sourceCode" id="cb16">
<pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true"></a><span class="co"># Large databases may timeout, increase PHP limits</span></span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true"></a><span class="ex">php</span> -d memory_limit=512M wp search-replace <span class="st">'old'</span> <span class="st">'new'</span></span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true"></a></span>
<span id="cb16-4"><a href="#cb16-4" aria-hidden="true"></a><span class="co"># Process tables individually for very large databases</span></span>
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> wp_posts</span>
<span id="cb16-6"><a href="#cb16-6" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> wp_postmeta</span>
<span id="cb16-7"><a href="#cb16-7" aria-hidden="true"></a><span class="ex">wp</span> search-replace <span class="st">'old'</span> <span class="st">'new'</span> wp_options</span></code></pre>
</div>



<h3 class="wp-block-heading" id="next-steps">Next Steps</h3>



<p>You now have professional WordPress search-replace skills for safe database migrations.</p>



<h4 class="wp-block-heading" id="recommended-learning-path">Recommended Learning Path</h4>



<p><strong>Week 1</strong>: Basic replacements</p>



<ul class="wp-block-list">
<li>Practice dry-run workflows</li>



<li>Test domain changes on staging</li>



<li>Learn serialization concepts</li>
</ul>



<p><strong>Week 2</strong>: Advanced techniques</p>



<ul class="wp-block-list">
<li>Master regex patterns</li>



<li>Handle complex migrations</li>



<li>Build verification scripts</li>
</ul>



<p><strong>Week 3</strong>: Production migrations</p>



<ul class="wp-block-list">
<li>Create complete migration workflows</li>



<li>Implement rollback procedures</li>



<li>Document migration playbooks</li>
</ul>



<p><strong>Week 4</strong>: Automation</p>



<ul class="wp-block-list">
<li>Build migration scripts</li>



<li>Add validation checks</li>



<li>Automate testing</li>
</ul>



<h4 class="wp-block-heading" id="advanced-topics">Advanced Topics</h4>



<ol class="wp-block-list">
<li><strong><a href="#">Multisite Search-Replace</a></strong> &#8211; Network-wide replacements</li>



<li><strong><a href="#">Large Database Migrations</a></strong> &#8211; Handle millions of records</li>



<li><strong><a href="#">Custom Table Migrations</a></strong> &#8211; Plugin-specific data</li>
</ol>



<h4 class="wp-block-heading" id="get-more-resources">Get More Resources</h4>



<p><strong><a href="#">Download migration scripts</a></strong> including:</p>



<ul class="wp-block-list">
<li>Complete migration automation</li>



<li>Rollback procedures</li>



<li>Validation checklists</li>
</ul>



<p><strong><a href="/#get-started">Join our email course</a></strong> for:</p>



<ul class="wp-block-list">
<li>Weekly WP-CLI tutorials</li>



<li>Migration best practices</li>



<li>Advanced database techniques</li>
</ul>



<h3 class="wp-block-heading" id="conclusion">Conclusion</h3>



<p>WP-CLI search-replace is the only safe way to modify WordPress database content, handling serialized data automatically while giving you complete control and visibility.</p>



<p>What we covered:</p>



<p>✅ Search-replace fundamentals with dry-run validation <br>✅ Common use cases (domain changes, HTTPS migration, paths) <br>✅ Advanced techniques (regex, table-specific, reporting) <br>✅ Safe migration workflows with backups and rollback <br>✅ Troubleshooting common search-replace issues</p>



<p>Master these techniques, and you’ll handle WordPress migrations and database modifications with confidence—whether changing domains, updating URLs, or migrating content.</p>



<p><strong>Ready for more?</strong> Learn <a href="#">WordPress database optimization</a> or <a href="#">advanced WP-CLI automation</a>.</p>



<p><strong>Questions about WP-CLI search-replace?</strong> Drop a comment below!</p>



<p><strong>Found this helpful?</strong> Share with other WordPress developers.</p>
<p>The post <a href="https://wpclimastery.com/blog/wp-cli-search-replace-safe-database-modifications-for-wordpress-migration/">WP-CLI Search-Replace: Safe Database Modifications for WordPress Migration</a> appeared first on <a href="https://wpclimastery.com">WP-CLI Mastery</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to Create Custom WP-CLI Commands: Developer&#8217;s Guide</title>
		<link>https://wpclimastery.com/blog/how-to-create-custom-wp-cli-commands-developers-guide/</link>
		
		<dc:creator><![CDATA[Krasen]]></dc:creator>
		<pubDate>Tue, 25 Nov 2025 09:00:00 +0000</pubDate>
				<category><![CDATA[Advanced WP-CLI Techniques]]></category>
		<category><![CDATA[custom wp-cli commands]]></category>
		<category><![CDATA[extend wp-cli]]></category>
		<category><![CDATA[wordpress cli development]]></category>
		<category><![CDATA[wp-cli package]]></category>
		<category><![CDATA[wpcli development]]></category>
		<guid isPermaLink="false">https://wpclimastery.com/?p=13</guid>

					<description><![CDATA[<p>WP-CLI comes with hundreds of built-in commands for managing WordPress. But what if you need a command specific to your workflow, plugin, or client requirements? Creating custom WP-CLI commands lets...</p>
<p>The post <a href="https://wpclimastery.com/blog/how-to-create-custom-wp-cli-commands-developers-guide/">How to Create Custom WP-CLI Commands: Developer&#8217;s Guide</a> appeared first on <a href="https://wpclimastery.com">WP-CLI Mastery</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>WP-CLI comes with hundreds of built-in commands for managing WordPress. But what if you need a command specific to your workflow, plugin, or client requirements? Creating custom WP-CLI commands lets you extend the CLI with your own functionality.</p>



<p>In this advanced guide, you&#8217;ll learn to create production-ready custom WP-CLI commands from scratch. You&#8217;ll understand command structure, parameter handling, the WP_CLI API, and how to package your commands for distribution.</p>



<p>By the end, you&#8217;ll have the skills to build professional CLI tools that make WordPress management even more powerful.</p>



<h3 class="wp-block-heading" id="why-create-custom-wp-cli-commands-why-custom-commands">Why Create Custom WP-CLI Commands?</h3>



<h4 class="wp-block-heading" id="use-cases-for-custom-commands">Use Cases for Custom Commands</h4>



<p><strong>Plugin-Specific Operations</strong></p>



<ul class="wp-block-list">
<li>Flush custom caches</li>



<li>Run migrations</li>



<li>Import/export plugin data</li>



<li>Administrative tasks</li>
</ul>



<p><strong>Agency/Enterprise Workflows</strong></p>



<ul class="wp-block-list">
<li>Client onboarding automation</li>



<li>Standardized site audits</li>



<li>Custom deployment tasks</li>



<li>Compliance reporting</li>
</ul>



<p><strong>Development Tools</strong></p>



<ul class="wp-block-list">
<li>Generate boilerplate code</li>



<li>Database seeding</li>



<li>Testing helpers</li>



<li>Performance profiling</li>
</ul>



<h4 class="wp-block-heading" id="benefits-over-manual-scripts">Benefits Over Manual Scripts</h4>



<p><strong>Integration with WP-CLI Ecosystem</strong></p>



<ul class="wp-block-list">
<li>Automatic WordPress context loading</li>



<li>Access to all WordPress functions</li>



<li>Works with WP-CLI packages</li>



<li>Follows CLI conventions</li>
</ul>



<p><strong>Professional API</strong></p>



<ul class="wp-block-list">
<li><code>WP_CLI::success()</code>,&nbsp;<code>WP_CLI::error()</code>,&nbsp;<code>WP_CLI::warning()</code></li>



<li>Built-in progress bars</li>



<li>Table formatting</li>



<li>Color output</li>
</ul>



<p><strong>Distribution</strong></p>



<ul class="wp-block-list">
<li>Share via Composer packages</li>



<li>Install with&nbsp;<code>wp package install</code></li>



<li>Version management</li>
</ul>



<h3 class="wp-block-heading" id="prerequisites-prerequisites">Prerequisites</h3>



<p>Before creating custom commands, you should have:</p>



<ul class="wp-block-list">
<li><strong>WP-CLI installed</strong>&nbsp;&#8211;&nbsp;<a href="#">Installation guide</a></li>



<li><strong>PHP knowledge</strong>&nbsp;&#8211; Object-oriented PHP basics</li>



<li><strong>WordPress familiarity</strong>&nbsp;&#8211; Understanding of WordPress hooks and functions</li>



<li><strong>Bash fundamentals</strong>&nbsp;&#8211;&nbsp;<a href="#">Bash scripting guide</a></li>
</ul>



<h3 class="wp-block-heading" id="command-structure-basics-command-structure">Command Structure Basics</h3>



<h4 class="wp-block-heading" id="command-anatomy">Command Anatomy</h4>



<p>WP-CLI commands follow this structure:</p>



<pre class="wp-block-code"><code>wp &lt;command&gt; &lt;subcommand&gt; &lt;arguments&gt; --&lt;flags&gt;
</code></pre>



<p><strong>Examples:</strong></p>



<pre class="wp-block-code"><code>wp plugin install woocommerce
<em>#  ^^^^^^ ^^^^^^^ ^^^^^^^^^^^</em>
<em>#  cmd    subcmd  argument</em>

wp post list --post_type=page --format=table
<em>#  ^^^^ ^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^</em>
<em>#  cmd  sub  flag=value        flag=value</em>
</code></pre>



<h4 class="wp-block-heading" id="command-registration">Command Registration</h4>



<p>Commands are registered using&nbsp;<code>WP_CLI::add_command()</code>:</p>



<pre class="wp-block-code"><code>WP_CLI::add_command('hello', 'My_Hello_Command');
</code></pre>



<p>This creates:&nbsp;<code>wp hello</code></p>



<h4 class="wp-block-heading" id="class-based-vs-function-based">Class-Based vs Function-Based</h4>



<p><strong>Class-based (recommended):</strong></p>



<pre class="wp-block-code"><code>class My_Command {
		public function greet($args, $assoc_args) {
				WP_CLI::success("Hello!");
		}
}

WP_CLI::add_command('mycommand greet', 'My_Command');
</code></pre>



<p><strong>Function-based:</strong></p>



<pre class="wp-block-code"><code>function my_greet_command($args, $assoc_args) {
		WP_CLI::success("Hello!");
}

WP_CLI::add_command('greet', 'my_greet_command');
</code></pre>



<p><strong>Best practice</strong>: Use classes for better organization and multiple subcommands.</p>



<h3 class="wp-block-heading" id="your-first-custom-command-first-command">Your First Custom Commandnd}</h3>



<p>Let&#8217;s create a simple custom command step-by-step.</p>



<h4 class="wp-block-heading" id="step-1-create-plugin-file">Step 1: Create Plugin File</h4>



<p>Create a plugin to hold your command:</p>



<pre class="wp-block-code"><code>&lt;?php
<em>/**
 * Plugin Name: My WP-CLI Commands
 * Description: Custom WP-CLI commands
 * Version: 1.0.0
 */</em>

<em>// Don't execute in non-CLI context</em>
if (!defined('WP_CLI')) {
		return;
}

<em>/**
 * Hello World command
 */</em>
class Hello_Command {

		<em>/**
		 * Prints a greeting
		 *
		 * ## EXAMPLES
		 *
		 *     wp hello greet
		 *     wp hello greet --name=John
		 */</em>
		public function greet($args, $assoc_args) {
				$name = isset($assoc_args&#91;'name']) ? $assoc_args&#91;'name'] : 'World';

				WP_CLI::success("Hello, {$name}!");
		}
}

<em>// Register the command</em>
WP_CLI::add_command('hello', 'Hello_Command');
</code></pre>



<h4 class="wp-block-heading" id="step-2-install-plugin">Step 2: Install Plugin</h4>



<p>Save this as&nbsp;<code>wp-content/plugins/my-cli-commands/my-cli-commands.php</code></p>



<p>Activate it:</p>



<pre class="wp-block-code"><code>wp plugin activate my-cli-commands
</code></pre>



<h4 class="wp-block-heading" id="step-3-test-your-command">Step 3: Test Your Command</h4>



<pre class="wp-block-code"><code>wp hello greet
<em># Success: Hello, World!</em>

wp hello greet --name=John
<em># Success: Hello, John!</em>
</code></pre>



<p><strong>Congratulations!</strong>&nbsp;You&#8217;ve created your first custom WP-CLI command.</p>



<h4 class="wp-block-heading" id="step-4-view-help">Step 4: View Help</h4>



<p>WP-CLI automatically generates help documentation:</p>



<pre class="wp-block-code"><code>wp help hello greet
</code></pre>



<h3 class="wp-block-heading" id="using-the-wp_cli-class-wpcli-class">Using the WP_CLI Class</h3>



<p>The&nbsp;<code>WP_CLI</code>&nbsp;class provides powerful methods for CLI interaction.</p>



<h4 class="wp-block-heading" id="output-methods">Output Methods</h4>



<pre class="wp-block-code"><code><em>// Success message (green)</em>
WP_CLI::success("Operation completed!");

<em>// Error message (red) - exits script</em>
WP_CLI::error("Something went wrong!");

<em>// Warning message (yellow)</em>
WP_CLI::warning("Proceeding with caution...");

<em>// Info message (no color)</em>
WP_CLI::log("Processing item 5 of 10");

<em>// Debug message (only shown with --debug)</em>
WP_CLI::debug("Variable value: " . $value);
</code></pre>



<p><strong>Example:</strong></p>



<pre class="wp-block-code"><code>public function process() {
		WP_CLI::log("Starting processing...");

		if (!file_exists($file)) {
				WP_CLI::warning("File not found, using defaults");
		}

		if ($error) {
				WP_CLI::error("Processing failed!");  <em>// Exits here</em>
		}

		WP_CLI::success("Processing complete!");
}
</code></pre>



<h4 class="wp-block-heading" id="progress-bars">Progress Bars</h4>



<p>For long-running operations:</p>



<pre class="wp-block-code"><code>public function import_posts($args) {
		$posts = range(1, 1000);

		$progress = \WP_CLI\Utils\make_progress_bar('Importing posts', count($posts));

		foreach ($posts as $post) {
				<em>// Do import work</em>
				sleep(0.01);  <em>// Simulate work</em>

				$progress-&gt;tick();
		}

		$progress-&gt;finish();
		WP_CLI::success("Imported " . count($posts) . " posts");
}
</code></pre>



<p><strong>Output:</strong></p>



<pre class="wp-block-code"><code>Importing posts  500/1000 &#91;==============&gt;              ] 50%
</code></pre>



<h4 class="wp-block-heading" id="confirmation-prompts">Confirmation Prompts</h4>



<p>Confirm destructive operations:</p>



<pre class="wp-block-code"><code>public function delete_all_posts() {
		WP_CLI::confirm("Are you sure you want to delete ALL posts?");

		<em>// User must type 'y' or 'yes' to continue</em>
		<em>// Otherwise, script exits</em>

		<em>// Proceed with deletion</em>
		WP_CLI::success("All posts deleted");
}
</code></pre>



<p><strong>Usage:</strong></p>



<pre class="wp-block-code"><code>wp mycommand delete-all-posts
<em># Are you sure you want to delete ALL posts? &#91;y/n]</em>
</code></pre>



<p><strong>Skip confirmation in scripts:</strong></p>



<pre class="wp-block-code"><code>wp mycommand delete-all-posts --yes
</code></pre>



<p>Learn more in the&nbsp;<a href="https://make.wordpress.org/cli/handbook/references/internal-api/">official WP_CLI class documentation</a>.</p>



<h3 class="wp-block-heading" id="parameter-handling-with-synopsis-parameters">Parameter Handling with Synopsis</h3>



<p>Synopsis defines your command&#8217;s parameters and flags.</p>



<h4 class="wp-block-heading" id="basic-synopsis">Basic Synopsis</h4>



<pre class="wp-block-code"><code><em>/**
 * Import data from file
 *
 * ## OPTIONS
 *
 * &lt;file&gt;
 * : Path to import file
 *
 * &#91;--format=&lt;format&gt;]
 * : Import format (json, csv)
 * ---
 * default: json
 * options:
 *   - json
 *   - csv
 * ---
 *
 * ## EXAMPLES
 *
 *     wp mycommand import data.json
 *     wp mycommand import data.csv --format=csv
 *
 * @when after_wp_load
 */</em>
public function import($args, $assoc_args) {
		list($file) = $args;  <em>// Required positional argument</em>

		$format = $assoc_args&#91;'format'];  <em>// Has default value</em>

		WP_CLI::log("Importing {$file} as {$format}");
}
</code></pre>



<h4 class="wp-block-heading" id="parameter-types">Parameter Types</h4>



<p><strong>Required positional argument:</strong></p>



<pre class="wp-block-code"><code><em>/**
 * &lt;file&gt;
 * : Path to file
 */</em>
</code></pre>



<p>Usage:&nbsp;<code>wp cmd import file.json</code></p>



<p><strong>Optional positional argument:</strong></p>



<pre class="wp-block-code"><code><em>/**
 * &#91;&lt;file&gt;]
 * : Path to file (optional)
 */</em>
</code></pre>



<p><strong>Required flag:</strong></p>



<pre class="wp-block-code"><code><em>/**
 * --user=&lt;id&gt;
 * : User ID (required)
 */</em>
</code></pre>



<p>Usage:&nbsp;<code>wp cmd process --user=1</code></p>



<p><strong>Optional flag with default:</strong></p>



<pre class="wp-block-code"><code><em>/**
 * &#91;--format=&lt;format&gt;]
 * : Output format
 * ---
 * default: table
 * options:
 *   - table
 *   - json
 *   - csv
 * ---
 */</em>
</code></pre>



<p><strong>Boolean flag:</strong></p>



<pre class="wp-block-code"><code><em>/**
 * &#91;--dry-run]
 * : Run without making changes
 */</em>
</code></pre>



<p>Usage:&nbsp;<code>wp cmd process --dry-run</code></p>



<p>Check in code:</p>



<pre class="wp-block-code"><code>$dry_run = isset($assoc_args&#91;'dry-run']) &amp;&amp; $assoc_args&#91;'dry-run'];
</code></pre>



<h4 class="wp-block-heading" id="accessing-parameters">Accessing Parameters</h4>



<pre class="wp-block-code"><code>public function command($args, $assoc_args) {
		<em>// Positional arguments (ordered)</em>
		$first_arg = isset($args&#91;0]) ? $args&#91;0] : '';
		$second_arg = isset($args&#91;1]) ? $args&#91;1] : '';

		<em>// Or use list()</em>
		list($file, $action) = $args;

		<em>// Associative arguments (flags)</em>
		$format = $assoc_args&#91;'format'];  <em>// No default, may not exist</em>
		$format = isset($assoc_args&#91;'format']) ? $assoc_args&#91;'format'] : 'table';

		<em>// Get with default helper</em>
		$format = \WP_CLI\Utils\get_flag_value($assoc_args, 'format', 'table');
}
</code></pre>



<h3 class="wp-block-heading" id="output-formatting-output-formatting">Output Formatting</h3>



<h4 class="wp-block-heading" id="table-format">Table Format</h4>



<p>Display data as tables:</p>



<pre class="wp-block-code"><code>public function list_users() {
		$users = get_users(array('number' =&gt; 10));

		$items = array();
		foreach ($users as $user) {
				$items&#91;] = array(
						'ID' =&gt; $user-&gt;ID,
						'Username' =&gt; $user-&gt;user_login,
						'Email' =&gt; $user-&gt;user_email,
						'Role' =&gt; implode(', ', $user-&gt;roles)
				);
		}

		\WP_CLI\Utils\format_items('table', $items, array('ID', 'Username', 'Email', 'Role'));
}
</code></pre>



<p><strong>Output:</strong></p>



<pre class="wp-block-code"><code>+----+----------+------------------+-------------+
| ID | Username | Email            | Role        |
+----+----------+------------------+-------------+
| 1  | admin    | admin@site.com   | administrator |
| 2  | editor   | editor@site.com  | editor      |
+----+----------+------------------+-------------+
</code></pre>



<h4 class="wp-block-heading" id="json-format">JSON Format</h4>



<pre class="wp-block-code"><code>\WP_CLI\Utils\format_items('json', $items, array('ID', 'Username', 'Email'));
</code></pre>



<p><strong>Output:</strong></p>



<pre class="wp-block-code"><code>&#91;
	{"ID":"1","Username":"admin","Email":"admin@site.com"},
	{"ID":"2","Username":"editor","Email":"editor@site.com"}
]
</code></pre>



<h4 class="wp-block-heading" id="csv-format">CSV Format</h4>



<pre class="wp-block-code"><code>\WP_CLI\Utils\format_items('csv', $items, array('ID', 'Username', 'Email'));
</code></pre>



<p><strong>Output:</strong></p>



<pre class="wp-block-code"><code>ID,Username,Email
1,admin,admin@site.com
2,editor,editor@site.com
</code></pre>



<h4 class="wp-block-heading" id="supporting-multiple-formats">Supporting Multiple Formats</h4>



<pre class="wp-block-code"><code><em>/**
 * &#91;--format=&lt;format&gt;]
 * : Output format
 * ---
 * default: table
 * options:
 *   - table
 *   - json
 *   - csv
 *   - count
 * ---
 */</em>
public function list($args, $assoc_args) {
		$format = \WP_CLI\Utils\get_flag_value($assoc_args, 'format', 'table');

		$items = $this-&gt;get_items();

		if ('count' === $format) {
				WP_CLI::log(count($items));
				return;
		}

		\WP_CLI\Utils\format_items($format, $items, array('ID', 'Title', 'Status'));
}
</code></pre>



<h3 class="wp-block-heading" id="error-handling-error-handling">Error Handling</h3>



<h4 class="wp-block-heading" id="validation">Validation</h4>



<p>Always validate parameters:</p>



<pre class="wp-block-code"><code>public function import($args, $assoc_args) {
		list($file) = $args;

		<em>// Check file exists</em>
		if (!file_exists($file)) {
				WP_CLI::error("File not found: {$file}");
		}

		<em>// Check file is readable</em>
		if (!is_readable($file)) {
				WP_CLI::error("File is not readable: {$file}");
		}

		<em>// Validate format</em>
		$format = $assoc_args&#91;'format'];
		$allowed = array('json', 'csv');
		if (!in_array($format, $allowed)) {
				WP_CLI::error("Invalid format: {$format}. Must be: " . implode(', ', $allowed));
		}

		<em>// Proceed with import...</em>
}
</code></pre>



<h4 class="wp-block-heading" id="try-catch">Try-Catch</h4>



<p>Handle exceptions gracefully:</p>



<pre class="wp-block-code"><code>public function process() {
		try {
				$this-&gt;do_risky_operation();
				WP_CLI::success("Operation completed");
		} catch (Exception $e) {
				WP_CLI::error("Operation failed: " . $e-&gt;getMessage());
		}
}
</code></pre>



<h4 class="wp-block-heading" id="exit-codes">Exit Codes</h4>



<p>Commands should return appropriate exit codes:</p>



<ul class="wp-block-list">
<li><code>0</code>&nbsp;&#8211; Success</li>



<li><code>1</code>&nbsp;&#8211; Error (automatic with&nbsp;<code>WP_CLI::error()</code>)</li>
</ul>



<h3 class="wp-block-heading" id="complete-example-site-audit-command-complete-example">Complete Example: Site Audit Command</h3>



<p>Let&#8217;s build a real-world command that audits a WordPress site.</p>



<pre class="wp-block-code"><code>&lt;?php
<em>/**
 * Plugin Name: Site Audit Command
 * Description: Custom WP-CLI command for site auditing
 * Version: 1.0.0
 */</em>

if (!defined('WP_CLI')) {
		return;
}

<em>/**
 * Performs comprehensive site audits
 */</em>
class Site_Audit_Command {

		<em>/**
		 * Run a complete site audit
		 *
		 * ## OPTIONS
		 *
		 * &#91;--format=&lt;format&gt;]
		 * : Output format
		 * ---
		 * default: table
		 * options:
		 *   - table
		 *   - json
		 * ---
		 *
		 * &#91;--save=&lt;file&gt;]
		 * : Save results to file
		 *
		 * ## EXAMPLES
		 *
		 *     wp site-audit run
		 *     wp site-audit run --format=json
		 *     wp site-audit run --save=audit-report.json
		 *
		 * @when after_wp_load
		 */</em>
		public function run($args, $assoc_args) {
				WP_CLI::log("Running site audit...");

				$results = array();

				<em>// WordPress version</em>
				$results&#91;] = $this-&gt;check_wordpress_version();

				<em>// Plugin updates</em>
				$results&#91;] = $this-&gt;check_plugin_updates();

				<em>// Theme updates</em>
				$results&#91;] = $this-&gt;check_theme_updates();

				<em>// Database size</em>
				$results&#91;] = $this-&gt;check_database_size();

				<em>// Upload directory size</em>
				$results&#91;] = $this-&gt;check_uploads_size();

				<em>// Orphaned data</em>
				$results&#91;] = $this-&gt;check_orphaned_data();

				<em>// Get format</em>
				$format = \WP_CLI\Utils\get_flag_value($assoc_args, 'format', 'table');

				<em>// Output results</em>
				if ('json' === $format) {
						WP_CLI::log(json_encode($results, JSON_PRETTY_PRINT));
				} else {
						\WP_CLI\Utils\format_items('table', $results, array('Check', 'Status', 'Details'));
				}

				<em>// Save to file if requested</em>
				if (isset($assoc_args&#91;'save'])) {
						file_put_contents($assoc_args&#91;'save'], json_encode($results, JSON_PRETTY_PRINT));
						WP_CLI::success("Audit saved to: " . $assoc_args&#91;'save']);
				}

				WP_CLI::success("Audit complete!");
		}

		private function check_wordpress_version() {
				global $wp_version;

				$updates = get_core_updates();
				$has_update = isset($updates&#91;0]) &amp;&amp; $updates&#91;0]-&gt;response === 'upgrade';

				return array(
						'Check' =&gt; 'WordPress Version',
						'Status' =&gt; $has_update ? '⚠ Update Available' : '✓ Up to Date',
						'Details' =&gt; 'Current: ' . $wp_version
				);
		}

		private function check_plugin_updates() {
				$updates = get_plugin_updates();
				$count = count($updates);

				return array(
						'Check' =&gt; 'Plugin Updates',
						'Status' =&gt; $count &gt; 0 ? "⚠ {$count} available" : '✓ All updated',
						'Details' =&gt; $count &gt; 0 ? implode(', ', array_keys($updates)) : 'None'
				);
		}

		private function check_theme_updates() {
				$updates = get_theme_updates();
				$count = count($updates);

				return array(
						'Check' =&gt; 'Theme Updates',
						'Status' =&gt; $count &gt; 0 ? "⚠ {$count} available" : '✓ All updated',
						'Details' =&gt; $count &gt; 0 ? implode(', ', array_keys($updates)) : 'None'
				);
		}

		private function check_database_size() {
				global $wpdb;

				$size = $wpdb-&gt;get_var("
						SELECT SUM(data_length + index_length) / 1024 / 1024
						FROM information_schema.TABLES
						WHERE table_schema = '{$wpdb-&gt;dbname}'
				");

				$size_mb = round($size, 2);
				$status = $size_mb &gt; 500 ? '⚠ Large' : '✓ Normal';

				return array(
						'Check' =&gt; 'Database Size',
						'Status' =&gt; $status,
						'Details' =&gt; "{$size_mb} MB"
				);
		}

		private function check_uploads_size() {
				$upload_dir = wp_upload_dir();
				$path = $upload_dir&#91;'basedir'];

				$size = $this-&gt;get_directory_size($path);
				$size_mb = round($size / 1024 / 1024, 2);
				$status = $size_mb &gt; 5000 ? '⚠ Large' : '✓ Normal';

				return array(
						'Check' =&gt; 'Uploads Directory',
						'Status' =&gt; $status,
						'Details' =&gt; "{$size_mb} MB"
				);
		}

		private function check_orphaned_data() {
				global $wpdb;

				<em>// Count orphaned post meta</em>
				$orphaned = $wpdb-&gt;get_var("
						SELECT COUNT(*)
						FROM {$wpdb-&gt;postmeta} pm
						LEFT JOIN {$wpdb-&gt;posts} p ON p.ID = pm.post_id
						WHERE p.ID IS NULL
				");

				$status = $orphaned &gt; 0 ? "⚠ {$orphaned} found" : '✓ None';

				return array(
						'Check' =&gt; 'Orphaned Post Meta',
						'Status' =&gt; $status,
						'Details' =&gt; $orphaned &gt; 0 ? "Run cleanup recommended" : 'Clean'
				);
		}

		private function get_directory_size($path) {
				$size = 0;

				foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)) as $file) {
						if ($file-&gt;isFile()) {
								$size += $file-&gt;getSize();
						}
				}

				return $size;
		}
}

WP_CLI::add_command('site-audit', 'Site_Audit_Command');
</code></pre>



<h4 class="wp-block-heading" id="usage">Usage</h4>



<pre class="wp-block-code"><code><em># Run audit</em>
wp site-audit run

<em># JSON output</em>
wp site-audit run --format=json

<em># Save to file</em>
wp site-audit run --save=audit-2025.json
</code></pre>



<p><strong>Output:</strong></p>



<pre class="wp-block-code"><code>+---------------------+-------------------+------------------+
| Check               | Status            | Details          |
+---------------------+-------------------+------------------+
| WordPress Version   | ✓ Up to Date      | Current: 6.4.2   |
| Plugin Updates      | ⚠ 3 available     | woocommerce, ... |
| Theme Updates       | ✓ All updated     | None             |
| Database Size       | ✓ Normal          | 45.23 MB         |
| Uploads Directory   | ⚠ Large           | 5432.12 MB       |
| Orphaned Post Meta  | ⚠ 156 found       | Run cleanup...   |
+---------------------+-------------------+------------------+
Success: Audit complete!
</code></pre>



<h3 class="wp-block-heading" id="packaging-commands-packaging">Packaging Commands</h3>



<h4 class="wp-block-heading" id="create-composer-package">Create Composer Package</h4>



<p>Structure your command as a Composer package:</p>



<pre class="wp-block-code"><code>my-wp-cli-command/
├── composer.json
├── command.php
└── README.md
</code></pre>



<p><strong>composer.json:</strong></p>



<pre class="wp-block-code"><code>{
		"name": "vendor/my-wpcli-command",
		"description": "Custom WP-CLI command for site auditing",
		"type": "wp-cli-package",
		"require": {
				"php": "&gt;=7.4"
		},
		"autoload": {
				"files": &#91;"command.php"]
		},
		"extra": {
				"commands": &#91;
						"site-audit"
				]
		}
}
</code></pre>



<h4 class="wp-block-heading" id="install-via-composer">Install via Composer</h4>



<p>Users can install your command:</p>



<pre class="wp-block-code"><code>wp package install vendor/my-wpcli-command
</code></pre>



<p>Or via&nbsp;<code>composer.json</code>:</p>



<pre class="wp-block-code"><code>{
		"require": {
				"vendor/my-wpcli-command": "^1.0"
		}
}
</code></pre>



<p>Learn more:&nbsp;<a href="https://wp-cli.org/package-index/">WP-CLI Package Index</a></p>



<h3 class="wp-block-heading" id="testing-custom-commands-testing">Testing Custom Commands</h3>



<h4 class="wp-block-heading" id="manual-testing">Manual Testing</h4>



<pre class="wp-block-code"><code><em># Test basic functionality</em>
wp site-audit run

<em># Test with flags</em>
wp site-audit run --format=json

<em># Test error handling (invalid flag)</em>
wp site-audit run --format=invalid
</code></pre>



<h4 class="wp-block-heading" id="automated-testing-with-behat">Automated Testing with Behat</h4>



<p>For serious projects, use WP-CLI&#8217;s testing framework:</p>



<pre class="wp-block-code"><code><em># Install testing framework</em>
composer require --dev wp-cli/wp-cli-tests
</code></pre>



<p><strong>Example test:</strong></p>



<pre class="wp-block-code"><code>Feature: Site audit command

	Scenario: Run basic audit
		Given a WP install

		When I run `wp site-audit run`
		Then STDOUT should contain "Audit complete"
		And the return code should be 0

	Scenario: Save audit to file
		Given a WP install

		When I run `wp site-audit run --save=test-audit.json`
		Then the test-audit.json file should exist
		And STDOUT should contain "Audit saved"
</code></pre>



<p>Learn more:&nbsp;<a href="https://make.wordpress.org/cli/handbook/misc/plugin-unit-tests/">WP-CLI Testing Documentation</a></p>



<h3 class="wp-block-heading" id="distribution-distribution">Distribution</h3>



<h4 class="wp-block-heading" id="github-repository">GitHub Repository</h4>



<p>Host your command on GitHub:</p>



<ol class="wp-block-list">
<li>Create repo:&nbsp;<code>my-wpcli-command</code></li>



<li>Push code with&nbsp;<code>composer.json</code></li>



<li>Tag releases:&nbsp;<code>v1.0.0</code>,&nbsp;<code>v1.1.0</code>, etc.</li>
</ol>



<h4 class="wp-block-heading" id="wp-cli-package-index">WP-CLI Package Index</h4>



<p>Submit to&nbsp;<a href="https://wp-cli.org/package-index/">WP-CLI Package Index</a>:</p>



<ol class="wp-block-list">
<li>Follow package guidelines</li>



<li>Submit pull request to package index</li>



<li>Wait for review and approval</li>
</ol>



<h4 class="wp-block-heading" id="wordpress-plugin-directory">WordPress Plugin Directory</h4>



<p>Optionally distribute as WordPress plugin:</p>



<ul class="wp-block-list">
<li>Works automatically when plugin is activated</li>



<li>Discoverable via WordPress.org</li>



<li>Easier for non-technical users</li>
</ul>



<h3 class="wp-block-heading" id="real-world-examples-examples">Real-World Examples</h3>



<h4 class="wp-block-heading" id="official-wp-cli-packages">Official WP-CLI Packages</h4>



<p>Study these for inspiration:</p>



<ul class="wp-block-list">
<li><a href="https://github.com/wp-cli/doctor-command">wp-cli/doctor-command</a>&nbsp;&#8211; Health checks</li>



<li><a href="https://github.com/wp-cli/profile-command">wp-cli/profile-command</a>&nbsp;&#8211; Performance profiling</li>



<li><a href="https://github.com/wp-cli/scaffold-command">wp-cli/scaffold-command</a>&nbsp;&#8211; Code generation</li>
</ul>



<h4 class="wp-block-heading" id="popular-third-party-commands">Popular Third-Party Commands</h4>



<ul class="wp-block-list">
<li><strong>WooCommerce CLI</strong>&nbsp;&#8211; WooCommerce management</li>



<li><strong>ACF CLI</strong>&nbsp;&#8211; Advanced Custom Fields operations</li>



<li><strong>Yoast SEO CLI</strong>&nbsp;&#8211; SEO management</li>
</ul>



<p>Browse more:&nbsp;<a href="https://wp-cli.org/package-index/">WP-CLI Package Index</a></p>



<h3 class="wp-block-heading" id="troubleshooting-troubleshooting">Troubleshooting</h3>



<h4 class="wp-block-heading" id="command-not-found">Command Not Found</h4>



<p><strong>Problem</strong>:&nbsp;<code>wp mycommand</code>&nbsp;returns &#8220;Error: &#8216;mycommand&#8217; is not a registered wp command.&#8221;</p>



<p><strong>Solutions</strong>:</p>



<ol class="wp-block-list">
<li>Check plugin is activated:</li>
</ol>



<pre class="wp-block-code"><code>wp plugin list
</code></pre>



<ol start="2" class="wp-block-list">
<li>Verify&nbsp;<code>WP_CLI</code>&nbsp;is defined:</li>
</ol>



<pre class="wp-block-code"><code>if (!defined('WP_CLI')) {
		return; <em>// Command won't register</em>
}
</code></pre>



<ol start="3" class="wp-block-list">
<li>Check command registration:</li>
</ol>



<pre class="wp-block-code"><code>WP_CLI::add_command('mycommand', 'My_Command');
</code></pre>



<h4 class="wp-block-heading" id="parameters-not-working">Parameters Not Working</h4>



<p><strong>Problem</strong>: Flags are ignored or cause errors.</p>



<p><strong>Solutions</strong>:</p>



<ol class="wp-block-list">
<li>Check synopsis syntax in docblock</li>



<li>Verify parameter names match:</li>
</ol>



<pre class="wp-block-code"><code>$format = $assoc_args&#91;'format'];  <em>// Must match --format</em>
</code></pre>



<ol start="3" class="wp-block-list">
<li>Provide defaults for optional parameters:</li>
</ol>



<pre class="wp-block-code"><code>$format = \WP_CLI\Utils\get_flag_value($assoc_args, 'format', 'table');
</code></pre>



<h4 class="wp-block-heading" id="memory-limit-errors">Memory Limit Errors</h4>



<p><strong>Problem</strong>: Command fails with &#8220;Allowed memory size exhausted.&#8221;</p>



<p><strong>Solution</strong>: Process items in batches:</p>



<pre class="wp-block-code"><code>public function process_all() {
		$per_page = 100;
		$page = 1;

		do {
				$items = get_posts(array(
						'posts_per_page' =&gt; $per_page,
						'paged' =&gt; $page
				));

				foreach ($items as $item) {
						<em>// Process item</em>
				}

				$page++;

		} while (count($items) === $per_page);
}
</code></pre>



<h3 class="wp-block-heading" id="next-steps-next-steps">Next Steps</h3>



<p>You now know how to create production-ready custom WP-CLI commands!</p>



<h4 class="wp-block-heading" id="continue-learning">Continue Learning</h4>



<ol class="wp-block-list">
<li><strong><a href="#">Bash Functions for WordPress</a></strong>&nbsp;&#8211; Combine with shell scripts</li>



<li><strong><a href="#">WordPress CI/CD with GitHub Actions</a></strong>&nbsp;&#8211; Use commands in pipelines</li>



<li><strong><a href="#">WP-CLI Config Files</a></strong>&nbsp;&#8211; Advanced configuration</li>
</ol>



<h4 class="wp-block-heading" id="build-these-commands">Build These Commands</h4>



<p><strong>Practice projects:</strong></p>



<ul class="wp-block-list">
<li>Content migration command</li>



<li>Security audit command</li>



<li>Performance optimization command</li>



<li>Database cleanup command</li>



<li>Multi-site sync command</li>
</ul>



<h4 class="wp-block-heading" id="master-wordpress-automation">Master WordPress Automation</h4>



<p>Want to build advanced automation systems with custom commands, APIs, and deployment pipelines?</p>



<p><strong><a href="/#get-started">Join the WPCLI Mastery waitlist</a></strong>&nbsp;and get:</p>



<ul class="wp-block-list">
<li>Advanced command development patterns</li>



<li>Real-world command examples library</li>



<li>Package creation templates</li>



<li>Early bird course pricing ($99 vs $199)</li>
</ul>



<h3 class="wp-block-heading" id="conclusion">Conclusion</h3>



<p>Creating custom WP-CLI commands lets you extend WordPress automation with your specific workflow needs. Whether you&#8217;re building internal tools, distributing plugins, or streamlining client management, custom commands are essential for professional WordPress development.</p>



<p><strong>Key takeaways:</strong></p>



<ul class="wp-block-list">
<li>Use classes for organization and multiple subcommands</li>



<li>Leverage&nbsp;<code>WP_CLI</code>&nbsp;class methods for professional output</li>



<li>Define parameters with synopsis for clear documentation</li>



<li>Support multiple output formats (table, JSON, CSV)</li>



<li>Package with Composer for easy distribution</li>



<li>Test thoroughly before releasing</li>
</ul>



<p>The custom commands you build today become reusable tools that save time forever.</p>



<p><strong>Ready to build?</strong>&nbsp;Start with the site audit example and adapt it to your needs.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><strong>Questions about custom WP-CLI commands?</strong>&nbsp;Drop a comment below!</p>



<p><strong>Found this helpful?</strong>&nbsp;Share with other WordPress developers building CLI tools.</p>



<p><strong>Next:</strong>&nbsp;Learn how to&nbsp;<a href="#">integrate external APIs with WordPress</a>&nbsp;using WP-CLI commands.</p>
<p>The post <a href="https://wpclimastery.com/blog/how-to-create-custom-wp-cli-commands-developers-guide/">How to Create Custom WP-CLI Commands: Developer&#8217;s Guide</a> appeared first on <a href="https://wpclimastery.com">WP-CLI Mastery</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Optimize WordPress Performance with WP-CLI: 15 Proven Techniques</title>
		<link>https://wpclimastery.com/blog/optimize-wordpress-performance-with-wp-cli-15-proven-techniques/</link>
					<comments>https://wpclimastery.com/blog/optimize-wordpress-performance-with-wp-cli-15-proven-techniques/#respond</comments>
		
		<dc:creator><![CDATA[Krasen]]></dc:creator>
		<pubDate>Mon, 24 Nov 2025 11:16:24 +0000</pubDate>
				<category><![CDATA[Advanced WP-CLI Techniques]]></category>
		<category><![CDATA[database optimization wpcli]]></category>
		<category><![CDATA[optimize wordpress cli]]></category>
		<category><![CDATA[wordpress performance]]></category>
		<category><![CDATA[wordpress speed]]></category>
		<category><![CDATA[wp-cli optimization]]></category>
		<guid isPermaLink="false">https://wpclimastery.com/?p=223</guid>

					<description><![CDATA[<p>Website performance directly impacts user experience, search engine rankings, and conversion rates. WP-CLI provides powerful tools to diagnose performance bottlenecks and implement optimizations efficiently, making it essential for developers managing...</p>
<p>The post <a href="https://wpclimastery.com/blog/optimize-wordpress-performance-with-wp-cli-15-proven-techniques/">Optimize WordPress Performance with WP-CLI: 15 Proven Techniques</a> appeared first on <a href="https://wpclimastery.com">WP-CLI Mastery</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Website performance directly impacts user experience, search engine rankings, and conversion rates. WP-CLI provides powerful tools to diagnose performance bottlenecks and implement optimizations efficiently, making it essential for developers managing high-traffic WordPress sites.</p>



<h2 class="wp-block-heading" id="why-use-wp-cli-for-performance-optimization">Why Use WP-CLI for Performance Optimization</h2>



<p>Traditional WordPress performance optimization through the dashboard is time-consuming and often incomplete. WP-CLI enables systematic, repeatable optimizations that can be automated, scripted, and applied across multiple sites simultaneously.</p>



<p>The command-line approach offers distinct advantages: batch processing of operations, precise control over optimization tasks, ability to schedule regular maintenance, detailed logging of changes, and integration with deployment pipelines. These capabilities make WP-CLI indispensable for professional WordPress performance management.</p>



<h2 class="wp-block-heading" id="1-database-optimization-and-cleanup">1. Database Optimization and Cleanup</h2>



<p>Database bloat significantly impacts query performance. Over time, WordPress databases accumulate revisions, spam comments, orphaned metadata, and expired transients that slow down queries.</p>



<pre class="wp-block-code"><code><em># Check database size before optimization</em>
wp db size --tables

<em># Optimize all database tables</em>
wp db optimize

<em># Remove post revisions older than 30 days</em>
wp post delete $(wp post list --post_type=revision --format=ids --date_query='&#91;{"before":"30 days ago"}]') --force

<em># Clean up spam and trashed comments</em>
wp comment delete $(wp comment list --status=spam --format=ids) --force
wp comment delete $(wp comment list --status=trash --format=ids) --force

<em># Remove orphaned post metadata</em>
wp db query "DELETE pm FROM wp_postmeta pm LEFT JOIN wp_posts wp ON wp.ID = pm.post_id WHERE wp.ID IS NULL"

<em># Clean expired transients</em>
wp transient delete --expired

<em># Remove all transients (use cautiously)</em>
wp transient delete --all
</code></pre>



<p>Create a comprehensive database cleanup script:</p>



<pre class="wp-block-code"><code>#!/bin/bash
<em># db-cleanup.sh - Comprehensive database optimization</em>

WP_PATH="/var/www/html"

echo "Starting database optimization..."

<em># Backup before cleanup</em>
wp --path="$WP_PATH" db export backup-pre-cleanup-$(date +%Y%m%d).sql

<em># Get initial database size</em>
INITIAL_SIZE=$(wp --path="$WP_PATH" db size --human-readable --format=json | jq -r '.Size')
echo "Initial database size: $INITIAL_SIZE"

<em># Remove old post revisions (keep last 5 for each post)</em>
wp --path="$WP_PATH" config set WP_POST_REVISIONS 5 --type=constant

<em># Delete old auto-drafts</em>
wp --path="$WP_PATH" post delete $(wp --path="$WP_PATH" post list --post_status=auto-draft --format=ids) --force

<em># Clean comments</em>
wp --path="$WP_PATH" comment delete $(wp --path="$WP_PATH" comment list --status=spam --format=ids) --force
wp --path="$WP_PATH" comment delete $(wp --path="$WP_PATH" comment list --status=trash --format=ids) --force

<em># Remove orphaned metadata</em>
wp --path="$WP_PATH" db query "DELETE FROM wp_commentmeta WHERE comment_id NOT IN (SELECT comment_ID FROM wp_comments)"
wp --path="$WP_PATH" db query "DELETE FROM wp_postmeta WHERE post_id NOT IN (SELECT ID FROM wp_posts)"
wp --path="$WP_PATH" db query "DELETE FROM wp_termmeta WHERE term_id NOT IN (SELECT term_id FROM wp_terms)"

<em># Clean transients</em>
wp --path="$WP_PATH" transient delete --expired

<em># Optimize tables</em>
wp --path="$WP_PATH" db optimize

<em># Get final database size</em>
FINAL_SIZE=$(wp --path="$WP_PATH" db size --human-readable --format=json | jq -r '.Size')
echo "Final database size: $FINAL_SIZE"
echo "Database optimization completed"
</code></pre>



<h2 class="wp-block-heading" id="2-cache-management">2. Cache Management</h2>



<p>Effective cache management is crucial for performance. WP-CLI provides commands to manage object cache, page cache, and opcode cache.</p>



<pre class="wp-block-code"><code><em># Flush object cache (Redis, Memcached, etc.)</em>
wp cache flush

<em># Test cache functionality</em>
wp cache set test_key test_value 300
wp cache get test_key

<em># For sites using Redis</em>
wp redis info

<em># Clear page cache for specific plugins</em>
<em># W3 Total Cache</em>
wp w3-total-cache flush all

<em># WP Super Cache</em>
wp cache flush

<em># Clear cache for specific pages/posts</em>
wp transient delete --all
</code></pre>



<p>Implement intelligent cache warming:</p>



<pre class="wp-block-code"><code>#!/bin/bash
<em># cache-warm.sh - Warm up cache for important pages</em>

SITE_URL="https://example.com"
WP_PATH="/var/www/html"

<em># Get list of published posts and pages</em>
URLS=$(wp --path="$WP_PATH" post list --post_type=post,page --post_status=publish --field=url)

<em># Warm cache by requesting each URL</em>
for url in $URLS; do
    echo "Warming cache: $url"
    curl -s -o /dev/null "$url"
    sleep 0.5  <em># Avoid overwhelming server</em>
done

<em># Warm up homepage and key pages</em>
curl -s -o /dev/null "$SITE_URL"
curl -s -o /dev/null "$SITE_URL/about"
curl -s -o /dev/null "$SITE_URL/contact"

echo "Cache warming completed"
</code></pre>



<h2 class="wp-block-heading" id="3-image-optimization">3. Image Optimization</h2>



<p>Images often account for the majority of page weight. WP-CLI can help automate image optimization tasks.</p>



<pre class="wp-block-code"><code><em># Regenerate thumbnails for all images</em>
wp media regenerate --yes

<em># Regenerate only missing thumbnails</em>
wp media regenerate --only-missing --yes

<em># Regenerate images uploaded in specific timeframe</em>
wp media regenerate --yes $(wp post list --post_type=attachment --format=ids --post_mime_type=image --year=2024)

<em># Install and configure image optimization plugin via CLI</em>
wp plugin install ewww-image-optimizer --activate
wp option update ewww_image_optimizer_cloud_key 'YOUR_API_KEY'

<em># Bulk optimize existing images</em>
wp ewwwio optimize all
</code></pre>



<h2 class="wp-block-heading" id="4-plugin-performance-audit">4. Plugin Performance Audit</h2>



<p>Identify and manage plugins that impact performance negatively.</p>



<pre class="wp-block-code"><code><em># List all active plugins</em>
wp plugin list --status=active

<em># Check for plugin updates</em>
wp plugin list --update=available

<em># Deactivate unused plugins</em>
wp plugin deactivate plugin-slug

<em># Remove unused plugins completely</em>
wp plugin delete plugin-slug

<em># Identify plugins loading on every page</em>
wp plugin list --status=active --field=name | while read plugin; do
    echo "Checking: $plugin"
    wp plugin path $plugin
done
</code></pre>



<p>Create a plugin performance audit script:</p>



<pre class="wp-block-code"><code>#!/bin/bash
<em># plugin-audit.sh - Audit plugin performance impact</em>

WP_PATH="/var/www/html"

echo "=== Plugin Performance Audit ==="

<em># List active plugins with version info</em>
echo -e "\nActive Plugins:"
wp --path="$WP_PATH" plugin list --status=active --fields=name,version,update,auto_update

<em># Check for outdated plugins</em>
echo -e "\nPlugins requiring updates:"
wp --path="$WP_PATH" plugin list --update=available --fields=name,version,update_version

<em># Identify plugins with known performance issues</em>
echo -e "\nChecking for resource-intensive plugins..."

<em># Get plugin file sizes</em>
for plugin in $(wp --path="$WP_PATH" plugin list --status=active --field=name); do
    PLUGIN_PATH=$(wp --path="$WP_PATH" plugin path $plugin)
    SIZE=$(du -sh "$PLUGIN_PATH" 2&gt;/dev/null | cut -f1)
    echo "$plugin: $SIZE"
done | sort -h
</code></pre>



<h2 class="wp-block-heading" id="5-autoload-optimization">5. Autoload Optimization</h2>



<p>Excessive autoloaded options slow down every WordPress request. Optimize autoloaded data to reduce overhead.</p>



<pre class="wp-block-code"><code><em># Check autoload size</em>
wp db query "SELECT SUM(LENGTH(option_value)) as autoload_size FROM wp_options WHERE autoload='yes'"

<em># List large autoloaded options</em>
wp db query "SELECT option_name, LENGTH(option_value) as size FROM wp_options WHERE autoload='yes' ORDER BY size DESC LIMIT 20"

<em># Convert large options to non-autoload</em>
wp option update large_option_name 'value' --autoload=no

<em># Remove obsolete autoloaded options</em>
wp db query "DELETE FROM wp_options WHERE autoload='yes' AND option_name LIKE '%_transient_%'"
</code></pre>



<h2 class="wp-block-heading" id="6-query-monitoring-and-optimization">6. Query Monitoring and Optimization</h2>



<p>Monitor database queries to identify performance bottlenecks.</p>



<pre class="wp-block-code"><code><em># Enable query logging (for development)</em>
wp config set SAVEQUERIES true --raw

<em># Check for slow queries in production</em>
wp db query "SELECT * FROM information_schema.processlist WHERE command != 'Sleep' AND time &gt; 5"

<em># Add database indexes for custom queries</em>
wp db query "CREATE INDEX idx_meta_key_value ON wp_postmeta(meta_key, meta_value(100))"

<em># Analyze table structure</em>
wp db query "SHOW INDEX FROM wp_posts"
wp db query "SHOW INDEX FROM wp_postmeta"
</code></pre>



<h2 class="wp-block-heading" id="7-content-delivery-network-integration">7. Content Delivery Network Integration</h2>



<p>Configure CDN settings programmatically for consistent deployment.</p>



<pre class="wp-block-code"><code><em># Update site URL for CDN</em>
wp search-replace 'https://example.com/wp-content' 'https://cdn.example.com/wp-content' --dry-run

<em># After verifying, perform the replacement</em>
wp search-replace 'https://example.com/wp-content' 'https://cdn.example.com/wp-content'

<em># Configure CDN plugin settings</em>
wp option update cdn_url 'https://cdn.example.com'
wp option update cdn_enabled 1
</code></pre>



<h2 class="wp-block-heading" id="8-lazy-loading-implementation">8. Lazy Loading Implementation</h2>



<p>Enable lazy loading for images and iframes to improve initial page load times.</p>



<pre class="wp-block-code"><code><em># WordPress native lazy loading (5.5+)</em>
wp option update wp_lazy_loading_enabled 1

<em># For older versions, use plugin</em>
wp plugin install lazy-load --activate

<em># Configure lazy loading settings</em>
wp option update lazy_load_images 1
wp option update lazy_load_iframes 1
</code></pre>



<h2 class="wp-block-heading" id="9-remove-unused-css-and-javascript">9. Remove Unused CSS and JavaScript</h2>



<p>Minimize loaded assets by disabling unnecessary scripts and styles.</p>



<pre class="wp-block-code"><code><em># Identify loaded scripts on homepage</em>
wp eval 'global $wp_scripts; print_r($wp_scripts-&gt;registered);'

<em># Dequeue unnecessary scripts via custom plugin</em>
cat &gt; wp-content/mu-plugins/performance-tweaks.php &lt;&lt; 'EOF'
&lt;?php
add_action('wp_enqueue_scripts', function() {
    // Remove jQuery migrate
    wp_deregister_script('jquery-migrate');

    // Remove emojis
    remove_action('wp_head', 'print_emoji_detection_script', 7);
    remove_action('wp_print_styles', 'print_emoji_styles');

    // Remove block library CSS if not using Gutenberg
    wp_dequeue_style('wp-block-library');
}, 100);
EOF
</code></pre>



<h2 class="wp-block-heading" id="10-php-opcode-cache-configuration">10. PHP Opcode Cache Configuration</h2>



<p>Ensure opcode cache is properly configured for optimal PHP performance.</p>



<pre class="wp-block-code"><code><em># Check if OPcache is enabled</em>
wp eval 'var_dump(function_exists("opcache_get_status"));'

<em># Get OPcache statistics</em>
wp eval 'print_r(opcache_get_status());'

<em># Clear OPcache when deploying code</em>
wp eval 'opcache_reset();'

<em># Check PHP version and loaded extensions</em>
wp cli info
</code></pre>



<h2 class="wp-block-heading" id="11-heartbeat-api-optimization">11. Heartbeat API Optimization</h2>



<p>The WordPress Heartbeat API can consume significant resources. Control its behavior to reduce server load.</p>



<pre class="wp-block-code"><code><em># Disable Heartbeat API completely</em>
cat &gt; wp-content/mu-plugins/disable-heartbeat.php &lt;&lt; 'EOF'
&lt;?php
add_action('init', function() {
    wp_deregister_script('heartbeat');
}, 1);
EOF

<em># Or modify Heartbeat interval</em>
cat &gt; wp-content/mu-plugins/modify-heartbeat.php &lt;&lt; 'EOF'
&lt;?php
add_filter('heartbeat_settings', function($settings) {
    $settings&#91;'interval'] = 60; // 60 seconds instead of 15
    return $settings;
});
EOF
</code></pre>



<h2 class="wp-block-heading" id="12-rest-api-performance">12. REST API Performance</h2>



<p>Optimize REST API endpoints to reduce unnecessary overhead.</p>



<pre class="wp-block-code"><code><em># Disable REST API for non-authenticated users</em>
cat &gt; wp-content/mu-plugins/restrict-rest-api.php &lt;&lt; 'EOF'
&lt;?php
add_filter('rest_authentication_errors', function($result) {
    if (!empty($result)) {
        return $result;
    }
    if (!is_user_logged_in()) {
        return new WP_Error(
            'rest_not_logged_in',
            'You are not logged in.',
            array('status' =&gt; 401)
        );
    }
    return $result;
});
EOF
</code></pre>



<h2 class="wp-block-heading" id="13-cron-optimization">13. Cron Optimization</h2>



<p>WordPress cron can impact performance during peak traffic. Disable WP-Cron and use system cron instead.</p>



<pre class="wp-block-code"><code><em># Disable WordPress cron</em>
wp config set DISABLE_WP_CRON true --raw

<em># Add system cron job to handle WP cron</em>
echo "*/15 * * * * wp --path=/var/www/html cron event run --due-now" | crontab -

<em># List scheduled cron events</em>
wp cron event list

<em># Test cron execution</em>
wp cron test
</code></pre>



<h2 class="wp-block-heading" id="14-asset-minification-and-concatenation">14. Asset Minification and Concatenation</h2>



<p>Reduce HTTP requests and file sizes through minification.</p>



<pre class="wp-block-code"><code><em># Install asset optimization plugin</em>
wp plugin install autoptimize --activate

<em># Configure via CLI</em>
wp option update autoptimize_html_optimize 1
wp option update autoptimize_js_optimize 1
wp option update autoptimize_css_optimize 1

<em># Clear optimization cache</em>
wp autoptimize cache clear
</code></pre>



<h2 class="wp-block-heading" id="15-performance-monitoring-and-benchmarking">15. Performance Monitoring and Benchmarking</h2>



<p>Establish baseline metrics and monitor performance over time.</p>



<pre class="wp-block-code"><code>#!/bin/bash
<em># performance-benchmark.sh - Monitor site performance</em>

WP_PATH="/var/www/html"
SITE_URL="https://example.com"

echo "=== WordPress Performance Benchmark ==="
echo "Date: $(date)"

<em># Database size</em>
echo -e "\nDatabase Size:"
wp --path="$WP_PATH" db size --human-readable

<em># Autoload size</em>
echo -e "\nAutoload Data:"
wp --path="$WP_PATH" db query "SELECT CONCAT(ROUND(SUM(LENGTH(option_value))/1024/1024,2),'MB') as autoload_size FROM wp_options WHERE autoload='yes'"

<em># Post counts</em>
echo -e "\nContent Statistics:"
wp --path="$WP_PATH" post list --post_type=post --format=count --post_status=publish | xargs -I {} echo "Published Posts: {}"
wp --path="$WP_PATH" post list --post_type=page --format=count --post_status=publish | xargs -I {} echo "Published Pages: {}"

<em># Plugin count</em>
echo -e "\nActive Plugins:"
wp --path="$WP_PATH" plugin list --status=active --format=count

<em># Theme check</em>
echo -e "\nActive Theme:"
wp --path="$WP_PATH" theme list --status=active --field=name

<em># Response time test</em>
echo -e "\nResponse Time:"
time curl -s -o /dev/null -w "Total: %{time_total}s\n" "$SITE_URL"

<em># Check for updates</em>
echo -e "\nAvailable Updates:"
wp --path="$WP_PATH" core check-update
wp --path="$WP_PATH" plugin list --update=available --format=count | xargs -I {} echo "Plugins: {}"
wp --path="$WP_PATH" theme list --update=available --format=count | xargs -I {} echo "Themes: {}"
</code></pre>



<h2 class="wp-block-heading" id="automated-performance-optimization">Automated Performance Optimization</h2>



<p>Combine techniques into a comprehensive automation script:</p>



<pre class="wp-block-code"><code>#!/bin/bash
<em># performance-optimize.sh - Complete performance optimization</em>

set -euo pipefail

WP_PATH="/var/www/html"
LOG_FILE="/var/log/wp-performance-$(date +%Y%m%d).log"

log() {
    echo "&#91;$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "Starting performance optimization"

<em># Backup first</em>
log "Creating backup"
wp --path="$WP_PATH" db export "backup-$(date +%Y%m%d-%H%M%S).sql"

<em># Database optimization</em>
log "Optimizing database"
wp --path="$WP_PATH" db optimize
wp --path="$WP_PATH" transient delete --expired

<em># Clear caches</em>
log "Clearing caches"
wp --path="$WP_PATH" cache flush

<em># Update everything</em>
log "Checking for updates"
wp --path="$WP_PATH" core update --minor
wp --path="$WP_PATH" plugin update --all --exclude=custom-plugin
wp --path="$WP_PATH" theme update --all

<em># Regenerate missing thumbnails</em>
log "Regenerating missing thumbnails"
wp --path="$WP_PATH" media regenerate --only-missing --yes

<em># Performance metrics</em>
log "Collecting performance metrics"
DB_SIZE=$(wp --path="$WP_PATH" db size --human-readable --format=json | jq -r '.Size')
log "Database size: $DB_SIZE"

log "Performance optimization completed"
</code></pre>



<h2 class="wp-block-heading" id="related-links">Related Links</h2>



<ul class="wp-block-list">
<li><a href="https://developer.wordpress.org/cli/commands/">WP-CLI Performance Commands</a></li>



<li><a href="https://wordpress.org/support/article/optimization/">WordPress Performance Best Practices</a></li>



<li><a href="https://developer.wordpress.org/advanced-administration/upgrade/optimizing/">Database Optimization Guide</a></li>



<li><a href="https://developer.wordpress.org/advanced-administration/performance/cache/">WordPress Caching Strategies</a></li>



<li><a href="https://querymonitor.com/">Query Monitor Plugin</a></li>
</ul>
<p>The post <a href="https://wpclimastery.com/blog/optimize-wordpress-performance-with-wp-cli-15-proven-techniques/">Optimize WordPress Performance with WP-CLI: 15 Proven Techniques</a> appeared first on <a href="https://wpclimastery.com">WP-CLI Mastery</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wpclimastery.com/blog/optimize-wordpress-performance-with-wp-cli-15-proven-techniques/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
