Skip to content
Now building for 2026 — tell us what your team needs. Start a project
Sembosi Digital Sembosi Digital

Building a Custom WordPress Plugin: A Practical Walkthrough

WordPress 1 min read
Building a Custom WordPress Plugin: A Practical Walkthrough

When a site needs to do something specific, a small custom plugin beats a heavy page builder. Here is the structure we use.

Most WordPress problems we are hired to solve come from sites built entirely out of plugins and page builders. When something genuinely custom is needed, a small, well-written plugin is usually the right answer — and it is easier to maintain than anyone expects.

Start with a proper plugin header

<?php
/**
 * Plugin Name: SD Toolkit
 * Description: Security hardening and structured data for Sembosi Digital sites.
 * Version: 1.0.0
 * Author: David Sembosi
 * Requires at least: 6.4
 * Requires PHP: 8.0
 */

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

That final check matters. Without it, the file can be requested directly and your code runs outside WordPress.

Register things on hooks, not at load time

add_action('init', function (): void {
    register_post_type('project', [
        'public' => true,
        'has_archive' => true,
        'supports' => ['title', 'editor', 'thumbnail', 'excerpt'],
        'rewrite' => ['slug' => 'projects'],
    ]);
});

Hooks keep your code predictable and let other developers unhook it cleanly.

Use the Settings API for options

Do not hand-roll admin forms and $_POST handling. The Settings API gives you sanitisation callbacks, nonce verification and a consistent admin experience for free.

register_setting('sd_toolkit', 'sd_toolkit_options', [
    'type' => 'array',
    'sanitize_callback' => 'sd_toolkit_sanitize_options',
    'default' => ['enable_schema' => true],
]);

Sanitise on input, escape on output

The same rule as every other PHP project:

  • Input: sanitize_text_field(), absint(), wp_kses_post()
  • Output: esc_html(), esc_attr(), esc_url(), wp_kses()

Add structured data properly

add_action('wp_head', function (): void {
    if (!is_singular('project')) {
        return;
    }
    $data = [
        '@context' => 'https://schema.org',
        '@type' => 'SoftwareApplication',
        'name' => get_the_title(),
        'description' => get_the_excerpt(),
    ];
    printf(
        '<script type="application/ld+json">%s</script>',
        wp_json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
    );
});

Enqueue assets the right way

add_action('wp_enqueue_scripts', function (): void {
    wp_enqueue_style('sd-toolkit', plugin_dir_url(__FILE__) . 'assets/style.css', [], '1.0.0');
    wp_enqueue_script('sd-toolkit', plugin_dir_url(__FILE__) . 'assets/app.js', [], '1.0.0', true);
});

Never hardcode <script> tags in templates — you lose dependency management and caching.

Keep it small

A plugin should do one job well. If your plugin is doing five unrelated jobs, split it. Small plugins are testable, reviewable and much easier to hand over to the next developer.