How to Create a Custom WordPress Plugin From Scratch in 2025 (Even If You’re Not a Coder)
Let’s be real at some point every site owner thinks, “Wouldn’t it be cool if WordPress just did this one extra thing?”
Maybe you want a tiny tweak to your checkout page. Maybe you need a custom dashboard widget for your team. Whatever it is, rolling your own plugin is way easier than juggling ten bloated add-ons that slow your site to a crawl.
So today, I’ll walk you through the exact steps I used last month to spin up a lightweight plugin for a bakery site. No fluff, no jargon just the stuff that matters.
Why roll your own plugin in 2025?
Pre-made plugins rock until they don’t. Here’s why a custom one often wins:
- Exact fit - Need to stamp every PDF invoice with your cat’s paw-print logo? Done.
- Zero bloat - You only ship the code you need, so your Core Web Vitals stay happy.
- Theme freedom - Switch themes tomorrow and your feature still works.
- Easy updates - No more crossing fingers when the “SEO Mega Pack Pro” plugin auto-updates and breaks everything.
- Reuse & sell - Drop the same plugin on five client sites or list it on wordpress.org for that sweet passive income.
Quick gear check (2-minute prep)
Before we touch code, grab these:
- Local playground - I use Local because it spins up a site faster than I make coffee.
- Editor - VS Code with the PHP Intelephense extension. Trust me, the red squiggly lines save hours.
- Basic PHP - If you can write a function and remember a semicolon, you’re golden.
- File access - FTP or your host’s file manager works fine.
Ready? Cool. Let’s write some code.
Step 1 - Birth of a plugin folder
- Open
wp-content/plugins
. - Create a new folder:
my-first-plugin
. - Inside, create
my-first-plugin.php
.
That’s it. No ceremony, no confetti (yet).
Step 2 - Tell WordPress “Hey, I exist!”
Pop open my-first-plugin.php
and drop in this header:
<?php
/**
* Plugin Name: My First Plugin
* Description: Adds a simple "Quote of the Day" widget to every post.
* Version: 1.0.0
* Author: You, the hero
* License: GPL v2 or later
*/
Save. Refresh Plugins in your dashboard boom, it shows up. Don’t activate yet; we’ll add brains first.
Step 3 - Make it do something fun
Let’s print a random quote at the end of every post. Paste right below the header:
function mfp_add_quote($content) {
$quotes = [
"Life is short. Eat dessert first.",
"Stressed is desserts spelled backwards.",
"You can't buy happiness, but you can buy cake."
];
$quote = $quotes[array_rand($quotes)];
return $content . '<blockquote><em>' . esc_html($quote) . '</em></blockquote>';
}
add_filter('the_content', 'mfp_add_quote');
Activate the plugin, visit any post, and ta-da random bakery wisdom appears.
Step 4 - Level up with CSS & JS (without tears)
We want that quote to pop, so let’s add some style.
Create folders:
my-first-plugin/├── css/│ └── quote.css└── js/ └── quote.js
quote.css
:
.mfp-quote {
border-left: 4px solid #ff6b6b;
padding-left: 1rem;
font-style: italic;
}
quote.js
:
document.addEventListener('DOMContentLoaded', () => {
const quote = document.querySelector('.mfp-quote');
if (quote) quote.style.animation = 'fadeIn 0.5s';
});
Enqueue them properly:
function mfp_assets() {
wp_enqueue_style('mfp-css', plugin_dir_url(__FILE__) . 'css/quote.css');
wp_enqueue_script('mfp-js', plugin_dir_url(__FILE__) . 'js/quote.js', [], '1.0.0', true);
}
add_action('wp_enqueue_scripts', 'mfp_assets');
Now your quotes look snazzy and fade in smoothly. Neat, right?
Step 5 - Give users a settings page (because options rock)
Imagine the bakery owner wants to switch quotes herself. Let’s build a 5-minute settings page.
// 1. Create menu item
function mfp_admin_menu() {
add_options_page(
'Quote Settings',
'Quote Settings',
'manage_options',
'mfp-settings',
'mfp_settings_html'
);
}
add_action('admin_menu', 'mfp_admin_menu');
// 2. The HTML form
function mfp_settings_html() { ?>
<div class="wrap">
<h1>Quote Settings</h1>
<form method="post" action="options.php">
<?php settings_fields('mfp_options'); ?>
<label>Your custom quote:<br>
<textarea name="mfp_custom_quote" rows="3" cols="50"><?= esc_textarea(get_option('mfp_custom_quote')); ?></textarea>
</label>
<?php submit_button(); ?>
</form>
</div>
<?php }
// 3. Register setting
function mfp_settings_init() {
register_setting('mfp_options', 'mfp_custom_quote', 'sanitize_text_field');
}
add_action('admin_init', 'mfp_settings_init');
Now admins can paste their own quote. Update your earlier filter to check for this option:
function mfp_add_quote($content) {
$custom = get_option('mfp_custom_quote');
$quote = $custom ?: "Default: Eat more cookies!";
return $content . '<blockquote class="mfp-quote"><em>' . esc_html($quote) . '</em></blockquote>';
}
Step 6 - Test like your reputation depends on it (because it does)
Quick checklist:
- Activate plugin.
- View a post quote appears?
- Change theme still there?
- Turn on
WP_DEBUG
inwp-config.php
to catch any screamy PHP notices. - Run Query Monitor to confirm no extra database hits.
Common rookie traps (and how to dodge them)
- Forgetting prefixes - Always name functions
mfp_
,yourbrand_
, etc. Collisions are real. - Echoing raw POST data - Sanitize everything with
sanitize_text_field()
,esc_html()
, or nonces. - Inline scripts/styles - Use
wp_enqueue_*
so caching plugins don’t hate you. - Hard-coding paths -
plugin_dir_url(__FILE__)
is your friend.
Mini-FAQ (because someone will ask)
Q: Can I sell my plugin?
Yep. GPL license lets you sell it, but you must share code if someone asks.
Q: Will it break on the next WordPress update?
Unlikely if you stick to core hooks. I’ve got plugins running since 2019 that never blinked.
Q: Do I need OOP for bigger plugins?
Eventually. Start procedural, refactor to classes once you hit ~500 lines. Baby steps.
Your next 3 moves
- Ship v1.0 - Zip the folder, install on a live site, celebrate with actual cake.
- Add features - Maybe a Gutenberg block or REST endpoint next weekend?
- Share it - wordpress.org repo submission is free and the community loves fresh blood.
“The best code is the code you actually ship.”
#WordPressPlugin #CustomCode #PHPBasics #WebDevTips