Developing and maintaining “freemium” WordPress plugins

Here I would like to share with you my strategy for developing and maintaining “freemium” WordPress plugins that are easy to maintain and release.

Monetizing WordPress software

WordPress is such a fun platform to work with not so much for its core features, but due to its vibrant ecosystem of themes and plugins.

On WordPress.org there’s a ton of high-quality software that you can use for free (they’re all GPL), and they cater to seemingly every need you can think of. This is perhaps the software channel with the largest exposure since it is integrated into the WordPress admin screen.

Envato is a very successful software market where developers can charge money for their themes and plugins, but there are strict rules for admission and the distribution channel does the pricing; not the developer. The channel also takes a huge cut which currently ranges from 50 to 70 percent depending on whether you give them exclusive rights over your creation.

Or you can buy access to a whole bunch of plugins from developer teams that have put a lot of stuff out there, such as WPMU DEV. Even factoring in the support and documentation you get, the prices you pay are very low.

Very few plugins will cost you a price higher that a two digit amount of US dollars. So who makes these free, or dirt-cheap themes and plugins, and how do they get paid for their work?

Just do a google search on WordPress and business models, and you will come across articles from developers who discuss their experience on how they monetize on their work. I feel that this article sums it up nicely:

So it turns out there are a bunch of way to make money selling your WordPress software. Today I’m going to focus on the simplest and perhaps most popular way:

The Freemium model

It’s all a numbers game. Sure, you might be selling your plugin for only a few bucks, but if a million people buy it, you’re rich. After all, a very very large percentage of all websites on the Internet run WordPress. That looks like a huge market. Surely even the slimest slice from that pie should be big enough by your standards?

Not so fast, though. How feasible is it to reach large numbers of people? Having built a good and robust product, on its own, is a good start, but you need to do more. You have to think of how to reach your customers, and how you will convince them to:

  1. look at your product,
  2. decide to use your product, and
  3. reach into their pockets and pay you for your hard work.

Think web marketing and promotion. Think exposure to the right channels. Think social media. Think landing pages and variable pricing, and promo codes and “growth hacking” and all that good stuff. How you go from having a product to having money in the bank is your business model. Your strategy. The Freemium model is a tried and true model in the WordPress world.

A portmanteau of “Free” and “Premium”, this model is what it says on the title: You build a product that is high-quality and fully functional. If it solves a real need, with the right kind of promotion you will have a chance of reaching large numbers of users. That’s step 1.

Don’t rush into asking for money at this stage. Nobody will be willing to pay for your product at first. Not even a dollar. Website developers and administrators these days are pandered with absolutely too many high-quality freebies out there. It’s a large Internet and competition is fierce.

At least, they won’t pay with money. But if they choose to give your free product a try, they will have to pay with their valuable time. Even the easiest piece of software takes you a few minutes to install, configure and figure out. You need to convince your potential customer that what they get for free is worth 5 or 10 minutes of their time, or they will spend those 10 minutes trying out someone else’s product.

Once you’ve done all that, you will begin to build a user base. Now it is out of that user base that you can begin to hope that a small percentage will maybe pay a small amount. These dedicated users of your community will pay for everybody else’s free ride.

Source code maintenance

So you have a free edition of your product, plus a premium edition that does all the stuff that the free does, plus a few more features. These features are not absolutely necessary for beginers, but that will come in handy to a few of your most dedicated users. You will have to put the free edition on software channels with high exposure, create some noise around them, and put the premium edition behind some kind of paywall or restricted area. In that same area you might offer support and additional freebies, and perhaps even a community for your users.

How do you maintain this? Surely you don’t want to have two copies of your source tree. That’s a maintainer’s nightmare. You don’t want to duplicate any code. But you do want to have variations between the two release types. Perhaps there is some placeholder text or popup in the free version where the premium feature is missing. You will want to add links to a landing page where users can purchase your premium version. In essence you have two products that are similar in some ways, but not exactly the same. And users who have the free edition installed might be baffled if they buy and install the premium edition and it clashes with their free installation, due to some namespace clash.

The solution that I prefer the best is to use a source code preprocessor. Preprocessors are tools that do stuff with your source before it runs or gets packaged. Let’s look at your requirements and how a preprocessor meets them:

  • You want to be able to include files conditionally into your deliverables. Perhaps this or that directory only goes into the Premium edition.
  • File-level granularity will probably not cut it on its own, though. You will have large files where only a few lines will change between editions. You want to be able to say “if i’m building the free edition, include this code segment in the build, else include that code”. So you need to be able to use inline conditionals into your code.
  • You want to be able to insert variables into your code that are different for each of your multiple editions. Your welcome message might be something along the lines of “thanks for downloading xyz free” versus “thanks for purchasing xyz premium”. Keeping the title of xyz in a preprocessor variable will com handy.
  • Also remember that you will not only need to do this in PHP code. Think CSS and JavaScript. In my case, I produce documentation with Markdown. So you need something that works well across multiple languages.

Readers of this blog will know how much I love Grunt. Grunt is something that in my opinion you should already be using in your project for all kinds of automation. I guess you could get away with plain old shell scripting or some other build tool, but you will definitely want to automate some tasks such as testing, building, producing documentation, packaging and releasing.

I have split my project into the following directories which represent the workflow from source to deliverables:

  • /src — I keep my source code here. This is just an assortment of PHP, JavaScript and CSS magic, as well as a few binary assets. The codes are sprinkled with preprocessor directives that grunt-preprocess will understand. This is a grunt plugin that meets the aforementioned requirements very well.
  • /doc — Here I keep my documentation in Markdown format. Read this other article I wrote on how I use grunt and pandoc to convert it into PDFs. Again, there is very little duplication here thanks to preprocessor directives.
  • /build/free and /build/premium — This is where the output of the preprocessor dumps the processed code and documentation. Some generated data files that contain lists of fonts are dumped here too. I use grunt-shell to run arbitrary linux commands and write the output here. I keep these to a minimum, since the preprocessor does a lot of the dynamic stuff. I also dump here my translation files.
  • /package/free and /package/premium — This is where the source code is zipped. I use grunt-contrib-compress to produce a WordPress plugin in installable .zip form. Also, pandoc dumps the PDF documentation here.
  • /deliverables — You might think that the product has been through enough steps by now, but you’d be wrong. The end deliverable is more than just code. So finally I have grunt-contrib-compress zip up the plugin’s .zip file and the PDFs into a final .zip file, once for each edition, free and premium.

Simply typing grunt on my command line will test and do all of the above. It will also use wp-cli to install and activate the plugins into my development environment so I can manually test.

Deployment considerations

I’m going to leave you with some code that makes sure not both editions are activated in your freemium plugin. It showcases the deactivate_plugins() function and at the same time gives you a taste of what it feels like to use preprocessor directives in your code. Enjoy!

 

<?php
/*
 * Plugin Name: // @echo name
 * Description: // @echo description
 * Version: // @echo version
 * Plugin URI: // @echo homepage
 * Author: // @echo author
 * Author URI: http://alexgeorgiou.gr
 * Text Domain: myplugin
 * Domain Path: /languages/
// @ifdef FREE
 * License: GPLv2 or later
// @endif
 *
 * @package myplugin
 * @since 1.0.0
 */

// don't load directly
defined( 'ABSPATH' ) || die( '-1' );

if ( ! class_exists( '/* @echo class */' ) ) {

    final class /* @echo class */ {

        // @ifdef PREMIUM
        private $_disabled_notice = false;

        private $_must_disable_notice = false;
        // @endif

        private function __clone() {
            // Cloning disabled
        }

        private function __construct() {
            set_error_handler( array( __CLASS__, 'error_handler' ) );
            
            // more stuff here

            // @ifdef PREMIUM
            add_action( 'admin_init', array( &$this, 'action_admin_init' ) );
            add_action( 'admin_notices', array( &$this, 'action_admin_notices' ) );
            // @endif

            // more stuff here

            restore_error_handler();
        }

        // @ifdef PREMIUM
        
        /**
         * Makes sure that free and premium editions are not both installed.
         */
        public function action_init() {
            set_error_handler( array( __CLASS__, 'error_handler' ) );

            // Disable free if free and premium are both installed
            if ( class_exists( '/* @echo pkg_name */_premium' ) && class_exists( '/* @echo pkg_name */_free' ) ) {
                if ( current_user_can( 'activate_plugins' ) && function_exists( 'deactivate_plugins' ) ) {
                    $plugin = plugin_basename( __FILE__ );
                    // @ifdef PREMIUM
                    $plugin = str_replace('myplugin-premium/', 'myplugin/', $plugin);
                    // @endif
                    deactivate_plugins( $plugin );
                    $this->_disabled_notice = true;
                } else {
                    $this->_must_disable_notice = true;
                }
            }

            restore_error_handler();
        }

        public function action_admin_notices() {
            set_error_handler( array( __CLASS__, 'error_handler' ) );

            if ( $this->_disabled_notice ) {
                echo '<div class="notice notice-warn is-dismissible"><p style=\"font-size: larger;">';
                echo '<strong>' . str_replace( '_', ' ', __CLASS__ ) . ':</strong> ';
                echo __( 'Thank you for installing the premium edition of this plugin. ' );
                echo __( 'The free edition has been deactivated.' );
                echo '</p></div>';
            }

            if ( $this->_must_disable_notice ) {
                echo '<div class="notice notice-error is-dismissible"><p style=\"font-size: larger;">';
                echo '<strong>' . str_replace( '_', ' ', __CLASS__ ) . ':</strong> ';
                echo __( 'Thank you for installing the premium edition of this plugin. ' );
                echo __( 'The free edition of the plugin must be deactivated, but your account lacks the necessary priviledges. Please ask the site administrator to disable it, or delete the directory <code>wp-content/plugins/myplugin</code> using FTP or some other means.' );
                echo '</p></div>';
            }
            restore_error_handler();
    }
    // @endif
}

Leave a Reply

Your email address will not be published. Required fields are marked *