Looking to join a great team — open to 186/482 visa sponsorship in Australia. Learn more

How to Build a WordPress Plugin from Scratch: Part 1 – Plugin Skeleton

Welcome to the first part of our series on building a custom WordPress plugin from scratch. We’re going to walk through the process step-by-step using a real-world example: a simple but scalable photo gallery plugin. In this part, we’ll create the foundation — the plugin skeleton — with all the essential files and folders set up correctly.

Prefer watching over reading? You can check out the full tutorial on YouTube in the player below. Or if you’re more of an old-school type who likes a proper walkthrough in text — just scroll down. Everything’s laid out step by step, like back in the good days of real how-to blogs.

How to Build a WordPress Plugin from Scratch: Part 1 – Plugin Skeleton
▶ Play

Table of Contents

  1. Why Start with a Skeleton?
  2. Directory Structure
  3. The Main Plugin File
  4. Core Module Placeholders
  5. Assets: CSS and JavaScript
  6. What’s Next
  7. FAQ

1. Why Start with a Skeleton?

Building a plugin without structure is like building a house without a blueprint. A clean skeleton:

  • Gives you a predictable layout
  • Makes it easier to separate logic (assets, templates, PHP classes)
  • Scales better when features grow

Even for small projects, a well-structured foundation saves time and reduces bugs later on.

2. Directory Structure

Here’s the initial file and folder structure for our plugin oz-photo-gallery:

PathDescription
oz-photo-gallery/Main plugin folder
assets/css/gallery.cssCompiled styles for frontend gallery
assets/js/gallery.jsVanilla JS file for interactivity
includes/All PHP logic and classes
templates/gallery-template.phpGallery HTML template
uninstall.phpCleanup logic on plugin uninstall
readme.txtPlugin description for wp.org

3. The Main Plugin File

The entry point for any plugin is its main PHP file. Ours is called oz-photo-gallery.php. Here’s the boilerplate:

<?php
/**
 * Plugin Name:       OZ Photo Gallery
 * Plugin URI:        https://ozwebexpert.com/
 * Description:       Lightweight custom photo gallery plugin with albums and photo pages. Built for Australian photographers and web creators.
 * Version:           1.0.0
 * Author:            Grigory Frolov
 * Author URI:        https://ozwebexpert.com/
 * License:           GPL-2.0+
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       oz-photo-gallery
 * Domain Path:       /languages
 */

defined('ABSPATH') || exit;

// Define plugin path and URL constants
define('OZPG_PATH', plugin_dir_path(__FILE__));
define('OZPG_URL', plugin_dir_url(__FILE__));

// Load helpers and classes
require_once OZPG_PATH . 'includes/helpers.php';
require_once OZPG_PATH . 'includes/class-ozpg-post-types.php';
require_once OZPG_PATH . 'includes/class-ozpg-assets.php';

// Register plugin activation hook (flush rewrite rules)
register_activation_hook(__FILE__, function () {
    if (function_exists('flush_rewrite_rules')) {
        flush_rewrite_rules();
    }
});

// Initialize after all plugins are loaded
add_action('plugins_loaded', function () {
    if (class_exists('OZPG_Post_Types')) {
        OZPG_Post_Types::init();
    }

    if (class_exists('OZPG_Assets')) {
        OZPG_Assets::init();
    }
});

This file loads the necessary components and ensures everything runs via hooks.

4. Core Module Placeholders

We’ll be splitting the logic into individual classes. Each file goes into the includes/ folder:

  • Post Types: class-ozpg-post-types.php – for Albums and Photos
  • Assets: class-ozpg-assets.php – for enqueuing CSS and JS
  • Helpers: helpers.php – for additional logic

includes/class-ozpg-post-types.php

<?php
defined('ABSPATH') || exit;

// Register custom post types: Album and Photo

class OZPG_Post_Types {
    public static function init() {
        add_action('init', [self::class, 'register_post_types']);
    }

    public static function register_post_types() {
        //future function
    }
}

includes/class-ozpg-assets.php

<?php
defined('ABSPATH') || exit;

// Enqueues CSS and JS for the gallery plugin
class OZPG_Assets {
    public static function init() {
        add_action('wp_enqueue_scripts', [self::class, 'enqueue_public_assets']);
    }

    public static function enqueue_public_assets() {
        $plugin_url = plugin_dir_url(__DIR__);      // points to /oz-photo-gallery/
        $plugin_path = plugin_dir_path(__DIR__);

        wp_enqueue_style(
            'ozpg-gallery-style',
            $plugin_url . 'assets/css/gallery.css',
            [],
            file_exists($plugin_path . 'gallery.css') ? filemtime($plugin_path . 'gallery.css') : false
        );

        wp_enqueue_script(
            'ozpg-gallery-script',
            $plugin_url . 'assets/js/gallery.js',
            [],
            file_exists($plugin_path . 'gallery.js') ? filemtime($plugin_path . 'gallery.js') : false,
            true
        );
    }
}

includes/helpers.php

<?php
defined('ABSPATH') || exit;

/**
 * Returns the base slug for the gallery.
 */
function oz_get_gallery_slug() {
    return 'gallery';
}

5. Assets: CSS and JavaScript

We’re keeping things lean — no jQuery, no frameworks. Just a bit of CSS Grid and plain JavaScript:

assets/css/gallery.css

.ozpg-gallery {
  display: grid;
  gap: 16px;
}

assets/js/gallery.js

// OZ Photo Gallery — Vanilla JS interactivity

document.addEventListener('DOMContentLoaded', function () {
  // Placeholder: Add gallery interactions here
  console.log('OZ Photo Gallery script loaded.');
});

Both files are registered using wp_enqueue_style and wp_enqueue_script inside our asset handler class.

templates/gallery-template.php

<?php
// Gallery template output
// Can be overridden via theme if needed

defined('ABSPATH') || exit;

echo '<div class="ozpg-gallery-template">';
echo '<p>This is the default gallery template.</p>';
echo '</div>';

uninstall.php

<?php
// Clean up custom post types and options if needed

defined('WP_UNINSTALL_PLUGIN') || exit;

// Example: delete_option('ozpg_settings');

readme.txt

=== OZ Photo Gallery ===
Contributors: ozwebexpert
Tags: photo, gallery, albums, images
Requires at least: 5.0
Tested up to: 6.8
Requires PHP: 7.4
Stable tag: 1.0.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

A lightweight, developer-friendly photo gallery plugin made for the Australian creative community.

6. What’s Next?

Now that we’ve got a clean structure, we can safely move forward with the next parts:

  1. Register custom post types: albums and photos
  2. Build the front-end template for galleries
  3. Add photo upload logic and thumbnail previews

In Part 2, we’ll focus on creating our custom post types with meaningful labels and admin UI integration.

FAQ

Can I use this structure for other plugin types?
Absolutely. This skeleton works for nearly any plugin that has frontend output and backend logic.
Why not use a plugin boilerplate generator?
Generators are great, but they often come bloated. Here we keep things minimal and educational.
Do I need to use OOP (Object-Oriented PHP)?
No, but it makes things easier to maintain when your plugin grows. We use it lightly here for structure.
Will this work with Classic and Block themes?
Yes. We’re keeping the output flexible enough to support both. Later, we’ll discuss block editor integration.
Can I publish this plugin on WordPress.org?
Yes, the structure here aligns with plugin repo requirements. Just remember to follow their security and licensing rules.