Skip to content
wp-skills
Register
tools/wordpress-starter-plugin-generator

Starter Plugin Generator

Build a whole plugin folder rather than a snippet: the main file with its header and constants, an optional main class, activation and deactivation hooks that actually fire, and an uninstall routine — each in the one file WordPress reads it from.

Header

Also the folder and main file name: WordPress identifies a plugin by the folder/file.php it was found at.

WordPress.org requires this to match the plugin slug.

Structure

Every function and constant carries it. PHP has one global namespace, so an unprefixed helper is a fatal error waiting for the next plugin that picks the same name.

Required from the main file and reached through a one-line accessor, so nothing needs a global.

Only for a plugin with its own /languages. Translations from WordPress.org load on first use with no call at all — measured on 7.1.

Lifecycle

Stays in the main file: called from an include, __FILE__ registers the hook under a path WordPress never activates, and it silently never runs.

For unscheduling events and flushing rules. Not for deleting data — a deactivated plugin keeps its settings.

Through add_option(), so reactivating does not overwrite what the site already has.

Adds the registration function, and makes activation call it before flushing — at that point init has already fired, so the plugin's own callback has not run yet.

One row in wp_options. Prefixed, for the same reason functions are.

If an uninstall.php exists it is the only one that runs: core includes it and returns without firing what register_uninstall_hook() stored.

my-plugin/my-plugin.php
<?php
/**
 * Plugin Name: My Plugin
 * Description: Lorem ipsum dolor sit amet.
 * Version: 1.0.0
 * Requires at least: 6.5
 * Requires PHP: 7.4
 * License: GPL v2 or later
 * Text Domain: my-plugin
 * Domain Path: /languages
 *
 * Generated by wp-skills.com
 *
 * @package My Plugin
 */

// A direct request for this file must not execute it.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/*
 * The version lives here and nowhere else. Anything that needs it — an asset
 * cache-buster, a stored option, an upgrade routine — reads the constant, so
 * there is one number to change on release rather than four.
 */
const MY_PLUGIN_VERSION = '1.0.0';

/*
 * __FILE__ captured here, at the top of the main file, because that is the only
 * place it means what everything else assumes it means: plugin_basename(),
 * plugin_dir_url() and the activation hooks all resolve against the plugin's
 * main file, and __FILE__ inside includes/ is a different path.
 *
 * MY_PLUGIN_PATH already ends in a slash — plugin_dir_path() adds a trailing
 * slash, verified — so concatenating a relative path needs no separator.
 */
const MY_PLUGIN_FILE = __FILE__;
define( 'MY_PLUGIN_PATH', plugin_dir_path( MY_PLUGIN_FILE ) );
define( 'MY_PLUGIN_URL', plugin_dir_url( MY_PLUGIN_FILE ) );

/*
 * These three stay in this file, and passing __FILE__ is why.
 *
 * register_activation_hook() turns its first argument into the plugin's
 * folder/file.php path and hangs the callback on "activate_" plus that path.
 * Called from includes/anything.php, __FILE__ is that file — so the hook is
 * registered under a path WordPress never activates, and the callback simply
 * never runs. Measured on WordPress 7.1: no error, no warning, nothing.
 */

/**
 * Runs once, when the plugin is activated.
 */
function my_plugin_activate(): void {
	// add_option() rather than update_option(): reactivating a plugin must
	// not overwrite settings the site already has.
	add_option(
		'my_plugin_settings',
		array(
			'version' => MY_PLUGIN_VERSION,
		)
	);
}
register_activation_hook( MY_PLUGIN_FILE, 'my_plugin_activate' );

/**
 * Runs once, when the plugin is deactivated.
 */
function my_plugin_deactivate(): void {
	// Runs on deactivation. Scheduled events belong here; stored data does
	// not — a deactivated plugin is expected to keep its settings.
}
register_deactivation_hook( MY_PLUGIN_FILE, 'my_plugin_deactivate' );

require_once MY_PLUGIN_PATH . 'includes/class-my-plugin-plugin.php';

/**
 * The one instance of the plugin.
 *
 * Returned rather than only constructed, so other code can reach it without a
 * global: my_plugin()->something().
 */
function my_plugin(): My_Plugin_Plugin {
	static $instance = null;

	if ( null === $instance ) {
		$instance = new My_Plugin_Plugin();
	}

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