A Composer-compatible repository of WordPress core distributions maintained by SolidBunch for the StarterKit.
New WordPress releases are checked for every 15 minutes. A Cloudflare Worker cron starts the release check workflow, and the Last release check badge below shows how long ago the last successful check finished.
This repository provides two variants of the WordPress core:
solidbunch/wordpress-coreβ full WordPress archive (identical to wordpress.org)solidbunch/wordpress-core-no-contentβ lightweight archive withoutwp-content/, ideal for CI/CD or StarterKit use
"repositories": [
{
"type": "composer",
"url": "https://solidbunch.github.io/wordpress-core"
}
]"require": {
"solidbunch/wordpress-core": "^7.1"
}"require": {
"solidbunch/wordpress-core-no-content": "^7.1"
}Any available version can be required exactly:
"require": {
"solidbunch/wordpress-core": "7.0.3"
}Every stable WordPress release from the 4.1 branch onwards is kept and is never removed from the repository. The oldest available version is 4.1; the newest is the latest WordPress release.
Branch releases are named exactly as WordPress names them (e.g. 7.1, 6.9, 4.1). Composer treats these as 7.1.0, 6.9.0 and 4.1.0.
Pre-release versions (X.Y-betaN, X.Y-RCN, X.Y.Z-RCN) appear in packages.json only while WordPress is in a beta/RC window for that release. Once the window closes they are not removed β they stay available for anyone who already depends on them.
Composer's default minimum-stability is stable, which will not install these versions. To install one, either:
- Lower the project's minimum stability and keep preferring stable releases otherwise:
{ "minimum-stability": "beta", "prefer-stable": true } - Or require the specific pre-release with an explicit stability flag:
{ "require": { "solidbunch/wordpress-core": "7.2.*@RC" } }
| Package name | Contents | Target use case |
|---|---|---|
solidbunch/wordpress-core |
Full WP archive | General usage, classic setup |
solidbunch/wordpress-core-no-content |
No wp-content/ |
DevOps, CI, custom themes |
All packages include:
license: GPL-2.0-or-later: the license of the WordPress archive itself (the scripts in this repository are MIT, seeLICENSE.md)require.php: the PHP requirement of each release (>=X.Yor>=X.Y.Z, exactly as WordPress states it), taken from the WordPress API or from the release's ownwp-includes/version.php- Optional
extra.mysql_versionfield for advanced tooling dist.shasum: the SHA-1 of the release archive, taken from the.sha1file that wordpress.org publishes next to each archive (e.g.https://downloads.wordpress.org/release/wordpress-7.1.zip.sha1). Composer verifies it on download.
By default Composer installs the WordPress core into vendor/, no plugin needed. To control where it is installed (e.g. web/wp-core/ instead of vendor/), use the optional Composer plugin (requires PHP β₯ 8.1):
composer require solidbunch/composer-installersComposer 2.2+ only runs plugins listed in allow-plugins, so allow it (composer require asks interactively; in CI, add it yourself) and add the installer path to your composer.json:
"config": {
"allow-plugins": {
"solidbunch/composer-installers": true
}
},
"extra": {
"installer-paths": {
"web/wp-core/": [
"type:wordpress-core"
]
}
}This plugin recognizes type: wordpress-core and places the archive into the specified directory.
β
solidbunch/composer-installersis not required inside the WordPress core package itself. It should be used by the consuming project.
Installers key off the package type (wordpress-core), not the vendor. Your existing installer therefore keeps placing the core in extra.wordpress-install-dir after the package name changes. This was checked with a placeholder package of type wordpress-core for both johnpbloch/wordpress-core-installer and roots/wordpress-core-installer, and with the real solidbunch/wordpress-core 6.9.8 archive for johnpbloch/wordpress-core-installer. Option A applies to both installers.
The metapackage johnpbloch/wordpress requires the installer, so after you remove the metapackage the installer must be required explicitly.
Before (johnpbloch):
{
"require": {
"johnpbloch/wordpress": "^6.9"
},
"extra": {
"wordpress-install-dir": "web/wp-core"
}
}After (solidbunch, same installer):
{
"repositories": [
{
"type": "composer",
"url": "https://solidbunch.github.io/wordpress-core"
}
],
"require": {
"johnpbloch/wordpress-core-installer": "^2.0",
"solidbunch/wordpress-core": "^6.9"
},
"extra": {
"wordpress-install-dir": "web/wp-core"
}
}Since solidbunch/composer-installers 1.1.1 it can be installed next to another core installer without taking over: it claims wordpress-core only when extra.installer-paths has a rule that can match it (a type:wordpress-core rule, or any rule that is not a type: rule, such as an exact package name). Without such a rule your current installer keeps using wordpress-install-dir. A package-name rule makes it claim the whole wordpress-core type, so the other installer is no longer consulted for it. Pick one mechanism for placement.
Before (johnpbloch):
{
"require": {
"johnpbloch/wordpress": "^6.9"
},
"extra": {
"wordpress-install-dir": "web/wp-core"
}
}After (solidbunch):
{
"repositories": [
{
"type": "composer",
"url": "https://solidbunch.github.io/wordpress-core"
}
],
"require": {
"solidbunch/wordpress-core": "^6.9",
"solidbunch/composer-installers": "^1.1"
},
"extra": {
"installer-paths": {
"web/wp-core/": [
"type:wordpress-core"
]
}
}
}Remove johnpbloch/wordpress (or johnpbloch/wordpress-core) and johnpbloch/wordpress-core-installer from require. Add the repository entry above and solidbunch/wordpress-core.
johnpbloch's installer places the archive using extra.wordpress-install-dir. With this repository, use solidbunch/composer-installers and the extra.installer-paths convention to place it:
"extra": {
"installer-paths": {
"web/wp-core/": [
"type:wordpress-core"
]
}
}solidbunch/composer-installers understands two kinds of installer-paths rules: type:<package-type> (for example type:wordpress-core) and an exact package name (for example solidbunch/wordpress-core-no-content). Other forms, including vendor:<name> and the wildcard type:*, are not matched: the package then falls back to vendor/. type:wordpress-core is the recommended rule because it covers both variants.
The packages.json is kept up to date by the Node.js script generate-packages-json.js (included in this repository), which is run by GitHub Actions. The script check-new-versions.js decides whether the generator needs to run; it never modifies packages.json (it only writes its run= output for the workflow). It queries both the stable and the beta channel (?channel=beta) of the WordPress version-check API; a beta-channel failure is reported (BETA-CHANNEL-UNAVAILABLE) and ignored rather than failing the check, since the beta channel is advisory only.
update-packages.ymlruns a light check. The check is started every 15 minutes by a Cloudflare Worker cron (cloudflare-worker/) that callsworkflow_dispatchwithmode=light. The check compares the versions offered by the WordPressversion-checkAPI withpackages.jsonand starts the generator only when a new release whose archive is already published is missing. Once a day GitHub's ownschedule(cron7 4 * * *) forces a full generator run, which also picks up versions that onlystable-checklists and serves as a fallback if the Worker stops. It can also be started manually (workflow_dispatch), which forces a full generator run unlessmodeislight. If theupdatejob fails, it opens a new GitHub issue or comments on the existing one; after a successful push,packages.jsonis attested withactions/attestβ this is an audit trail for the file GitHub Actions produced, it does not prove the authenticity of the upstream WordPress archives themselves. A separatepublishjob is prepared to tag new versions in per-variant repositories for Packagist. It is inert until thePUBLISH_REPO_*repository variables and thePUBLISH_TOKENsecret are configured, and it never pushes to this repository or changes what GitHub Pages serves.audit-checksums.ymlruns weekly (Monday 05:17 UTC) and re-checks the published.sha1of every entry already stored inpackages.jsonagainst wordpress.org. It never overwrites anything; a mismatch fails the run and opens or comments on an issue.ci.ymlrunsnode --testandnode generate-packages-json.js --checkon every pull request and on every push tomain.keepalive.ymlmakes a monthly heartbeat commit (1st of the month, 06:00 UTC)cloudflare-worker/holds the Cloudflare Worker that triggersupdate-packages.ymlevery 15 minutes and serves/badge.json, a shields.io endpoint badge with the age of the last successful run of that workflow (green up to 30 minutes, yellow up to 2 hours, red beyond). To deploy it, from that directory runnpx wrangler secret put GITHUB_TOKEN(a fine-grained token limited to this repository withActions: Read and write) and thennpx wrangler deploy.
All workflow steps that run a third-party action pin it to a commit SHA (not a floating tag); Dependabot proposes updates to those pins weekly.
Each generator run also writes status.json and the badges/ directory alongside packages.json in the same commit, so the README badges above always reflect the same run.
The release check runs every 15 minutes, started by the Cloudflare Worker. When the check finds a release with a published archive, the generator downloads and verifies each new archive before committing. After the commit, GitHub Pages rebuilds (typically 1β2 minutes) and serves packages.json through its CDN with max-age=600. Each run's job summary lists every version it added, with the archive's own Last-Modified time and the time the generator observed it, so the latency of every release can be read from the run.
On every generator run:
- the generator merges into the existing
packages.jsonand never removes a version - it reads the official WordPress APIs
https://api.wordpress.org/core/version-check/1.7/andhttps://api.wordpress.org/core/stable-check/1.0/, and adds every missing stable release from 4.1 onwards with its checksum - for each new version it downloads the archive and hashes it locally, comparing against the published
.sha1(and the published.md5, when wordpress.org publishes one) rather than trusting the published checksum alone; a mismatch defers that version instead of writing it - it reads the PHP and MySQL requirements from the API or from the release's own
wp-includes/version.php - it validates the result before writing it and refuses to write on any violation
- the workflow commits only when
packages.jsonchanged - the run fails, with nothing committed, if the WordPress API is unreachable or validation fails
- a version that could not be fully fetched, or whose downloaded archive fails checksum verification, is deferred: it is reported and picked up by the next run, while the rest is committed and the run then ends red
To check a local packages.json offline, run node generate-packages-json.js --check from the repository root. It validates the file and, inside a git checkout, reports versions that are missing compared with HEAD. It exits with code 1 on any violation and never writes anything. Running the generator without --check rewrites packages.json and calls the WordPress API. node generate-packages-json.js --backfill rebuilds every entry from the data already in the file β no network access, deterministic and idempotent β for the rare case where a computed field's logic changes and existing entries need to be recomputed without re-fetching anything.
See the StarterKit installation guide.
- Participate on GitHub Discussions
- Connect via LinkedIn