DEV Community

Rebel Studios
Rebel Studios

Posted on

How to structure a freemium WordPress plugin that passes wp.org review

If WordPress.org rejected your plugin for "trialware" or "locked
functionality," you didn't do anything unusual — you did the thing every SaaS
tutorial tells you to do, and it's exactly what the directory forbids. Here's the
rule, and the structure that actually passes.

The rule that trips everyone up

Guideline 5 says a plugin in the directory can't contain locked, disabled, or
paywalled
code. The pattern you reach for by instinct — ship one plugin, grey out
the Pro buttons, gate them behind a license check — is trialware, and it's an
automatic rejection. It doesn't matter that the code is GPL. If the free download
contains features the user can't use without paying, it's out.

The structure that passes: two plugins, not one

  • The free plugin lives in the directory. It is fully functional on its own and contains zero paid code. No disabled buttons, no dead "upgrade to unlock" screens wired to features that ship inside it.
  • The Pro add-on is a separate plugin you sell off-site (Gumroad, Lemon Squeezy, your own site). When it's installed and licensed, it hooks into extension points the free plugin exposes and adds the paid features.

The free plugin never contains the Pro code. The Pro code never ships in the
directory. That's the whole trick, and it's the difference between approved and
rejected.

The mechanism

The free plugin exposes a filter for its tier and action/filter hooks where an
add-on can attach:

// In the FREE plugin. Ships alone => always false.
function myplugin_is_pro() {
    return (bool) apply_filters( 'myplugin_is_pro', false );
}

// A gated capability degrades to free behaviour, never to a broken screen.
if ( myplugin_is_pro() ) {
    do_action( 'myplugin_render_pro_panel' );
} else {
    echo '<a href="https://your-site.com/pro">Upgrade to Pro</a>';
}
Enter fullscreen mode Exit fullscreen mode
// In the SEPARATE Pro plugin, sold off-site.
add_filter( 'myplugin_is_pro', fn( $v ) => MyPro_License::is_valid() ? true : $v );
add_action( 'myplugin_render_pro_panel', [ MyPro_Feature::class, 'render' ] );
Enter fullscreen mode Exit fullscreen mode

Because the free plugin only ever links out to a sales page and its
myplugin_is_pro() is false when it ships alone, there's no locked code in the
directory download. The value is real (the free tier works), and the upgrade is a
genuine separate product — which is what the guideline actually wants.

Four more landmines (all real, all cost time)

  1. readme.txt is published verbatim. Don't advertise features you haven't built yet — a dead promise on a public plugin page is worse than a missing feature, and reviewers check the code against the readme.
  2. New authors get one plugin in review at a time. Queue matters; you can't parallelise your first few submissions.
  3. Trademark terms are banned in slugs. A slug starting with a protected term (e.g. woocommerce- / wc-) gets rejected on name grounds alone. Put the "for WooCommerce" in the display name, not the slug.
  4. Run Plugin Check before submitting (wp plugin check <slug>). It catches the escaping, i18n, and header issues reviewers would bounce you for — fix them first and you skip a review round-trip.

Skip the boilerplate rebuild

The annoying part is that none of this is your feature — it's the same license gate,
settings framework, wp.org-ready readme, and build-to-zip you rewrite every time. I
packaged the whole compliant structure (plus a one-command scaffolder that stamps out
a namespaced, activation-ready plugin) so you add your feature and ship:
Freemium WordPress Plugin Boilerplate.
Optional — the structure above is the real lesson, and it's yours whether you build it
by hand or not.

Top comments (0)