koodimonni/composer-dropin-installer
Composer plugin for Drupal that installs contributed modules, themes, and libraries into the correct project directories as drop-in packages. It simplifies dependency management for Drupal sites by letting Composer fetch and place code where Drupal expects it, reducing manual downloads and keeping b...
This is a Composer plugin, not a runtime library. Nothing ends up in your autoloader; its only job is to run during composer install/update and copy files from packages into paths you specify — and to skip the copy if the destination already exists. That "seed once, never clobber" behavior is the whole value proposition, so treat it as a deploy-time file placement tool, not a package manager feature.
Minimal setup:
require-dev — see Gotchas):
composer require koodimonni/composer-dropin-installer
composer.json:
"config": {
"allow-plugins": {
"koodimonni/composer-dropin-installer": true
}
}
extra.dropin-paths, then run composer install and confirm the file landed and that a second run does not overwrite a file you edited.First use case to prove it out: a caching plugin that ships object-cache.php. You want that file in wp-content/ on first install, but once a developer has hand-tuned it you never want Composer replacing it. Map wp-content/object-cache.php to the providing package, run the install, edit the file, run composer install again, and verify your edit survived. That single loop is the entire mental model for the package.
For Laravel shops the same logic applies to files an application is expected to own afterwards: a public/.htaccess variant, a phpunit.xml or .php-cs-fixer.php stub from a shared dev-tools package, Docker/compose files, or a CI config. Anything php artisan vendor:publish handles badly because the file is supposed to drift from the package version is a candidate.
Root-project mapping (the common pattern). The root project owns the policy; packages don't need to know anything:
"extra": {
"dropin-paths": {
"wp-content/object-cache.php": ["acme/redis-object-cache"],
"wp-content/advanced-cache.php": ["acme/page-cache"],
"public/.htaccess": ["acme/laravel-server-config"]
}
}
Package-declared drops. If a package ships a dropin type and its own path config, the installer intercepts it so consumers get the file with zero configuration. Useful for internal packages where you control both ends; use the root mapping when consuming third-party packages.
Whole-directory vs. single files. "A few files from a package" is the interesting mode: you can pull one artifact out of a package that otherwise installs normally as a library. That lets a library ship an optional drop-in without forcing a custom type.
Bedrock / Trellis-style layouts. These projects already relocate wp-content; drop-ins are the classic case that composer/installers doesn't cover because they live at hardcoded paths (wp-content/object-cache.php, wp-content/db.php) rather than in a package directory. This plugin slots in next to composer/installers without conflict — keep installer-paths for plugins/themes and dropin-paths for the loose files.
Deploy workflow. Commit the mapping, let CI run composer install --no-dev --optimize-autoloader, and verify the drop-ins exist post-install. Because the plugin skips existing files, the first deploy to a new environment is when the copy actually happens — make that an explicit assertion in your deploy script rather than assuming it silently worked.
Upgrade path for a modified drop-in. Since existing files are never overwritten, upgrading the underlying package means: delete the drop-in (or move it aside), run composer install, diff the new stock file against your customization, and re-apply. Script this if you have more than a couple of drop-ins.
allow-plugins is mandatory on Composer 2.2+. Without it the plugin is silently not loaded and nothing gets copied — no error, just a missing file. It's the single most common "it doesn't work" cause.require, not require-dev. Plugins in require-dev aren't installed when you run composer install --no-dev in production, so the drop-ins won't be placed on the deploy that needs them most.composer update to refresh a drop-in, it won't. Plan the manual delete-and-rediff cycle.composer remove to clean up paths — deletion would defeat the safety guarantee. Clean up manually and document it.composer install -vvv and watch for the installer's copy messages.composer/installers. installer-paths matches on package type and relocates the whole package; dropin-paths targets individual files/paths. Mixing the two up produces packages installed in the wrong place, not an error.type must be one the installer supports for the package-declared mode; a plain library won't be intercepted, so use the root-project mapping instead.composer install -vvv and composer diagnose. If a file isn't appearing, check in order: plugin approved, plugin in require, path correct, destination not already present (that's the silent success case).post-install-cmd PHP script (if (!file_exists($dest)) copy(...)) — the behavior is ~20 lines and you may prefer owning it. WTFPL is permissive but will still trigger a legal review at most companies; flag it before it lands in a commercial dependency graph.chmod, patch, or template the file after placement, do it in a Composer script (post-install-cmd / post-update-cmd) that runs after the plugin.How can I help you explore Laravel packages today?