WPackagist: putting WordPress plugins and themes into composer.json
WPackagist — manage your plugins with Composer
At a glance
- What is it?
- WPackagist is the service behind wpackagist.org, which turns plugins and themes from the WordPress.org SVN repositories into Composer packages you can require and pin by version. It is useful when you want plugins under version control and reproducible builds, and it comes with real constraints, including a legacy Composer v1 version parser, a four part version limit, and no WordPress core in the box.
- Who is it for?
- Adopt WPackagist if you want WordPress plugins and themes resolved by Composer, pinned in composer.json and installed from a single lock file rather than by hand through an admin UI. Do not adopt it if your plugins come from somewhere other than WordPress.org SVN, if you need WordPress core itself from the same source, since the service explicitly does not provide it, or if a plugin uses a version string with more than four parts.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 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 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
WordPress.org SVN repositories exposed as Composer packages
WPackagist is a repository service for Composer that mirrors the way WordPress plugins and themes are actually distributed, which is a set of SVN repositories rather than a package index. Its job is to give those repositories a Composer-shaped interface so that a plugin can be named, versioned and locked alongside the PHP libraries your site already depends on.
The mapping is direct. Each package name points at a wordpress.org SVN repository, and versions correspond to tags in that repository, with the special dev-trunk version mapped to trunk. To use it you declare a repository of type composer pointing at wpackagist.org and then require packages with the wpackagist-plugin or wpackagist-theme prefix.
The service implements the wordpress-plugin and wordpress-theme Composer Installers, so the packages land in the directories the WordPress installer expects. That detail is the reason the prefix exists. A require of wpackagist-plugin/akismet is not a package you have to rename or move afterwards.
One thing it is not is a source of WordPress itself. The documentation states plainly that this does not provide WordPress, and points to separate projects for that.
Declaring the repository and pinning plugin versions
The composer.json for a project using WPackagist declares the repository with an only filter, which is what keeps Composer from treating the whole service as a fallback for every package you depend on:
{
"repositories":[
{
"name": "wpackagist",
"type": "composer",
"url": "https://wpackagist.org",
"only": ["wpackagist-plugin/*", "wpackagist-theme/*"]
}
],
"require": {
"wpackagist-plugin/akismet":"dev-trunk",
"wpackagist-theme/hueman":"*"
}
}The two require styles show the two behaviours you will use. dev-trunk tracks the trunk branch of the SVN repository and is what you want for a plugin that never tags releases, while a constraint such as >=7.0.2 gives you a normal semver style range resolved against the tags the plugin does have. Themes are often required with a wildcard in practice, which is worth understanding rather than copying by habit.
Because this is ordinary Composer, the result is a composer.lock and a vendor directory, so a deployment installs a known set of plugin versions on every machine.
The static JSON package tree behind packages.json
The service does not generate metadata on the fly for every request. The lookup table is a hierarchy of static JSON files, and the entry point is https://wpackagist.org/packages.json, which consists of a series of sub-tables, each its own JSON file.
Those sub-tables are grouped by last commit date, with an effort to keep them roughly the same size, and they contain references to individual packages. Each package then has its own JSON file detailing its versions, found under a path of the form https://wpackagist.org/p/wpackagist-{theme|plugin}/{package-name-and-hash}.json. The hash in the filename is what makes the file name change when the package data changes, so it can be cached hard.
The grouping by commit date is the part worth understanding. It means the top level of the tree is a set of dated slices rather than one alphabetical list, which keeps each file small enough to serve cheaply and lets the service publish an update by adding a slice rather than rewriting everything.
Behind that static tree there is still a database and an update pipeline, which is what the next sections cover.
Why the service still uses the Composer v1 VersionParser
This is the most surprising implementation detail in the project, and it is deliberate.
To maintain Composer v1 compatibility as well as v2 for dev- versions, the service uses the VersionParser from composer/composer v1.x rather than a newer release branch. Correct resolution of these depends on legacy behaviour where dev-trunk and similar versions correspond to a specific normalized form, described in the documentation as a version_normalized value of 9999999-dev.
The reason is that Composer v1 and v2 disagree about how dev- branches are normalized and compared. Keeping the v1 parser means the versions a plugin declares resolve the same way for clients on either major version of Composer. The cost is that the service is pinned to a legacy dependency, and anyone auditing the dependency tree should know that the pin is intentional rather than an oversight.
There is a second limit in the same area. Currently WPackagist can only process packages with up to 4 parts in their version numbers, in line with the internal handling of Composer v1.x. A plugin whose own version scheme has five segments will not be processed, and that failure will surface as a package that cannot be found rather than as a clear error message.
Populating the database with four console commands
Running your own instance is a Symfony application with a set of console commands that populate the data the static tree is generated from. The documentation warns that the first database population may easily take hours.
The sequence is:
bin/console doctrine:migrations:migrate
bin/console refresh
bin/console update
bin/console buildThe migration step brings the schema up to date with the code. The refresh step queries the WordPress.org SVN to find new and updated packages. The update step refreshes version information for the packages identified in the previous step, using the WordPress.org API. The build step rebuilds all PackageStore data.
The ordering matters, and it explains why the initial run is slow: the SVN query has to enumerate the whole of WordPress.org before any version data can be fetched, and the build only happens at the end. If you are skipping the database for PackageStore, the documentation notes an alternative where you instead ensure the PACKAGE_PATH directory is writable.
For installation itself the steps are to have Composer dependencies installed including extensions, create a .env.local that overrides anything you want from .env, run composer install, populate the database, and then point your web server at the web directory, where a .htaccess file is provided for Apache.
Docker Compose setup and the local service ports
The documentation says running locally with Docker may be simpler than setting up native dependencies, and describes it as experimental. The environment file is copied first:
cp .env .env.localThen the database is set up and updated through the cron service, which is the service that runs the console commands:
docker-compose run --rm cron composer install
docker-compose run --rm cron deploy/migrate-db.sh
docker-compose run --rm cronStarting the web server and the database browser is a separate step:
docker-compose up web adminerThe compose file gives you the layout. There is a postgres service on port 5432 with a bind mount to ./local_data for the data directory and credentials from .env.postgres.local, a redis service on image redis:6.2, and the web service on port 30100 mapped to 8080 in the container. Adminer is on port 30101, again mapped to 8080, and its credentials are in .env.postgres.local. The web and cron services are built from a Dockerfile with a dev environment argument and an aws target, and both mount the working directory into the container.
One build detail matters if you build the image yourself: the Dockerfile installs the svn client, described as a requirement for the current native exec approach, along with git for Composer pulls, libpq-dev for Postgres, libicu-dev for intl and libonig-dev for mbstring.
Deployment pipeline, licence and release trail
The live application runs on ECS and is deployed by CircleCI. Automatic deploys promote from develop to a staging environment and from main to production, and the configuration for that pipeline lives in .circleci/config.yml.
The repository has a Makefile with build targets for the three deployment flavours, which tells you the image is built with different targets depending on where the secrets come from. The build-aws target passes env=prod and target=aws, and the Dockerfile has matching branches: the aws target installs the AWS CLI to load secrets safely from S3, and a gcp target installs the Google Cloud CLI for secrets from Google Secret Manager. The build-gcp target takes a LABEL of stg or prd, with stg as the default, and produces an image tagged with that label. There is also a build-local target using env=dev and target=local, and a clean target that removes the local, aws and gcp images.
On licensing, the project is MIT, with the licence in the LICENSE file at the root. The repository is not archived and the last push was on 2026-10-19, on the develop branch that is the default for this repository, so the trunk of development is a moving target. There are no GitHub releases, so there is no tagged version to pin to, and the CHANGELOG and milestone history in the repository are where the release notes live.
The default branch being develop rather than main is a small detail with a large consequence for anyone automating a deployment against this repository.
Editorial conclusion
Adopt WPackagist if you want WordPress plugins and themes resolved by Composer, pinned in composer.json and installed from a single lock file rather than by hand through an admin UI. Do not adopt it if your plugins come from somewhere other than WordPress.org SVN, if you need WordPress core itself from the same source, since the service explicitly does not provide it, or if a plugin uses a version string with more than four parts. Before you point a build at it, check the plugin version you want against the four part limit, and confirm that dev-trunk still resolves the way your CI expects, because the service pins the Composer v1 VersionParser to keep that behaviour working.
Frequently asked questions
How do I add a WordPress plugin to composer.json with WPackagist?
Declare a repository of type composer with url https://wpackagist.org and an only filter for wpackagist-plugin/* and wpackagist-theme/*, then require the package, for example wpackagist-plugin/akismet at dev-trunk or wpackagist-plugin/wordpress-seo at >=7.0.2. Versions correspond to tags in the plugin's WordPress.org SVN repository, with dev-trunk mapped to trunk.
Does WPackagist provide WordPress core?
No. The documentation states explicitly that it does not provide WordPress itself, and points to separate projects for installing it. WPackagist covers plugins and themes only.
Why does WPackagist use the Composer v1 VersionParser?
To keep Composer v1 and v2 compatibility for dev- versions, the service uses the VersionParser from composer/composer v1.x, relying on the legacy behaviour where dev-trunk and similar versions normalise to 9999999-dev. The practical consequence is that packages with more than 4 parts in their version numbers cannot be processed.
How do I run my own WPackagist instance locally?
Copy .env to .env.local, then use docker-compose to install dependencies and migrate and populate the database through the cron service, and start the services with docker-compose up web adminer. The web service listens on localhost:30100 and Adminer on localhost:30101, and the documentation describes the Docker route as experimental.
What licence is WPackagist released under?
MIT, with the licence text in the LICENSE file in the root of the repository. The service itself is hosted at wpackagist.org and the source repository carries no GitHub releases, so there is no tagged version to pin to.
Official sources
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.
[](https://hysenlabs.com/projects/wpengine-wpackagist)Was this useful?