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.