Skip to main content
Adzbyte
TutorialsWordPress

Build a Reproducible WordPress Plugin Release ZIP

Adrian Saycon
Adrian Saycon
September 30, 20265 min read
Build a Reproducible WordPress Plugin Release ZIP

A trustworthy WordPress plugin release ZIP should contain the same production files whenever it is built from the same tagged source. It should exclude tests, local configuration, caches, source maps, and development dependencies; include compiled assets and required production libraries; carry consistent version metadata; and install successfully on a clean WordPress site. This tutorial creates a release directory from an explicit allowlist, validates it, builds the archive, and smoke-tests the artifact rather than assuming the repository itself is deployable. The finished pipeline makes the artifact itself the unit that passes or fails release.

Make the release artifact a separate product

A Git repository contains materials developers need: tests, source files, CI configuration, documentation, and build tools. A WordPress site needs a smaller runtime package. Treating git archive or a folder ZIP as the release process can omit generated files or include secrets and unnecessary attack surface.

Define the plugin directory name inside the archive, for example adz-example/. WordPress expects the files under that directory rather than loose at the ZIP root.

Use one authoritative version

The main plugin header, a PHP constant, readme.txt stable tag when relevant, and release tag must agree. Prefer a build check that reads those locations and fails on mismatch instead of a script that silently edits source during packaging.

Plugin Name: Adz Example
Version: 2.4.0
Requires at least: 6.8
Requires PHP: 8.1

Support declarations should match the environments covered by CI. A release archive cannot compensate for untested compatibility claims.

Start from a clean source checkout

Build from the exact commit associated with the release tag. Refuse to build with uncommitted changes unless the operator intentionally requests a development artifact. Record the commit hash and tool versions in the build log, not necessarily inside the public package.

For automated releases, use a fresh CI checkout. That prevents ignored local files from leaking into the archive and makes the process easier to reproduce.

Install production dependencies and compile assets

Run Composer with development packages excluded and optimized autoloading. Install Node dependencies from the lockfile, compile assets, and ensure the build does not depend on machine-specific paths.

composer install --no-dev --classmap-authoritative --no-interaction
npm ci
npm run build

If Composer packages are prefixed to avoid collisions in WordPress, run that scoped build step before copying files. Check licenses and redistribution terms for every bundled dependency.

Copy an allowlist into a staging directory

An allowlist is easier to audit than a growing list of exclusions. Copy the main plugin file, production PHP directories, compiled assets, languages, vendor dependencies, license, and public readme.

release/adz-example/
├── adz-example.php
├── assets/
├── includes/
├── languages/
├── vendor/
├── LICENSE
└── readme.txt

Do not include .env, .git, node modules, tests, screenshots used only for source documentation, editor settings, caches, raw design files, or private keys. Search the staged directory for common secret patterns before archiving.

Validate the staged package

Run PHP syntax checks over included PHP files, confirm Composer’s production autoloader resolves, verify required build assets exist, and scan for development references. Check that no symlink points outside the package.

  • The main plugin header parses and contains the expected version.
  • The plugin directory and text domain use the intended slug.
  • No development-only Composer package remains.
  • Every file referenced by block.json exists.
  • Translation files and license notices are present.
  • The unpacked size is within an expected limit.

Fail the build at the first broken invariant. A warning that scrolls past in CI does not protect the release.

Create deterministic archives where practical

Sort file paths, normalize timestamps to a value derived from the release commit, and use consistent permissions before zipping. Exclude operating-system metadata such as .DS_Store. Generate a SHA-256 checksum for the final ZIP and publish it with the release when your distribution process supports verification.

Exact byte reproducibility can vary by ZIP tooling and metadata, but the goal remains useful even when perfect determinism is unavailable: the same source should yield the same file set, content, paths, and permissions.

Inspect the archive, not just the staging folder

List the ZIP contents and extract it into a new temporary directory. Attackers and accidents both exploit archive boundaries, so reject absolute paths and ../ traversal entries. Confirm there is one expected top-level plugin directory and no additional payload.

Compare the extracted file manifest with the staged manifest. This catches archiver flags that introduce or omit files.

Smoke-test on a clean WordPress installation

  1. Install the ZIP through the same mechanism users will use.
  2. Activate the plugin with debugging enabled.
  3. Visit the admin screen and one front-end path central to the plugin.
  4. Run database upgrades and confirm repeated activation is safe.
  5. Deactivate, reactivate, and uninstall according to the documented data policy.
  6. Upgrade from the previous supported release using realistic existing data.

Tests against the repository do not prove the archive contains everything those tests imported locally. Artifact testing finds missing vendor classes, unbuilt blocks, wrong paths, and case-sensitivity errors.

Release the exact artifact that passed

Do not rebuild after smoke testing. Promote the same ZIP and checksum that passed validation. Attach it to the release, store the build log, and retain enough information to reproduce it from the tag. If the artifact changes, its checksum changes and it must pass the pipeline again.

Turn packaging into a release gate

A reproducible ZIP is the last link between source control and a customer’s WordPress installation. An explicit allowlist, production-only dependencies, deterministic staging, archive inspection, and clean-site smoke tests make that link reviewable. WordPress’s readme guidance documents public metadata, while the packaging rules belong in versioned automation beside the plugin. Build once, test that artifact, and distribute exactly what passed.

Photo by ThisIsEngineering on Pexels.

Adrian Saycon

Written by

Adrian Saycon

A developer with a passion for emerging technologies, Adrian Saycon focuses on transforming the latest tech trends into great, functional products.

Discussion (0)

Sign in to join the discussion

No comments yet. Be the first to share your thoughts.

Latest Articles

From the Blog

View all articles