Open-source project
awesomemotive/easy-digital-downloads avatar
awesomemotive/easy-digital-downloads

Easy Digital Downloads: a deliberately narrow WordPress plugin for selling files

Sell digital downloads through WordPress

894 stars462 forksPHPGPL-2.0

At a glance

What is it?
Easy Digital Downloads is a GPL-2.0 WordPress plugin whose stated ambition is to be small: it says it tries to provide only the features you really need rather than every feature under the sun. The engineering is more interesting than the marketing, because it bundles payment libraries through Mozart to avoid namespace collisions with other plugins, and it runs Playwright end to end tests against a throwaway WordPress site.
Who is it for?
Easy Digital Downloads fits a WordPress site that sells files rather than physical goods and wants a plugin small enough to read, and it fits a developer who wants to extend a downloads flow with a handful of hooks rather than configure a large commerce suite.
Can I use it commercially?
Yes, with conditions. GPL-2.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The plugin's stated identity is its narrowness

The opening line of the documentation is an argument rather than a feature list. Selling digital downloads is something that not a single one of the large WordPress ecommerce plugins has ever gotten really right, and this plugin aims to fix that. Instead of focusing on providing every single feature under the sun, Easy Digital Downloads tries to provide only the ones that you really need, and it aims to make selling digital downloads through WordPress easy, and complete.

That positioning has consequences for an evaluation. A large commerce plugin is a configuration surface you learn by reading documentation. A narrow one is a codebase you can read. The repository listing supports that second picture: a includes directory for the PHP, a src directory, a templates directory for the output, a tests directory, an i18n and languages pair for translation, and a small number of front end dependencies.

The front end dependency list is short and tells you what the checkout actually does. There is jquery.payment for card handling, jquery-creditcardvalidator and jquery-validation for form validation, chart.js and flot for the admin charts, moment and moment-timezone for date handling, and to-string-loader. That is a plugin whose interactive surface is a checkout form and an admin dashboard.

The plugin also ships two editions, and the build scripts name them explicitly: a lite version and a pro version, each packaged as its own zip file, with a separate script for each. The topics attached to the repository list stripe among others, and the Composer section names three production dependencies.

So the honest way to describe the project is a small, deliberately scoped WordPress plugin with a paid tier, built by a team that ships frequently and treats its own tooling as part of the product.

Mozart exists because WordPress plugins share one namespace

The most interesting engineering decision in the repository is the Composer section, and it is a problem specific to WordPress that other projects never have to think about.

The documentation explains that a handful of production dependencies, stripe/stripe-php, nesbot/carbon and square/square, are bundled into /libraries/ rather than loaded from vendor/. The reason given is that Mozart rewrites them into the EDD\Vendor\ namespace so they cannot collide with a different copy of the same library shipped by another plugin on the site.

That is a real and well known failure mode. WordPress loads every active plugin's PHP into one process, and if two plugins both bundle, say, the same Carbon release, the second autoloader to register wins and the two copies fight. Prefixing the namespace is the standard fix, and the plugin uses the standard tool for it, Mozart by Coen Jacobs.

The workflow has a sharp edge that the documentation spells out. Mozart is installed globally rather than as a project dependency:

bash
composer global require coenjacobs/mozart

The compose step copies each package into /libraries/, prefixes its namespace, and then deletes the original from vendor/, with the delete_vendor_directories setting in the extra.mozart configuration named directly. The documentation is emphatic that the delete is intentional, because leaving the unprefixed copy in vendor/ would ship the library twice and re-register its original namespace, defeating the prefixing.

And because the source packages are removed from vendor/ after a compose, you must restore them before composing again:

bash
composer install        # restore the source packages into vendor/ (respects composer.lock)
composer run mozart     # compose into /libraries/, then dump the production autoloader

The documentation says to run this only when a bundled library actually needs to change, and notes that the npm run update script wraps the same Mozart step for the full update flow. This is the kind of thing that costs an afternoon the first time and is invisible afterwards.

A build pipeline with nine scripts and a distinction most plugins skip

The build scripts section is the most complete part of the documentation and it is worth reading in full, because it explains how a single repository produces several different artifacts.

The build script regenerates all CSS and JavaScript, runs translations, and copies files to a build directory, and it explicitly does not generate zip files. The dev script watches for changes and automatically rebuilds assets, which is the loop a contributor spends most of their time in. The package script is the full release path: it rebuilds assets, runs translations, copies files, and compresses files for installation on any WordPress site, for both the lite and the pro versions.

The separation between build and package is the point. The lite and pro scripts each generate an installable zip with a versioned filename and do not rebuild assets, which the documentation says is useful for quickly creating an installable package. The repo script generates a directory of files to copy over to the public repository and updates the translation. The build:blocks script is marked as for internal use only and rebuilds the block asset files, which tells you the plugin has a block editor surface.

There are three translation scripts and they are scoped differently. The translate script translates the pro edition, translate:lite translates the public version and runs as part of npm run lite, and translate:repo translates the repository copy and runs as part of npm run repo. The documentation warns against running the latter two directly, which is the sort of instruction that saves an hour when a translation is unexpectedly missing from a build.

The update script is the one to be careful with. It updates all Composer packages and runs the Mozart script, and the documentation says to run it only if a Composer library needs updating. The postinstall script runs automatically after npm install and the documentation says not to use it directly.

Underneath all of this is a mixed toolchain. The package manifest lists webpack.config.js as the main entry and a Gruntfile.js sits at the root, along with phpcs.xml.dist for coding standards, phpunit.xml and a docker-compose-phpunit.yml for the PHP test suite, and apigen.neon for API documentation. Webpack builds the assets while Grunt still handles tasks such as text domain checking, compression, copying and rtlcss.

Playwright tests run against a disposable WordPress in Docker

The end to end test section is short and describes a genuinely good approach to testing a WordPress plugin.

The documentation says the end to end tests use Playwright against an ephemeral Docker managed WordPress plus EDD site, and that no manual site setup is required, with the details in e2e/README.md. The Playwright dependency is present in the manifest at a 1.60 or newer range, and there is a dotenv dependency, which is what you would use to point the tests at a different base URL.

Ephemeral matters more than it sounds. A WordPress plugin test suite that runs against a shared site accumulates state: orders from a previous run, a changed option value, a payment record that changes what the checkout page renders. A container that is created and destroyed per run removes that class of flake entirely, and it also means the test does not need a web server the developer configures by hand.

The PHP side has its own separate setup. The repository carries phpunit.xml, phpunit-printer.yml, docker-compose-phpunit.yml and a tests directory, so there are two distinct test layers: a PHPUnit suite for the PHP code and a Playwright suite for the browser behaviour. The phpunit-printer.yml configuration is a small detail that suggests a readable test output format is wanted.

The node requirements are stated in the manifest as Node 20 or newer and npm 10.2.5 or newer, with the package marked as private, so it is a build tool rather than a published library. A developer cloning the repository on an older Node will hit the engine check before anything else.

Where a narrow downloads plugin stops and a commerce suite starts

The clearest limitation is the one the project advertises, and it is worth taking seriously rather than treating as marketing.

There is no shipping, no inventory, no physical product catalogue and no tax engine in the described feature set. A site selling files does not need them. A site that also sells physical goods, or that needs to charge sales tax by jurisdiction, or that has to manage stock across warehouses, will find this plugin does not address those problems. The claim that the large ecommerce plugins never got digital downloads right is a claim about a niche, and a business that straddles both is outside it.

The second limitation is the extension surface. The repository shows an includes directory, a src directory, a templates directory and an uninstall.php, and a code style configuration file, but the documentation does not describe a public hook or filter API in any detail. For a plugin whose selling point is that it is small, that cuts both ways: small code is easier to read and to fork, but there is no documented contract for a third party extension, so a team that expects to bolt on a custom checkout step or a bespoke order pipeline is doing it by editing the plugin rather than by hooking into it. The included blocks directory and the internal build:blocks script suggest a block editor surface, but the documentation is not explicit about how much of the interface is block based.

The third is operational. Two editions with different licensing, a pro build script, a paid tier and a separate translation pipeline for each is a commercial structure. A team that wants to fork the plugin and run it internally has the GPL-2.0-or-later licence to do so, but the split between the lite and the pro code in one repository means a fork has to decide which half it is taking.

The licensing and the release record

The licence is GPL-2.0, and the manifest records it as GPL-2.0-or-later, with a license.txt file in the repository root and a badge in the documentation. For a WordPress plugin that is the expected position, and it means the code can be modified and redistributed under the same terms, which is what a site owner would want if they want to fork it for their own shop.

The release record shows a project shipping on a normal cadence. The recent versions are 3.7.2 on 2026-10-14, 3.7.1.1 on 2026-10-06 and 3.7.1 on 2026-09-21, and the last push to the repository was on 2026-10-14. Patch releases on a 3.7 line with a hotfix build numbered 3.7.1.1 in between is the shape of a plugin that is actively patched for a live installed base.

That matters for anyone running this in production, because a WordPress plugin runs inside someone else's application and gets updated on a schedule you may not control. The version number in the plugin header, the repository tag and the published zip need to agree, and the build scripts in the documentation are what produce that consistency.

The support boundary is also clear and worth noting. The documentation states plainly that the repository is a developer's portal and should not be used for support, and directs support requests to the support page on the project website. The bug tracker is for issues, and the contributing guidelines are in a CONTRIBUTING.md file, with SUPPORT.md sitting alongside it in the repository root. The documentation also points at the WordPress.org translation project as a way to contribute a language, which is the standard route for a plugin distributed through the WordPress directory.

Editorial conclusion

Easy Digital Downloads fits a WordPress site that sells files rather than physical goods and wants a plugin small enough to read, and it fits a developer who wants to extend a downloads flow with a handful of hooks rather than configure a large commerce suite. It is the wrong choice if you need shipping, tax and inventory handling, because the plugin deliberately leaves those out, and it is the wrong choice if your site already loads a payment library under its original namespace, since the bundled copies are prefixed precisely to avoid that collision. Before adopting it, read the build scripts section of the documentation rather than the marketing site, check which of the lite and pro packages your licensing covers, and confirm the Node and npm versions in the package manifest match your build environment before you run npm install.

Frequently asked questions

What is Easy Digital Downloads?

It is a WordPress plugin for selling digital downloads, released under GPL-2.0. The project positions itself deliberately narrowly, saying it tries to provide only the features you really need rather than every feature under the sun, and it ships a lite edition and a pro edition.

Why does Easy Digital Downloads bundle Stripe, Carbon and Square in /libraries/?

Because WordPress loads every active plugin into one process, so an unprefixed copy of a library can collide with the same library shipped by another plugin. The project uses Mozart to rewrite those packages into the EDD\Vendor\ namespace, and the compose step deletes the original from vendor/ so the library is not shipped twice.

How do I install Easy Digital Downloads?

The documentation gives two developer routes, cloning the GitHub repository or downloading a ZIP archive of the master branch, both of which give the latest developer copy. Detailed setup instructions are on the official documentation page, and support requests go to the project support page rather than the repository.

How are the end to end tests for Easy Digital Downloads run?

They use Playwright against an ephemeral Docker managed WordPress and Easy Digital Downloads site, so no manual site setup is needed, and the details are in the e2e README. The PHP side has a separate PHPUnit suite with a docker-compose file and a phpunit.xml at the repository root.

How do I build an installable Easy Digital Downloads package?

The package script rebuilds assets, runs translations, copies files and compresses them for both the lite and pro versions. The lite and pro scripts produce a versioned zip each without rebuilding assets, which is the fast path, and the build script alone stops short of producing a zip file.

Official sources

  1. awesomemotive/easy-digital-downloads on GitHub
  2. License: GPL-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/awesomemotive-easy-digital-downloads.svg)](https://hysenlabs.com/projects/awesomemotive-easy-digital-downloads)

Was this useful?