Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Domain Parser Laravel Package

bakame/laravel-domain-parser

Laravel wrapper around the PHP Domain Parser library. Easily resolve registrable domains, public suffixes, and subdomains using the Public Suffix List, with a service provider, facade, and validation rule for reliable domain handling in Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install (Laravel 9–11, auto-discovered provider + DomainParser facade).

composer require bakame/laravel-domain-parser
php artisan vendor:publish --tag=domain-parser-config   # or --provider="Bakame\LaravelDomainParser\DomainParserServiceProvider"

The published config/domain-parser.php is the first place to look: it holds the PSL source URL / local path, the cache store + TTL, and often a storage path for the downloaded rules file. Read that before writing any code — most surprises live there.

2. Seed the rules cache. The package wraps jeremykendall/php-domain-parser, which needs the Public Suffix List rules loaded (downloaded + cached). Run the package's refresh command once (confirm the exact name with php artisan list | grep -i domain):

php artisan domain-parser:update   # warms the cache from the configured source

Do this in your deploy pipeline, not on the first user request.

3. First use case — get the registrable ("root") domain from a host:

use Bakame\LaravelDomainParser\Facades\DomainParser;

DomainParser::resolve('shop.mysite.co.uk')->getRegistrableDomain(); // "mysite.co.uk"
DomainParser::resolve('shop.mysite.co.uk')->getPublicSuffix();      // "co.uk"
DomainParser::resolve('shop.mysite.co.uk')->getSubdomain();         // "shop"

If you only need one-shot parsing, DomainParser::parse($host) returns a parsed domain object directly; use resolve() when you also want suffix/registrability info. Method names are stable against the underlying Pdp\ResolvedDomain, but verify against the facade in your installed version.

Mental model: this is not DNS and not a validator. It answers "what is the effective root here, per the Public Suffix List?" — e.g. foo.github.io → registrable domain foo.github.io, because github.io is itself on the PSL.

Implementation Patterns

Bind it behind your own interface. The facade is convenient but couples app code to a global. Wrap it:

final class RootDomainResolver
{
    public function root(string $host): ?string
    {
        $host = strtolower(rtrim(trim($host), '.'));
        if ($host === '' || filter_var($host, FILTER_VALIDATE_IP)) {
            return null; // PSL has no meaning for IPs
        }
        return DomainParser::resolve($host)->getRegistrableDomain()?->toString();
    }
}

Inject this everywhere. It gives you one normalization point, one place to swap/cache, and a trivial fake in tests.

Multi-tenant routing. Resolve the host in middleware, look up the tenant by registrable domain, and stash it on the request. This is the canonical use: <tenant>.app.example.com → tenant app.example.com or <tenant> depending on which level you scope to. Combine with getSubdomain() to detect vanity subdomains.

Cookie / session scoping. Set cookie domain to the registrable domain so www.example.com and api.example.com share a session:

$domain = app(RootDomainResolver::class)->root($request->getHost());
config(['session.domain' => $domain ? ".{$domain}" : null]);

Email domain normalization. For signup analytics or blocklist matching, resolve the email domain's registrable domain: [email protected] → example.co.uk. Do this once at import, not on every read.

Batch / analytics jobs. Resolve in a queued job over a list of hosts and persist the root domain as a column. Do not resolve inside Eloquent accessors — it hits the rules cache per row and pollutes serialization.

Offline-safe deploys. Ship a pinned copy of the PSL. Download the .dat file at build time to storage/app/domain-parser/, point config's source at it, and refresh the cache in the deploy script. Your app then never needs egress to publicsuffix.org at runtime, and behaviour is reproducible across environments.

Testing. Fake your RootDomainResolver interface in feature tests. For unit tests of the wrapper itself, use real PSL rules — the parsing is deterministic and cheap once the rules object exists.

Gotchas and Tips

  • PSL ≠ DNS. foo.github.io, myblog.herokuapp.com, x.s3.amazonaws.com resolve to the subdomain being the registrable domain, because the parent is a listed suffix. Don't use this to infer "one organization = one root".
  • IPs and localhost. IP addresses have no registrable domain. resolve() may return null on getRegistrableDomain() for these, or throw depending on version — guard before calling ->toString().
  • getSubdomain() returns null, not '', for example.com. Null-check; don't empty()-test and then assume a string.
  • Trailing dots and case. Example.COM. will bite you. Lowercase and rtrim('.') before resolving — do it in your wrapper, once.
  • IDN / punycode. Non-ASCII hosts must be converted (idn_to_ascii) before resolution. Decide whether you store the ASCII or Unicode form; be consistent.
  • Cache driver matters. File cache in a single-node app is fine; on multi-server or Octane workers use Redis/Memcached so all workers see the same rules object and you avoid serialization surprises. Check the configured cache.store.
  • Warm before traffic. If the rules cache is cold and network is reachable, first request triggers a download. In a locked-down network that becomes a timeout or 500. Always refresh in deploy; never lazily on request.
  • Refresh cadence, not per-request. PSL changes weekly-ish. A scheduled domain-parser:update (daily or weekly) is enough. Don't refresh on every boot.
  • Rules version is inspectable. The underlying rules object exposes a version / modified date — log or expose it in /health so you can confirm prod is on the snapshot you think it is.
  • Don't use it as security. Root-domain extraction is not a substitute for origin checks, CSP, or SSRF allowlists. An attacker can control a registrable domain on a public suffix host (e.g. a *.github.io page is its own root here).
  • Extension points: the rules source (local path vs URL) and cache store are the two config knobs you'll actually touch; the facade resolves Pdp\Rules under the hood, so if you need finer control, resolve that contract from the container rather than the facade and hand it to your own service.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core