Contents
Introduction
This article is a complete, detailed tutorial on creating a basic WordPress plugin that includes a proper plugin header and activation logic in PHP. Youll learn the required plugin header format, how to register activation and deactivation callbacks, how to create database tables safely on activation, how to clean up on uninstall, important security and best-practice notes, and practical code examples you can drop into a plugin file.
What a plugin needs at minimum
A WordPress plugin must be a PHP file placed inside wp-content/plugins/ and contain a plugin header comment block at the top. That header lets WordPress identify the plugin and show it in the Plugins screen. To run activation logic you register a callback with register_activation_hook. For deactivation use register_deactivation_hook. For uninstall cleanup use register_uninstall_hook or create an uninstall.php file that WordPress will call.
Minimal plugin header example
Registering an activation hook
Use register_activation_hook( __FILE__, your_activation_function ). The hook triggers when a user activates the plugin via the WordPress admin. The activation callback should be lightweight and must not produce output (no echo/print) because activation runs during HTTP headers processing. If you need to show status or errors to the admin, save a transient or option and render an admin notice on the next admin page load.
Simple activation and deactivation example
Creating database tables on activation (dbDelta)
If your plugin needs its own database tables, use the WordPress function dbDelta which safely creates or updates tables. Important steps:
- Use wpdb-gtprefix to prefix your table name.
- Use wpdb-gtget_charset_collate() to set charset/collation correctly.
- Include require_once( ABSPATH . wp-admin/includes/upgrade.php ) before calling dbDelta.
- Make sure SQL column definitions are formatted precisely to match dbDelta expectations (e.g., same spacing and use of key names).
Activation example creating a table
prefix . pwtb_items charset_collate = wpdb->get_charset_collate() // SQL must use the exact format expected by dbDelta. sql = CREATE TABLE {table_name} ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(191) NOT NULL, value text NOT NULL, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) {charset_collate} require_once ABSPATH . wp-admin/includes/upgrade.php dbDelta( sql ) // Save plugin version for later upgrades. add_option( pwtb_version, PWTB_VERSION ) } register_activation_hook( PWTB_PLUGIN_FILE, pwtb_activate )
Deactivation and uninstall
Deactivation is for stopping plugin behavior and optionally cleaning up runtime data (transients, scheduled jobs). Uninstall should remove persistent data like options and custom tables if the plugin author chooses to do so. There are two ways to implement uninstall behavior:
- Create an uninstall.php file at the plugin root. WordPress will load and run it when the plugin is uninstalled from admin. Remember to check for defined(WP_UNINSTALL_PLUGIN) to guard the file.
- Use register_uninstall_hook( __FILE__, my_uninstall_function ).
uninstall.php example (recommended for most cases)
prefix . pwtb_items wpdb->query( DROP TABLE IF EXISTS {table_name} )
Using register_uninstall_hook example
prefix . my_plugin_table wpdb->query( DROP TABLE IF EXISTS {table} ) } register_uninstall_hook( __FILE__, my_plugin_uninstall )
Class-based plugin structure (recommended)
Encapsulating functionality in a class avoids polluting the global namespace and makes maintenance easier. Use a bootstrap that instantiates the main class. You can still hook activation to the class static method.
Class-based plugin with activation/deactivation
Common activation pitfalls and how to handle them
- Do not echo output during activation. Activation runs before the admin page is displayed output can break headers. To inform the user, set an option or transient and display an admin notice on the next admin page load.
- Multisite (Network) activation: If the plugin is network-activated, WordPress will run the activation hook once for the network. If you require per-site setup, iterate over sites using get_sites() and switch_to_blog() to run per-site installation code.
- File path for register_activation_hook: Use the main plugin file path (commonly __FILE__). If registering the hook from an included file, ensure the constant points at the main plugin file path you want WordPress to correlate with.
- Long running operations: Avoid long operations during activation (e.g., processing thousands of items). Instead set a flag and schedule a background job (using Cron or an async approach) to perform heavy migration tasks.
Example: reporting activation errors using admin notice
. esc_html( msg ) .