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.

Plugin vs. theme's 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
*/
Only 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');
Activation runs once per activation, not every page load: The function passed to 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.

With the basics of building a plugin covered, the next lesson looks at two of the most common building blocks plugins (and themes) provide to site editors — continue to Widgets & Shortcodes.