WordPress Plugin Development: Getting Started

A WordPress plugin is a PHP file (or a folder of files) placed in wp-content/plugins/. The only hard requirement is the plugin header comment in the main file. Everything else — hooks, classes, assets — is up to you.

Problem: What is the minimal file structure needed to create a valid WordPress plugin, and how do you organise it to avoid polluting the global namespace?

Solution: A plugin needs one PHP file in wp-content/plugins/ with a plugin header comment. Wrap all logic in a class instantiated after plugins_loaded — this keeps your code isolated and prevents function name collisions.

A minimal plugin file with a proper header and OOP structure:

<?php
/**
 * Plugin Name:       My Plugin
 * Plugin URI:        https://example.com/my-plugin
 * Description:       A brief description of what the plugin does.
 * Version:           1.0.0
 * Author:            Your Name
 * Author URI:        https://example.com
 * License:           GPL-2.0+
 * Text Domain:       my-plugin
 * Domain Path:       /languages
 */

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

define( 'MY_PLUGIN_VERSION', '1.0.0' );
define( 'MY_PLUGIN_DIR',     plugin_dir_path( __FILE__ ) );
define( 'MY_PLUGIN_URL',     plugin_dir_url( __FILE__ ) );

class My_Plugin {

    public function __construct() {
        add_action( 'init',              [ $this, 'load_textdomain' ] );
        add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] );
    }

    public function load_textdomain() {
        load_plugin_textdomain( 'my-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
    }

    public function enqueue_assets() {
        wp_enqueue_style( 'my-plugin', MY_PLUGIN_URL . 'css/style.css', [], MY_PLUGIN_VERSION );
    }
}

new My_Plugin();

The activation and deactivation hooks are common places for setup and cleanup:

register_activation_hook( __FILE__, function() {
    // create DB tables, add options, flush rewrite rules
    flush_rewrite_rules();
} );

register_deactivation_hook( __FILE__, function() {
    // clear scheduled events, flush rewrite rules
    flush_rewrite_rules();
} );

register_uninstall_hook( __FILE__, 'my_plugin_uninstall' );

function my_plugin_uninstall() {
    // delete options and custom tables
    delete_option( 'my_plugin_settings' );
}

NOTE: The if ( ! defined( 'ABSPATH' ) ) exit; guard is essential — it prevents the file from being accessed directly via a URL. Always use plugin_dir_path() and plugin_dir_url() for paths and URLs rather than hardcoding them.