Plugin Boilerplate Generator

A plugin that runs the moment you paste it in: header, activation hooks, loader, and the settings page, assets, REST route and uninstall you tick.

Enable JavaScript to change the fields; the files below are for the defaults.

What the Plugins screen shows.

Left empty it is the plugin name, slugged. The folder has to match it.

Shown on the Plugins screen. Over 150 characters it is cut for the readme's short description.

Structure

One file for something small. Classes for the usual WordPress layout. Namespaced classes for an autoloader and Composer.

Include

Each one is a class the loader already constructs, so nothing needs wiring up by hand.

Prefixes every function, constant and option. Left empty it is the folder name with underscores.

Only used by the namespaced structure. Left empty it is the prefix in StudlyCaps.

Also becomes the readme contributor and the Composer vendor, slugged.

WordPress.org uses the first five.

Create this folder: wp-content/plugins/contact-bucket

The loader builds the plugin class on plugins_loaded, and its hooks() constructs Contact_Bucket_Settings. Nothing here needs wiring up by hand.

Most plugin boilerplates give you a folder of classes that do nothing, and a TODO where the wiring should be. This one runs. Paste the files in, activate it, and the settings page is under Settings, the REST route answers, and the assets enqueue: the loader already constructs every class it wrote.

How to use

  1. Name the plugin. The folder and text domain default to the name, slugged, and the folder has to match.
  2. Choose the structure. single file for something small enough to read in one sitting. classes for the usual WordPress layout, class-my-plugin.php and friends. namespaced classes for an autoloader, PSR-4 and Composer.
  3. Tick what you want. Each one is a class, and each one is constructed by the plugin class’s hooks().
  4. Create wp-content/plugins/<folder>/ and write each file from the tabs.
  5. Activate it.

Example

With classes and all six options, the main file requires what it wrote:

require_once CONTACT_BUCKET_PATH . 'includes/class-contact-bucket.php';
require_once CONTACT_BUCKET_PATH . 'includes/class-contact-bucket-settings.php';
require_once CONTACT_BUCKET_PATH . 'includes/class-contact-bucket-assets.php';
require_once CONTACT_BUCKET_PATH . 'includes/class-contact-bucket-rest.php';

function contact_bucket() {
	static $instance = null;

	if ( null === $instance ) {
		$instance = new Contact_Bucket();
		$instance->hooks();
	}

	return $instance;
}
add_action( 'plugins_loaded', 'contact_bucket' );

and the plugin class constructs the rest:

public function hooks(): void {
	add_action( 'init', array( $this, 'init' ) );

	( new Contact_Bucket_Settings() )->hooks();
	( new Contact_Bucket_Assets() )->hooks();
	( new Contact_Bucket_Rest() )->hooks();
}

Two details in there are the point of the whole thing. The instance is built on plugins_loaded, not at the bottom of the file, so every other plugin has had its chance to add the filters yours reads. And hooks are registered in hooks() rather than in a constructor, so a class can be built in a test without attaching itself to WordPress.

The REST route’s permission callback is a real capability check:

'permission_callback' => array( $this, 'can_read' ),

Not __return_true, which is how plugins end up with public endpoints they did not mean to publish.

Pitfalls

The folder name has to match the text domain. WordPress.org builds translation files from the slug, and a mismatch means they are never loaded.

Deactivation is not uninstall. The generated deactivation hook flushes rewrites and deletes nothing, because a deactivated plugin is usually a plugin about to be reactivated. Deleting data belongs in uninstall.php, which only runs when the plugin is deleted from the Plugins screen.

flush_rewrite_rules() on activation only. Never on init: it rewrites an option on every request and is one of the classic causes of a slow site.

There is no load_plugin_textdomain(). For a plugin hosted on WordPress.org, WordPress has loaded translations by itself since 4.6, and calling it too early is what produces the “translation loading was triggered too early” notice in 6.7 and later. If you ship translations in the plugin instead, add the call on init.

The single file structure has no autoloader. That is the point of it. If it grows past a few hundred lines, regenerate with classes and move the work across; the loader is the only thing that changes.

Compatibility

Every file is also downloadable as a single .zip, built in your browser. The archive stores the files rather than deflating them, which is what WordPress’s own installer reads, and the whole set goes in a folder named after the plugin, so Plugins → Add New → Upload installs it directly.

The generated code targets WordPress 6.5 and PHP 7.4, which is what the defaults say. Two pieces need a recent WordPress: the array( 'strategy' => 'defer' ) argument to wp_enqueue_script() is 6.3 and later, and typed return values on the class methods need PHP 7.4.

Every combination of structure and options was run through php -l, and the full namespaced variant was activated in a real WordPress: the autoloader resolves, register_setting accepts the option and its sanitiser, the REST route answers 200 for an administrator and 401 for anyone else, and both assets enqueue.

The file names follow the WordPress coding standards, so phpcs --standard=WordPress finds the classes where it expects them. The Composer option adds phpcs and WPCS as dev dependencies with lint and fix scripts.

Frequently asked questions

Which structure should I pick?
Classes, unless you know otherwise. It is what most WordPress plugins look like, so the next person to open the folder recognises it. Namespaced classes are worth it when you have Composer in the project already, or a name that collides.
Does the settings page store anything sensitive?
It stores one option, <prefix>_settings, with a sanitiser that keeps only the keys it knows. Everything that reaches the option goes through that function, which is the property worth keeping as you add fields.
Can I remove a class later?
Yes. Delete the file, delete its require_once, and delete the one line in hooks() that constructs it. That is the whole coupling.
Why is hooks() separate from the constructor?
So the class can be built without attaching anything to WordPress. A constructor that calls add_action cannot be instantiated in a test, and cannot be instantiated twice without registering its hooks twice.
What is missing from this that a real plugin needs?
Tests, a build step for assets, and a languages/ folder with a POT file. The first two depend on your toolchain; the third is generated by wp i18n make-pot in one command, which is why it is not duplicated here.

From the people who built this tool

WP Adminify

The WordPress admin, rebuilt: a dashboard worth looking at, menu and column control, a real file manager and the login page your client sees.

See WP Adminify Free version on WordPress.org

Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.