Writing a WordPress Plugin From Scratch: A Step-by-Step Tutorial

wordpress plugin development tutorial

This WordPress plugin development tutorial captures the steps I went through on a real project, including the mistakes I made and how I debugged them.

This week’s article shows how to create a WordPress plugin from scratch. Rather than showing you only the finished code, I’ve written a step-by-step WordPress plugin development tutorial, capturing the steps I actually went through on a real plugin project, including the mistakes I made and how I debugged them.

So this article is designed to give you insight into a WordPress developer’s real process in creating a WordPress plugin from scratch, beginning only from a feature need. Other developers’ processes would be different from mine: a lot of what you’ll see below are my own approaches to the plugin development process, which you can’t necessarily see in the final code.

Read on for a true step-by-step plugin development example, including mistakes, and see the Conclusion section for thoughts on the WordPress plugin development process in general.

WordPress Plugin Development: Start with the Feature Need

Remember that “write a WordPress plugin from scratch” actually means “add any code you want into WordPress,” so what you need to learn is a set of processes.

It’s important to remember that “write a WordPress plugin from scratch” actually means “add absolutely any code you want into WordPress”: what you write is totally, 100% dependent on the feature need.

As a result, there’s no one answer to “how to write a WordPress plugin,” any more than there’s one answer to “how to cook a meal.” (What are you hungry for?) Instead, what you can learn are plugin development processes, using specific plugins as examples that teach those processes.

Describing the Feature Need

For this plugin development project, I chose something that’ll actually be useful for our internal accounting on WPShout. Here’s the feature need:

“I need a plugin that will tell me what posts (of any post type) were published so far this calendar month on WPShout, and who their authors were.”

A Single-Site (Not Wide Release) Plugin

An important note: I’ve written this plugin as being only for one site. In other words, it’s the kind of plugin a WordPress freelancer would write for a single client, and not one you’d release for potentially thousands of users on the WordPress plugin repository. Because this plugin is just for a single user, I can code it much more simply—without needing to, for example, make the plugin extensible with hooks or do elaborate namespacing and translation.

Lastly, before we start: the plugin below (as you’d expect) relies on knowledge of both PHP in general and of WordPress’s PHP codebase specifically. If you’re looking to get more comfortable on those, our WordPress development course Up and Running is the best way to learn.

Serious About Learning WordPress Development?

Get Up and Running Today

Up and Running is our complete “learn WordPress development” course. Now in its updated and expanded Third Edition, it’s helped hundreds of happy buyers learn WordPress development the fast, smart, and thorough way.

 

I think anyone interested in learning WordPress development NEEDS this course.

Before I purchased Up and Running, I had taught myself some WordPress code, but lacked direction. Watching the course’s videos was like a bunch of lights being turned on.

I went from being vaguely familiar with how themes, functions and WordPress itself worked to mastering these. Everything became much clearer.

I very happily recommend this course to anyone willing to listen.”

–Jason Robie, WordPress developer

Take the next step in your WordPress development journey!

With that, let’s dive into the steps I followed to develop this plugin!

Step-by-Step WordPress Plugin Development Tutorial

Here are the steps I followed, with full code for each step.

1. Just Do Something

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_action( 'init', 'wpshout_do_thing' );
function wpshout_do_thing() {
		if( ! isset( $_GET['wpsdt'] ) ) :
			return;
		endif;

		var_dump("Here");
		die;
}

Notes on Step 1

This step is just to make sure WordPress is listening to my plugin, and that I can get the plugin to print out something on the screen. If you’re curious about the WordPress plugin boilerplate at the top that actually registers the plugin itself, see our Quick Guide on the topic.

Note the if( ! isset( $_GET['wpsdt'] ) ) : trick I used here. It uses PHP’s $_GET superglobal variable to run the plugin’s code if and only if the URL has a query string with wpsdt in it. I often use this trick in plugin development, since it’s an easy “on/off” switch for the plugin that lets me run it only by manually altering URL parameters.

Note that I’m also hooking into WordPress’s init action hook. Why? Because I want my plugin to run before everything else and I don’t want to think about it. That’s also why I named my hooked function wpshout_do_thing(): because I know it won’t cause a namespace collision (the wpshout_ is very simple and good-enough function namespacing) and I don’t want to think about it. We’ll make it good later.

The var_dump(); die; pattern I use here is part of my bread and butter in WordPress plugin development as well, because it lets me see exactly what the plugin is currently outputting and then kills any other PHP processes that might want to run afterward. Never do this in a way that affects users, obviously, or they’ll see a thoroughly broken site.

The result of the code above, when I do pass in the wpsdt query string, is:

create a wordpress plugin from scratch

2. Query Recent Posts of All Post Types

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_action( 'init', 'wpshout_do_thing' );
function wpshout_do_thing() {
		if( ! isset( $_GET['wpsdt'] ) ) :
			return;
		endif;

		$args = array(
			'posts_per_page' => 5,
			'post_type' => 'any',
			'post_status' => 'publish',
			'orderby' => 'date',
			'order' => 'DESC',
		);

		$query = new WP_Query( $args );

		var_dump( $query );
		die;
}

Notes on Step 2

We’re now writing a custom WP_Query, and var_dump()ing the result to make sure it works. The query itself is for “the most recent five posts, of any post type, whose post status is published,” which is partly what we want (any post type, post status published) and partly not what we want (the most recent five). We’re doing the easy part first.

This step leads to the following output. What’s good is that the var_dump()ed WP_Query object’s posts property is an array of five elements. If you know enough about how WP_Querys work, you know that the count of the posts array reflects the number of posts actually fetched in that query. In other words, we really did manage to get five posts.

wordpress plugin development tutorial step by step

3. Research How to Query All the Posts Published This Month

Now we’re at the part I don’t know how to do, so I won’t be writing plugin code in this step. What I don’t know how to do is: how do I write a WP_Query that fetches only posts published this calendar month? (Remember, that would mean “November 1 through 10” on November 10, not “the past 30 days.”)

Time to Google, which is such an important part of WordPress plugin development (and WordPress development generally) that I’m putting it in bold: Time to Google. And when you Google, it’s super-important to know two things:

  1. How to write your Google queries.
  2. How to pick out relevant results.

The Google search I ran was for “wpquery publish date.” Try the search yourself! What looks relevant?

I thought the best fit was a Stack overflow article titled Query posts published after a certain date in WordPress. In its answers, it mentions that you can pass a date_query array element into WP_Query, and gives the following sample code:

'date_query' => array(
	'after' => array(
		'year'  => 2012,
		'month' => 3,
		'day'   => 1,
	),
),

That’s fine , but we don’t want “month 3,” we want this month. How do we say this month in PHP?

The Google search I ran was for “php this month,” and it led me here: https://stackoverflow.com/questions/5347217/simplest-way-to-display-current-month-and-year-like-aug-2016-in-php

That led me to the following code for our date_query:

'date_query' => array(
	'after' => array(
		'year'  => date( 'Y' ),
		'month' => date( 'M' ),
		'day'   => 1,
	),
),

4. Combine the Date Query with the Main Query

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_action( 'init', 'wpshout_do_thing' );
function wpshout_do_thing() {
		if( ! isset( $_GET['wpsdt'] ) ) :
			return;
		endif;

		$args = array(
			'posts_per_page' => -1,
			'post_type' => 'any',
			'post_status' => 'publish',
			'date_query' => array(
				'after' => array(
					'year' => date( 'Y' ),
					'month' => date( 'M' ),
				),
				'day' => 1,
			),
			'orderby' => 'date',
			'order' => 'DESC',
		);

		$query = new WP_Query( $args );

		var_dump( $query );
		die;
}

Notes on Step 4

In this step, I’ve tried to fold the date_query parameter from my Google learning into my existing WP_Query. I’ve also changed posts_per_page to -1 so that we get all the posts we’re interested in.

Running the code above gives this result:

wordpress plugin development tutorial debugging

Is that code good or bad? It’s bad. Why? Because so many of the post_date elements that show up in our var_dump() date back to February or January of the current year. Our query is definitely not fetching the posts it’s supposed to be fetching. Time to figure out what went wrong.

5. That Didn’t Work, Debug

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_action( 'init', 'wpshout_do_thing' );
function wpshout_do_thing() {
		if( ! isset( $_GET['wpsdt'] ) ) :
			return;
		endif;

		// $args = array(
		// 	'posts_per_page' => -1,
		// 	'post_type' => 'any',
		// 	'post_status' => 'publish',
		// 	'date_query' => array(
		// 		'after' => array(
		// 			'year' => date( 'Y' ),
		// 			'month' => date( 'M' ),
		// 		),
		// 		'day' => 1,
		// 	),
		// 	'orderby' => 'date',
		// 	'order' => 'DESC',
		// );

		// $query = new WP_Query( $args );

		$after = array(
			'year' => date( 'Y' ),
			'month' => date( 'M' ),
		);

		var_dump( $after );
		die;
}

Notes on Step 5

In this step, I do two things:

  1. Comment out (but don’t delete!) the code that’s acting strangely.
  2. Try to get visibility into the part of that strangely behaving code.

What’s acting weirdly is the after element of our date_query. Why is it acting strangely? Maybe date_query doesn’t actually work or something, but it’s a lot more likely that I’ve written something wrong. So let’s just see if we can inspect what that piece of the array is actually generating, by making $after a variable and var_dump()ing that.

That gives this result:

wordpress plugin tutorial debugging

Wait, that’s not right: month is a string, "Oct". date_query is definitely expecting something numeric.

To make sure this was the mistake I made, I Googled “php date,” and checked this result: https://www.w3schools.com/php/func_date_date.asp. And yep, date( 'M' )  prints out the first three letters of the date’s name in English. Not what we want.

6. Retry

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_action( 'init', 'wpshout_do_thing' );
function wpshout_do_thing() {
		if( ! isset( $_GET['wpsdt'] ) ) :
			return;
		endif;

		$args = array(
			'posts_per_page' => -1,
			'post_type' => 'any',
			'post_status' => 'publish',
			'date_query' => array(
				'after' => array(
					'year' => date( 'Y' ),
					'month' => date( 'm' ),
				),
				'day' => 1,
			),
			'orderby' => 'date',
			'order' => 'DESC',
		);

		$query = new WP_Query( $args );

		// $after = array(
		// 	'year' => date( 'Y' ),
		// 	'month' => date( 'M' ),
		// );

		var_dump( $query );
		die;
}

Notes on Step 6

This step involves me commenting out the test code, uncommenting the query code itself, and changing 'M' to 'm' in the date_query, since I’ve just realized that 'm' is what actually gives you a numeric month when passed into date().

Running this code gives the following:

how to create a custom wordpress plugin debugging wp_query

Is this good or bad? It’s bad: the WP_Query‘s posts property is an empty array, meaning that we didn’t fetch any posts.

7. That Didn’t Work, Debug

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_action( 'init', 'wpshout_do_thing' );
function wpshout_do_thing() {
		if( ! isset( $_GET['wpsdt'] ) ) :
			return;
		endif;

		$args = array(
			'posts_per_page' => -1,
			'post_type' => 'any',
			'post_status' => 'publish',
			'date_query' => array(
				'after' => array(
					'year' => date( 'Y' ),
					'month' => date( 'm' ),
					'day' => 1,
				),
			),
			'orderby' => 'date',
			'order' => 'DESC',
		);

		$query = new WP_Query( $args );

		// $after = array(
		// 	'year' => date( 'Y' ),
		// 	'month' => date( 'M' ),
		// );

		var_dump( $query );
		die;
}

Notes on Step 7

In this step, I needed to re-run my Google search for “wpquery publish date” to get back to the Stack Overflow article, Query posts published after a certain date in WordPress, and check if I did anything wrong.

Yes, I did: for some odd reason, I put day as its own array element of date_query rather than following the code example in putting day inside the after element of date_query. Fixing that and running the code again gives the following:

wordpress plugin development tutorial post date

Is this good or bad? It’s good: we fetched ten posts, all ten of which have post_date properties within the current month. Progress!

8. UI and Shortcode

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_shortcode( 'wpshout_show_this_months_posts_by_author', 'wpshout_show_this_months_posts_by_author' );
function wpshout_show_this_months_posts_by_author() {
		if( ! current_user_can( 'administrator' ) ) :
			return;
		endif;

		$args = array(
			'posts_per_page' => -1,
			'post_type' => 'any',
			'post_status' => 'publish',
			'date_query' => array(
				'after' => array(
					'year' => date( 'Y' ),
					'month' => date( 'm' ),
					'day' => 1,
				),
			),
			'orderby' => 'date',
			'order' => 'DESC',
		);

		$query = new WP_Query( $args );

		ob_start();

		while( $query->have_posts() ) :
			$query->the_post(); ?>

			<h2><?php the_title(); ?></h2>
			By <?php the_author(); ?> on <?php the_date(); ?>

		<?php endwhile;

		wp_reset_postdata();

		return ob_get_clean();
}

Notes on Step 8

Now the hard stuff is done, so it’s time to name our hooked function something non-silly, decide on a user interface, and do general cleanup.

This is a plugin by and for a single WordPress developer, and so the natural choice of interface seems to be a shortcode. I change add_action() to add_shortcode(), and name the shortcode function something accurate, wpshout_show_this_months_posts_by_author(). I use a favorite tool for shortcode-based plugin development, PHP output buffering, to control what markup the shortcode will return without needing to build a giant $return variable.

Within that output-buffered zone, I freely use WordPress template tags like the_author() and the_date(), since we’re in the Loop of a custom query.

I also remembered that it’s good to call wp_reset_postdata() after running a custom query, but couldn’t remember if that or wp_reset_query() was the right function to use. I Googled “wp_reset_postdata or wp_reset_query” and found this article: https://wordpress.stackexchange.com/questions/144343/wp-reset-postdata-or-wp-reset-query-after-a-custom-loop, which made me conclude wp_reset_postdata() will work just fine.

Running the plugin on a page with the wpshout_show_this_months_posts_by_author shortcode in place gives the following:

https://wpshout.com/wp-content/uploads/2019/10/wordpress plugin development troubleshooting example

This is pretty good, but what’s the missing date in the article one from the bottom?

8. So the_date() is Totally Weird?

<?php 
/**
 * Plugin Name: WPShout Show Authorship this Month
 * Description: Show who's written what this month on WPShout
 * Version: 1.0
 * Author: WPShout
 * Author URI: https://wpshout.com
*/

add_shortcode( 'wpshout_show_this_months_posts_by_author', 'wpshout_show_this_months_posts_by_author' );
function wpshout_show_this_months_posts_by_author() {
		if( ! current_user_can( 'administrator' ) ) :
			return;
		endif;

		$args = array(
			'posts_per_page' => -1,
			'post_type' => 'any',
			'post_status' => 'publish',
			'date_query' => array(
				'after' => array(
					'year' => date( 'Y' ),
					'month' => date( 'm' ),
					'day' => 1,
				),
			),
			'orderby' => 'date',
			'order' => 'DESC',
		);

		$query = new WP_Query( $args );

		ob_start();

		while( $query->have_posts() ) :
			$query->the_post(); ?>

			<h2><?php the_title(); ?></h2>
			By <?php the_author(); ?> on <?php echo get_the_date( 'l, F d, Y' ); ?>

		<?php endwhile;

		wp_reset_postdata();

		return ob_get_clean();
}

Notes on Step 9

I suspect that our call to the_date() isn’t working the way I want it to, so I do a Google search for “wordpress the_date.” I start with the official page: https://developer.wordpress.org/reference/functions/the_date/ It doesn’t tell me much. I also see https://wordpress.stackexchange.com/questions/52489/the-date-not-working, and look into it.

It turns out that the_date() will only show one post per date. If there are two posts with the same publication date, it stops working. And it also turns out that get_the_date() doesn’t have this problem, so I change that line of code from the_date(); to echo get_the_date();.

Is this a weird problem to have? Extremely. I have no idea why the_date() would be coded in this half-broken way, or why get_the_date() wouldn’t share the issue. But running the new code gives us this:

wordpress plugin tutorial user interface design

And that’s exactly what we’re looking for.

General Thoughts on WordPress Plugin Development

The work above took me about two hours. Is that a lot or a little? It would be a long time to type out the working plugin code once you know how to write it, but it’s just about right for what WordPress plugin development is really about, which is learning, experimentation, research, and trial-and-error.

In that two hours, I did an awful lot of the following things:

  • Relying on in-depth knowledge of core PHP features and core WordPress APIs.
  • Googling things I forgot or didn’t know how to do.
  • Making mistakes.
  • Debugging mistakes.

And in that two hours, I did none of the following things:

  • Writing code that relies on the coder being really smart rather than on having in-depth knowledge.
  • Relying on super-obscure tools, languages, or “hacks” that only the cool kids know about.

That’s how WordPress plugin development is—especially for relatively simple plugins like this one that a freelancer would write for a single client. It’s all about leveraging your existing knowledge to do the easy stuff, and knowing how to acquire and combine new knowledge to do the hard stuff, while continuously testing and fixing the errors that crop up—both the ones you cause, and the ones PHP and WordPress cause (the best example above is our the_date() problem) by being sometimes-strange software systems.

The Best Way to Learn WordPress Development

Get Up and Running Today

Up and Running is our complete “learn WordPress development” course. Now in its updated and expanded Third Edition, it’s helped hundreds of happy buyers learn WordPress development the fast, smart, and thorough way.

Here’s what they have to say:

wordpress freelancer caroline “Other courses I’ve tried nearly always lack clear explanations for why WordPress does things a certain way, or how things work together.

Up and Running does all of this, and everything is explained clearly and in easy-to-understand language.”

–Caroline, WordPress freelancer

Up and Running really brought everything together for me. I already knew of some of the pieces, but the course filled in the gaps and provided a proper understanding of how WordPress really works.

I found it easy to follow, delivering just the right depth of knowledge in the right sequence.”

–Hugues, freelance web developer

Thanks for reading! We’d love to hear your thoughts and questions below, or in our Facebook group.

 


3 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Steve G
April 30, 2020 8:56 am

Great write-up, very helpful in de-mystifying the skill of “plugin creation”.

Viktor Borítás
December 16, 2019 8:45 am

This is absolutely amazing. High standards style & value, thank you very much, it’s always a huge gift to be allowed to look into other people’s thinking-methods and general FLOW (hopefully;).
Best wishes from Hungary

Nisha Batel
October 30, 2019 6:11 am

Thank you for such an amazing post of plugin development.