wp-packages: WP Packages: The Go Registry Rebuilding WordPress Composer from First Principles

A fast, SQLite-backed bridge from WordPress.org SVN to Composer that fixes package metadata, makes installs reproducible, and shows how much better a registry can be when it behaves like a live system.

9 min read • View on GitHub • More from roots

A crowded archive room on the left gives way to a compact precision machine on the right. The scene contrasts bulky package ledgers and index books with a small registry engine that emits exact package files on demand, explaining the shift from static bulk downloads to live per-package retrieval.
WP Packages turns WordPress packaging from a giant dump into a queryable registry.
Key Takeaways

For years, WordPress Composer users lived with a blunt tradeoff: use the ecosystem, but accept a registry that felt heavy and static. WP Packages changes the unit of work. It does not ship giant prebuilt indexes and hope for the best. It answers queries, one package at a time, and lets Composer v2 do less work.

The registry that refuses to ship giant indexes

That sounds like a small implementation detail. It is not. In package tooling, the difference between downloading a few targeted metadata records and fetching huge provider files is the difference between a quick resolve and a ritual.

Roots built WP Packages as a modern Composer repository for WordPress plugins and themes, but the real move is architectural. It treats package metadata as a live index that can be queried on demand, updated continuously, and served from a compact system of record.

DimensionWP PackagesWPackagist
Registry modelLive per-package metadataStatic generated indexes
Composer protocolComposer v2 metadata-urlOlder provider-includes model
SpeedFast cold resolvesSlower bulk fetches
Metadata richnessAuthors, descriptions, homepages, support linksMore limited package metadata
Update cadenceFrequent syncs from WordPress.orgSlower refresh cycle
ReproducibilityPinned untagged installs via SVN revisionsLess deterministic for trunk-style installs
GovernanceOpen, community-run, transparentControlled outside the community team

The comparison is really about mental models. WPackagist behaves like a warehouse. WP Packages behaves like a service desk.

Why SQLite is the real engine here

Composer v2 only asks for what it needs, and WP Packages uses SQLite to answer that request quickly.

SQLite is not acting like a humble embedded database here. It is the registry’s memory, state machine, and lookup layer. The service writes to it in batches, locks it during coordinated work, and uses it to serialize package metadata with deterministic behavior.

That matters because package registries fail in boring ways: partial writes, stale indexes, and race conditions between sync jobs. WP Packages avoids those traps by treating the database as the source of truth for both build state and serving state.

We just generally try to keep Composer referring to CLI/client side things and Packagist for the server/repository. Otherwise people just end up getting even more confused by these tools.

Nils Adermann, Co-creator of Composer · WP Composer is now WP Packages | Roots

The pipeline that keeps WordPress.org in sync

A close-up of a pipeline made of four chambers arranged in sequence. A single flow passes through discovery, update, build, and deploy while a locking pin blocks a second flow from entering too early. The image explains that WP Packages is disciplined like a CI system, with sequencing and file locking preventing contention.
The registry behaves more like a CI pipeline than a traditional web app.

The pipeline command is the quiet center of the project. It runs discovery, update, build, and deploy in order, and it uses a system-wide file lock so those steps do not collide when cron, a human operator, or a restart tries to trigger work at the same time.

func runPipeline() error {
    lock, err := acquirePipelineLock()
    if err != nil {
        return err
    }
    defer lock.Close()

    steps := []string{"discover", "update", "build", "deploy"}
    for _, step := range steps {
        start := time.Now()
        if err := runStep(step); err != nil {
            return err
        }
        recordBuildStep(step, time.Since(start))
    }
    return nil
}

That is the kind of code you write when you care about state as much as throughput. The point is not to do everything at once. The point is to make the registry predictable enough that everyone else can trust it.

Composer v2 changes everything

Composer v2’s metadata protocol is the real unlock. Instead of asking for a giant precomputed catalog, the client starts with a root query, then fetches package metadata only when it needs it. That reduces cold-start work and makes the registry feel faster even before caching kicks in.

WP Packages supports the newer metadata-url and metadata-changes-url flow, which means the server can reveal only the minimum required information at each step. The result is less wasted bandwidth, fewer round trips, and a cleaner fit between client and repository.

Client behaviorOld registry modelWP Packages model
First requestDownload a large indexFetch root metadata
Package lookupRely on prebuilt provider filesRequest only the needed package
FreshnessWait for the next bulk regenerationUse changes feed and background sync
Latency feelHeavy on cold startLight on cold start

This is why the project can advertise big wins without magic. It is not hiding work. It is refusing to do unnecessary work.

Immutable installs for a mutable ecosystem

WordPress has always had a tricky edge case: the untagged install. If a package points at trunk, the source can drift underneath you. WP Packages fixes that by pinning those installs to specific SVN revisions, which turns a moving target into a reproducible artifact.

That is a deceptively important feature. It means a build today can match a build tomorrow, even when the upstream source tree is still in motion. For teams shipping production sites, that is the difference between convenience and trust.

The modern Go stack behind the scenes

The service stack is intentionally plain. Cobra powers the CLI, net/http handles serving, embed ships assets inside the binary, slog keeps logs structured, Sentry catches errors, and systemd socket activation lets the process behave like real infrastructure instead of a one-off script.

That discipline shows up in the repo layout too. The command layer stays separate from internal packages, deployment lives in Ansible, and the whole thing reads like a production service with a registry problem, not a registry toy with a deployment hobby.

WP Packages vs. WPackagist

CategoryWP PackagesWPackagist
OwnershipCommunity-run and open sourceAcquired and operated under corporate ownership
ProtocolComposer v2 lazy metadataBulk index style repository
PerformanceFaster cold resolvesSlower cold resolves
MetadataRicher and more completeLess complete
Update modelFrequent syncs from sourceSlower refresh cadence
ReproducibilityPinned revisions for untagged installsLess deterministic trunk behavior
Namingwp-plugin and wp-theme namespaceswpackagist-plugin and wpackagist-theme namespaces

The difference is not cosmetic. It is a better contract between the WordPress ecosystem and the dependency tooling that sits on top of it.

Why this matters beyond WordPress

WP Packages is a reminder that infrastructure can be redesigned in public without inventing a new category of software. The trick is not novelty for its own sake. The trick is choosing the right abstraction, then making it fast, transparent, and maintainable.

That is why the project matters to more than WordPress developers. It is a case study in how to modernize a legacy ecosystem by tightening the feedback loop between source, metadata, and delivery.