<?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>WPSifero — Programming courses and field notes</title>
    <link>https://wpsifero.com/</link>
    <description>Text courses and technical writing on Python, JavaScript and TypeScript, PHP, SQL, Go, WordPress and WooCommerce. No video: lessons you read, run and keep, from a first script to a service running in production.</description>
    <language>en</language>
    <lastBuildDate>Fri, 16 Oct 2026 20:57:27 +0000</lastBuildDate>
    <atom:link href="https://wpsifero.com/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>Building Order Tracking in WooCommerce Without a Tracking Subscription</title>
      <link>https://wpsifero.com/blog/delivery-tracking-without-a-saas-subscription/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/delivery-tracking-without-a-saas-subscription/</guid>
      <pubDate>Tue, 06 Oct 2026 09:00:00 +0000</pubDate>
      <category>WooCommerce &amp; Storefront</category>
      <dc:creator>Ana Ferreiro</dc:creator>
      <description>Most post-purchase support tickets are "where is my order". Here is the plugin architecture that answers them without shipping customer data to a tracking service.</description>
    </item>
    <item>
      <title>Tracking Pages That Work When JavaScript Does Not Run</title>
      <link>https://wpsifero.com/blog/tracking-pages-without-javascript/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/tracking-pages-without-javascript/</guid>
      <pubDate>Thu, 01 Oct 2026 09:00:00 +0000</pubDate>
      <category>Frontend &amp; Accessibility</category>
      <dc:creator>Priya Raghunathan</dc:creator>
      <description>A tracking page should answer the question before any script runs. That means a real form, a stable URL and a server-rendered result, with JavaScript added on top.</description>
    </item>
    <item>
      <title>WP-Cron Fires When a Visitor Arrives, Not When the Clock Says So</title>
      <link>https://wpsifero.com/blog/wp-cron-dispatch-under-real-traffic/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/wp-cron-dispatch-under-real-traffic/</guid>
      <pubDate>Tue, 29 Sep 2026 09:00:00 +0000</pubDate>
      <category>WordPress Internals</category>
      <dc:creator>Martin Kiel</dc:creator>
      <description>WP-Cron is a request-triggered scheduler. What wp-cron.php does on a quiet site versus a busy one, and where the pattern stops being enough.</description>
    </item>
    <item>
      <title>Idempotency Keys for a POST Endpoint That Must Survive Retries</title>
      <link>https://wpsifero.com/blog/idempotency-keys-for-rest-endpoints/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/idempotency-keys-for-rest-endpoints/</guid>
      <pubDate>Thu, 24 Sep 2026 09:00:00 +0000</pubDate>
      <category>APIs &amp; Integrations</category>
      <dc:creator>Devon Parker</dc:creator>
      <description>A client that times out cannot know whether the write committed, so it retries. A unique key and a stored response are what make that retry harmless.</description>
    </item>
    <item>
      <title>The wp_options Autoload Flag, and the Rows That Tax Every Request</title>
      <link>https://wpsifero.com/blog/wp-options-autoload-table-bloat/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/wp-options-autoload-table-bloat/</guid>
      <pubDate>Fri, 18 Sep 2026 09:00:00 +0000</pubDate>
      <category>Performance</category>
      <dc:creator>Martin Kiel</dc:creator>
      <description>Autoloaded options are read on every uncached request, not only on admin screens. Here is the query that finds the expensive rows and what to do with each kind.</description>
    </item>
    <item>
      <title>The Capability Checks People Forget at Three Entry Points</title>
      <link>https://wpsifero.com/blog/capability-checks-people-forget/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/capability-checks-people-forget/</guid>
      <pubDate>Tue, 15 Sep 2026 09:00:00 +0000</pubDate>
      <category>Security &amp; Hardening</category>
      <dc:creator>Tomas Lindqvist</dc:creator>
      <description>REST routes, admin-ajax handlers and admin-post actions each need their own guard. Here is the check each one requires and why the admin menu is not protection.</description>
    </item>
    <item>
      <title>The LCP Element on a Product Page Is Usually the Wrong Image</title>
      <link>https://wpsifero.com/blog/core-web-vitals-on-product-pages/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/core-web-vitals-on-product-pages/</guid>
      <pubDate>Fri, 11 Sep 2026 09:00:00 +0000</pubDate>
      <category>Performance</category>
      <dc:creator>Priya Raghunathan</dc:creator>
      <description>What LCP measures on a product page, why the hero image usually loses to the product title, and which shifts you can actually fix.</description>
    </item>
    <item>
      <title>What PHPStan Catches in a WordPress Plugin, and What It Misses</title>
      <link>https://wpsifero.com/blog/static-analysis-for-wordpress-plugins/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/static-analysis-for-wordpress-plugins/</guid>
      <pubDate>Fri, 04 Sep 2026 09:00:00 +0000</pubDate>
      <category>Tooling &amp; Workflow</category>
      <dc:creator>Tomas Lindqvist</dc:creator>
      <description>PHPStan finds undefined variables and wrong argument counts in plugin code. It cannot see a wrong capability name, a filter arity bug or a SQL injection.</description>
    </item>
    <item>
      <title>What Actually Changes When WooCommerce Orders Leave Postmeta</title>
      <link>https://wpsifero.com/blog/woocommerce-hpos-migration-notes/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/woocommerce-hpos-migration-notes/</guid>
      <pubDate>Mon, 31 Aug 2026 09:00:00 +0000</pubDate>
      <category>WooCommerce &amp; Storefront</category>
      <dc:creator>Ana Ferreiro</dc:creator>
      <description>On a store that has moved to HPOS, get_post_meta returns nothing and a direct wp_postmeta query returns stale rows. Here is what to replace both with.</description>
    </item>
    <item>
      <title>Why One update_post_meta() Call Empties Your Query Cache</title>
      <link>https://wpsifero.com/blog/wp-query-object-cache-misses/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/wp-query-object-cache-misses/</guid>
      <pubDate>Mon, 24 Aug 2026 09:00:00 +0000</pubDate>
      <category>WordPress Internals</category>
      <dc:creator>Martin Kiel</dc:creator>
      <description>WP_Query caches its results, then a single metadata write clears the whole group. How to read that behaviour with SAVEQUERIES and what to turn off.</description>
    </item>
    <item>
      <title>Live Regions for Order Status, Announced Once and Not Twice</title>
      <link>https://wpsifero.com/blog/accessible-live-regions-for-order-status/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/accessible-live-regions-for-order-status/</guid>
      <pubDate>Mon, 17 Aug 2026 09:00:00 +0000</pubDate>
      <category>Frontend &amp; Accessibility</category>
      <dc:creator>Priya Raghunathan</dc:creator>
      <description>A live region for order status fails in both directions. It either interrupts on every poll or never speaks at all, and the fix starts before the first update.</description>
    </item>
    <item>
      <title>Webhook Retries, and Why They Must Not Create a Second Order</title>
      <link>https://wpsifero.com/blog/webhook-retries-that-do-not-duplicate-orders/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/webhook-retries-that-do-not-duplicate-orders/</guid>
      <pubDate>Mon, 10 Aug 2026 09:00:00 +0000</pubDate>
      <category>APIs &amp; Integrations</category>
      <dc:creator>Devon Parker</dc:creator>
      <description>Senders retry because they cannot tell a slow receiver from a dead one. Here is the receiver that survives the fifth delivery of the same event.</description>
    </item>
    <item>
      <title>Redis or Memcached: What the WordPress Object Cache Needs</title>
      <link>https://wpsifero.com/blog/redis-and-memcached-as-object-caches/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/redis-and-memcached-as-object-caches/</guid>
      <pubDate>Fri, 07 Aug 2026 09:00:00 +0000</pubDate>
      <category>Hosting &amp; Infrastructure</category>
      <dc:creator>Tomas Lindqvist</dc:creator>
      <description>Both backends store the same WordPress keys. What decides the choice is eviction, restarts, shared servers and what wp_cache_flush really clears.</description>
    </item>
    <item>
      <title>A Nonce Proves Intent, and Never Permission to Act</title>
      <link>https://wpsifero.com/blog/nonces-are-not-authorisation/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/nonces-are-not-authorisation/</guid>
      <pubDate>Mon, 03 Aug 2026 09:00:00 +0000</pubDate>
      <category>Security &amp; Hardening</category>
      <dc:creator>Tomas Lindqvist</dc:creator>
      <description>WordPress nonces are short-lived CSRF tokens, not permission checks. Where they fail, why the failure looks like a logout, and what to pair them with.</description>
    </item>
    <item>
      <title>The Checkout That Forgets the Cart, and the Five Things That Cause It</title>
      <link>https://wpsifero.com/blog/woocommerce-checkout-session-loss/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/woocommerce-checkout-session-loss/</guid>
      <pubDate>Thu, 23 Jul 2026 09:00:00 +0000</pubDate>
      <category>WooCommerce &amp; Storefront</category>
      <dc:creator>Ana Ferreiro</dc:creator>
      <description>WooCommerce keeps the cart in a table row that a cookie points at. Here is the order to check when a customer's session disappears at checkout.</description>
    </item>
    <item>
      <title>When wp_postmeta Stops Being the Right Place for Plugin Data</title>
      <link>https://wpsifero.com/blog/wp-post-meta-vs-custom-tables/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/wp-post-meta-vs-custom-tables/</guid>
      <pubDate>Thu, 16 Jul 2026 09:00:00 +0000</pubDate>
      <category>WordPress Internals</category>
      <dc:creator>Martin Kiel</dc:creator>
      <description>Postmeta is correct for sparse per-object keys and wrong for range queries and aggregation. How to tell which one you have before the table outgrows the store.</description>
    </item>
    <item>
      <title>Building a Plugin Zip You Can Trust on Release Day</title>
      <link>https://wpsifero.com/blog/shipping-a-plugin-release-reliably/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/shipping-a-plugin-release-reliably/</guid>
      <pubDate>Thu, 09 Jul 2026 09:00:00 +0000</pubDate>
      <category>Tooling &amp; Workflow</category>
      <dc:creator>Devon Parker</dc:creator>
      <description>A release script turns a working tree into a zip you can verify. The build order, the checks worth automating, and the property that makes them mean anything.</description>
    </item>
    <item>
      <title>Where $wpdb-&gt;prepare() Protects You, and Where It Cannot</title>
      <link>https://wpsifero.com/blog/wpdb-prepared-statements-and-their-gaps/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/wpdb-prepared-statements-and-their-gaps/</guid>
      <pubDate>Thu, 02 Jul 2026 09:00:00 +0000</pubDate>
      <category>Security &amp; Hardening</category>
      <dc:creator>Tomas Lindqvist</dc:creator>
      <description>prepare() escapes each value and splices it into a query string. Here is what that contract covers, and the identifiers, wildcards and shapes it never touches.</description>
    </item>
    <item>
      <title>Versioning a Plugin REST API Without Breaking Other People's Code</title>
      <link>https://wpsifero.com/blog/versioning-a-plugin-rest-api/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/versioning-a-plugin-rest-api/</guid>
      <pubDate>Sat, 27 Jun 2026 09:00:00 +0000</pubDate>
      <category>APIs &amp; Integrations</category>
      <dc:creator>Devon Parker</dc:creator>
      <description>A version bump tells clients something changed but not what. How to tell additive changes from breaking ones, and how to retire a route on a schedule you publish.</description>
    </item>
    <item>
      <title>The Thundering Herd When a Flash Sale Cache Entry Expires</title>
      <link>https://wpsifero.com/blog/cache-stampedes-on-sale-day/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/cache-stampedes-on-sale-day/</guid>
      <pubDate>Thu, 18 Jun 2026 09:00:00 +0000</pubDate>
      <category>Performance</category>
      <dc:creator>Tomas Lindqvist</dc:creator>
      <description>When one popular cache entry expires under load, every request recomputes the same value. Here is how to spread that work, and where a lock makes it worse.</description>
    </item>
    <item>
      <title>Where Block Themes Break a Plugin's Front-End Output</title>
      <link>https://wpsifero.com/blog/block-themes-and-plugin-ui/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/block-themes-and-plugin-ui/</guid>
      <pubDate>Thu, 11 Jun 2026 09:00:00 +0000</pubDate>
      <category>Frontend &amp; Accessibility</category>
      <dc:creator>Priya Raghunathan</dc:creator>
      <description>A block theme generates the CSS your plugin output sits inside. Here are the three collisions, the tokens to inherit, and what you cannot design for.</description>
    </item>
    <item>
      <title>From WP-Cron to a Job Queue With Claims, Retries and Dead Letters</title>
      <link>https://wpsifero.com/blog/real-scheduling-with-system-cron/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/real-scheduling-with-system-cron/</guid>
      <pubDate>Thu, 04 Jun 2026 09:00:00 +0000</pubDate>
      <category>Hosting &amp; Infrastructure</category>
      <dc:creator>Tomas Lindqvist</dc:creator>
      <description>WP-Cron runs when traffic arrives. System cron fixes the trigger; a queue with an atomic claim, backoff retries and a dead-letter state fixes the work.</description>
    </item>
    <item>
      <title>Deciding What Invalidates What Before the Cache Bugs Arrive</title>
      <link>https://wpsifero.com/blog/object-cache-invalidation-strategy/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/object-cache-invalidation-strategy/</guid>
      <pubDate>Wed, 27 May 2026 09:00:00 +0000</pubDate>
      <category>Hosting &amp; Infrastructure</category>
      <dc:creator>Tomas Lindqvist</dc:creator>
      <description>An object cache with no invalidation plan serves stale data either on a timer or forever. How to choose between counter bumps, key deletes and versioned keys.</description>
    </item>
    <item>
      <title>Hook Priority Is a Coordination Mechanism, Not a Tuning Knob</title>
      <link>https://wpsifero.com/blog/hook-priority-and-ordering/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/hook-priority-and-ordering/</guid>
      <pubDate>Wed, 20 May 2026 09:00:00 +0000</pubDate>
      <category>WordPress Internals</category>
      <dc:creator>Martin Kiel</dc:creator>
      <description>Priority decides who runs when, and two plugins at 10 are arguing without knowing it. How WP_Hook orders callbacks and how to read the argument from your own site.</description>
    </item>
    <item>
      <title>Forms That Survive a Slow Network and an Impatient Tap</title>
      <link>https://wpsifero.com/blog/forms-that-survive-a-slow-network/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/forms-that-survive-a-slow-network/</guid>
      <pubDate>Wed, 13 May 2026 09:00:00 +0000</pubDate>
      <category>Frontend &amp; Accessibility</category>
      <dc:creator>Priya Raghunathan</dc:creator>
      <description>A form loses work in ordinary ways: the double submit, the response that never arrives, the validation error that clears the fields, the back button.</description>
    </item>
    <item>
      <title>Why WooCommerce Order Email Lands in Spam, and What Actually Fixes It</title>
      <link>https://wpsifero.com/blog/woocommerce-transactional-email-deliverability/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/woocommerce-transactional-email-deliverability/</guid>
      <pubDate>Thu, 07 May 2026 09:00:00 +0000</pubDate>
      <category>WooCommerce &amp; Storefront</category>
      <dc:creator>Ana Ferreiro</dc:creator>
      <description>WordPress sets the From header and leaves the envelope sender to the host. That mismatch is why SPF fails and DMARC buries the receipt.</description>
    </item>
    <item>
      <title>Threat Modelling a WooCommerce Plugin in Ninety Minutes</title>
      <link>https://wpsifero.com/blog/threat-modelling-a-wordpress-plugin/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/threat-modelling-a-wordpress-plugin/</guid>
      <pubDate>Tue, 28 Apr 2026 09:00:00 +0000</pubDate>
      <category>Security &amp; Hardening</category>
      <dc:creator>Tomas Lindqvist</dc:creator>
      <description>Four questions, seven trust boundaries and a table you can fill in as a team. What a ninety-minute threat model finds, and the class of bug it will never find.</description>
    </item>
    <item>
      <title>Build a WordPress Benchmark You Can Trust Before You Change Anything</title>
      <link>https://wpsifero.com/blog/measuring-wordpress-before-optimising/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/measuring-wordpress-before-optimising/</guid>
      <pubDate>Wed, 15 Apr 2026 09:00:00 +0000</pubDate>
      <category>Performance</category>
      <dc:creator>Martin Kiel</dc:creator>
      <description>A benchmark you cannot repeat is not evidence. Fix the dataset, fix the cache state, and report median and p95 server time instead of how the page felt.</description>
    </item>
    <item>
      <title>Closing the Gap Between a Bug That Will Not Reproduce and Production</title>
      <link>https://wpsifero.com/blog/local-environment-that-matches-production/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/local-environment-that-matches-production/</guid>
      <pubDate>Wed, 08 Apr 2026 09:00:00 +0000</pubDate>
      <category>Tooling &amp; Workflow</category>
      <dc:creator>Devon Parker</dc:creator>
      <description>PHP version, extensions, object cache, database engine and WP_DEBUG explain most bugs that happen on the server and not on your laptop. Matching six is enough.</description>
    </item>
    <item>
      <title>CSS Architecture for Admin Screens Your Plugin Does Not Own</title>
      <link>https://wpsifero.com/blog/css-architecture-for-plugin-screens/</link>
      <guid isPermaLink="true">https://wpsifero.com/blog/css-architecture-for-plugin-screens/</guid>
      <pubDate>Thu, 26 Mar 2026 09:00:00 +0000</pubDate>
      <category>Frontend &amp; Accessibility</category>
      <dc:creator>Priya Raghunathan</dc:creator>
      <description>Your admin stylesheet lands in a page that already holds core CSS, other plugins' CSS and a theme's admin CSS. Scoping and specificity are what you control.</description>
    </item>
  </channel>
</rss>
