How to Analyze Supply-Chain Risks in WordPress Plugins
A WordPress plugin can download and run code from its vendor after installation. So even an authentic, current plugin can run malicious…
For a WordPress block plugin, keep the PHP file WordPress loads in the plugin’s root directory, edit the block code in src/, and use @wordpress/scripts to create browser-ready files in build/. The PHP file registers the compiled block with WordPress. The build tool compiles the JavaScript and styles based on the block’s metadata, the settings in block.json that describe the block.
A typical layout looks like this:
my-plugin/
├── my-plugin.php
├── package.json
├── package-lock.json
├── src/
│ ├── block.json
│ ├── index.js
│ └── style.scss
└── build/
├── block.json
├── index.js
├── index.asset.php
└── style-index.css
my-plugin.php is the plugin’s PHP entry point. WordPress loads it when the plugin is active. For a single block, it can register the metadata that the build places in build/:
<?php
/**
* Plugin Name: My Plugin
*/
add_action(
'init',
function () {
register_block_type( __DIR__ . '/build' );
}
);
src/ holds the source files you edit. The wp-scripts build defaults use src/ for input and build/ for output. The tool uses each block.json file to find a block’s JavaScript entry point. If it finds no block metadata, it uses src/index.js instead. The tool also copies block metadata and creates supporting files, including dependency information WordPress needs to load scripts. The root PHP file remains in the plugin but does not serve as a JavaScript entry point. The tool copies PHP files referenced by block metadata for rendering or variations, but leaves other PHP files in src/ out by default.
Install the package as a development dependency. It supports development and builds on your computer; WordPress does not need it to run the plugin:
npm install --save-dev @wordpress/scripts
Add these commands to the scripts field in package.json:
{
"scripts": {
"start": "wp-scripts start",
"build": "wp-scripts build"
}
}
Keep any existing package settings and merge these entries into the existing scripts object. Run npm install to install the project’s dependencies, then use npm run start while editing; it watches for changes and rebuilds. Run npm run build to create production output. The Block Editor Handbook’s JavaScript overview describes how the tooling transforms modern JavaScript and JSX for use in the editor.
Run npm run build before packaging the plugin. The command runs wp-scripts build, which compiles source code for production, including minifying JavaScript. WordPress block projects typically keep editable files in src/ and write the browser-ready output to build/. Keep the source files for future changes; include the generated output so WordPress can load the compiled block.
The build copies the block.json files it finds into build/. It puts imported CSS in output files and creates asset metadata listing the script dependencies WordPress needs. Standard @wordpress/scripts tooling externalizes WordPress-provided packages, meaning it leaves those packages out of the JavaScript bundle and records the dependencies for WordPress to load at runtime. Include the generated dependency metadata alongside the compiled scripts. See the @wordpress/scripts build behavior and block metadata fields.
The build/ directory does not hold every file WordPress needs. Include the root PHP file that registers the block, the readme, and any other files the plugin needs to run or distribute. The build copies PHP files referenced by block metadata fields such as render and variations; use --webpack-copy-php to copy all PHP files from the source directory and its subdirectories. The root plugin file still needs to be included separately.
If you create a ZIP with wp-scripts plugin-zip, run the production build first: ZIP creation is a separate command, and the packaging command does not automatically establish that fresh build output is present. Check the archive for the plugin PHP file and build/ assets and metadata. Also check for the readme and any other required files. If package.json defines a files allowlist, verify that it includes them. The ZIP command documentation describes how file selection can be customized.
Run a production build before packaging so the ZIP contains the compiled files WordPress needs. The plugin-zip command is available only in versions of @wordpress/scripts that include it; check the installed CLI with npx wp-scripts --help and confirm the command appears. The Block Editor Handbook documents plugin-zip as a separate step from build, so do not assume packaging builds the plugin automatically.
When the command is available, add it to package.json and run both steps:
{
"scripts": {
"build": "wp-scripts build",
"plugin-zip": "wp-scripts plugin-zip"
}
}
npm run build
npm run plugin-zip
The command selects files for the archive using package-file rules. A .distignore file lists files and directories to leave out of the release, such as .git/, node_modules/, and test directories. Keep the exclusions specific to your plugin: removing build/, the main PHP file, or another runtime dependency leaves the installed plugin incomplete. If package.json defines a files allowlist, check that it includes every required runtime file as well.
If your installed version does not provide plugin-zip, or you need to control the file set yourself, copy the plugin’s runtime files into a clean staging directory and create a ZIP from its parent directory. For example, adapt these commands to your plugin’s filenames and required assets:
mkdir -p ../release/my-plugin
cp my-plugin.php readme.txt ../release/my-plugin/
cp -R build ../release/my-plugin/
cd ../release
zip -r my-plugin.zip my-plugin
The archive should contain a top-level plugin folder, with the main PHP file and required build output inside it. Before release, inspect the ZIP to confirm that it includes the built block.json and generated assets your plugin uses, while excluding development files. Install it on a clean WordPress test site and activate it. Then verify that the block appears and its editor and front-end assets load. A ZIP command succeeding only confirms that an archive was created; it does not confirm that WordPress can run the plugin.
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com