PHPackages                             mnsami/composer-custom-directory-installer - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. mnsami/composer-custom-directory-installer

ActiveComposer-plugin[Utility &amp; Helpers](/categories/utility)

mnsami/composer-custom-directory-installer
==========================================

A composer plugin, to help install packages of different types in custom paths.

2.2.1(1mo ago)1465.4M↓24.1%2420MITPHPPHP &gt;=8.1CI passing

Since Jul 28Pushed 1mo ago5 watchersCompare

[ Source](https://github.com/mnsami/composer-custom-directory-installer)[ Packagist](https://packagist.org/packages/mnsami/composer-custom-directory-installer)[ GitHub Sponsors](https://github.com/mnsami)[ RSS](/packages/mnsami-composer-custom-directory-installer/feed)WikiDiscussions master Synced 2d ago

READMEChangelog (9)Dependencies (5)Versions (23)Used By (20)

composer-custom-directory-installer
===================================

[](#composer-custom-directory-installer)

A Composer plugin to install packages in custom directories outside the default `vendor` folder.

This is not another `composer-installer` library for supporting non-composer package types such as `application`. By default it handles `library`-type packages, but you can extend it to any Composer package type via `extra.installer-types` in your root `composer.json`.

Table of Contents
-----------------

[](#table-of-contents)

- [Why this plugin?](#why-this-plugin)
- [Requirements](#requirements)
- [Installation](#installation)
- [How to use](#how-to-use)
- [Path Variables](#path-variables)
    - [Path Variable Flags](#path-variable-flags)
- [Matching Strategies](#matching-strategies)
    - [Exact package name](#1-exact-package-name-highest-precedence)
    - [Package type prefix](#2-package-type-prefix)
    - [Wildcard vendor glob](#3-wildcard-vendor-glob-lowest-precedence)
- [Custom `installer-name`](#custom-installer-name)
- [Supporting Custom Package Types](#supporting-custom-package-types)
- [Complete example](#complete-example)
- [Real-world use cases](#real-world-use-cases)
    - [WordPress with WPackagist](#wordpress-with-wpackagist)
    - [Docker / keeping vendor outside the web root](#docker--keeping-vendor-outside-the-web-root)
    - [Monorepos with sibling library directories](#monorepos-with-sibling-library-directories)
    - [PascalCase or namespaced paths](#pascalcase-or-namespaced-paths)
- [Migrating from oomphinc/composer-installers-extender](#migrating-from-oomphinccomposer-installers-extender)
- [Security](#security)
- [Upgrading from v1.x](#upgrading-from-v1x)
- [Note](#note)

> The type of the package. It defaults to library.
>
> Package types are used for custom installation logic. If you have a package that needs some special logic, you can define a custom type. This could be a symfony-bundle, a wordpress-plugin or a typo3-module. These types will all be specific to certain projects, and they will need to provide an installer capable of installing packages of that type.

Why this plugin?
----------------

[](#why-this-plugin)

Composer has a built-in `installer-paths` key, but it only works if the package itself declares a dependency on `composer/installers` — something most packages on Packagist don't do. If you add an `installer-paths` rule for a package that doesn't opt in, Composer silently ignores it and installs the package to `vendor/` anyway.

This plugin removes that constraint. It intercepts installation at the root-project level, so any package can be redirected to a custom path without requiring any changes to the package itself.

In short: if you've ever added `installer-paths` and nothing happened, this plugin is the fix.

Requirements
------------

[](#requirements)

- PHP &gt;= 8.1
- Composer 2.x

Installation
------------

[](#installation)

Add the plugin to the `require` section of your `composer.json`:

```
"require": {
    "mnsami/composer-custom-directory-installer": "^2.1"
}
```

**Important — Composer 2.2+ plugin trust:**
Composer 2.2 and later require you to explicitly allow third-party plugins. Add the following to your `composer.json`:

```
"config": {
    "allow-plugins": {
        "mnsami/composer-custom-directory-installer": true
    }
}
```

Without this, Composer will either prompt interactively or block the plugin entirely in non-interactive (CI) environments.

How to use
----------

[](#how-to-use)

In the `extra` section of your root `composer.json`, define the custom directory for each package:

```
"extra": {
    "installer-paths": {
        "./monolog/": ["monolog/monolog"]
    }
}
```

This tells Composer to install `monolog/monolog` into the `./monolog/` directory instead of `vendor/monolog/monolog`.

Path Variables
--------------

[](#path-variables)

You can use the following variables in your `installer-paths` to build dynamic paths:

VariableDescriptionExample value`{$vendor}`The vendor portion of the package name`monolog``{$name}`The package name (or `installer-name` override)`monolog``{$type}`The Composer package type`library````
"extra": {
    "installer-paths": {
        "./customlibs/{$vendor}/db/{$name}": ["doctrine/orm"],
        "./custom/{$type}/{$vendor}/{$name}": ["acme/*"]
    }
}
```

### Path Variable Flags

[](#path-variable-flags)

You can append transformation flags after a pipe (`|`) to modify how a variable is substituted:

```
{$token|flags}

```

Flags are applied **left-to-right** in the order given:

FlagTransformationExample inputExample output`F`Capitalize first letter (`ucfirst`)`my-package``My-package``P`Strip hyphens/underscores and capitalize each following word`my-package``myPackage``U`Uppercase all characters`my-package``MY-PACKAGE`Flags can be combined. `FP` together produces **PascalCase** (capitalize first + strip separators):

ExpressionInputOutput`{$name|F}``my-package``My-package``{$name|P}``my-package``myPackage``{$name|FP}``my-package``MyPackage``{$name|U}``my-package``MY-PACKAGE``{$vendor|U}``acme``ACME`**Example:**

```
"installer-paths": {
    "src/{$vendor|U}/{$name|FP}/": ["acme/my-package"],
    "modules/{$name|FP}/":         ["type:drupal-module"]
}
```

For a package `acme/my-package` (type `library`), this resolves to `src/ACME/MyPackage/`.

Matching Strategies
-------------------

[](#matching-strategies)

`installer-paths` supports three matching strategies. Precedence is evaluated **globally across all entries**: all entries are first scanned for an exact name match, then for a type match, then for a wildcard match. An exact match in a later-listed entry always wins over a wildcard match in an earlier-listed entry.

### 1. Exact package name (highest precedence)

[](#1-exact-package-name-highest-precedence)

Matches one specific package:

```
"installer-paths": {
    "./libs/monolog/": ["monolog/monolog"]
}
```

### 2. Package type prefix

[](#2-package-type-prefix)

Matches all packages of a given Composer type using the `type:` prefix:

```
"installer-paths": {
    "./wp-content/plugins/{$name}/": ["type:wordpress-plugin"]
}
```

### 3. Wildcard vendor glob (lowest precedence)

[](#3-wildcard-vendor-glob-lowest-precedence)

Matches all packages from a given vendor using `*`:

```
"installer-paths": {
    "./acme-libs/{$name}/": ["acme/*"]
}
```

Custom `installer-name`
-----------------------

[](#custom-installer-name)

A package can override the `{$name}` variable by setting `installer-name` in its own `extra` section (inside the *package's* `composer.json`, not the root project):

```
"extra": {
    "installer-name": "my-custom-name"
}
```

When set, `{$name}` in the path template will resolve to `my-custom-name` instead of the package's actual name.

Supporting Custom Package Types
-------------------------------

[](#supporting-custom-package-types)

By default the plugin only handles the `library` package type. To install packages of other types (e.g. `drupal-module`, `wordpress-plugin`) into custom directories, declare those types in `extra.installer-types`:

```
"extra": {
    "installer-types": ["drupal-module", "wordpress-plugin"],
    "installer-paths": {
        "web/modules/{$name}/": ["type:drupal-module"],
        "wp-content/plugins/{$name}/": ["type:wordpress-plugin"]
    }
}
```

The plugin will claim any package whose type appears in `installer-types` and apply the matching `installer-paths` rule. Without this list, packages of non-`library` types are left to Composer's default installer.

Complete example
----------------

[](#complete-example)

```
{
    "require": {
        "mnsami/composer-custom-directory-installer": "^2.1",
        "monolog/monolog": "*",
        "acme/foo": "*",
        "acme/bar": "*"
    },
    "config": {
        "allow-plugins": {
            "mnsami/composer-custom-directory-installer": true
        }
    },
    "extra": {
        "installer-types": ["wordpress-plugin"],
        "installer-paths": {
            "./logger/":              ["monolog/monolog"],
            "./acme/{$name}/":        ["acme/*"],
            "./plugins/{$name}/":     ["type:wordpress-plugin"]
        }
    }
}
```

Real-world use cases
--------------------

[](#real-world-use-cases)

### WordPress with WPackagist

[](#wordpress-with-wpackagist)

Most WordPress plugins available via [WPackagist](https://wpackagist.org) don't require `composer/installers`, so native `installer-paths` silently fails for them. This plugin makes it work:

```
{
    "require": {
        "mnsami/composer-custom-directory-installer": "^2.1",
        "wpackagist-plugin/contact-form-7": "*",
        "wpackagist-theme/twentytwentyfour": "*"
    },
    "config": {
        "allow-plugins": {
            "mnsami/composer-custom-directory-installer": true
        }
    },
    "extra": {
        "installer-types": ["wordpress-plugin", "wordpress-theme"],
        "installer-paths": {
            "wp-content/plugins/{$name}/": ["type:wordpress-plugin"],
            "wp-content/themes/{$name}/":  ["type:wordpress-theme"]
        }
    }
}
```

### Docker / keeping vendor outside the web root

[](#docker--keeping-vendor-outside-the-web-root)

Put one package (e.g. a CLI tool) in a path that's bind-mounted into a container, while everything else stays in `vendor/`:

```
"extra": {
    "installer-paths": {
        "/tools/{$name}/": ["vendor/some-cli-tool"]
    }
}
```

### Monorepos with sibling library directories

[](#monorepos-with-sibling-library-directories)

Route internal packages to their canonical location in the repo tree:

```
"extra": {
    "installer-types": ["company-module"],
    "installer-paths": {
        "../modules/{$name}/": ["type:company-module"]
    }
}
```

### PascalCase or namespaced paths

[](#pascalcase-or-namespaced-paths)

Use [path variable flags](#path-variable-flags) to match the naming convention your framework expects:

```
"extra": {
    "installer-paths": {
        "src/Modules/{$name|FP}/": ["acme/*"]
    }
}
```

`acme/my-module` installs to `src/Modules/MyModule/`.

Migrating from oomphinc/composer-installers-extender
----------------------------------------------------

[](#migrating-from-oomphinccomposer-installers-extender)

[`oomphinc/composer-installers-extender`](https://packagist.org/packages/oomphinc/composer-installers-extender) has not had a functional release since December 2021. This plugin is a maintained, drop-in alternative with a superset of its features (path variable flags, three-pass precedence, traversal guards).

**Before:**

```
"require": {
    "composer/installers": "^2.0",
    "oomphinc/composer-installers-extender": "^2.0"
},
"config": {
    "allow-plugins": {
        "composer/installers": true,
        "oomphinc/composer-installers-extender": true
    }
},
"extra": {
    "installer-types": ["drupal-module"],
    "installer-paths": {
        "web/modules/contrib/{$name}/": ["type:drupal-module"]
    }
}
```

**After:**

```
"require": {
    "mnsami/composer-custom-directory-installer": "^2.1"
},
"config": {
    "allow-plugins": {
        "mnsami/composer-custom-directory-installer": true
    }
},
"extra": {
    "installer-types": ["drupal-module"],
    "installer-paths": {
        "web/modules/contrib/{$name}/": ["type:drupal-module"]
    }
}
```

The `extra.installer-types` and `extra.installer-paths` keys are identical — only the `require` and `allow-plugins` entries change. If you required `composer/installers` solely for path routing (not for any package's own declared dependency), you can remove it too.

Security
--------

[](#security)

Resolved install paths are validated against two attack vectors:

- **Directory traversal** — a resolved path containing `..` throws an `InvalidArgumentException`.
- **Absolute path injection** — a resolved path that is absolute (starting with `/` or a Windows drive letter) throws an `InvalidArgumentException`. This can occur when a package's `installer-name` is set to an absolute path value.

Both checks apply after all `{$variable}` substitutions are complete.

Upgrading from v1.x
-------------------

[](#upgrading-from-v1x)

v1.xv2.xPHP&gt;= 5.3&gt;= 8.1Composer1.x / 2.x2.x onlyRequire string`"1.*"``"^2.1"``type:` matchingNoYesWildcard `vendor/*`NoYes`{$type}` variableNoYes`allow-plugins` neededNoYes (Composer 2.2+)`installer-types` supportNoYesPath variable flags (`|F`, `|P`, `|U`)NoYesExisting `installer-paths` configurations (exact package names) are fully backwards-compatible and require no changes.

Note
----

[](#note)

Composer `type: project` is not supported by this installer, as packages with type `project` only make sense to be used with application shells like `symfony/framework-standard-edition`.

###  Health Score

70

—

ExcellentBetter than 100% of packages

Maintenance91

Actively maintained with recent releases

Popularity60

Solid adoption and visibility

Community34

Small or concentrated contributor base

Maturity81

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 90.4% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~431 days

Recently: every ~525 days

Total

11

Last Release

45d ago

Major Versions

1.1.1 → 2.0.02020-08-18

1.0.1 → 2.1.02026-05-07

PHP version history (2 changes)1.0.2PHP &gt;=5.3

2.1.0PHP &gt;=8.1

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/579064?v=4)[Mina Nabil Sami](/maintainers/mnsami)[@mnsami](https://github.com/mnsami)

---

Top Contributors

[![mnsami](https://avatars.githubusercontent.com/u/579064?v=4)](https://github.com/mnsami "mnsami (66 commits)")[![claude](https://avatars.githubusercontent.com/u/81847?v=4)](https://github.com/claude "claude (4 commits)")[![alesrebec](https://avatars.githubusercontent.com/u/426177?v=4)](https://github.com/alesrebec "alesrebec (1 commits)")[![chr-hertel](https://avatars.githubusercontent.com/u/2852185?v=4)](https://github.com/chr-hertel "chr-hertel (1 commits)")[![sagudev](https://avatars.githubusercontent.com/u/16504129?v=4)](https://github.com/sagudev "sagudev (1 commits)")

---

Tags

composercomposer-installercomposer-packagescomposer-pluginflexibilitycomposerwordpressdrupalcomposer-plugincomposer-installermonorepocustom pathinstaller-paths

###  Code Quality

TestsPHPUnit

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/mnsami-composer-custom-directory-installer/health.svg)

```
[![Health](https://phpackages.com/badges/mnsami-composer-custom-directory-installer/health.svg)](https://phpackages.com/packages/mnsami-composer-custom-directory-installer)
```

###  Alternatives

[automattic/jetpack-autoloader

Creates a custom autoloader for a plugin or theme.

576.1M126](/packages/automattic-jetpack-autoloader)[drupal/core-composer-scaffold

A flexible Composer project scaffold builder.

5344.1M564](/packages/drupal-core-composer-scaffold)[drupal/core-project-message

Adds a message after Composer installation.

2124.7M203](/packages/drupal-core-project-message)[typisttech/imposter-plugin

Composer plugin that wraps all composer vendor packages inside your own namespace. Intended for WordPress plugins.

158263.4k2](/packages/typisttech-imposter-plugin)[szepeviktor/composer-envato

Composer plugin for Envato

3716.8k1](/packages/szepeviktor-composer-envato)[avored/module-installer

A composer plugin, to help install modules for AvoREd e commerce applications.

139.2k14](/packages/avored-module-installer)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
