<?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>Forem: Kyle Niemiec</title>
    <description>The latest articles on Forem by Kyle Niemiec (@kyle-niemiec).</description>
    <link>https://forem.com/kyle-niemiec</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%2F3806865%2Fe0b75551-3923-47b7-a4e6-4d879e9c970a.jpg</url>
      <title>Forem: Kyle Niemiec</title>
      <link>https://forem.com/kyle-niemiec</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kyle-niemiec"/>
    <language>en</language>
    <item>
      <title>How the WPPF Update Helper Connects Private Plugins to Native WordPress Updates</title>
      <dc:creator>Kyle Niemiec</dc:creator>
      <pubDate>Tue, 24 Mar 2026 14:41:29 +0000</pubDate>
      <link>https://forem.com/kyle-niemiec/how-the-wppf-update-helper-connects-private-plugins-to-native-wordpress-updates-hpb</link>
      <guid>https://forem.com/kyle-niemiec/how-the-wppf-update-helper-connects-private-plugins-to-native-wordpress-updates-hpb</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In the previous article, I wrote about the &lt;strong&gt;WP Plugin Update Server&lt;/strong&gt;, a plugin that allows a WordPress site to act as a self-hosted update server for privately distributed plugins.&lt;/p&gt;

&lt;p&gt;But the server is only one half of the system.&lt;/p&gt;

&lt;p&gt;A private plugin still needs a way to participate in WordPress’ native update workflow. It needs to:&lt;/p&gt;

&lt;p&gt;check whether a new version exists&lt;br&gt;
show update notices in the admin&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;populate the “&lt;strong&gt;View Details&lt;/strong&gt;” modal for plugins
download protected packages when needed
install correctly even when GitHub ZIP archives use inconsistent folder names&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s the role of the &lt;strong&gt;WPPF Update Helper&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is the client-side package that lives inside the plugin being distributed and connects that plugin to a configured update server.&lt;/p&gt;
&lt;h1&gt;
  
  
  Why the Helper Exists
&lt;/h1&gt;

&lt;p&gt;The update server exposes metadata, but WordPress does not automatically know how to use that metadata for a privately hosted plugin.&lt;/p&gt;

&lt;p&gt;By default, WordPress expects plugin update information to come from the official WordPress.org infrastructure.&lt;/p&gt;

&lt;p&gt;The Update Helper bridges that gap by hooking into WordPress’ normal update lifecycle and translating remote update responses into the structures WordPress already expects.&lt;/p&gt;

&lt;p&gt;In other words, it makes a privately hosted plugin behave like a normal updatable plugin inside the WordPress admin.&lt;/p&gt;
&lt;h1&gt;
  
  
  How the Pieces Fit Together
&lt;/h1&gt;

&lt;p&gt;The overall flow looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5grij7eyai29j6enr8w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5grij7eyai29j6enr8w.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More specifically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A WordPress plugin includes the WPPF Update Helper.&lt;/li&gt;
&lt;li&gt;The plugin registers with the helper.&lt;/li&gt;
&lt;li&gt;During WordPress update checks, the helper requests update metadata from the server.&lt;/li&gt;
&lt;li&gt;If a newer version exists, the helper injects that response into the normal plugin update transient.&lt;/li&gt;
&lt;li&gt;WordPress displays the private plugin update in the admin as if it were part of the standard update system.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The helper also supports the plugin details modal and the package download/install process.&lt;/p&gt;
&lt;h1&gt;
  
  
  Registering a Plugin With the Helper
&lt;/h1&gt;

&lt;p&gt;A host plugin registers itself with the helper by providing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the plugin slug&lt;/li&gt;
&lt;li&gt;the update server URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From there, the helper can determine which plugins it should manage during update checks.&lt;/p&gt;

&lt;p&gt;One implementation detail I like here is that the helper uses a &lt;strong&gt;WP filter-based registry&lt;/strong&gt; rather than relying on a single mutable global store. That makes it easy for plugins to opt in while keeping the registration flow simple.&lt;/p&gt;

&lt;p&gt;The implementation assumes the primary plugin file follows the standard naming pattern 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="n"&gt;slug&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This assumption is important because it affects both version lookup and post-install folder normalization.&lt;/p&gt;

&lt;h1&gt;
  
  
  Hooking Into WordPress Update Checks
&lt;/h1&gt;

&lt;p&gt;The core of the update flow happens through the normal WordPress plugin update transient.&lt;/p&gt;

&lt;p&gt;The helper hooks into:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;When WordPress prepares the plugin update transient, the helper inspects the registered plugins, groups them by domain, and requests remote update metadata from the configured server.&lt;/p&gt;

&lt;p&gt;The server endpoint used for that flow is:&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/wppf/api/plugin-updates/transients
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the response indicates that a remote version is newer than the installed version, the helper writes that response into:&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;$transient&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that point, WordPress takes over and displays the update through the normal admin interface.&lt;/p&gt;

&lt;p&gt;This is one of the most useful aspects of the design: the helper doesn’t create a parallel update UI. It plugs into the existing one.&lt;/p&gt;

&lt;h1&gt;
  
  
  Supporting the “View Details” Modal
&lt;/h1&gt;

&lt;p&gt;WordPress also expects plugin metadata when a user clicks View details in the admin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5z505538b1w7i59yomam.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5z505538b1w7i59yomam.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To support that, the helper hooks into:&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="n"&gt;plugins_api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When WordPress requests plugin information for a registered plugin, the helper calls the update server’s plugin information endpoint:&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/wppf/api/plugin-updates/plugins-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It then returns that response in the shape WordPress expects for the plugin details modal.&lt;/p&gt;

&lt;p&gt;This means privately distributed plugins can provide a more complete native experience, including things like descriptions, changelogs, icons, and other plugin metadata.&lt;/p&gt;

&lt;h1&gt;
  
  
  Handling Private Package Downloads
&lt;/h1&gt;

&lt;p&gt;One of the more interesting parts of the helper is how it handles protected package downloads.&lt;/p&gt;

&lt;p&gt;If an update response includes a token, the helper intercepts the package download step using:&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="n"&gt;upgrader_pre_download&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that point it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;reads the update response from the update transient&lt;/li&gt;
&lt;li&gt;checks whether a token is present&lt;/li&gt;
&lt;li&gt;retrieves the configured SSL key from WPPF settings&lt;/li&gt;
&lt;li&gt;decrypts the token&lt;/li&gt;
&lt;li&gt;performs an authenticated request for the package&lt;/li&gt;
&lt;li&gt;writes the ZIP to a temporary file&lt;/li&gt;
&lt;li&gt;returns the local temp path to the WordPress upgrader&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This allows the plugin update flow to work even when the package is protected behind authentication.&lt;/p&gt;

&lt;p&gt;A nice implementation detail here is that the token is decrypted only at install time rather than being stored in a persistently usable form inside the plugin.&lt;/p&gt;

&lt;h1&gt;
  
  
  Fixing GitHub ZIP Folder Name Problems
&lt;/h1&gt;

&lt;p&gt;If you have ever installed a package generated from a GitHub ZIP archive, you’ve probably run into the folder naming problem.&lt;/p&gt;

&lt;p&gt;GitHub-generated ZIP files often extract into a directory name that does not match the canonical plugin folder WordPress expects, appending the primary branch name to the end of the plugin directory name.&lt;/p&gt;

&lt;p&gt;The helper works around this by hooking into:&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="n"&gt;upgrader_post_install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, it renames the extracted directory to the expected plugin slug folder inside &lt;code&gt;wp-content/plugins&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That makes GitHub-based releases much easier to use in a normal WordPress plugin workflow.&lt;/p&gt;

&lt;h1&gt;
  
  
  Interesting Implementation Details
&lt;/h1&gt;

&lt;p&gt;As a side note, some of the additional details surrounding the functionality can be found below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it batches update checks by domain, which reduces duplicate remote requests when multiple plugins use the same host&lt;/li&gt;
&lt;li&gt;it falls back to plugin header inspection if the transient does not already contain the local version&lt;/li&gt;
&lt;li&gt;it will first use project releases for versioning before falling back to commit versioning&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Why This Part of the Ecosystem Matters
&lt;/h1&gt;

&lt;p&gt;The update server gets most of the attention because it exposes the metadata and distribution endpoints.&lt;/p&gt;

&lt;p&gt;But the helper is what makes the full experience possible inside an actual plugin.&lt;/p&gt;

&lt;p&gt;Without it, the server is just an API.&lt;/p&gt;

&lt;p&gt;With it, a private plugin can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;appear in normal WordPress update checks&lt;/li&gt;
&lt;li&gt;show version updates in the admin&lt;/li&gt;
&lt;li&gt;support the details modal&lt;/li&gt;
&lt;li&gt;download protected packages&lt;/li&gt;
&lt;li&gt;install more cleanly from GitHub-based sources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is what turns the system into a usable private plugin distribution workflow rather than just a backend service.&lt;/p&gt;

&lt;h1&gt;
  
  
  Explore the Project
&lt;/h1&gt;

&lt;p&gt;If you want to inspect the implementation, you can explore the helper here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://github.com/kyle-niemiec/wppf-update-helper" rel="noopener noreferrer"&gt;https://github.com/kyle-niemiec/wppf-update-helper&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the update server it connects to is here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update Server&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://github.com/kyle-niemiec/wp-plugin-update-server" rel="noopener noreferrer"&gt;https://github.com/kyle-niemiec/wp-plugin-update-server&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Part of the WPPF ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/kyle-niemiec/wp-plugin-framework" rel="noopener noreferrer"&gt;WordPress Plugin Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kyle-niemiec/wppf-test-plugin" rel="noopener noreferrer"&gt;WPPF Test Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kyle-niemiec/wp-plugin-update-server" rel="noopener noreferrer"&gt;Plugin Update Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kyle-niemiec/wppf-update-helper" rel="noopener noreferrer"&gt;WPPF Update Helper&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Documentation&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://wp-plugin-framework.codeflower.io" rel="noopener noreferrer"&gt;https://wp-plugin-framework.codeflower.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>wordpress</category>
      <category>opensource</category>
      <category>php</category>
    </item>
    <item>
      <title>Running Your Own WordPress Plugin Update Server Using GitHub</title>
      <dc:creator>Kyle Niemiec</dc:creator>
      <pubDate>Tue, 17 Mar 2026 14:45:39 +0000</pubDate>
      <link>https://forem.com/kyle-niemiec/running-your-own-wordpress-plugin-update-server-using-github-3lga</link>
      <guid>https://forem.com/kyle-niemiec/running-your-own-wordpress-plugin-update-server-using-github-3lga</guid>
      <description>&lt;p&gt;WordPress plugins typically receive automatic updates through the official WordPress plugin repository.&lt;/p&gt;

&lt;p&gt;But many developers build plugins that aren’t meant for the public directory. These might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;internal company plugins&lt;/li&gt;
&lt;li&gt;premium plugins&lt;/li&gt;
&lt;li&gt;client-specific functionality&lt;/li&gt;
&lt;li&gt;plugins distributed privately through GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these cases, developers still want the convenience of the &lt;strong&gt;native WordPress update system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The challenge is that WordPress expects updates to come from the WordPress.org infrastructure.&lt;/p&gt;

&lt;p&gt;To solve this problem, I built a plugin that allows a WordPress site to act as its own &lt;strong&gt;plugin update server&lt;/strong&gt;, using GitHub repositories as the source of truth.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Problem With Private Plugin Updates
&lt;/h1&gt;

&lt;p&gt;When WordPress checks for plugin updates, it builds a site transient called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;update_plugins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This transient contains information about available plugin versions.&lt;/p&gt;

&lt;p&gt;WordPress also calls the &lt;code&gt;plugins_api()&lt;/code&gt; endpoint to populate the plugin details modal that appears when you click &lt;strong&gt;View Details&lt;/strong&gt; in the admin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F15qn5bomn8f8dci88p0n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F15qn5bomn8f8dci88p0n.png" alt="A screenshot of a custom plugin's information" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're distributing plugins outside the official repository, you need to replicate this behavior yourself.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;an API that serves update metadata&lt;/li&gt;
&lt;li&gt;a way to serve plugin ZIP packages&lt;/li&gt;
&lt;li&gt;a system for generating plugin information (description, changelog, icons, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Turning WordPress Into an Update Server
&lt;/h1&gt;

&lt;p&gt;The &lt;strong&gt;WP Plugin Update Server&lt;/strong&gt; plugin allows a WordPress site to act as a lightweight update server.&lt;/p&gt;

&lt;p&gt;Instead of building a separate service, the plugin uses WordPress itself to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;store hosted plugin metadata&lt;/li&gt;
&lt;li&gt;manage update information through the admin interface&lt;/li&gt;
&lt;li&gt;expose update data through REST endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hosted plugins are managed as a custom post type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plugins → Hosted Plugins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each entry represents a plugin that the server manages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe05e2lmronlzwsisg7jd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe05e2lmronlzwsisg7jd.png" alt="A single " width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  How The Update Flow Works
&lt;/h1&gt;

&lt;p&gt;The system involves two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the &lt;strong&gt;update server plugin&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;client helper library&lt;/strong&gt; installed in the plugin receiving updates&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When a WordPress site checks for updates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The helper library intercepts the &lt;code&gt;update_plugins&lt;/code&gt; transient.&lt;/li&gt;
&lt;li&gt;It queries the configured update server.&lt;/li&gt;
&lt;li&gt;The server returns update metadata if a newer version exists.&lt;/li&gt;
&lt;li&gt;WordPress adds the update to the normal plugin update list.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When a user clicks &lt;strong&gt;View Details&lt;/strong&gt;, the helper calls another endpoint that returns the plugin metadata normally provided by WordPress.org.&lt;/p&gt;

&lt;h1&gt;
  
  
  REST Endpoints
&lt;/h1&gt;

&lt;p&gt;The update server exposes two REST endpoints:&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/wppf/api/plugin-updates/transients
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/wp-json/wppf/api/plugin-updates/plugins-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These endpoints return the data WordPress expects when checking for updates or displaying plugin details.&lt;/p&gt;

&lt;p&gt;This allows the WordPress admin experience to remain completely native.&lt;/p&gt;

&lt;h1&gt;
  
  
  GitHub Integration
&lt;/h1&gt;

&lt;p&gt;The plugin integrates directly with GitHub repositories.&lt;/p&gt;

&lt;p&gt;When a hosted plugin is requested, the server:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Looks for the latest GitHub &lt;strong&gt;release&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Falls back to the latest &lt;strong&gt;tag&lt;/strong&gt; if no release exists.&lt;/li&gt;
&lt;li&gt;Retrieves the plugin’s main file from GitHub.&lt;/li&gt;
&lt;li&gt;Parses the plugin headers to extract metadata.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The resulting response includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the latest version number&lt;/li&gt;
&lt;li&gt;the download package URL&lt;/li&gt;
&lt;li&gt;plugin metadata&lt;/li&gt;
&lt;li&gt;icons and banners&lt;/li&gt;
&lt;li&gt;changelog and description sections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows developers to manage updates simply by publishing new releases on GitHub.&lt;/p&gt;

&lt;h1&gt;
  
  
  Supporting Private Repositories
&lt;/h1&gt;

&lt;p&gt;For private repositories, the server can use a GitHub access token.&lt;/p&gt;

&lt;p&gt;The token is encrypted before being sent to client plugins and is used during the download process to authenticate the request.&lt;/p&gt;

&lt;p&gt;This makes it possible to distribute private plugins securely without exposing credentials.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why This Approach Is Interesting
&lt;/h1&gt;

&lt;p&gt;Most custom update systems rely on a separate API service or a static manifest file.&lt;/p&gt;

&lt;p&gt;This project takes a different approach:&lt;/p&gt;

&lt;p&gt;It uses &lt;strong&gt;WordPress itself as the update server&lt;/strong&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;plugin metadata can be managed through the admin UI&lt;/li&gt;
&lt;li&gt;update endpoints are automatically available&lt;/li&gt;
&lt;li&gt;GitHub repositories become the source of truth for releases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For developers already comfortable with WordPress, this makes the system easy to manage.&lt;/p&gt;

&lt;h1&gt;
  
  
  Try It Out
&lt;/h1&gt;

&lt;p&gt;If you're interested in running your own update server, you can explore the project here:&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/kyle-niemiec/wp-plugin-update-server" rel="noopener noreferrer"&gt;https://github.com/kyle-niemiec/wp-plugin-update-server&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can install it like a normal WordPress plugin and begin hosting updates for your own projects.&lt;/p&gt;




&lt;p&gt;Part of the WPPF ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress Plugin Framework&lt;/li&gt;
&lt;li&gt;WPPF Test Plugin&lt;/li&gt;
&lt;li&gt;Plugin Update Server&lt;/li&gt;
&lt;li&gt;WPPF Update Helper&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Documentation:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://wp-plugin-framework.codeflower.io" rel="noopener noreferrer"&gt;https://wp-plugin-framework.codeflower.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>wordpress</category>
      <category>opensource</category>
      <category>php</category>
    </item>
    <item>
      <title>Exploring the WPPF Test Plugin: A Working Example of the WordPress Plugin Framework</title>
      <dc:creator>Kyle Niemiec</dc:creator>
      <pubDate>Tue, 10 Mar 2026 14:47:51 +0000</pubDate>
      <link>https://forem.com/kyle-niemiec/exploring-the-wppf-test-plugin-a-working-example-of-the-wordpress-plugin-framework-2op4</link>
      <guid>https://forem.com/kyle-niemiec/exploring-the-wppf-test-plugin-a-working-example-of-the-wordpress-plugin-framework-2op4</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;If you've ever explored a framework and wondered &lt;em&gt;“What does a real project built with this actually look like?”&lt;/em&gt;, you're not alone.&lt;/p&gt;

&lt;p&gt;Example projects are often the fastest way to understand how a framework works in practice.&lt;/p&gt;

&lt;p&gt;When I released the &lt;strong&gt;&lt;a href="https://dev.to/kyle-niemiec/after-building-so-many-wordpress-plugins-i-made-wppf-36o8"&gt;WordPress Plugin Framework (WPPF)&lt;/a&gt;&lt;/strong&gt;, one of the most important supporting pieces was a companion repository: a working demonstration plugin that shows how the framework is meant to be used.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href="https://github.com/kyle-niemiec/wppf-test-plugin" rel="noopener noreferrer"&gt;WPPF Test Plugin&lt;/a&gt;&lt;/strong&gt; acts as a reference implementation for building real WordPress plugins with the framework.&lt;/p&gt;

&lt;h1&gt;
  
  
  What the Test Plugin Demonstrates
&lt;/h1&gt;

&lt;p&gt;The test plugin demonstrates several real-world plugin patterns, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;plugin bootstrapping and module discovery&lt;/li&gt;
&lt;li&gt;admin module separation&lt;/li&gt;
&lt;li&gt;custom post types&lt;/li&gt;
&lt;li&gt;typed meta with validation&lt;/li&gt;
&lt;li&gt;WooCommerce email integration&lt;/li&gt;
&lt;li&gt;upgrade routines that run on version updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to show how a typical plugin can be structured when built using WPPF conventions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Convention Over Configuration
&lt;/h1&gt;

&lt;p&gt;One of the central ideas behind WPPF is &lt;strong&gt;filesystem-driven discovery&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of manually requiring files and wiring components together, the framework automatically discovers classes placed in specific directories.&lt;/p&gt;

&lt;p&gt;A typical plugin installation might have a folder structure 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;my-plugin/
├─ admin/
│  ├─ includes/
│  │  ├─ meta-boxes/
│  │  └─ screens/
│  ├─ templates/
│  └─ my-plugin-admin.php
│
├─ assets/
│  ├─ css/
│  └─ js/
│
├─ includes/
│  ├─ classes/
│  ├─ modules/
│  └─ post-types/
│
├─ vendor/
└─ my-plugin.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the plugin boots, WPPF scans these directories and automatically constructs the relevant components.&lt;/p&gt;

&lt;p&gt;This allows plugin developers to focus on implementing functionality instead of wiring everything together manually.&lt;/p&gt;

&lt;h1&gt;
  
  
  Example Feature: Custom Post Type With Typed Meta
&lt;/h1&gt;

&lt;p&gt;The test plugin includes a custom post type called Test Posts, which demonstrates a full feature slice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiuahxq5d8fok7okb2rsw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiuahxq5d8fok7okb2rsw.png" alt=" " width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This includes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The custom post type definition&lt;/li&gt;
&lt;li&gt;A meta box UI in the admin&lt;/li&gt;
&lt;li&gt;Typed meta validation&lt;/li&gt;
&lt;li&gt;Persistence logic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The post type is defined here:&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="n"&gt;includes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wppf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The meta box UI lives in:&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="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;includes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;boxes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wppf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the data model is represented by a typed meta object:&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="n"&gt;includes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;designink&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure helps separate concerns between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI&lt;/li&gt;
&lt;li&gt;Data validation&lt;/li&gt;
&lt;li&gt;Domain logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  WooCommerce Integration Example
&lt;/h1&gt;

&lt;p&gt;The plugin also demonstrates an early example of how WPPF can integrate with WooCommerce.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghvs08rm1173dnyxbhn2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghvs08rm1173dnyxbhn2.png" alt=" " width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It includes a custom email class:&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="n"&gt;includes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;emails&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wppf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Along with a corresponding email template:&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="n"&gt;woocommerce&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;emails&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;wppf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern allows plugin developers to extend WooCommerce functionality while keeping email logic and templates organized.&lt;/p&gt;

&lt;h1&gt;
  
  
  Running the Plugin Locally
&lt;/h1&gt;

&lt;p&gt;To experiment with the test plugin:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the plugin ZIP file in your WordPress installation.&lt;/li&gt;
&lt;li&gt;Activate the plugin in the WordPress admin panel.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once activated, you'll see the &lt;strong&gt;Test Posts&lt;/strong&gt; custom post type where you can experiment with the plugin's features.&lt;/p&gt;

&lt;h1&gt;
  
  
  Exploring the Framework in Practice
&lt;/h1&gt;

&lt;p&gt;Documentation can explain how a framework works, but seeing it applied in a real project is often the easiest way to understand its structure.&lt;/p&gt;

&lt;p&gt;The WPPF Test Plugin exists as a reference implementation that demonstrates how the framework’s conventions come together in a functioning plugin.&lt;/p&gt;

&lt;p&gt;If you're curious about how WPPF organizes plugin components, this repository is a good place to explore the architecture in practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/kyle-niemiec/wppf-test-plugin" rel="noopener noreferrer"&gt;https://github.com/kyle-niemiec/wppf-test-plugin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framework:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/kyle-niemiec/wp-plugin-framework" rel="noopener noreferrer"&gt;https://github.com/kyle-niemiec/wp-plugin-framework&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Part of the WPPF ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/kyle-niemiec/wp-plugin-framework" rel="noopener noreferrer"&gt;WordPress Plugin Framework (WPPF)&lt;/a&gt; – Core plugin architecture framework&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kyle-niemiec/wppf-test-plugin" rel="noopener noreferrer"&gt;WPPF Test Plugin&lt;/a&gt; – Example project demonstrating an implementation of a plugin using WPPF.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kyle-niemiec/wp-plugin-update-server" rel="noopener noreferrer"&gt;WP Plugin Update Server&lt;/a&gt; – Self-hosted WordPress plugin update infrastructure with GUI management.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kyle-niemiec/wppf-update-helper" rel="noopener noreferrer"&gt;WPPF Update Helper&lt;/a&gt; – Simple integration layer for the WP Plugin Update Server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Documentation:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://wp-plugin-framework.codeflower.io" rel="noopener noreferrer"&gt;https://wp-plugin-framework.codeflower.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>showdev</category>
      <category>webdev</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>After Building So Many WordPress Plugins, I Made WPPF</title>
      <dc:creator>Kyle Niemiec</dc:creator>
      <pubDate>Thu, 05 Mar 2026 15:31:01 +0000</pubDate>
      <link>https://forem.com/kyle-niemiec/after-building-so-many-wordpress-plugins-i-made-wppf-36o8</link>
      <guid>https://forem.com/kyle-niemiec/after-building-so-many-wordpress-plugins-i-made-wppf-36o8</guid>
      <description>&lt;h1&gt;
  
  
  The problem and introducing WPPF
&lt;/h1&gt;

&lt;p&gt;If you've built more than a few WordPress plugins, you've probably run into the same pattern.&lt;/p&gt;

&lt;p&gt;A plugin starts clean, but as features grow it slowly becomes a mix of&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scattered &lt;code&gt;add_action&lt;/code&gt; calls,&lt;/li&gt;
&lt;li&gt;copied boilerplate code,&lt;/li&gt;
&lt;li&gt;and an &lt;code&gt;includes/&lt;/code&gt; folder full of dissimilar items.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After dealing with this enough times while building client plugins, I decided to standardize how I structure my projects.&lt;/p&gt;

&lt;p&gt;That eventually became the &lt;strong&gt;WordPress Plugin Framework (WPPF)&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why this happens&lt;/li&gt;
&lt;li&gt;My solution&lt;/li&gt;
&lt;li&gt;The core idea: Modules&lt;/li&gt;
&lt;li&gt;CLI scaffolding&lt;/li&gt;
&lt;li&gt;Versioned namespaces&lt;/li&gt;
&lt;li&gt;Who this framework is for&lt;/li&gt;
&lt;li&gt;Try it out&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Why this happens
&lt;/h1&gt;

&lt;p&gt;One of the strengths of WordPress plugin development is also one of its biggest challenges: &lt;strong&gt;flexibility&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;WordPress doesn’t impose a strict project structure on plugin developers. In many ways that’s a good thing, since it lowers the barrier to entry and lets developers solve problems however they want.&lt;/p&gt;

&lt;p&gt;But along with that freedom has come little guidance around how plugins should be organized.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;WordPress gives you incredible flexibility, but large plugins benefit from a little structure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As plugins grow, it can be common to see things like&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hooks scattered across files top to bottom,&lt;/li&gt;
&lt;li&gt;features implemented wherever it was convenient at the time,&lt;/li&gt;
&lt;li&gt;utility functions copied between projects,&lt;/li&gt;
&lt;li&gt;or folders like &lt;code&gt;includes/&lt;/code&gt; slowly becoming a "stuff drawer".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is necessarily wrong, but over time it can make a plugin harder to understand and maintain.&lt;/p&gt;

&lt;p&gt;After running into this pattern repeatedly, I started wondering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What would a consistent, feature-oriented structure for WordPress plugins look like?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question eventually led to the framework described in the rest of this article.&lt;/p&gt;

&lt;h1&gt;
  
  
  My solution
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;After running into these problems repeatedly, I started standardizing how I structured my own plugins.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of organizing code primarily by what it is (classes, templates, utilities, etc.), I started organizing plugins by &lt;strong&gt;what they do&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In other words, a feature-based architecture.&lt;/p&gt;

&lt;p&gt;A plugin might contain things like&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a REST API,&lt;/li&gt;
&lt;li&gt;admin UI screens,&lt;/li&gt;
&lt;li&gt;custom post types,&lt;/li&gt;
&lt;li&gt;scheduled tasks,&lt;/li&gt;
&lt;li&gt;or integrations with other platforms/plugins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these areas can be treated as a feature module that owns its own hooks, logic, and supporting code.&lt;/p&gt;

&lt;p&gt;That approach turned out to make plugins significantly easier to reason about.&lt;/p&gt;

&lt;p&gt;Instead of asking,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Where is the code that registers this feature?",&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you can usually find it directly inside the module responsible for that behavior.&lt;/p&gt;

&lt;p&gt;Over time, this approach evolved into a small framework while working at &lt;a href="https://designinkdigital.com" rel="noopener noreferrer"&gt;DesignInk Digital&lt;/a&gt; that I now call the &lt;strong&gt;WordPress Plugin Framework&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The framework focuses on a few practical goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organizing plugin code around feature modules.&lt;/li&gt;
&lt;li&gt;Keeping WordPress hooks close to the feature they belong to.&lt;/li&gt;
&lt;li&gt;Using a custom PSR-4 autoloader for predictable project structure.&lt;/li&gt;
&lt;li&gt;Providing CLI scaffolding for common plugin components.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The result is a structure that still feels very “WordPress”, but scales better as plugins grow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  The core idea: Modules
&lt;/h1&gt;

&lt;p&gt;The central concept in WPPF is the &lt;strong&gt;module&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A module represents a single feature area within a plugin and acts as the place where that feature connects to WordPress.&lt;/p&gt;

&lt;p&gt;Instead of scattering hooks across multiple files, a module becomes the entry point for a feature's behavior.&lt;/p&gt;

&lt;p&gt;For example, if a plugin exposes a REST API, the module responsible for that feature might look like this:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;My_API&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;construct&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&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;'rest_api_init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;__CLASS__&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Register routes, controllers, permissions, etc.&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;When the plugin loads, the framework automatically calls each module's &lt;code&gt;construct()&lt;/code&gt; method and registers the hooks needed for that feature.&lt;/p&gt;

&lt;p&gt;This approach keeps the WordPress wiring close to the feature itself.&lt;/p&gt;

&lt;p&gt;Instead of searching through multiple files for where a hook is registered, you can usually find it directly inside the module responsible for that functionality.&lt;/p&gt;

&lt;p&gt;As plugins grow, this makes it much easier to reason about how different parts of the plugin interact with WordPress.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Modules help keep plugins organized around &lt;strong&gt;behavior instead of file types&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Scaffolding common plugin components
&lt;/h1&gt;

&lt;p&gt;One of the most repetitive parts of plugin development is creating the same structural pieces over and over.&lt;/p&gt;

&lt;p&gt;Things like&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bootstrapping plugin files,&lt;/li&gt;
&lt;li&gt;registering custom post types,&lt;/li&gt;
&lt;li&gt;creating meta boxes,&lt;/li&gt;
&lt;li&gt;and configuring admin screens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even with a good architecture in place, these components still require a fair amount of setup.&lt;/p&gt;

&lt;p&gt;To reduce that friction, WPPF includes a CLI tool that can scaffold common plugin components.&lt;/p&gt;

&lt;p&gt;For example, creating a new plugin can be done with:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vendor/bin/wppf make:plugin&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1frxemb3xemjeik4ih9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1frxemb3xemjeik4ih9.png" alt=" " width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The CLI generates the basic structure needed to start building a plugin using the framework.&lt;/p&gt;

&lt;p&gt;The other commands can generate other components such as&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;custom post types,&lt;/li&gt;
&lt;li&gt;meta boxes,&lt;/li&gt;
&lt;li&gt;admin modules,&lt;/li&gt;
&lt;li&gt;and other common plugin scaffolding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps eliminate a lot of the repetitive setup work that tends to happen when starting a new plugin or adding a new feature.&lt;/p&gt;

&lt;p&gt;Instead of copying code from previous projects, you can generate a consistent starting point and focus on the actual functionality you want to build.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The goal is to remove as much boilerplate as possible while keeping plugin code predictable and organized.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Versioned namespaces
&lt;/h1&gt;

&lt;p&gt;One of the challenges of building reusable libraries in the WordPress ecosystem is that multiple plugins can exist in the same runtime environment.&lt;/p&gt;

&lt;p&gt;Unlike many modern PHP applications where dependencies are centrally managed, WordPress sites often load many independent plugins at once. If two plugins include different versions of the same framework or library, conflicts can occur.&lt;/p&gt;

&lt;p&gt;To help avoid this problem, WPPF uses versioned namespaces.&lt;/p&gt;

&lt;p&gt;Instead of a static namespace like&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WPPF\Plugin&lt;/code&gt;,&lt;/p&gt;

&lt;p&gt;each framework release is namespaced with its version. For example,&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WPPF\v1_2_2\Plugin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This means different plugins can safely ship with different versions of the framework without creating conflicts.&lt;/p&gt;

&lt;p&gt;In practice, this allows plugin developers to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;upgrade framework versions gradually,&lt;/li&gt;
&lt;li&gt;avoid dependency conflicts across plugins,&lt;/li&gt;
&lt;li&gt;and maintain stability on sites running multiple custom plugins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to make the framework easier to adopt without forcing every plugin on a site to upgrade at the same time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This design helps keep plugins isolated from each other while still allowing developers to benefit from shared framework improvements.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Versioned namespaces
&lt;/h1&gt;

&lt;p&gt;One of the challenges of building reusable libraries in the WordPress ecosystem is that multiple plugins can exist in the same runtime environment.&lt;/p&gt;

&lt;p&gt;Unlike many modern PHP applications where dependencies are centrally managed, WordPress sites often load many independent plugins at once. If two plugins include different versions of the same framework or library, conflicts can occur.&lt;/p&gt;

&lt;p&gt;To help avoid this problem, WPPF uses versioned namespaces.&lt;/p&gt;

&lt;p&gt;Instead of a static namespace like&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WPPF\Plugin&lt;/code&gt;,&lt;/p&gt;

&lt;p&gt;each framework release is namespaced with its version. For example,&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WPPF\v1_2_2\Plugin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This means different plugins can safely ship with different versions of the framework without creating conflicts.&lt;/p&gt;

&lt;p&gt;In practice, this allows plugin developers to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;upgrade framework versions gradually,&lt;/li&gt;
&lt;li&gt;avoid dependency conflicts across plugins,&lt;/li&gt;
&lt;li&gt;and maintain stability on sites running multiple custom plugins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to make the framework easier to adopt without forcing every plugin on a site to upgrade at the same time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This design helps keep plugins isolated from each other while still allowing developers to benefit from shared framework improvements.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Try it out
&lt;/h1&gt;

&lt;p&gt;If you're interested in exploring the framework or trying it in your own projects, you can find the documentation and source code here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://wp-plugin-framework.codeflower.io/" rel="noopener noreferrer"&gt;https://wp-plugin-framework.codeflower.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/kyle-niemiec/wp-plugin-framework/" rel="noopener noreferrer"&gt;https://github.com/kyle-niemiec/wp-plugin-framework/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project is open source, and feedback from other plugin developers is always welcome.&lt;/p&gt;

&lt;p&gt;I'm particularly interested in hearing how other developers structure larger plugins and what kinds of tooling would make plugin development easier.&lt;/p&gt;

&lt;p&gt;If you end up trying the framework or have ideas for improving it, feel free to open an issue or start a discussion on GitHub.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;WordPress plugin development is incredibly flexible and this framework is simply an attempt to add a little more structure where it helps.&lt;/p&gt;
&lt;/blockquote&gt;

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