Plugin Development Basics
A WordPress plugin is a self-contained folder (or single file) placed inside
wp-content/plugins/ that adds functionality to a WordPress site — completely
independent of whichever theme happens to be active. This independence is the whole point: if you
build a "contact form" feature or a custom post type inside your theme's functions.php,
switching themes silently deletes that feature. Put it in a plugin instead, and it keeps working no
matter what theme is active.
functions.php: Anything that should keep
working regardless of which theme is active — custom post types, hooks that change core behavior,
integrations with third-party services — belongs in a plugin. Reserve the theme's
functions.php for things that are genuinely about that theme's presentation.The Required Plugin Header
WordPress identifies a plugin by reading a specially formatted comment block at the very top of its main PHP file. This is how your plugin shows up, with the correct name and description, on the admin Plugins page:
<?php
/*
Plugin Name: My First Plugin
Description: A minimal example plugin that demonstrates the basics.
Version: 1.0.0
Author: Your Name
*/
Plugin Name is strictly required for WordPress to
recognize the file as a plugin — but always fill in Description, Version,
and Author too, since they're displayed on the Plugins admin screen and help you (and
anyone else who installs it) keep track of what it does.A Full Minimal Plugin Example
Here's a complete, working plugin file. Save this as
wp-content/plugins/my-first-plugin/my-first-plugin.php in your own WordPress
installation:
<?php
/*
Plugin Name: My First Plugin
Description: Adds a friendly message to the end of every post.
Version: 1.0.0
Author: Your Name
*/
if (!defined('ABSPATH')) { exit; } // block direct access
function mfp_append_message($content) {
return $content . '<p><em>Thanks for reading!</em></p>';
}
add_filter('the_content', 'mfp_append_message');
Once this file exists in its own folder under wp-content/plugins/, it appears in the
admin dashboard's Plugins list, ready to be activated with a click — no database changes, no
special installer needed for something this simple.
The ABSPATH Security Check
Notice the line near the top of the example above:
<?php
if (!defined('ABSPATH')) { exit; }
ABSPATH is a constant WordPress defines very early during its own bootstrap process.
If someone requests your plugin file directly in a browser (e.g.
yoursite.com/wp-content/plugins/my-first-plugin/my-first-plugin.php) instead of it
being loaded through WordPress, ABSPATH won't be defined — and this check immediately
exits, preventing the file from executing outside of WordPress's own context. This is a standard
convention you should add to the top of every plugin file, not just the main one.
Activation and Deactivation Hooks
Plugins often need setup work done once, right when they're activated — like creating a custom database table — and cleanup work when they're deactivated. WordPress provides dedicated hooks for exactly this:
<?php
function mfp_create_table_on_activate() {
global $wpdb;
$table_name = $wpdb->prefix . 'mfp_logs';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
message TEXT NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
}
register_activation_hook(__FILE__, 'mfp_create_table_on_activate');
function mfp_cleanup_on_deactivate() {
// e.g. clear a scheduled cron event registered by this plugin
wp_clear_scheduled_hook('mfp_daily_cleanup');
}
register_deactivation_hook(__FILE__, 'mfp_cleanup_on_deactivate');
register_activation_hook() only fires the moment the plugin is activated (or
reactivated) — it's the right place for one-time setup like creating tables, not for logic that
needs to run on every request.Organizing a Growing Plugin
A single-file plugin is fine for something small like the example above, but as functionality grows
it's common to split code into multiple files — for example an includes/ folder with
one file per feature area, each pulled in from the main plugin file with require_once.
The main plugin file (the one with the header comment) then mostly just wires everything together
by including those files and registering top-level hooks. This is a deeper topic on its own, but
the key idea is simple: keep the header/bootstrap file thin, and let it delegate to organized,
focused files as the plugin grows.
Plugin vs. Theme, One More Time
It's worth restating the distinction clearly now that you've seen both in depth: a plugin adds functionality (a feature that should work no matter what the site looks like), while a theme controls presentation (how content is displayed — layout, styling, templates). See Theme Development Basics for the presentation side of this split. A well-built WordPress site keeps these concerns separate: swapping the theme should never break a plugin's functionality, and deactivating a plugin should never break the theme's layout.