WordPress Plugin Fundamentals

A guide to modern plugin development.

What is a Plugin?

A WordPress plugin is a standalone PHP file (or set of files) that enhances or changes WordPress functionality without altering the WordPress core. It lets you "hook into" WordPress to add, modify, or extend features for your entire site or for a single use case.

Basic Plugin Structure

Always create your plugin inside its own directory under wp-content/plugins/your-plugin-name/. The main PHP file should be named after your plugin, and it's the only file that requires the plugin header comment.

/your-plugin-name
    your-plugin-name.php      (Main plugin file with header)
    uninstall.php             (Optional for clean-up on uninstall)
    /admin                    (Admin-only code)
    /public                   (Frontend code)
    /includes                 (Reusable code across front-end and back-end)
    /languages                (Localization files)
    /js, /css, /images        (Assets)

Core Concepts: Hooks & Lifecycle

Hooks are the backbone of plugin flexibility in WordPress. They let your code interact with core WordPress events, themes, and other plugins.

Actions
Do something at a certain point (e.g., after saving a post).
Example: add_action('init', 'my_init_function');
Filters
Change or filter data before it’s rendered or saved.
Example: add_filter('the_title', 'my_filter_function');
Custom Hooks
Allow others to extend your plugin via do_action() and apply_filters().

Essential Plugin Lifecycle Hooks

You should always handle plugin installation, deactivation, and uninstallation cleanly to provide a good user experience.

register_activation_hook(__FILE__, 'my_activate_function');
Runs setup code when the plugin is activated, like creating database tables or setting default options.
register_deactivation_hook(__FILE__, 'my_deactivate_function');
Runs when the plugin is deactivated. Good for cleaning temporary data or caches.
register_uninstall_hook(__FILE__, 'my_uninstall_function');
Runs when a user deletes the plugin. This is where you should permanently remove all plugin-created data and settings.

Best Practices & Architecture

Following established best practices ensures your plugin is secure, maintainable, and compatible with the broader WordPress ecosystem.

Avoid Naming Collisions
Prefix all your functions, classes, constants, and options with a unique, plugin-specific prefix (e.g., wpbp_).
Use Classes or Namespaces
An object-oriented structure simplifies maintenance, improves readability, and avoids conflicts.
File Organization
Keep code modular. Separate admin, public, and shared logic into different directories and files.
Check for Existing Implementations
PHP provides a number of functions to verify existence of variables, functions, classes and constants. All of these will return true if the entity exists.
  • Variables: isset() (includes arrays, objects, etc.)
  • Functions: function_exists()
  • Classes: class_exists()
  • Constants: defined()
Security: Prevent Direct File Access
Add if ( ! defined( 'ABSPATH' ) ) exit; to the top of every PHP file to prevent it from being accessed directly via its URL.
Conditional Loading
Load admin-specific code only when needed by using the is_admin() check.
Use WordPress APIs
Leverage built-in APIs (Options API, Transients API, HTTP API) to interact safely and efficiently with data and WordPress core.
Translation Ready
Use the Text Domain and Domain Path headers in conjunction with localization functions to make your plugin translatable.

Determining Plugin and Content Directories

Never hardcode paths or URLs like /wp-content/plugins/my-plugin/. This will break if a user renames their wp-content directory or installs WordPress in a subdirectory. Instead, use WordPress helper functions with PHP's __FILE__ magic constant. The __FILE__ constant always returns the full server path to the file it's used in, providing a reliable "anchor" for WordPress to locate your plugin's files and folders.

plugin_dir_path( __FILE__ )

Purpose: To get the absolute server file system path to your plugin's directory. This is used for including or requiring other PHP files, like require_once( plugin_dir_path( __FILE__ ) . 'includes/functions.php' );. This path is for internal use and not accessible in a browser.

Sample Output: /var/www/html/wp-content/plugins/your-plugin-name/ (Note the trailing slash)

plugins_url( 'images/logo.png', __FILE__ )

Purpose: To get the full, web-accessible URL to a specific file inside your plugin directory. This is essential for linking to assets like CSS, JavaScript, or image files from the front-end.

Sample Output: https://yourdomain.com/wp-content/plugins/your-plugin-name/images/logo.png

plugin_dir_url( __FILE__ )

Purpose: To get the full, web-accessible URL to your plugin's main directory. This is useful for more complex situations, like passing the base URL to a JavaScript file.

Sample Output: https://yourdomain.com/wp-content/plugins/your-plugin-name/ (Note the trailing slash)

Example: A Complete, Simple Plugin

This basic plugin incorporates all the essential elements described above. It adds a customizable greeting message before the content of every single post and enqueues a stylesheet.

Licensing & Sharing

Before sharing, always provide a license compatible with WordPress (GPLv2 or later is recommended) so others know how they can legally use, modify, and distribute your code.

<?php
/*
* Plugin Name:     Custom Greetings
* Plugin URI:      https://yourdomain.com/custom-greetings
* Description:     Displays a customizable greeting message on posts.
* Version:         1.0
* Requires at least:6.0
* Requires PHP:    7.2
* Author:          Jane Doe
* Author URI:      https://yourdomain.com/
* License:         GPL v2 or later
* License URI:     https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain:     custom-greetings
* Domain Path:     /languages
*/

// Security: Prevent direct file access
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// Activation: Set default option
function cg_activate() {
    add_option('cg_greeting', 'Hello, welcome to my website!');
}
register_activation_hook( __FILE__, 'cg_activate' );

// Deactivation: Clean up temporary data (optional)
function cg_deactivate() {
    // Nothing to do here in this example.
}
register_deactivation_hook( __FILE__, 'cg_deactivate' );

// Uninstall: Delete the option from the database
function cg_uninstall() {
    delete_option('cg_greeting');
}
register_uninstall_hook( __FILE__, 'cg_uninstall' );

// Filter 'the_content' to add our greeting
function cg_display_greeting($content) {
    if ( is_singular('post') ) {
        $greeting = get_option('cg_greeting', 'Hello!');
        $greeting_html = "<p class='cg-greeting'>" . esc_html($greeting) . "</p>";
        return $greeting_html . $content;
    }
    return $content;
}
add_filter('the_content', 'cg_display_greeting');

// Enqueue a simple stylesheet for the front-end
function cg_enqueue_styles() {
    wp_enqueue_style( 'cg-style', plugins_url('css/cg-style.css', __FILE__) );
}
add_action( 'wp_enqueue_scripts', 'cg_enqueue_styles' );