kanopi / wp-core-installer
A Composer plugin that safely installs WordPress core files without overwriting project files, and manages .gitignore for all Composer-managed WordPress packages.
Package info
github.com/kanopi/wp-core-installer
Type:composer-plugin
pkg:composer/kanopi/wp-core-installer
Requires
- php: >=8.0
- composer-plugin-api: ^2.0
Requires (Dev)
- composer/composer: ^2.0
README
A Composer plugin that safely installs WordPress core files into a configured
web-root without overwriting your project's own files, and automatically
manages a .gitignore block for all Composer-managed WordPress packages.
The problem it solves
The default johnpbloch/wordpress-core-installer maps the install path
directly to your project root or web-root, causing Composer's extractor to
mirror / clobber the entire target directory — wiping composer.json,
composer.lock, .env, and everything else that lives there.
This plugin instead:
- Extracts WordPress core into a private staging directory inside
vendorso Composer's internal tracking works correctly. - Selectively copies files from staging into your web-root.
- Skips a built-in list of protected paths (and any extras you declare).
- Manages two
.gitignoreblocks — one for WP core files, one for every Composer-managed plugin, theme, and the vendor directory — so you never accidentally commit them.
Installation
composer require kanopi/wp-core-installer
Then require WordPress core:
composer require johnpbloch/wordpress-core
Configuration
All configuration lives under extra in your project's composer.json:
{
"extra": {
"wordpress-install-dir": ".",
"wp-core-installer": {
"protected-paths": [
"my-custom-loader.php",
"config"
],
"skip-if-exists": [
"robots.txt"
]
}
}
}
wordpress-install-dir
| Value | Meaning |
|---|---|
"." |
Deploy WP files into the project root itself |
"public" |
Deploy into <project-root>/public/ |
"public/wp" |
Deploy into <project-root>/public/wp/ |
| (omitted) | Defaults to "public" |
wp-core-installer.protected-paths
Additional paths relative to the web-root that must never be touched. Extends the built-in list.
wp-core-installer.skip-if-exists
Paths that are copied on first install only, never overwritten on update.
The built-in list covers .htaccess, wp-config-sample.php, and the
silence-is-golden wp-content/index.php stubs.
Three-tier protection model
| Tier | Behaviour | Examples |
|---|---|---|
protected-paths |
Never copied, never gitignored | wp-config.php, wp-content/themes, wp-content/plugins |
skip-if-exists |
Copied on first install only; not gitignored | .htaccess, wp-content/index.php |
| Everything else | Always synced from core; gitignored | wp-admin/, wp-includes/, wp-login.php |
Managed .gitignore blocks
The plugin writes and maintains two independent marked blocks in your
project's .gitignore. Everything outside these blocks is untouched.
Core block
Updated whenever composer install or composer update runs the WordPress
core installer:
# <kanopi/wp-core-installer:core:begin> # Managed by kanopi/wp-core-installer — do not edit this block manually. # WordPress core staging directory (Composer internal — do not commit) /wp-content/mu-plugins/vendor/.wordpress-core-staging/ # WordPress core files (managed via Composer — do not commit) /wp-admin/ /wp-includes/ /index.php /wp-activate.php … # <kanopi/wp-core-installer:core:end>
Packages block
Updated on every composer install / composer update run via a
post-install-cmd / post-update-cmd hook:
# <kanopi/wp-core-installer:packages:begin> # Managed by kanopi/wp-core-installer — do not edit this block manually. # Composer vendor directory /wp-content/mu-plugins/vendor/ # Composer-managed WordPress plugins /wp-content/plugins/akismet/ /wp-content/plugins/woocommerce/ # Composer-managed WordPress themes /wp-content/themes/twentytwentyfive/ # <kanopi/wp-core-installer:packages:end>
Both blocks are replaced wholesale on each run (not appended), so adding
or removing a package automatically keeps the ignore list in sync.
Running composer remove johnpbloch/wordpress-core strips the core block.
Built-in protected paths (never overwritten)
| Path | Reason |
|---|---|
composer.json, composer.lock |
Project manifests |
wp-config.php |
WordPress runtime config |
wp-content/themes |
Project-owned themes |
wp-content/plugins |
Project-owned plugins |
wp-content/mu-plugins |
Project-owned must-use plugins |
wp-content/uploads |
User-uploaded media |
wp-content/upgrade, wp-content/languages |
WordPress-managed dirs |
.env / .env.* |
Environment / secrets |
.git, .gitignore, .gitattributes |
VCS metadata |
node_modules, vendor |
Other dependency trees |
Typical project layout
my-wordpress-site/
├── composer.json ← protected
├── composer.lock ← protected
├── wp-config.php ← protected (you create this)
├── wp-admin/ ← deployed by this plugin; gitignored
├── wp-includes/ ← deployed by this plugin; gitignored
├── index.php ← deployed by this plugin; gitignored
├── .htaccess ← skip-if-exists (first install only)
└── wp-content/
├── index.php ← skip-if-exists (first install only)
├── mu-plugins/
│ └── vendor/ ← gitignored (Composer vendor dir)
├── plugins/
│ ├── index.php ← skip-if-exists
│ └── akismet/ ← gitignored (Composer-managed plugin)
└── themes/
├── index.php ← skip-if-exists
├── twentytwentyfive/ ← gitignored (Composer-managed theme)
└── my-custom-theme/ ← NOT gitignored (not Composer-managed)
License
MIT