<?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: KAZI</title>
    <description>The latest articles on DEV Community by KAZI (@kai01916).</description>
    <link>https://dev.to/kai01916</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4088712%2F8e46dcc9-ae6a-4265-b3a8-c14f0dd98824.png</url>
      <title>DEV Community: KAZI</title>
      <link>https://dev.to/kai01916</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kai01916"/>
    <language>en</language>
    <item>
      <title>Building a Custom REST API in WordPress the Right Way</title>
      <dc:creator>KAZI</dc:creator>
      <pubDate>Sat, 22 Aug 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/kai01916/building-a-custom-rest-api-in-wordpress-the-right-way-164m</link>
      <guid>https://dev.to/kai01916/building-a-custom-rest-api-in-wordpress-the-right-way-164m</guid>
      <description>&lt;p&gt;WordPress is often treated as a traditional CMS, but its REST API makes it possible to use WordPress as the backend for applications, dashboards, mobile clients, automation systems, and external services.&lt;/p&gt;

&lt;p&gt;The difficult part isn't registering an endpoint.&lt;/p&gt;

&lt;p&gt;The difficult part is designing the endpoint so that authentication, authorization, validation, error handling, and data access are all handled correctly.&lt;/p&gt;

&lt;p&gt;A production API needs a contract.&lt;/p&gt;

&lt;p&gt;It needs to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who can access it&lt;/li&gt;
&lt;li&gt;What data they can access&lt;/li&gt;
&lt;li&gt;What input is accepted&lt;/li&gt;
&lt;li&gt;What output is returned&lt;/li&gt;
&lt;li&gt;What happens when something fails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a practical approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Register a Custom Route
&lt;/h2&gt;

&lt;p&gt;A basic WordPress REST API route can be registered with &lt;code&gt;register_rest_route()&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_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rest_api_init'&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;register_rest_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'myplugin/v1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'methods'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WP_REST_Server&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;READABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'myplugin_get_posts'&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;p&gt;This creates an endpoint similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/wp-json/myplugin/v1/posts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The namespace matters.&lt;/p&gt;

&lt;p&gt;Using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;myplugin/v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gives the API a version boundary.&lt;/p&gt;

&lt;p&gt;If the response structure changes later, a new version can be introduced without immediately breaking existing clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't Put Authorization Inside the Callback
&lt;/h2&gt;

&lt;p&gt;A common beginner implementation does everything inside the callback:&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="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;myplugin_get_posts&lt;/span&gt;&lt;span class="p"&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="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;current_user_can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'manage_options'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'forbidden'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'Access denied'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;403&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;// Query data...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but WordPress provides a cleaner place for the permission decision.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;permission_callback&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;register_rest_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'myplugin/v1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'methods'&lt;/span&gt;             &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WP_REST_Server&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;READABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'callback'&lt;/span&gt;            &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'myplugin_get_posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'permission_callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;current_user_can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'manage_options'&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;p&gt;Now the endpoint has a clearer separation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   ↓
Permission check
   ↓
Callback
   ↓
Data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That separation becomes increasingly valuable as an API grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication Is Not Authorization
&lt;/h2&gt;

&lt;p&gt;These concepts are easy to mix up.&lt;/p&gt;

&lt;p&gt;Authentication asks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who are you?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Authorization asks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are you allowed to perform this operation?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A valid authenticated user should not automatically receive access to every endpoint.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authenticated user
      ↓
Role
      ↓
Capability
      ↓
Resource ownership
      ↓
Allowed operation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The permission decision should reflect the actual operation.&lt;/p&gt;

&lt;p&gt;A user might be allowed to read a resource but not delete it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate Input
&lt;/h2&gt;

&lt;p&gt;Never assume API input is valid.&lt;/p&gt;

&lt;p&gt;Suppose an endpoint accepts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;?page=2
&amp;amp;per_page=20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validate the values.&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;$page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;absint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'page'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nv"&gt;$per_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;absint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'per_page'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&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="nv"&gt;$page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$per_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;max&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="nv"&gt;$per_page&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does two useful things.&lt;/p&gt;

&lt;p&gt;It converts input into an expected type.&lt;/p&gt;

&lt;p&gt;It also places an upper limit on the amount of data requested.&lt;/p&gt;

&lt;p&gt;Without limits, a client could request an unreasonable number of records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanitization and Validation Are Different
&lt;/h2&gt;

&lt;p&gt;These terms are often used interchangeably.&lt;/p&gt;

&lt;p&gt;They shouldn't be.&lt;/p&gt;

&lt;p&gt;Validation asks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this value acceptable?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sanitization asks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can this value be safely normalized for its intended use?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, an email address can be validated:&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;$email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_param&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="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="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'invalid_email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'A valid email address is required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;400&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;p&gt;The exact approach depends on the data type and where the value will be used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't Trust IDs
&lt;/h2&gt;

&lt;p&gt;Suppose the endpoint receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/post?id=123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't assume post 123 is something the current user should access.&lt;/p&gt;

&lt;p&gt;The endpoint should check:&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;$post_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;absint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post_id&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="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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'not_found'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'Post not found'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;404&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;p&gt;Then apply whatever authorization rules the application requires.&lt;/p&gt;

&lt;p&gt;The existence of a resource and permission to access it are separate questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Return Structured Errors
&lt;/h2&gt;

&lt;p&gt;Avoid returning random strings from different parts of the API.&lt;/p&gt;

&lt;p&gt;A consistent error structure makes client-side development much easier.&lt;/p&gt;

&lt;p&gt;WordPress provides &lt;code&gt;WP_Error&lt;/code&gt; for this purpose.&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'invalid_request'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'The requested resource is invalid.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'field'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'id'&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;p&gt;Now the client has a machine-readable error code and HTTP status.&lt;/p&gt;

&lt;p&gt;That is much better than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Something went wrong.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Keep the Response Contract Stable
&lt;/h2&gt;

&lt;p&gt;Suppose version one returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then version two suddenly changes it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"post_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Existing clients can break.&lt;/p&gt;

&lt;p&gt;API responses should therefore be treated as contracts.&lt;/p&gt;

&lt;p&gt;If a breaking change is necessary, version the endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/myplugin/v1/posts
/myplugin/v2/posts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't necessarily need a new version for every small change.&lt;/p&gt;

&lt;p&gt;But breaking response changes deserve careful handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't Return Everything
&lt;/h2&gt;

&lt;p&gt;A database object can contain much more information than the client needs.&lt;/p&gt;

&lt;p&gt;Returning everything creates unnecessary coupling.&lt;/p&gt;

&lt;p&gt;Instead, build a deliberate response:&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'id'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'title'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_the_title&lt;/span&gt;&lt;span class="p"&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;'url'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_permalink&lt;/span&gt;&lt;span class="p"&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;'excerpt'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_the_excerpt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&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;p&gt;This gives the client a stable, predictable structure.&lt;/p&gt;

&lt;p&gt;It also reduces the amount of data transferred.&lt;/p&gt;

&lt;h2&gt;
  
  
  Think About Pagination Early
&lt;/h2&gt;

&lt;p&gt;An endpoint that returns ten posts today may return ten thousand posts next year.&lt;/p&gt;

&lt;p&gt;Design pagination from the beginning.&lt;/p&gt;

&lt;p&gt;A common approach is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;?page=1&amp;amp;per_page=20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then return metadata that helps the client understand the collection.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"per_page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact response design depends on the client.&lt;/p&gt;

&lt;p&gt;The important thing is avoiding an endpoint whose response size grows without a limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache Expensive API Responses
&lt;/h2&gt;

&lt;p&gt;If an endpoint repeatedly performs an expensive query, caching can help.&lt;/p&gt;

&lt;p&gt;WordPress transients can work for many cases:&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;$cache_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'myplugin_posts_page_1'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_transient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cache_key&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="kc"&gt;false&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;expensive_query&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;set_transient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;$cache_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;HOUR_IN_SECONDS&lt;/span&gt;
    &lt;span class="p"&gt;);&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;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caching strategy depends on how frequently the underlying data changes.&lt;/p&gt;

&lt;p&gt;Don't cache everything automatically.&lt;/p&gt;

&lt;p&gt;Cache data where repeated computation is actually expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Log Failures, Not Sensitive Data
&lt;/h2&gt;

&lt;p&gt;Logs are useful when debugging API failures.&lt;/p&gt;

&lt;p&gt;But logging entire requests can accidentally expose credentials, tokens, cookies, or personal information.&lt;/p&gt;

&lt;p&gt;A better log entry might contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request ID
Endpoint
HTTP method
User ID
Status code
Execution time
Error code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avoid dumping complete authorization headers or sensitive request payloads into logs.&lt;/p&gt;

&lt;p&gt;Debugging data should help diagnose the problem without creating another security problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design for Failure
&lt;/h2&gt;

&lt;p&gt;External API consumers will send unexpected requests.&lt;/p&gt;

&lt;p&gt;Networks will fail.&lt;/p&gt;

&lt;p&gt;Database queries will fail.&lt;/p&gt;

&lt;p&gt;Permissions will be wrong.&lt;/p&gt;

&lt;p&gt;Third-party services will time out.&lt;/p&gt;

&lt;p&gt;A good API expects failure.&lt;/p&gt;

&lt;p&gt;For each endpoint, define:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Success
Invalid input
Unauthenticated
Unauthorized
Not found
Rate limited
Server error
External dependency failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes both the backend and client more predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Endpoint Architecture
&lt;/h2&gt;

&lt;p&gt;For a larger plugin, I like thinking about an endpoint in layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REST Route
    ↓
Permission Callback
    ↓
Input Validation
    ↓
Service Layer
    ↓
Repository / WordPress Data
    ↓
Response Transformer
    ↓
REST Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact architecture can be simpler for small plugins.&lt;/p&gt;

&lt;p&gt;But the separation becomes valuable as functionality grows.&lt;/p&gt;

&lt;p&gt;It prevents the REST callback from turning into a 500-line function that handles authentication, database queries, external APIs, formatting, and error handling all at once.&lt;/p&gt;

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

&lt;p&gt;A REST API isn't successful because its endpoint returns JSON.&lt;/p&gt;

&lt;p&gt;It is successful when another application can depend on that endpoint without needing to understand the internal implementation.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear routes&lt;/li&gt;
&lt;li&gt;Stable contracts&lt;/li&gt;
&lt;li&gt;Explicit permissions&lt;/li&gt;
&lt;li&gt;Validated input&lt;/li&gt;
&lt;li&gt;Predictable errors&lt;/li&gt;
&lt;li&gt;Controlled response sizes&lt;/li&gt;
&lt;li&gt;Sensible caching&lt;/li&gt;
&lt;li&gt;Useful logging&lt;/li&gt;
&lt;li&gt;Versioning where necessary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those pieces are in place, WordPress becomes much more than a content management system.&lt;/p&gt;

&lt;p&gt;It becomes a capable application backend.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>webdev</category>
      <category>restapi</category>
      <category>php</category>
    </item>
    <item>
      <title>Advanced WordPress Performance Optimization: A Developer's Guide</title>
      <dc:creator>KAZI</dc:creator>
      <pubDate>Fri, 21 Aug 2026 18:53:05 +0000</pubDate>
      <link>https://dev.to/kai01916/advanced-wordpress-performance-optimization-a-developers-guide-2a73</link>
      <guid>https://dev.to/kai01916/advanced-wordpress-performance-optimization-a-developers-guide-2a73</guid>
      <description>&lt;p&gt;WordPress performance problems are rarely caused by one thing.&lt;/p&gt;

&lt;p&gt;A slow site might have an inefficient database query, excessive JavaScript, a poorly configured cache, slow third-party APIs, expensive PHP execution, or a combination of several smaller problems.&lt;/p&gt;

&lt;p&gt;That is why blindly installing another caching plugin is often the wrong first move.&lt;/p&gt;

&lt;p&gt;Performance work should start with measurement.&lt;/p&gt;

&lt;p&gt;This guide looks at WordPress performance from the developer's perspective, from the server and PHP layer through database queries, assets, caching, and external services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start With the Request Lifecycle
&lt;/h2&gt;

&lt;p&gt;Before optimizing anything, understand what happens when a visitor requests a WordPress page.&lt;/p&gt;

&lt;p&gt;A simplified request looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   ↓
DNS
   ↓
Web Server
   ↓
PHP
   ↓
WordPress Bootstrap
   ↓
Plugins
   ↓
Theme
   ↓
Database
   ↓
HTML Response
   ↓
Browser
   ↓
CSS / JS / Images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are multiple places where latency can appear.&lt;/p&gt;

&lt;p&gt;If the server takes 800 ms before sending the first byte, optimizing a 200 KB image won't solve the primary problem.&lt;/p&gt;

&lt;p&gt;If PHP responds quickly but the browser spends three seconds executing JavaScript, server optimization alone won't fix the experience.&lt;/p&gt;

&lt;p&gt;Performance optimization is therefore a diagnosis problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure Before Changing Code
&lt;/h2&gt;

&lt;p&gt;A useful baseline should include more than a single performance score.&lt;/p&gt;

&lt;p&gt;Look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time to First Byte&lt;/li&gt;
&lt;li&gt;Largest Contentful Paint&lt;/li&gt;
&lt;li&gt;Interaction to Next Paint&lt;/li&gt;
&lt;li&gt;Cumulative Layout Shift&lt;/li&gt;
&lt;li&gt;Total Blocking Time&lt;/li&gt;
&lt;li&gt;Database query time&lt;/li&gt;
&lt;li&gt;PHP execution time&lt;/li&gt;
&lt;li&gt;Number of HTTP requests&lt;/li&gt;
&lt;li&gt;JavaScript execution&lt;/li&gt;
&lt;li&gt;Image size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A score can tell you that something is wrong.&lt;/p&gt;

&lt;p&gt;It doesn't necessarily tell you why.&lt;/p&gt;

&lt;p&gt;For server-side debugging, tools such as Query Monitor can expose database queries, hooks, HTTP requests, PHP errors, and other information directly inside WordPress.&lt;/p&gt;

&lt;p&gt;That is much more useful than changing five settings and hoping the score improves.&lt;/p&gt;

&lt;h2&gt;
  
  
  PHP Is Often the First Hidden Bottleneck
&lt;/h2&gt;

&lt;p&gt;WordPress plugins can add significant work to every request.&lt;/p&gt;

&lt;p&gt;Consider this pattern:&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_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'init'&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_posts&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'numberposts'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'post_type'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;// Process every post...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code may work perfectly on a development site.&lt;/p&gt;

&lt;p&gt;It becomes a problem when the site contains thousands of posts.&lt;/p&gt;

&lt;p&gt;Loading large datasets on every request increases memory usage and execution time.&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this operation really need to run during every request?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Often the answer is no.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move Expensive Work Out of the Request
&lt;/h2&gt;

&lt;p&gt;Suppose a plugin needs to scan 10,000 posts.&lt;/p&gt;

&lt;p&gt;Doing that during a visitor's HTTP request is a bad design.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visitor request
      ↓
Fast response

Background job
      ↓
Process 100 records
      ↓
Save state
      ↓
Process next batch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Batch processing is especially useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content analysis&lt;/li&gt;
&lt;li&gt;Link scanning&lt;/li&gt;
&lt;li&gt;Product monitoring&lt;/li&gt;
&lt;li&gt;Large database migrations&lt;/li&gt;
&lt;li&gt;API synchronization&lt;/li&gt;
&lt;li&gt;SEO audits&lt;/li&gt;
&lt;li&gt;Image processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The user should not have to wait for work that doesn't affect the current page.&lt;/p&gt;

&lt;h2&gt;
  
  
  WordPress Cron Has Limits
&lt;/h2&gt;

&lt;p&gt;WP-Cron is useful, but it isn't a traditional system cron.&lt;/p&gt;

&lt;p&gt;By default, WordPress schedules cron execution based on site traffic.&lt;/p&gt;

&lt;p&gt;That means a low-traffic site may not execute scheduled tasks exactly when expected.&lt;/p&gt;

&lt;p&gt;For important background jobs, a real server-side cron can provide more predictable execution.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server Cron
     ↓
wp-cron.php
     ↓
WordPress scheduled tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact setup depends on the hosting environment.&lt;/p&gt;

&lt;p&gt;The important architectural idea is separating scheduled processing from normal page requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Queries Need Attention
&lt;/h2&gt;

&lt;p&gt;One of the easiest ways to create a slow WordPress site is to make the database do unnecessary work.&lt;/p&gt;

&lt;p&gt;For example:&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;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_Query&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'post_type'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'posts_per_page'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Loading every matching post may be fine for a small dataset.&lt;/p&gt;

&lt;p&gt;It becomes expensive as the database grows.&lt;/p&gt;

&lt;p&gt;Pagination, targeted queries, indexed fields, and smaller datasets can make a substantial difference.&lt;/p&gt;

&lt;p&gt;Also watch for queries inside loops.&lt;/p&gt;

&lt;p&gt;This pattern deserves suspicion:&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="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_post_meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'some_key'&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may generate more database work than expected.&lt;/p&gt;

&lt;p&gt;The correct optimization depends on the actual query behavior, which is why profiling matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Object Caching Is Different From Page Caching
&lt;/h2&gt;

&lt;p&gt;These two are often mixed together.&lt;/p&gt;

&lt;p&gt;Page caching stores generated responses.&lt;/p&gt;

&lt;p&gt;Object caching stores frequently requested data.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Page Cache
Request → HTML

Object Cache
WordPress → Cached database/object result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They solve different problems.&lt;/p&gt;

&lt;p&gt;A page cache can eliminate much of the PHP work for anonymous visitors.&lt;/p&gt;

&lt;p&gt;Object caching can reduce repeated database operations when WordPress still needs to execute PHP.&lt;/p&gt;

&lt;p&gt;Both can be useful.&lt;/p&gt;

&lt;p&gt;Neither replaces good application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  External APIs Can Destroy Performance
&lt;/h2&gt;

&lt;p&gt;Modern WordPress plugins often communicate with external services.&lt;/p&gt;

&lt;p&gt;Amazon APIs.&lt;/p&gt;

&lt;p&gt;AI providers.&lt;/p&gt;

&lt;p&gt;Analytics systems.&lt;/p&gt;

&lt;p&gt;Payment services.&lt;/p&gt;

&lt;p&gt;Image generation APIs.&lt;/p&gt;

&lt;p&gt;Search APIs.&lt;/p&gt;

&lt;p&gt;A remote request can easily become the slowest part of a WordPress operation.&lt;/p&gt;

&lt;p&gt;Never make an unnecessary external API request during the visitor's page request.&lt;/p&gt;

&lt;p&gt;Instead, cache the result whenever possible.&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;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_transient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'remote_data'&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="kc"&gt;false&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_remote_data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;set_transient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'remote_data'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;HOUR_IN_SECONDS&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;p&gt;Now the external service doesn't have to be contacted on every request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be Careful With JavaScript
&lt;/h2&gt;

&lt;p&gt;A page can have a fast PHP response and still feel slow.&lt;/p&gt;

&lt;p&gt;Large JavaScript bundles can block rendering or delay interaction.&lt;/p&gt;

&lt;p&gt;Audit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bundle size&lt;/li&gt;
&lt;li&gt;Third-party scripts&lt;/li&gt;
&lt;li&gt;Unused JavaScript&lt;/li&gt;
&lt;li&gt;Script loading order&lt;/li&gt;
&lt;li&gt;Long-running event handlers&lt;/li&gt;
&lt;li&gt;DOM manipulation&lt;/li&gt;
&lt;li&gt;Analytics scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WordPress developers should also avoid loading plugin assets globally when they are only needed on specific screens.&lt;/p&gt;

&lt;p&gt;Instead of:&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;wp_enqueue_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my-plugin-script'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;on every page, conditionally load assets where possible.&lt;/p&gt;

&lt;p&gt;For example:&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is_singular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;wp_enqueue_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my-product-script'&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;p&gt;The exact condition depends on the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Images Still Matter
&lt;/h2&gt;

&lt;p&gt;Images remain one of the easiest performance wins.&lt;/p&gt;

&lt;p&gt;Use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Appropriate dimensions&lt;/li&gt;
&lt;li&gt;Modern formats where supported&lt;/li&gt;
&lt;li&gt;Responsive images&lt;/li&gt;
&lt;li&gt;Lazy loading where appropriate&lt;/li&gt;
&lt;li&gt;Compression&lt;/li&gt;
&lt;li&gt;Correct aspect ratios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But don't blindly compress everything.&lt;/p&gt;

&lt;p&gt;A 40 KB icon and a 2 MB hero image are different problems.&lt;/p&gt;

&lt;p&gt;Measure the largest assets first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't Ignore Third-Party Scripts
&lt;/h2&gt;

&lt;p&gt;A website may contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analytics
Advertising
Chat
Heatmaps
Social widgets
Affiliate widgets
Tracking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every external dependency has a cost.&lt;/p&gt;

&lt;p&gt;You don't fully control the response time of a third-party server.&lt;/p&gt;

&lt;p&gt;That means third-party scripts should be treated as dependencies with performance consequences.&lt;/p&gt;

&lt;p&gt;Load them only where they provide enough value to justify their cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a Performance Budget
&lt;/h2&gt;

&lt;p&gt;A useful development practice is to define limits.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial JavaScript:      &amp;lt; X KB
Hero image:              &amp;lt; X KB
External requests:       &amp;lt; X
Database queries:        &amp;lt; X
Server response:         &amp;lt; X ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact values depend on the project.&lt;/p&gt;

&lt;p&gt;The point is to stop performance from becoming a vague goal.&lt;/p&gt;

&lt;p&gt;Once there is a budget, regressions become measurable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimize One Layer at a Time
&lt;/h2&gt;

&lt;p&gt;A common mistake is changing everything simultaneously.&lt;/p&gt;

&lt;p&gt;Install a cache plugin.&lt;/p&gt;

&lt;p&gt;Change the CDN.&lt;/p&gt;

&lt;p&gt;Minify everything.&lt;/p&gt;

&lt;p&gt;Optimize the database.&lt;/p&gt;

&lt;p&gt;Remove plugins.&lt;/p&gt;

&lt;p&gt;Change the theme.&lt;/p&gt;

&lt;p&gt;Then run the test again.&lt;/p&gt;

&lt;p&gt;If performance improves, you don't know which change helped.&lt;/p&gt;

&lt;p&gt;A better workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Measure
  ↓
Identify bottleneck
  ↓
Change one variable
  ↓
Measure again
  ↓
Keep or revert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is slower for the first hour.&lt;/p&gt;

&lt;p&gt;It is much faster when debugging a complex site.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Developer's Rule
&lt;/h2&gt;

&lt;p&gt;Don't optimize what you haven't measured.&lt;/p&gt;

&lt;p&gt;A slow WordPress site is not a single problem.&lt;/p&gt;

&lt;p&gt;It is a chain of components.&lt;/p&gt;

&lt;p&gt;Find the slow component.&lt;/p&gt;

&lt;p&gt;Measure it.&lt;/p&gt;

&lt;p&gt;Fix it.&lt;/p&gt;

&lt;p&gt;Measure again.&lt;/p&gt;

&lt;p&gt;Then move to the next bottleneck.&lt;/p&gt;

&lt;p&gt;That mindset is more valuable than any particular caching configuration because the underlying architecture changes from project to project.&lt;/p&gt;

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