Episode 1 – WordPress Plugin Development with WooCommerce
The shell
Step 1. Make the folder and the file
Type this in your terminal:
cd "/Users/sejim/Local Sites/simple-boards/app/public/wp-content/plugins"
mkdir order-boost
touch order-boost/order-boost.php
open -a "Visual Studio Code" order-boost/order-boost.php
WordPress looks for plugins in one place only. That place is wp-content/plugins. It reads every folder in there.
Think of a shelf in a shop. The shop only sells what sits on the shelf. Your box is now on the shelf, but the box is empty.
The folder name and the file name match. That is a habit, not a law. Keep the habit. It makes the file easy to find later, when you support 30 sites.
Step 2. Open the tag
The file is empty. Type this first line:
<?php
This tells the server “PHP starts here”. Everything after it is code, not text for the browser.
Note one thing. We never type ?> at the end of a PHP file. A blank line after ?> gets sent to the browser. That blank line breaks headers and cookies. So we leave the file open.
Step 3. Type the header
Type this under the <?php line:
/**
* Plugin Name: Order Boost
* Description: Sale badges and a free shipping nudge for WooCommerce.
* Version: 1.0.0
* Author: Rafiz
* Text Domain: order-boost
* Requires at least: 6.0
* Requires PHP: 7.4
*/
This is a comment. PHP ignores it. WordPress does not.
This block is the label on the box. WordPress opens the first 8 kilobytes of your file, it reads this label, and then it closes the file again. Nothing else in the file matters at this moment.
Line by line:
- Plugin Name is the only line you must have. No name, no plugin. The file stays invisible.
- Description is the small grey text under the name in the plugin list.
- Version shows in the plugin list. Update sites also use it to decide “is a newer one out?”.
- Author shows your name.
- Text Domain is the name tag for translations. Every language file for this plugin starts with
order-boost. We will use it later. - Requires at least is the minimum WordPress version. WordPress refuses to activate the plugin on an older site.
- Requires PHP is the same idea for the PHP version.
Those last two lines are the warning on the box, like “not for children under 3”. They stop a person from breaking their own site with your plugin.
Test it now
Go to your site admin. Open Plugins. You see Order Boost in the list.
Do not activate it yet.
This is your first checkpoint. The plugin is now real to WordPress. It has a name, and it has no code.
Step 4. Lock the side door
Type this under the header:
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
Here is the problem this solves.
Your file sits in a public folder. A person can type the address of the file straight into a browser. That runs your file alone, with no WordPress around it. Some plugin files leak errors, paths, or worse when you do that.
ABSPATH is a value that WordPress sets when it starts. If WordPress did not start, the value is missing. Then exit stops the file dead.
Think of the side door of a hotel kitchen. Guests come through the lobby. The side door stays locked. Same door, but only the correct path opens it.
Put these three lines at the top of every PHP file you write. Every one. No exceptions.
Step 5. Add the constants
Type this next:
define( 'ORB_VERSION', '1.0.0' );
define( 'ORB_DIR', plugin_dir_path( __FILE__ ) );
define( 'ORB_URL', plugin_dir_url( __FILE__ ) );
A constant is a value with a name that you set one time. You cannot change it later. That is the point.
Why these three?
ORB_VERSION is your version number in one place. Later we load a CSS file, and we send this number with it. Browsers keep old files in memory for speed. When you change the version, the browser sees a new address and it downloads the new file. This is the reason a customer says “I see the old style”. Their browser is holding the old file. We fix that with a version number.
ORB_DIR is the path on the disk, like /Users/sejim/Local Sites/.../plugins/order-boost/. PHP uses paths on disk to load files.
ORB_URL is the web address of the same folder, like https://simple-boards.local/wp-content/plugins/order-boost/. Browsers use web addresses to load CSS and images.
Those two are not the same thing, and people mix them up all the time. Here is the picture. A path is “the third drawer in the grey cabinet”, which only staff in the building can use. A web address is “send it to 21 Baker Street”, which anybody outside can use. PHP is staff. The browser is outside.
__FILE__ is a magic word. PHP swaps it for the full path of the file you are in. So the two functions always find the correct folder, on any site, on any server. You never write a path by hand.
ORB_ is your name tag. Two plugins with a constant called VERSION will fight, and one site will crash. So we put three or four letters in front of every name we invent. Order Boost becomes ORB_ for constants and classes, and orb_ for functions.
Step 6. Wake the plugin up
Type this at the bottom of the file:
add_action( 'plugins_loaded', 'orb_init' );
function orb_init() {
if ( ! class_exists( 'WooCommerce' ) ) {
return;
}
// TEMPORARY. We delete this in the next episode.
add_action( 'admin_notices', function () {
echo '<div class="notice notice-success"><p>Order Boost is awake.</p></div>';
} );
}
This is the most important idea in the whole course. Read this part twice.
WordPress runs from top to bottom on every page load. On the way, it rings bells at fixed moments. “Plugins are all loaded now.” “I am about to print the admin messages.” “The cart page starts here.”
add_action means one thing: when you ring that bell, call my function.
You never call orb_init yourself. WordPress calls it. You only say which bell you wait for.
This is why a plugin never edits WordPress files. It waits, and then it steps in at the correct moment.
Why plugins_loaded and not straight away?
Look at the check inside. We ask “does the class WooCommerce exist?”.
WordPress loads plugins in alphabetical order. Our plugin starts with o. WooCommerce starts with w. So when our file runs, WooCommerce has not loaded yet. If we ask the question at that moment, the answer is always “no”. That is wrong.
plugins_loaded rings after every plugin is in memory. Now the question is fair.
This is a real support pattern. A customer writes “your plugin says WooCommerce is missing, but WooCommerce is active”. Nine times out of ten, the check ran too early.
Why the check at all?
Our plugin talks to WooCommerce. It reads carts and products. If WooCommerce is not there, those calls hit nothing and the site dies with a fatal error. A white screen.
So we ask first. If WooCommerce is missing, we return. return means “stop this function here”. The plugin stays quiet and the site stays alive.
Think of a kettle with no water in it. A good kettle switches off. A bad kettle burns.
Test it now
- Go to Plugins in your admin.
- Make sure WooCommerce is active.
- Activate Order Boost.
You see a green box at the top: Order Boost is awake.
Now do the opposite test. Deactivate WooCommerce, and look again. The green box is gone. Your plugin is still active, but it sits quiet, and nothing breaks.
Turn WooCommerce back on. The box comes back.
You just tested a dependency guard. That is a real task in support work.
Your file, all together
Check yours against this. It must match, line for line.
<?php
/**
* Plugin Name: Order Boost
* Description: Sale badges and a free shipping nudge for WooCommerce.
* Version: 1.0.0
* Author: Rafiz
* Text Domain: order-boost
* Requires at least: 6.0
* Requires PHP: 7.4
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
define( 'ORB_VERSION', '1.0.0' );
define( 'ORB_DIR', plugin_dir_path( __FILE__ ) );
define( 'ORB_URL', plugin_dir_url( __FILE__ ) );
add_action( 'plugins_loaded', 'orb_init' );
function orb_init() {
if ( ! class_exists( 'WooCommerce' ) ) {
return;
}
// TEMPORARY. We delete this in the next episode.
add_action( 'admin_notices', function () {
echo '<div class="notice notice-success"><p>Order Boost is awake.</p></div>';
} );
}
One more test, because breakage teaches faster
Change the bell name to a wrong one:
add_action( 'plugins_loadedd', 'orb_init' );
Reload the admin. The green box is gone. There is no error message. There is no warning in the log. Nothing at all happens.
WordPress never rings a bell called plugins_loadedd. So your function sits and waits forever.
Remember this feeling. “The plugin does nothing, and there is no error” is the most common ticket you will ever read. Most of the time the code is fine. The code is only listening for the wrong bell, or listening too late.
Put the correct name back. The box returns.
Where we are
You have a live plugin with four parts:
- A header, so WordPress can see it.
- A lock, so nobody runs the file from outside.
- Constants, so the plugin knows its own version, its folder, and its web address.
- A boot function on a bell, with a safety check for WooCommerce.
Every plugin in the world starts the same way. This part does not get harder.
Next episode: the admin screen. We delete the temporary green box, we add a real page under WooCommerce, and we save the first setting to the database.
Two questions before we go on:
- We used
ORB_DIRandORB_URL. One of them will load a CSS file into the browser later. Which one, and why the other one cannot do it? - Your plugin folder starts with o, and WooCommerce starts with w. What would happen to our WooCommerce check if we deleted the
add_actionline, and just calledorb_init()at the bottom of the file?
Answer those in your own words. Then go to Episode 2.