consolidation/site-alias
Site Alias lets Laravel apps give each site its own domain aliases and resolve the correct site from the incoming request, with support for multiple aliases per site and fast lookup caching.
consolidation/site-alias is the alias engine that powers Drush and Pantheon Terminus. It does one job: turn a string like @mysite.dev into a structured, validated record describing a site (local path, remote host, SSH user, URI, file paths) so that other code can build commands against it.
composer require consolidation/site-alias
Where to look first, in this order:
SiteAliasManager — the entry point. It resolves alias names to records and holds the @self alias.SiteAliasFileLoader (with SiteAliasFileDiscovery) — reads YAML files off disk from a configurable search path.SiteAlias / AliasRecord — the value objects. SiteAlias is the raw data; AliasRecord pairs that data with the name it was resolved from (and proxies get()/set()/root()/uri() down to the SiteAlias).First use case: read a site's root and URI out of a YAML alias file and use it to locate a docroot.
# drush/sites/example.site.yml
dev:
host: dev.example.com
user: deploy
root: /var/www/dev/docroot
uri: https://dev.example.com
use Consolidation\SiteAlias\SiteAliasFileLoader;
use Consolidation\SiteAlias\SiteAliasManager;
$loader = (new SiteAliasFileLoader())->addSearchPath(__DIR__ . '/drush/sites');
$manager = new SiteAliasManager($loader);
$record = $manager->get('@example.dev'); // null if not found
if ($record === null) {
throw new \RuntimeException('Unknown alias');
}
print $record->get('root'); // /var/www/dev/docroot
print $record->isRemote() ? 'remote' : 'local';
That null check is not optional — see the Gotchas section.
The filename prefix becomes the alias namespace, so example.site.yml with a key dev yields @example.dev. Put shared values at the top level and per-environment overrides beneath; the loader handles self specially so you can write self.site.yml and get @self plus @group.self.
# drush/sites/example.site.yml
common:
user: deploy
paths:
files: sites/default/files
ssh:
options: '-o StrictHostKeyChecking=no'
dev:
host: dev.example.com
root: /var/www/dev/docroot
uri: https://dev.example.com
prod:
host: prod.example.com
root: /var/www/prod/docroot
uri: https://example.com
Other keys you will actually use: os (Windows remotes), php, command (per-command option overrides), env-vars, and paths (arbitrary named paths that downstream code resolves against root).
Treat the manager as immutable for the life of a command. Add all search paths up front, then resolve. Search-path order determines precedence when two files define the same alias.
$loader = new SiteAliasFileLoader();
$loader->addSearchPath($projectRoot . '/drush/sites'); // project wins
$loader->addSearchPath($home . '/.drush/sites'); // user fallback
$manager = new SiteAliasManager($loader);
If you are writing Drush commands (or any container-wired CLI), implement SiteAliasManagerAwareInterface and use SiteAliasManagerAwareTrait. The container inflector calls setSiteAliasManager() for you, and $this->siteAliasManager() is available inside execute().
class StatusCommand extends DrushCommands implements SiteAliasManagerAwareInterface
{
use SiteAliasManagerAwareTrait;
public function execute(string $alias): void
{
$record = $this->siteAliasManager()->get($alias);
// ...
}
}
getMultiple('@example.*') returns an array keyed by alias name and is the clean way to implement "run this against every environment" commands. Pair it with remotes() when you want to exclude @self and anything else without a host.
foreach ($manager->getMultiple('@example.*') as $name => $record) {
if (!$record->isRemote()) {
continue;
}
$this->runOn($record);
}
The whole point of the package is that command construction is centralized. Read host, user, root, uri, and ssh.options off the record rather than parsing alias strings yourself.
$ssh = sprintf(
'ssh %s %s@%s',
$record->get('ssh.options') ?? '',
$record->get('user'),
$record->get('host')
);
@self for tests and embedded use@self is normally synthesized from the current working directory. When you embed the manager in a library or a test, set it explicitly so behavior does not depend on getcwd().
$manager->setSelf(SiteAlias::create([
'root' => '/tmp/fixture/docroot',
'uri' => 'http://localhost:8080',
]));
export() returns the underlying array, which is what you want to hand to a child process (serialize it, or pass it as --define-style options). The child can rebuild an identical SiteAlias::create($data), which keeps alias semantics consistent across process boundaries instead of re-parsing YAML in two places.
get() returns null for unknown aliases — it does not throw. Every call site needs a null check; a missing alias should produce your own clear error message, not a fatal "call to a member function on null" three frames deeper.
@self is synthesized, not required. If no self.site.yml exists, the manager still produces a usable @self pointing at the cwd. This surprises people who expect a "no aliases defined" error. If you need a defined self, check has('@self') explicitly or compare fields against the synthesized value.
Filename prefix vs. key confusion is the #1 support question. foo.site.yml containing key bar is @foo.bar, not @bar and not @foo. Legacy Drush 8–style .alias.yml files in a drush/ directory use bare @name keys with no prefix — if the same site is reachable under two different alias spellings, you have a mix of legacy and modern files on the search path.
Path precedence is silent. Two files defining the same alias do not error; the earlier search path simply wins. When an alias "won't update," verify which file actually loaded before editing anything. drush sa --format=json @example.dev dumps the fully-resolved record and is the fastest way to see the effective values, including everything inherited from top-level keys.
Malformed YAML surfaces as an exception, usually at first resolution rather than at construction, because loading is lazy. A typo in a prod block that nothing references yet will not fail until someone targets @prod. Lint your alias files in CI.
Do not change search paths after the first lookup. Configuration added mid-command may not take effect and, worse, may take effect for some aliases and not others. Build the loader, then the manager, then resolve — in that order.
No environment-variable interpolation in YAML. Alias files are static data. If you need a hostname from $_ENV or a CI variable, construct the SiteAlias in code and register it with add(), or generate the file before invoking the tool.
add() argument order has shifted between versions. When registering programmatic aliases, check the signature in the installed version rather than trusting an older example. setSelf() is stable and covers most cases.
Extension points worth knowing: swap in a custom loader by constructing SiteAliasManager with your own loader implementation (useful for database-backed or API-backed alias sources, e.g. a hosting provider's environment list); and prefer injecting the manager via SiteAliasManagerAwareInterface over new SiteAliasManager() inside a command, so tests can substitute a manager pre-loaded with fixtures.
Testing recipe: build a SiteAliasManager with a SiteAliasFileLoader pointed at a fixture directory containing one .site.yml, then assert on get('@fixture.dev')->export(). If you need to test alias-less code paths, setSelf() to a temp directory and skip files entirely.
How can I help you explore Laravel packages today?