How to Add Software Licensing to your Plugin Using Easy Digital Downloads

to be continued sigange
Photo by Reuben Juarez on Unsplash

Creating your own plugin is tough and it’s tougher to sell! I know! I have one plugin for sale over at BB Vapor Modules, and they honestly haven’t been selling well, but time will tell.

I use Easy Digital Downloads because it makes the shopping experience much nicer and it has a plethora of options. Easy Digital Downloads and software licensing do not work out of the box, however. You’ll need some extensions, and I’ll go over that here.

Pre-Requisite Extensions

All of these add-ons are expensive on their own, which is why I highly recommend just getting the Easy Digital Downloads All Access Pass, which gives you access to all extensions for unlimited sites. It’s a must-own product.


Turn on SSL via Let’s Encrypt. If your hosting doesn’t offer free SSL certificates, it’s time to move to a better host. I’m currently hosted on Kinsta and it’s been a great service. SiteGround and WP Engine also offer free SSL certificates.

Auto Register Add-On

You’ll want your users to be able to log in and view past purchases. This is why I recommend the Auto Register Add-On.

Custom Prices Add-On

If you have any kind of donate form or pay me what you want, then you’ll want the Custom Prices Add-On. I use it on my Give Back section.

Discounts Pro Add-On

If you want to apply discounts to a product group over a period of time, this plugin is a must. I highly recommend the Discounts Pro Add-On.

Manual Purchases Add-On

If you want to send out trials to members or invite others to review your plugin, this add-on is a must. I’ve used Manual Purchases numerous times to invite others to try my products.

Recurring Payments Add-On

If you’re needed a per-year or per-month (or any time) recurrence, then the Recurring Payments Add-On is a no-brainer.

Software Licensing Add-On

This is the meat. You’ll need the Software Licensing Add-On to allow you to sell your product digitally.

Finally, the Stripe Payment Gateway Add-On

If you use Stripe, and I don’t see why not, you’ll want the Stripe Payment Gateway Add-On.

Adding Software Licensing to Your Plugin

Once you download the Software Licensing Add-On, you’ll find a samples directory. From there, it has an example of how to include the class into your plugin or theme. In this case, I’m concentrating on the plugin side of things.

You’ll find that you need to include the file EDD_SL_Plugin_Updater.php in your plugin. I typically just throw this into an includes folder in my plugin. Once thing to note here is that other plugins may be using the same class name, so I recommend wrapping it with a class_exists check.

if ( ! class_exists( 'EDD_SL_Plugin_Updater' ) ): class EDD_SL_Plugin_Updater { ... } endif;
Code language: PHP (php)

From there, include and instantiate the class. I’m assuming you have a base plugin file and the updater class in an includes folder.

// Auto Update class define( 'YOUR_PLUGIN_VERSION', '1.0.1' ); add_action( 'admin_init', 'your_plugin_updater_init', 0 ); function your_plugin_updater_init() { require_once 'includes/EDD_SL_Plugin_Updater.php'; $license = get_site_option( 'your_plugin_license', false ); if ( false !== $license ) { // setup the updater $edd_updater = new EDD_SL_Plugin_Updater( '', __FILE__, array( 'version' => YOUR_PLUGIN_VERSION, 'license' => $license, 'item_id' => 123, 'author' => 'Your Name', 'beta' => false, 'url' => home_url(), ) ); } }
Code language: PHP (php)

I’m assuming you have an option named your_plugin_license and a constant named YOUR_PLUGIN_VERSION. You’ll also want to point to whatever domain you are selling your licenses from. Setting the item_id will require that you add the download to your domain first. This is a chicken and egg scenario, but you can draft a download and steal the ID it generates.

For example, here’s mine for my plugin BB Vapor Modules for Beaver Builder.

require_once BBVAPOR_PRO_BEAVER_BUILDER_DIR . 'includes/EDD_SL_Plugin_Updater.php'; $license = get_site_option( 'bbvm_for_beaver_builder_license', false ); if ( false !== $license ) { // setup the updater $edd_updater = new BBVM_EDD_SL_Plugin_Updater( '', BBVAPOR_PRO_BEAVER_BUILDER_FILE, array( 'version' => BBVAPOR_PRO_BEAVER_BUILDER_VERSION, 'license' => $license, 'item_id' => 688, 'author' => 'Ronald Huereca', 'beta' => false, 'url' => home_url(), ) ); }
Code language: PHP (php)

I’ve placed the EDD Updater class in its own includes directory and changed the class name so it doesn’t conflict with other plugins using the EDD Plugin Updater.

Here’s an example of customizing the class name.

<?php // Exit if accessed directly if ( ! defined( 'ABSPATH' ) ) exit; /** * Allows plugins to use their own update API. * * @author Easy Digital Downloads * @version 1.6.18 */ class BBVM_EDD_SL_Plugin_Updater { private $api_url = ''; private $api_data = array(); private $name = ''; private $slug = ''; private $version = ''; private $wp_override = false; private $cache_key = ''; private $health_check_timeout = 5; // ...code
Code language: PHP (php)

Add a Plugin Row Option for the License

Since I’m using class notation, this may be a little tricky to understand, but here are some actions I use to show a license option under the plugin row.

<?php if (!defined('ABSPATH')) die('No direct access.'); class BBVapor_BeaverBuilder_Admin { /** * Holds the slug to the admin panel page * * @since 1.0.0 * @static * @var string $slug */ private static $slug = 'bb-vapor-modules-pro'; /** * Holds the URL to the admin panel page * * @since 1.0.0 * @static * @var string $url */ private static $url = ''; public function __construct() { add_action( 'admin_init', array( $this, 'init' ) ); add_action( 'admin_menu', array( $this, 'register_sub_menu') ); }
Code language: PHP (php)

In the above code, I use admin_init to register the upgrade class and to add the plugin row.

Here’s the code I use to register my admin menu settings and the plugin row action.

/** * Initializes admin menus, plugin settings links. * * @since 1.0.0 * @access public * @see __construct */ public function init() { require_once BBVAPOR_PRO_BEAVER_BUILDER_DIR . 'includes/EDD_SL_Plugin_Updater.php'; $license = get_site_option( 'bbvm_for_beaver_builder_license', false ); if ( false !== $license ) { // setup the updater $edd_updater = new BBVM_EDD_SL_Plugin_Updater( '', BBVAPOR_PRO_BEAVER_BUILDER_FILE, array( 'version' => BBVAPOR_PRO_BEAVER_BUILDER_VERSION, 'license' => $license, 'item_id' => 688, 'author' => 'Ronald Huereca', 'beta' => false, 'url' => home_url(), ) ); } // Add settings link add_action( 'plugin_action_links_' . BBVAPOR_PRO_BEAVER_BUILDER_SLUG, array( $this, 'plugin_settings_link' ) ); add_action( 'after_plugin_row_' . BBVAPOR_PRO_BEAVER_BUILDER_SLUG, array( $this, 'after_plugin_row' ), 10, 3 ); }
Code language: PHP (php)

I have a few constants, which I define in the main plugin file. Here’s what they look like.

define( 'BBVAPOR_PRO_PLUGIN_NAME', 'BB Vapor Modules Pro' ); define( 'BBVAPOR_PRO_BEAVER_BUILDER_DIR', plugin_dir_path( __FILE__ ) ); define( 'BBVAPOR_PRO_BEAVER_BUILDER_URL', plugins_url( '/', __FILE__ ) ); define( 'BBVAPOR_PRO_BEAVER_BUILDER_VERSION', '1.1.0' ); define( 'BBVAPOR_PRO_BEAVER_BUILDER_SLUG', plugin_basename( __FILE__ ) ); define( 'BBVAPOR_PRO_BEAVER_BUILDER_FILE', __FILE__ );
Code language: PHP (php)

Here’s what my after plugin row method looks like.

/** * Adds license information * * @since 1.4.1. * @access public * @see __construct * @param string $plugin_File Plugin file * @param array $plugin_data Array of plugin data * @param string $status If plugin is active or not * @return null HTML Settings */ public function after_plugin_row( $plugin_file, $plugin_data, $status ) { $license = get_site_option( 'bbvm_for_beaver_builder_license', '' ); $license_status = get_site_option( 'bbvm_for_beaver_builder_license_status', false ); $options_url = add_query_arg( array( 'page' => 'bb-vapor-modules-pro' ), admin_url( 'options-general.php' ) ); if( empty( $license ) || false === $license_status ) { echo sprintf( '<tr class="active"><td colspan="3">%s <a href="%s">%s</a></td></tr>', __( 'Please enter a license to receive automatic updates.', 'bb-vapor-modules-pro' ), esc_url( $options_url ), __( 'Enter License.', 'bb-vapor-modules-pro' ) ); } }
Code language: PHP (php)

And a screenshot of what it looks like:

After Plugin Row Action

Notice I added a settings link to my plugin. Here’s how I accomplished that.

/** * Adds plugin settings page link to plugin links in WordPress Dashboard Plugins Page * * @since 1.0.0 * @access public * @see __construct * @param array $settings Uses $prefix . "plugin_action_links_$plugin_file" action * @return array Array of settings */ public function plugin_settings_link( $settings ) { $admin_anchor = sprintf( '<a href="%s">%s</a>', esc_url($this->get_url()), esc_html__( 'Settings', 'bb-vapor-modules-pro' ) ); if (! is_array( $settings )) { return array( $admin_anchor ); } else { return array_merge( array( $admin_anchor ), $settings) ; } }
Code language: PHP (php)

You’ll notice I use $this->get_url(). This is what the code looks like.

/** * Return the URL to the admin panel page. * * Return the URL to the admin panel page. * * @since 1.0.0 * @access static * * @return string URL to the admin panel page. */ public static function get_url() { $url = self::$url; if ( empty( $url ) ) { $url = add_query_arg(array( 'page' => self::$slug ), admin_url('options-general.php')); self::$url = $url; } return $url; }
Code language: PHP (php)

And finally, here’s my options page code where the user enters the license and validates.

/** * Output admin menu * * @since 1.0.0 * @access public * @see register_sub_menu */ public function admin_page() { if ( isset( $_POST['submit'] ) && isset( $_POST['options'] ) ) { // Check for valid license $store_url = ''; $api_params = array( 'edd_action' => 'activate_license', 'license' => $_POST['options']['license'], 'item_name' => urlencode( 'BB Vapor Modules Pro' ), 'url' => home_url() ); // Call the custom API. $response = wp_remote_post( $store_url, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) ); // make sure the response came back okay if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { if ( is_wp_error( $response ) ) { $license_message = $response->get_error_message(); } else { $license_message = __( 'An error occurred, please try again.', 'bb-vapor-modules-pro' ); } } else { $license_data = json_decode( wp_remote_retrieve_body( $response ) ); if ( false === $license_data->success ) { delete_site_option( 'bbvm_for_beaver_builder_license_status' ); switch( $license_data->error ) { case 'expired' : $license_message = sprintf( __( 'Your license key expired on %s.', 'bb-vapor-modules-pro' ), date_i18n( get_option( 'date_format' ), strtotime( $license_data->expires, current_time( 'timestamp' ) ) ) ); break; case 'disabled' : case 'revoked' : $license_message = __( 'Your license key has been disabled.', 'bb-vapor-modules-pro' ); break; case 'missing' : $license_message = __( 'Invalid license.', 'bb-vapor-modules-pro' ); break; case 'invalid' : case 'site_inactive' : $license_message = __( 'Your license is not active for this URL.', 'bb-vapor-modules-pro' ); break; case 'item_name_mismatch' : $license_message = sprintf( __( 'This appears to be an invalid license key for %s.', 'bb-vapor-modules-pro' ), 'Simple Comment Editing Options' ); break; case 'no_activations_left': $license_message = __( 'Your license key has reached its activation limit.', 'bb-vapor-modules-pro' ); break; default : $license_message = __( 'An error occurred, please try again.', 'bb-vapor-modules-pro' ); break; } } if ( empty( $license_message ) ) { update_site_option( 'bbvm_for_beaver_builder_license_status', $license_data->license ); update_site_option( 'bbvm_for_beaver_builder_license', sanitize_text_field( $_POST['options']['license'] ) ); } } } $license_status = get_site_option( 'bbvm_for_beaver_builder_license_status', false ); $license = get_site_option( 'bbvm_for_beaver_builder_license', '' ); ?> <div class="wrap"> <form action="<?php echo esc_url( add_query_arg( array( 'page' => 'bb-vapor-modules-pro' ), admin_url( 'options-general.php' ) ) ); ?>" method="POST"> <?php wp_nonce_field('save_bbvm_beaver_builder_options'); ?> <h2><?php esc_html_e( 'Vapor Modules for Beaver Builder', 'breadcrumbs-for-beaver-builder' ); ?></h2> <table class="form-table"> <tbody> <tr> <th scope="row"><label for="bbvm-license"><?php esc_html_e( 'Enter Your License', 'bb-vapor-modules-pro' ); ?></label></th> <td> <input id="bbvm-license" class="regular-text" type="text" value="<?php echo esc_attr( $license ); ?>" name="options[license]" /><br /> <?php if( false === $license || empty( $license ) ) { printf('<p>%s</p>', esc_html__( 'Please enter your licence key.', 'bb-vapor-modules-pro' ) ); } else { printf('<p>%s</p>', esc_html__( 'Your license is valid and you will now receive update notifications.', 'bb-vapor-modules-pro' ) ); } ?> <?php if( ! empty( $license_message ) ) { printf( '<div class="updated error"><p><strong>%s</p></strong></div>', esc_html( $license_message ) ); } ?> </td> </tr> </tbody> </table> <?php submit_button( __( 'Save Options', 'bb-vapor-modules-pro' ) ); ?> </form> </div> <?php }
Code language: PHP (php)

Setting up Software Licensing

Install the Software Licensing add-on and configure its settings. You’ll fid it under Downloads->Extensions->Software Licensing. I’ve enabled pro-rated upgrades to various licenses, added a readme option, and added a renewal discount.

Software Licensing Settings
Software Licensing Settings

Set up PDF Invoices

I use the PDF Invoices add-on for Easy Digital Downloads so users can download an invoice.

PDF Invoices Add-on Settings
PDF Invoices Add-on Settings

Here’s an example of what an invoice looks like.

PDF Invoices Sample
PDF Invoices Sample

Setting Up the Download

As I mentioned before, you can set up the download as a draft to get the download ID for automatic updates.

Let’s add some pricing tiers for a starter license, unlimited license, and a lifetime license.

You’ll notice I set up variable pricing and set up three price points as referenced in the screenshot below.

Variable pricing Options
Variable pricing Options

If you drill down to the advanced settings, you can set up recurring (using the Subscriptions add-on) and even allow for a free trial. Here’s an example of a recurring license and a lifetime license.

Starter License with Recurring and 7-day Free Trial
Starter License with Recurring and 7-day Free Trial
Lifetime License
Lifetime License

The next step is you’ll want to set up a download image. Make sure this is square as this will be the plugin image that will be shown to your users.

Plugin Featured Image
Plugin Featured Image

Next is to set up the versioning information (this will be used for automatic updates.

Software Versioning
Software Versioning

Setting the version number is extremely important and you’ll want to update that every time you release a new update to your plugin. Make sure the update file is selected correctly as that will be the source file for your automatic updates.

Finally, let’s get to the readme portion. You’ll want to set a readme file source. In my case, I use a GitHub repository to store my readme. My server will not allow me to upload readme.txt files, so you’ll want to be sure to find a location where your readme file will be stored. Please note that it has to be in a valid readme format. You can test that using a WP Readme Validator.

Readme Settings

The readme section also includes a link to upload your plugin banner images, set a plugin homepage, and show a readme to users of your plugin. Here’s what it looks like when there’s an update.

Plugin Update Screen
Plugin Update Screen

And here’s what the remote readme looks like.

Plugin Readme Popup
Plugin Readme Popup

Additional Extensions

  1. I use Discounts Pro to set up discounts for my products. It’s really handy.
  2. I use the Mailchimp extension to allow users to sign up to my mailing list during checkout
  3. I use the Manual Purchases extension to send out licenses to users manually.
  4. I use the Auto Register extension to allow users to create accounts and login.

Again, all these extensions by themselves cost lots of $$$, so to save you some time and money, just get the All Access Pass.


Setting up a plugin download can be a chore, but hopefully this tutorial gets you in the right direction. If you have any questions, feel free to leave a comment.

Ronald Huereca
Ronald Huereca

Ronald Huereca

Ronald has been part of the WordPress community since 2006, starting off writing and eventually diving into WordPress plugin development and writing tutorials and opinionated pieces.

No stranger to controversy and opinionated takes on tough topics, Ronald writes honestly when he covers a topic.

6 thoughts on “How to Add Software Licensing to your Plugin Using Easy Digital Downloads”

  1. Thank you for the code example but i wish it would work. It doesnt. It fails at line 12 Adds license information. Should that be Public Function inside a class or Function? If i call it Function the next line down fails as well.

  2. Hello, i collected all your classes and place them into a file that is in the /includes folder. Then i included it into my main plugin just like the SSL Updater. But im stuck right here. Not sure what to do next to activite its functions. I tried the following;

    $run = new GBM_Admin();

    and the plugin now shows the settings option which is what i was hoping to see. But when i click on that i get this message;

    Sorry you dont have permission to access this page.

    Im sure im missing something more.

    What would be the code i need to add after the above to execute the functions in that class? Kind of stuck there.


  3. Hi. But if we are using recurring payments do we really need this plugin? I mean if someone buys one of my pricing packages and that package is billed every year, why do I need the Software Licensing for?

  4. Hey Ron, thank you very much for all this including the new ajax version.

    I uploaded the entire folder to my wordpress install and tested it. The key did not work so i assume you removed its ability to register, that’s fine.

    When i install this i see a label followed by the license filed then under that a send button. Lets say i upload another app that has the same code in it. Does a new set of label followed by a license field appear below the existing one?

    How would i go about including this in my application? Say i have my php script i would have to include something there at least at the top of the script im protecting.

    Finally, i dont want users to be able to see the class codes. Can i encrypt it using ioncube?



Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.