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 Email Domain Rule Laravel Package

maize-tech/laravel-email-domain-rule

A Laravel validation rule that restricts email addresses by domain, letting you allow or block specific domains and disposable providers. Drop it into form requests or validators to keep signups clean with one simple, configurable rule.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small, single-purpose package: it ships one validation rule that constrains an email address to (or away from) a list of domains. Release 3.2.0 is pure maintenance — PHP 8.3 support and Laravel 11.x compatibility — so there is no new API surface to learn.

  1. composer require maize-tech/laravel-email-domain-rule
  2. There is nothing to publish and no service provider configuration to worry about; the rule is instantiated inline wherever you validate.
  3. Open src/Rules/EmailDomainRule.php first — it is the entire package. Read the constructor signature (allow / deny arrays) and the domain-extraction logic before you build on top of it, because that logic is the thing you will eventually need to extend.
  4. First use case: registration. Restrict signups to corporate domains, or block the usual free/disposable providers:
use Maize\EmailDomainRule\Rules\EmailDomainRule;

$request->validate([
    'email' => [
        'required',
        'email',
        new EmailDomainRule(deny: ['gmail.com', 'yahoo.com', 'hotmail.com']),
    ],
]);

If your Laravel/PHP floor is below PHP 8.2 / Laravel 10, pin to the previous 3.x line; 3.2.0 is aligned with the PHP 8.3 + Laravel 11 support window. There are no breaking changes in this release, so the upgrade is a safe composer update.

Implementation Patterns

Prefer FormRequests over inline controller validation. The rule is stateful (it carries your domain lists), so keeping the lists next to the request class keeps them discoverable:

class StoreRegistrationRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'email' => ['required', 'email', new EmailDomainRule(allow: config('signup.allowed_domains'))],
        ];
    }
}

Keep the lists in config, not in code. A config/signup.php with allowed_domains / blocked_domains lets ops change policy without a deploy of application code, and it keeps the same list reusable across registration, invite, and "change email" flows.

Allow-list vs deny-list are different products. Use allow for B2B/corporate-gated products (fails closed, new domains are blocked by default) and deny for consumer products filtering out free providers (fails open). Do not mix both unless you have thought through precedence — confirm which wins in the rule source before relying on it.

Reuse it beyond the request layer. The domain check is often needed outside validation — e.g. "is this existing user's domain now blocked?" — so extract the normalization/parse step into your own small helper and let both the validation rule and any backfill command share it. Otherwise you end up with two subtly different notions of "same domain".

Pair with the standard rules and order them. ['required', 'email', new EmailDomainRule(...)] — the email rule does the format work so the domain rule only ever sees something with an @.

Test with datasets. Because the rule is deterministic on input strings, Pest/PHPUnit datasets are the cheapest way to lock in policy: valid corporate domain, blocked free provider, subdomain, uppercase, IDN. A Validator::make(...)->passes() assertion is enough.

Multi-tenant / per-account allow-lists work fine — just build the rule inside rules() where you have access to the authenticated tenant, rather than as a static array on the class.

Gotchas and Tips

  • Subdomains are the classic trap. mail.example.com is not example.com. A plain in_array comparison will reject subdomains; decide explicitly whether you want exact match or suffix match, and write a test for it.
  • Normalize case. Domains are case-insensitive; Example.COM must be lowercased before comparison. Check whether the rule does this — if not, do it in your own extension.
  • IDN / punycode. A user typing bücher.de may arrive as xn--bcher-kva.de after browser normalization. Domain lists authored in Unicode will silently never match.
  • Malformed input. If email fails first, the domain rule may still be invoked depending on bail; parsing a string with no @ is where naive implementations throw. Adding bail after required avoids odd error messages stacking up.
  • Error messages are generic by default. Override per-field with a custom message array key or by customizing the rule's message, otherwise users see "The email is invalid" when the real problem is "your company domain isn't on the list yet" — a support ticket you can avoid with one sentence of copy.
  • Config caching. If the allowed/blocked lists live in config, remember php artisan config:cache in your deploy pipeline, or a list change will appear to do nothing in production.
  • This is policy, not spam defence. Disposable-domain lists go stale within weeks. Treat deny as a UX filter and keep real abuse handling (rate limiting, email verification) separate.
  • Extension points are thin by design. There is no config file or driver system to hook into; the intended extension is subclassing the rule (or writing a Rule of your own that wraps it) and, if you want the string syntax email_domain:..., registering a custom validator extension via Validator::extend. Both are a few lines and easier to own than waiting on upstream.
  • Don't fork for a bugfix in the parsing logic — this package is small enough that a local subclass overriding the domain-extraction method is lower maintenance than a VCS repository entry in composer.json.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource