| name | add-mu-plugin |
| description | Scaffold a new must-use plugin in packages/mu-plugins/ - either a simple utility or a service-class integration. |
Skill: Create MU-Plugin
Scaffold a new must-use plugin under packages/mu-plugins/. MU-plugins are loaded automatically by WordPress before regular plugins and are used for site-wide utilities or third-party integrations that must always be active.
Inputs
The following inputs are required. If any are missing from the request, ask for them before writing any code.
| Input | Required | Notes |
|---|
| Plugin slug | Yes | kebab-case, e.g. acme-utility or acme-crm |
| Plugin title | Yes | Human-readable name for the Plugin Name: header, e.g. Acme Utility or Acme CRM |
| PHP namespace | Yes | e.g. Acme\Utility or Acme\Crm |
| Type | Yes | utility (plain functions, no class) or service (singleton class, external API integration) |
| Description | No | One sentence for the Description: plugin header. Defaults to the plugin title. |
Steps
For a utility plugin
-
Create the directory: packages/mu-plugins/{plugin-slug}/
-
Create plugin.php:
<?php
declare( strict_types=1 );
namespace {PhpNamespace};
function utility_function( mixed $value ): void {
if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
return;
}
}
- Create
composer.json:
{
"name": "dekode/{plugin-slug}",
"description": "{Description}",
"type": "wordpress-muplugin",
"require": {}
}
For a service plugin
-
Create the directory: packages/mu-plugins/{plugin-slug}/
-
Create plugin.php - entry point that defines constants and requires class files:
<?php
declare( strict_types=1 );
namespace {PhpNamespace};
define( '{PLUGIN_SLUG}_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( '{PLUGIN_SLUG}_PLUGIN_FILE', __FILE__ );
require_once __DIR__ . '/class-{plugin-slug}-service.php';
{PluginSlug}Service::get_instance();
- Create the service class
class-{plugin-slug}-service.php:
<?php
declare( strict_types=1 );
namespace {PhpNamespace};
class {PluginSlug}Service {
private static ?self $instance = null;
public static function get_instance(): self {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
\add_action( 'init', [ $this, 'init' ] );
}
public function init(): void {
}
public function call_api( string $endpoint, array $body ): array {
$url = \trailingslashit( \env( 'EXTERNAL_API_URL' ) ) . $endpoint;
$response = \wp_remote_post( $url, [
'headers' => [
'Authorization' => 'Bearer ' . \env( 'EXTERNAL_API_KEY' ),
'Content-Type' => 'application/json',
],
'body' => \wp_json_encode( $body ),
] );
if ( \is_wp_error( $response ) ) {
return [ 'success' => false, 'error' => $response->get_error_message() ];
}
$code = \wp_remote_retrieve_response_code( $response );
if ( ! \in_array( $code, [ 200, 201 ], true ) ) {
return [ 'success' => false, 'error' => __( 'Unexpected API response.', '{plugin-slug}' ) ];
}
return [ 'success' => true, 'data' => \json_decode( \wp_remote_retrieve_body( $response ), true ) ];
}
}
- Create
composer.json:
{
"name": "dekode/{plugin-slug}",
"description": "{Description}",
"type": "wordpress-muplugin",
"require": {}
}
- Register in root
composer.json: add a path repository and require the package:
"repositories": [
{ "type": "path", "url": "packages/mu-plugins/{plugin-slug}" }
],
"require": {
"dekode/{plugin-slug}": "*"
}
-
Install: composer install - Composer symlinks the plugin into public/content/mu-plugins/.
-
Lint:
composer lint
composer lint-fix
composer lint
Conventions
- Utility plugins: use plain functions, no classes - simple, single-purpose
- Service plugins: always use a singleton class; never instantiate directly from outside
- Environment variables: always
\env( 'VAR_NAME' ) - never $_ENV, getenv(), or $_SERVER
- External API calls: always
wp_remote_*() + is_wp_error() + response code check
- Transient caching: use for expensive external calls -
get_transient / set_transient with false !== check
- No block registration here: blocks belong in regular plugins (
packages/plugins/), not MU-plugins
Output
Utility: plugin.php with namespaced helper functions. Auto-loaded by WordPress.
Service: plugin.php (bootstrap) + class-{plugin-slug}-service.php (singleton service). Registered in root composer.json, symlinked by Composer into public/content/mu-plugins/, and active on every page load without manual activation. Lint passes clean.