<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Muazzam</title>
    <description>The latest articles on DEV Community by Muazzam (@muazzami).</description>
    <link>https://dev.to/muazzami</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F151435%2F44dc0c44-7eec-433e-b494-e84d8c7a0ffc.jpeg</url>
      <title>DEV Community: Muazzam</title>
      <link>https://dev.to/muazzami</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muazzami"/>
    <language>en</language>
    <item>
      <title>Escaping Managed Hosting: What Happened When We Migrated a WooCommerce Site to a VPS (And Got Attacked)</title>
      <dc:creator>Muazzam</dc:creator>
      <pubDate>Sat, 25 Apr 2026 03:02:47 +0000</pubDate>
      <link>https://dev.to/muazzami/escaping-managed-hosting-what-happened-when-we-migrated-a-woocommerce-site-to-a-vps-and-got-365m</link>
      <guid>https://dev.to/muazzami/escaping-managed-hosting-what-happened-when-we-migrated-a-woocommerce-site-to-a-vps-and-got-365m</guid>
      <description>&lt;h1&gt;
  
  
  Escaping Managed Hosting: What Happened When We Migrated a WooCommerce Site to a VPS (And Got Attacked)
&lt;/h1&gt;

&lt;p&gt;Managed WordPress hosting sounds like a great deal — until it isn't. This is the story of migrating a WooCommerce + WPML site off a major managed host, the chaos that followed immediately after, and the hard lessons learned about what managed hosting was silently doing for us that we didn't fully appreciate until it was gone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why We Left Managed Hosting
&lt;/h2&gt;

&lt;p&gt;The decision wasn't dramatic. It came down to three compounding frustrations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost vs. control.&lt;/strong&gt; Managed WordPress hosting at the enterprise tier isn't cheap. As traffic and complexity grew, so did the invoice. But the control stayed locked down — no custom server config, limited cache tuning, no ability to see what was actually hitting the server at a low level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance ceiling.&lt;/strong&gt; A WooCommerce store with WPML (multilingual) generates a lot of unique URLs — filtered shop pages, language variants, paginated archives. The managed host's caching layer was a black box. When performance degraded, the answer was always "upgrade your plan." There was no way to diagnose what was actually happening underneath.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visibility.&lt;/strong&gt; When something went wrong, we couldn't see access logs in real time, couldn't inspect PHP worker counts, couldn't adjust server-level settings. Everything was abstracted. The host was the gatekeeper between us and the actual machine.&lt;/p&gt;

&lt;p&gt;The move to a self-managed VPS with RunCloud and OpenLiteSpeed (OLS) promised full visibility and control. It delivered on that promise — but it also immediately exposed us to everything the managed host had been silently absorbing on our behalf.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Migration: What We Were Walking Into
&lt;/h2&gt;

&lt;p&gt;The stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WordPress + WooCommerce&lt;/strong&gt; with WPML (multilingual, two languages)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elementor&lt;/strong&gt; as the page builder&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LSCache&lt;/strong&gt; for caching, &lt;strong&gt;Redis&lt;/strong&gt; for object caching&lt;/li&gt;
&lt;li&gt;Migrating from managed hosting to a &lt;strong&gt;VPS via RunCloud&lt;/strong&gt; with &lt;strong&gt;OpenLiteSpeed&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The migration itself was technically straightforward: export, import, update DNS. What wasn't straightforward was what we discovered in the database afterward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 1: Serialized Data URL Contamination
&lt;/h3&gt;

&lt;p&gt;During migration, the site temporarily lived on a staging domain (something like &lt;code&gt;mysite.staging.temphost.link&lt;/code&gt;). The standard search-replace after migration should catch all references to the old domain and replace them with the new one.&lt;/p&gt;

&lt;p&gt;It didn't catch everything.&lt;/p&gt;

&lt;p&gt;Several plugins store data in WordPress's &lt;code&gt;wp_options&lt;/code&gt; table as &lt;strong&gt;serialized PHP arrays&lt;/strong&gt;. A normal string search-replace on serialized data corrupts it — because serialized strings encode their own length, and changing the URL changes the string length without updating the length prefix.&lt;/p&gt;

&lt;p&gt;The plugins that caused contamination:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A media carousel plugin&lt;/strong&gt; — stored 400KB+ of serialized image data in &lt;code&gt;wp_options&lt;/code&gt;, all with the temp domain baked in for image paths&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elementor&lt;/strong&gt; — had generated CSS files on disk at &lt;code&gt;wp-content/uploads/elementor/google-fonts/css/&lt;/code&gt; with &lt;code&gt;http://&lt;/code&gt; URLs hardcoded, not regenerated after migration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An image optimization plugin&lt;/strong&gt; — had a WebP cache directory full of references to the old domain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix required using WP-CLI's search-replace with the &lt;code&gt;--precise&lt;/code&gt; flag, which handles serialized data correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp search-replace &lt;span class="s1"&gt;'mysite.staging.temphost.link'&lt;/span&gt; &lt;span class="s1"&gt;'mysite.com'&lt;/span&gt; &lt;span class="nt"&gt;--precise&lt;/span&gt; &lt;span class="nt"&gt;--all-tables&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the Elementor font CSS files on disk, a direct &lt;code&gt;find&lt;/code&gt; + &lt;code&gt;sed&lt;/code&gt; was needed since WP-CLI doesn't touch files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find /var/www/mysite/wp-content/uploads/elementor/google-fonts/css/ &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.css"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s|http://mysite.staging.temphost.link|https://mysite.com|g'&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Problem 2: Missing SSL Config in wp-config.php
&lt;/h3&gt;

&lt;p&gt;Despite the site running on HTTPS, Elementor kept generating &lt;code&gt;http://&lt;/code&gt; asset URLs. The reason was that &lt;code&gt;wp-config.php&lt;/code&gt; was missing two lines that tell WordPress it's behind HTTPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'HTTPS'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'on'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FORCE_SSL_ADMIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without these, WordPress doesn't know the request came in over SSL (especially behind a proxy or load balancer), so dynamic URLs default to &lt;code&gt;http://&lt;/code&gt;. A subtle issue that caused a surprisingly large number of mixed content problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 3: A Security Plugin Blocking Its Own Admin
&lt;/h3&gt;

&lt;p&gt;One of the installed security plugins had added a &lt;code&gt;.htaccess&lt;/code&gt; rule that was blocking all PHP file access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="nc"&gt;RewriteRule&lt;/span&gt; ^.*\.php$ - [F,L,NC]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule was intended to block direct PHP file execution in upload directories. But it was placed at the wrong level — it blocked &lt;code&gt;wp-admin&lt;/code&gt;, &lt;code&gt;wp-login.php&lt;/code&gt;, and every other PHP file on the site. The admin panel was completely inaccessible. The fix was removing that rule from &lt;code&gt;.htaccess&lt;/code&gt; manually via SSH before anything else could be done.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Attack: Day One on the VPS
&lt;/h2&gt;

&lt;p&gt;The migration was complete. The site was live. And then within hours, the server was at &lt;strong&gt;700%+ CPU&lt;/strong&gt; — that's 7 full cores pinned on a machine that should have been comfortably handling the traffic.&lt;/p&gt;

&lt;p&gt;On managed hosting, this had never happened. Not because the traffic wasn't there — but because the managed host was absorbing it silently. Now it was hitting our VPS directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attacker 1: Malicious Bots
&lt;/h3&gt;

&lt;p&gt;Access log analysis revealed two suspicious IPs hammering the site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"111.88.x.x"&lt;/span&gt; /var/log/ols/mysite.com_access.log | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $7}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both IPs were sending dozens of requests per minute to &lt;code&gt;wp-admin/admin-ajax.php&lt;/code&gt;, WooCommerce AJAX endpoints, and Contact Form 7 REST endpoints — the fingerprint of bots probing for vulnerabilities and scraping data.&lt;/p&gt;

&lt;p&gt;Fix: blocked in &lt;code&gt;.htaccess&lt;/code&gt; at the top of the file, before any WordPress rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# Block malicious IPs&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;RequireAll&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="nc"&gt;Require&lt;/span&gt; &lt;span class="ss"&gt;all&lt;/span&gt; granted
    &lt;span class="nc"&gt;Require&lt;/span&gt; not ip 111.88.x.x
    &lt;span class="nc"&gt;Require&lt;/span&gt; not ip 45.77.x.x
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;RequireAll&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Attacker 2: The Meta Crawler (The Real Problem)
&lt;/h3&gt;

&lt;p&gt;Blocking the malicious IPs brought load down — but not to safe levels. Something else was still hammering the server. Back to the access logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"meta-externalagent"&lt;/span&gt; /var/log/ols/mysite.com_access.log | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $7}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Facebook's Meta crawler (&lt;code&gt;meta-externalagent/1.1&lt;/code&gt;) was systematically crawling every combination of the WooCommerce shop's filter URLs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/shop/?filter_color=red&amp;amp;filter_size=small
/shop/?filter_color=red&amp;amp;filter_size=medium
/shop/?filter_color=blue&amp;amp;filter_size=small
... hundreds of unique combinations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's why this is catastrophic for a WooCommerce + WPML site:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every unique URL is a cache miss.&lt;/strong&gt; LSCache serves cached pages instantly with zero PHP. But a cached page is keyed by URL. Each filter combination is a different URL — so each one bypasses the cache entirely, boots WordPress, boots WooCommerce, boots WPML, runs a database query, and renders a response. The crawler was generating thousands of cache misses per hour.&lt;/p&gt;

&lt;p&gt;The fix — immediate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# Block Meta crawler&lt;/span&gt;
&lt;span class="nc"&gt;RewriteEngine&lt;/span&gt; &lt;span class="ss"&gt;On&lt;/span&gt;
&lt;span class="nc"&gt;RewriteCond&lt;/span&gt; %{HTTP_USER_AGENT} meta-externalagent [NC]
&lt;span class="nc"&gt;RewriteRule&lt;/span&gt; ^ - [F,L]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After adding this rule and restarting OLS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;load average: 0.94 — 4 lsphp processes
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From 700%+ CPU to essentially idle. The Meta crawler was the primary load driver the entire time.&lt;/p&gt;

&lt;p&gt;The longer-term fix: add the shop's filter URL pattern to &lt;code&gt;robots.txt&lt;/code&gt; so crawlers stop attempting them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;meta&lt;/span&gt;-&lt;span class="n"&gt;externalagent&lt;/span&gt;
&lt;span class="n"&gt;Disallow&lt;/span&gt;: /

&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: *
&lt;span class="n"&gt;Disallow&lt;/span&gt;: /&lt;span class="n"&gt;shop&lt;/span&gt;/?*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What Managed Hosting Was Silently Doing
&lt;/h2&gt;

&lt;p&gt;This is the part that changed how we think about managed hosting.&lt;/p&gt;

&lt;p&gt;The managed host had several layers we never thought about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bot filtering at the edge&lt;/strong&gt; — known bad actors and aggressive crawlers were blocked before they reached WordPress at all&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DDoS mitigation&lt;/strong&gt; — volumetric attacks were absorbed by the host's network layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; — aggressive crawlers were throttled automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this was documented prominently. It was just... happening. Moving to a raw VPS removed all of it at once. The site went from being behind a shield to being fully exposed to the internet with only &lt;code&gt;.htaccess&lt;/code&gt; and OLS between it and every bot on the planet.&lt;/p&gt;

&lt;p&gt;The visibility was exactly what we wanted — we could finally see everything. But we also now had to handle everything ourselves.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Permanent Solution: Cloudflare
&lt;/h2&gt;

&lt;p&gt;Fixing bots with &lt;code&gt;.htaccess&lt;/code&gt; rules is whack-a-mole. Block one IP, another appears. Block one user agent, it rotates. The real fix is a layer in front of the server that handles this at scale before it ever reaches OLS.&lt;/p&gt;

&lt;p&gt;Cloudflare's free plan provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bot Fight Mode&lt;/strong&gt; — automatically identifies and blocks known bot fingerprints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; — caps any single IP's request rate before it can cause load spikes
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge caching&lt;/strong&gt; — WooCommerce shop pages can be cached at Cloudflare's edge, so even cache misses on the origin become cache hits at the CDN level&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time traffic analytics&lt;/strong&gt; — finally, full visibility into what's hitting the site and from where&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture after adding Cloudflare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet → Cloudflare edge (bot filtering, rate limiting, CDN cache)
         → VPS / OpenLiteSpeed (LSCache, Redis)
         → WordPress / WooCommerce / WPML
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bad traffic is rejected at Cloudflare before it touches the server. The PHP worker pool stays available for real users.&lt;/p&gt;

&lt;p&gt;This is what managed hosting was providing implicitly. Cloudflare makes it explicit, configurable, and visible — and the free tier handles the vast majority of what a typical WooCommerce store needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Additional Server Hardening After Stabilization
&lt;/h2&gt;

&lt;p&gt;With the immediate crisis resolved, we locked down the remaining attack surface:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security headers via OLS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;PHP worker limits&lt;/strong&gt; — OLS was configured with a hard cap on concurrent PHP workers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;maxConns 8
&lt;span class="nv"&gt;PHP_LSAPI_CHILDREN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this cap, a flood of requests spawns unlimited PHP workers and exhausts RAM. With the cap, excess requests queue rather than spawning new processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snapshot immediately after stabilization&lt;/strong&gt; — once the server was clean and stable, we took a VPS snapshot as a known-good baseline. If anything goes wrong in future, rollback is one click.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Managed hosting hides its value until it's gone.&lt;/strong&gt;&lt;br&gt;
The bot filtering, DDoS mitigation, and crawler management that managed hosts provide are rarely documented but deeply valuable. Budget for replacing that capability explicitly when moving to a VPS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Always use &lt;code&gt;--precise&lt;/code&gt; for search-replace on migrated WordPress sites.&lt;/strong&gt;&lt;br&gt;
Standard search-replace corrupts serialized data. The &lt;code&gt;--precise&lt;/code&gt; flag in WP-CLI handles it correctly. Make it a default step in every migration checklist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Cloudflare is not optional for a self-managed WooCommerce store.&lt;/strong&gt;&lt;br&gt;
Put it in front on day one. Not after you've been attacked. The free plan covers the essentials, and the visibility alone is worth it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. WooCommerce filter URLs are a crawler trap.&lt;/strong&gt;&lt;br&gt;
Any WooCommerce store with faceted filtering generates effectively infinite unique URLs. Configure LSCache to ignore query strings on shop pages, and disallow filter URL patterns in &lt;code&gt;robots.txt&lt;/code&gt; before crawlers index them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Access logs are your best friend on a VPS.&lt;/strong&gt;&lt;br&gt;
The moment something goes wrong, SSH in and read the access log. The answer is almost always there — which IPs, which user agents, which URLs, how many requests per minute. On managed hosting, you often can't do this. On a VPS, it's the first thing you reach for.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Outcome
&lt;/h2&gt;

&lt;p&gt;The site runs more reliably now on the VPS than it ever did on managed hosting — and at significantly lower cost. Load averages stay under 1.0 under normal traffic. The Cloudflare layer handles bot traffic before it reaches the server. LSCache and Redis handle the WordPress-level caching. And when something goes wrong, we can actually see it.&lt;/p&gt;

&lt;p&gt;The migration pain was real. But it was a one-time cost that permanently increased visibility, control, and resilience. The managed host was comfortable — but comfort was masking problems we couldn't see or fix.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>woocommerce</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>How to Filter and Sort Posts by Custom Field Value Using JetSmartFilters + Bricks Builder</title>
      <dc:creator>Muazzam</dc:creator>
      <pubDate>Sat, 25 Apr 2026 02:45:19 +0000</pubDate>
      <link>https://dev.to/muazzami/how-to-filter-and-sort-posts-by-custom-field-value-using-jetsmartfilters-bricks-builder-57an</link>
      <guid>https://dev.to/muazzami/how-to-filter-and-sort-posts-by-custom-field-value-using-jetsmartfilters-bricks-builder-57an</guid>
      <description>&lt;h1&gt;
  
  
  How to Filter and Sort Posts by Custom Field Value Using JetSmartFilters + Bricks Builder
&lt;/h1&gt;

&lt;p&gt;If you've ever tried to let users filter and sort a post listing by a numeric custom field — say, a product spec, a rating, or a measurement — you've probably hit the wall where JetSmartFilters (JSF) and Bricks Builder don't quite connect out of the box.&lt;/p&gt;

&lt;p&gt;This post documents exactly how to wire them together: a radio filter that, when clicked, filters out posts missing that field &lt;strong&gt;and&lt;/strong&gt; sorts the remaining results by that field's numeric value in descending order.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;p&gt;A sidebar radio list of custom field names (e.g. "Weight", "Length", "Rating"). When a user selects one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Only posts that &lt;strong&gt;have a value&lt;/strong&gt; for that field are shown&lt;/li&gt;
&lt;li&gt;Results are &lt;strong&gt;sorted highest to lowest&lt;/strong&gt; by that field's numeric value&lt;/li&gt;
&lt;li&gt;All other active filters (taxonomy, search, etc.) continue to stack correctly&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bricks Builder&lt;/strong&gt; — query loop with a custom Query ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JetEngine&lt;/strong&gt; — CPT with custom meta fields (all numeric)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JetSmartFilters&lt;/strong&gt; — Radio filter + existing taxonomy filters on the same page&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why This Isn't Straightforward
&lt;/h2&gt;

&lt;p&gt;JSF has a Sort widget and a Radio filter widget, but they operate independently. Clicking a radio does not trigger a sort. You can't natively say "when this radio value is selected, sort by that meta key."&lt;/p&gt;

&lt;p&gt;The solution is to use JSF's radio filter to pass a plain query variable, then intercept Bricks' query via the &lt;code&gt;bricks/posts/query_vars&lt;/code&gt; filter hook to inject the correct &lt;code&gt;meta_query&lt;/code&gt;, &lt;code&gt;orderby&lt;/code&gt;, and &lt;code&gt;meta_key&lt;/code&gt; — all server-side, no JavaScript needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Set Up the Radio Filter in JSF
&lt;/h2&gt;

&lt;p&gt;In JetSmartFilters, create a new &lt;strong&gt;Radio&lt;/strong&gt; filter with the following settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Source:&lt;/strong&gt; Manual Input&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Options List:&lt;/strong&gt; One entry per custom field. Label = display name, Value = the meta key
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Label: Weight       Value: weight
Label: Length       Value: length
Label: Rating       Value: rating
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This filter for:&lt;/strong&gt; Bricks query loop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query ID:&lt;/strong&gt; your-listing-id &lt;em&gt;(must match the Query ID set on your Bricks loop element)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply on:&lt;/strong&gt; Value change&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query Variable:&lt;/strong&gt; &lt;code&gt;_plain_query::sort_by_field&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;_plain_query::&lt;/code&gt; prefix is important. It tells JSF to pass the value as a plain query variable rather than attempting its own meta query processing. JSF will internally convert this to &lt;code&gt;_plain_query_sort_by_field&lt;/code&gt; in the POST payload.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — Note Your Bricks Loop Query ID
&lt;/h2&gt;

&lt;p&gt;In Bricks Builder, select your query loop element and set a &lt;strong&gt;Query ID&lt;/strong&gt; under the JSF settings panel (the field labelled "Query ID" added by JSF when the element is marked as filterable).&lt;/p&gt;

&lt;p&gt;Example: &lt;code&gt;my-listing&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;All JSF filter widgets on the page must have the same Query ID set in their "This filter for" setting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — The PHP Hook
&lt;/h2&gt;

&lt;p&gt;Add this to your theme's &lt;code&gt;functions.php&lt;/code&gt; or a site-specific plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bricks/posts/query_vars'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$element_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$element_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Target only your specific Bricks loop by its JSF Query ID&lt;/span&gt;
    &lt;span class="c1"&gt;// Note: Bricks stores this under 'jsfb_query_id', not 'query_id'&lt;/span&gt;
    &lt;span class="nv"&gt;$query_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'jsfb_query_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query_id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'my-listing'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// JSF sends filter data via POST during AJAX requests&lt;/span&gt;
    &lt;span class="c1"&gt;// The _plain_query:: prefix becomes _plain_query_ in the POST key&lt;/span&gt;
    &lt;span class="nv"&gt;$query_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'query'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nv"&gt;$sort_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_plain_query_sort_by_field'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sort_field&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Whitelist allowed field keys — never trust user input directly&lt;/span&gt;
    &lt;span class="nv"&gt;$allowed_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'weight'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'length'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rating'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// add all your fields&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;in_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sort_field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$allowed_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Filter: only show posts that have a non-empty value for this field&lt;/span&gt;
    &lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'meta_query'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'relation'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'AND'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'key'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$sort_field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'compare'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'EXISTS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'key'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$sort_field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'value'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'compare'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'!='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// Sort: numeric descending by the selected field&lt;/span&gt;
    &lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'orderby'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'meta_value_num'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'order'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DESC'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'meta_key'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$sort_field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Priority 99 is critical — see note below&lt;/span&gt;


&lt;span class="c1"&gt;// Register the query var so WordPress doesn't strip it&lt;/span&gt;
&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'query_vars'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$vars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$vars&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'sort_by_field'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$vars&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Critical Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The settings key is &lt;code&gt;jsfb_query_id&lt;/code&gt;, not &lt;code&gt;query_id&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When you check &lt;code&gt;$settings&lt;/code&gt; inside the hook, Bricks stores the JSF Query ID under &lt;code&gt;jsfb_query_id&lt;/code&gt;. Using &lt;code&gt;query_id&lt;/code&gt; will always return empty and your hook will silently skip every request.&lt;/p&gt;

&lt;p&gt;To discover the correct key for your setup, temporarily dump the full settings array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bricks/posts/query_vars'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$element_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$element_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. JSF uses POST, not GET, for AJAX filter requests
&lt;/h3&gt;

&lt;p&gt;When a user clicks a filter, JSF fires an AJAX request. The filter values are in &lt;code&gt;$_POST['query']&lt;/code&gt;, not &lt;code&gt;$_GET&lt;/code&gt;. Reading &lt;code&gt;$_GET&lt;/code&gt; will always be empty on filter interactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The &lt;code&gt;_plain_query::&lt;/code&gt; prefix transforms the POST key
&lt;/h3&gt;

&lt;p&gt;Setting the Query Variable in JSF to &lt;code&gt;_plain_query::sort_by_field&lt;/code&gt; results in the POST key being &lt;code&gt;_plain_query_sort_by_field&lt;/code&gt; (the &lt;code&gt;::&lt;/code&gt; becomes &lt;code&gt;_&lt;/code&gt;). Read it accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$query_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'query'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="nv"&gt;$sort_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_plain_query_sort_by_field'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Hook priority must be higher than JSF's own hooks
&lt;/h3&gt;

&lt;p&gt;This is the most painful one. If you register the hook at the default priority of &lt;code&gt;10&lt;/code&gt;, JSF will run its own &lt;code&gt;bricks/posts/query_vars&lt;/code&gt; hook afterward and overwrite your &lt;code&gt;orderby&lt;/code&gt; back to &lt;code&gt;date&lt;/code&gt; (the default).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always use priority &lt;code&gt;99&lt;/code&gt; or higher&lt;/strong&gt; when your hook needs to be the final word on query arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Not 10. Not 20. Use 99.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. The hook signature takes 4 parameters
&lt;/h3&gt;

&lt;p&gt;The Bricks documentation shows 4 arguments: &lt;code&gt;$query_vars&lt;/code&gt;, &lt;code&gt;$settings&lt;/code&gt;, &lt;code&gt;$element_id&lt;/code&gt;, &lt;code&gt;$element_name&lt;/code&gt;. The last argument (&lt;code&gt;$element_name&lt;/code&gt;) was added in Bricks 1.11.1. Always declare all 4 and pass &lt;code&gt;4&lt;/code&gt; as the last argument to &lt;code&gt;add_filter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bricks/posts/query_vars'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$element_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$element_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How the Full Flow Works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User clicks radio → "Weight"
        ↓
JSF fires AJAX: POST query[_plain_query_sort_by_field] = weight
        ↓
bricks/posts/query_vars hook fires (priority 99)
        ↓
Hook reads _plain_query_sort_by_field from $_POST['query']
Validates against whitelist
Injects meta_query (EXISTS + not empty)
Injects orderby=meta_value_num, order=DESC, meta_key=weight
        ↓
Bricks executes WP_Query with modified args
        ↓
Results: only posts WITH a weight value, sorted highest → lowest
Other active filters (taxonomy, search) remain stacked — unaffected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Debugging Tips
&lt;/h2&gt;

&lt;p&gt;Add temporary logging to trace exactly where things break:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bricks/posts/query_vars'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$element_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$element_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'HOOK FIRED — element: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$element_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'jsfb_query_id: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'jsfb_query_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'NOT SET'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nv"&gt;$query_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'query'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POST query: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nv"&gt;$sort_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_plain_query_sort_by_field'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sort_field extracted: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sort_field&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s1"&gt;'EMPTY'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// ... rest of your logic&lt;/span&gt;

    &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Final query_vars: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$query_vars&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your PHP error log (usually at &lt;code&gt;/wp-content/debug.log&lt;/code&gt; with &lt;code&gt;WP_DEBUG_LOG&lt;/code&gt; enabled) after each filter interaction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pass selected field to server&lt;/td&gt;
&lt;td&gt;JSF Radio filter with &lt;code&gt;_plain_query::sort_by_field&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read the value in PHP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$_POST['query']['_plain_query_sort_by_field']&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target the right loop&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;$settings['jsfb_query_id']&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter posts missing the field&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;meta_query&lt;/code&gt; with &lt;code&gt;EXISTS&lt;/code&gt; + &lt;code&gt;!= ''&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sort numerically descending&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;orderby=meta_value_num&lt;/code&gt; + &lt;code&gt;meta_key&lt;/code&gt; + &lt;code&gt;order=DESC&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prevent JSF from overriding&lt;/td&gt;
&lt;td&gt;Hook priority &lt;code&gt;99&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct hook signature&lt;/td&gt;
&lt;td&gt;4 params, last arg to add_filter is &lt;code&gt;4&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>wordpress</category>
      <category>jetsmartfilters</category>
      <category>bricksbuilder</category>
      <category>php</category>
    </item>
    <item>
      <title>How to Use the `bricks/form/custom_action` Hook in Bricks Builder</title>
      <dc:creator>Muazzam</dc:creator>
      <pubDate>Sat, 25 Apr 2026 02:41:00 +0000</pubDate>
      <link>https://dev.to/muazzami/how-to-use-the-bricksformcustomaction-hook-in-bricks-builder-1ca8</link>
      <guid>https://dev.to/muazzami/how-to-use-the-bricksformcustomaction-hook-in-bricks-builder-1ca8</guid>
      <description>&lt;h1&gt;
  
  
  How to Use the &lt;code&gt;bricks/form/custom_action&lt;/code&gt; Hook in Bricks Builder
&lt;/h1&gt;

&lt;p&gt;You can run custom PHP code after a Bricks Builder form submission using the &lt;code&gt;bricks/form/custom_action&lt;/code&gt; hook. This is ideal if you need to send emails, save entries, or connect with external services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Set Up the Form Action
&lt;/h2&gt;

&lt;p&gt;In your Bricks Builder form settings, go to "Actions after successful form submit" and select &lt;strong&gt;Custom&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Write Your PHP Logic
&lt;/h2&gt;

&lt;p&gt;Add this code to your theme's &lt;code&gt;functions.php&lt;/code&gt; file or use a WordPress code snippets plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Hook your function to the Bricks custom action&lt;/span&gt;
&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bricks/form/custom_action'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my_form_custom_action'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;my_form_custom_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get all submitted form fields as an array&lt;/span&gt;
    &lt;span class="nv"&gt;$fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_fields&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Example: ['name' =&amp;gt; 'Jane', 'email' =&amp;gt; 'jane@example.com']&lt;/span&gt;

    &lt;span class="c1"&gt;// Always check the form ID so your code only runs for the intended form&lt;/span&gt;
    &lt;span class="nv"&gt;$form_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'formId'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'formId'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$form_id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'replace_with_your_form_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Not the intended form — stop here&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Custom logic goes here!&lt;/span&gt;
    &lt;span class="c1"&gt;// Example: integrate with an API, save a post, or trigger a notification&lt;/span&gt;

    &lt;span class="c1"&gt;// Set a user-facing message after submit&lt;/span&gt;
    &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set_result&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'action'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'my_custom_action'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 'success', 'error', or 'info'&lt;/span&gt;
        &lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;esc_html__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Custom action executed!'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'your-textdomain'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Helpful Notes for Junior Devs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always check &lt;code&gt;formId&lt;/code&gt;&lt;/strong&gt; — This ensures your code only runs for the correct form, especially if you use custom actions on more than one form.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;$form-&amp;gt;get_fields()&lt;/code&gt;&lt;/strong&gt; returns every value submitted by the user. Use it to process, validate, or forward data to external tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;$form-&amp;gt;set_result()&lt;/code&gt;&lt;/strong&gt; controls what users see after submitting. Always provide friendly messages and handle errors gracefully.&lt;/li&gt;
&lt;li&gt;For external integrations (webhooks, APIs), use WordPress's built-in &lt;code&gt;wp_remote_post()&lt;/code&gt; rather than raw curl.&lt;/li&gt;
&lt;li&gt;Replace all placeholder form IDs and field keys with the actual values from your Bricks form setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example: Creating a WordPress Comment from Form Submission
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bricks/form/custom_action'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'create_comment_from_form'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;create_comment_from_form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_fields&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Only execute for the intended form&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'formId'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'myformid'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Prepare comment data from submitted fields&lt;/span&gt;
    &lt;span class="nv"&gt;$comment_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'comment_post_ID'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'postid'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'comment_author'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'comment_author_email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'comment_content'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'comment'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'comment_approved'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;wp_insert_comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;wp_slash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$comment_data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set_result&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'action'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'my_custom_action'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;esc_html__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Comment created!'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'your-textdomain'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set_result&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'action'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'my_custom_action'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;esc_html__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Something went wrong. Please try again.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'your-textdomain'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="c1"&gt;// Log the actual error server-side for debugging&lt;/span&gt;
        &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Bricks form error: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note on error messages:&lt;/strong&gt; Avoid exposing raw exception messages to users with &lt;code&gt;$e-&amp;gt;getMessage()&lt;/code&gt; — they can leak implementation details. Log errors server-side and show a generic message to the user instead.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>wordpress</category>
      <category>bricksbuilder</category>
      <category>php</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
