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

Idna Laravel Package

mlocati/idna

Pure-PHP implementation of IDNA 2008 and UTS #46 for converting internationalized domain names between Unicode and ASCII (punycode). Validates and normalizes domain names, supports PHP 5.3+, and works without the intl extension, making it a lightweight drop-in for encoding/decoding IDN domains.

Deep Wiki
Context7

Getting Started

mlocati/idna is a standalone PHP implementation of IDNA 2008 (not a Laravel package), so there is no service provider, config file, or facade to publish. Install it and start converting names:

composer require mlocati/idna

Where to look first, in order:

  1. README.md — the canonical usage examples and the exact factory/accessor names for the installed version.
  2. src/DomainName.php — the single value object you will use 95% of the time.
  3. src/Exception/ — the exception types thrown on invalid input.
  4. The bundled data/tables directory — useful when you need to understand why a specific code point is rejected.

The first real use case is almost always: accept a user-entered Unicode domain, validate it, and persist the ASCII/Punycode form for DNS and mail while keeping the Unicode form for display.

use MLocati\IDNA\DomainName;

try {
    $domain = DomainName::fromUnicode('münchen.example');
    $punycode = $domain->getPunycode(); // xn--mnchen-3ya.example
    $unicode  = $domain->getUnicode();  // münchen.example
} catch (\Throwable $e) {
    // invalid domain name
}

If your framework or app has no IDN support yet, wire this in as the single conversion entry point (a small DomainNormalizer service) so conversions are consistent across validation, storage, and outbound HTTP/DNS.

Implementation Patterns

Normalize once, store twice. Keep the Punycode form as the canonical, unique, lowercase column and the Unicode form for rendering. Never compare Unicode forms directly.

$ascii = DomainName::fromUnicode($input)->getPunycode();
$model->domain_ascii   = strtolower($ascii);
$model->domain_unicode = DomainName::fromUnicode($input)->getUnicode();

Laravel validation rule. Wrap construction in try/catch so malformed input becomes a validation message instead of a 500. In Laravel 10+:

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use MLocati\IDNA\DomainName;

class IdnDomain implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (!is_string($value) || $value === '') {
            $fail("The {$attribute} must be a domain name.");
            return;
        }
        try {
            $ascii = DomainName::fromUnicode($value)->getPunycode();
        } catch (\Throwable $e) {
            $fail("The {$attribute} is not a valid internationalized domain name.");
            return;
        }
        if (strlen($ascii) > 253) {
            $fail("The {$attribute} is too long.");
        }
    }
}

Use it as ['required', new IdnDomain] anywhere a hostname arrives (signup, custom domains, mail-from addresses).

Email addresses. Split on the last @, convert only the domain part, and store the ASCII form for delivery while displaying the Unicode form:

$pos = strrpos($email, '@');
$local  = substr($email, 0, $pos);
$domain = substr($email, $pos + 1);
$asciiEmail = $local . '@' . DomainName::fromUnicode($domain)->getPunycode();

URLs and redirects. Let parse_url() do URL parsing; pass only the host component into this library. This keeps the package's responsibility narrow and avoids surprises with paths, ports, and userinfo.

$host = parse_url($url, PHP_URL_HOST);
$asciiHost = DomainName::fromUnicode($host)->getPunycode();

Outbound HTTP / DNS. Any client that resolves hostnames (Guzzle, Laravel's HTTP client, curl) should receive the Punycode host, not the Unicode string. Centralize that in a middleware, a Guzzle handler, or a small wrapper service so outbound calls cannot accidentally send raw Unicode.

Bulk imports and queues. Conversion is pure CPU and fast, but for large imports (tens of thousands of rows) process in chunks inside a queued job and cache results keyed by input string. The underlying IDNA tables are loaded into memory per process, so a long-running worker amortizes that cost naturally.

Optional container binding. Since DomainName is an immutable value object, there is nothing to singleton-bind. If you want testability, wrap it:

$this->app->bind(DomainNormalizer::class, MlOcatiNormalizer::class);

and inject that everywhere instead of calling DomainName::fromUnicode() directly, so tests can stub conversions.

Display layer. Add a Blade helper/accessor such as $model->display_domain that returns the stored Unicode form, falling back to Punycode. Keep the ASCII value as the source of truth for uniqueness constraints and lookups.

Gotchas and Tips

  • IDNA2008 vs UTS #46 vs IDNA2003. This library implements IDNA 2008. PHP's idn_to_ascii() follows UTS #46 (and can be transitional), so the two disagree on characters like ß, ς, and ZWJ/ZWNJ sequences. Pick one implementation and use it consistently; don't mix this package with intl for the same field.
  • Double-encoding. Passing an already-Punycode host into fromUnicode() treats xn--... as a literal ASCII label. Detect the xn-- prefix and choose fromPunycode() vs fromUnicode() explicitly, or normalize both directions and compare.
  • Normalization form. IDNA 2008 processing assumes NFC. Normalize user input with Normalizer::normalize($value, Normalizer::FORM_C) (ext-intl) before conversion if you accept pasted or imported data; otherwise visually identical strings can map to different labels.
  • Case and trailing dots. ASCII labels are case-insensitive; lowercase before storing or comparing. Decide explicitly whether to accept or strip a trailing root dot (example.com.), because it affects uniqueness checks.
  • Length limits are octet-based. Each label is capped at 63 octets after Punycode expansion and the whole name at 253 (255 with the final dot). A Unicode label that looks short can exceed 63 bytes after encoding — validate the encoded form, not the input.
  • Don't leak exceptions. Construction throws on invalid input; wrap it at every untrusted boundary (form requests, API resources, webhook handlers) so malformed hosts don't surface as 500s.
  • Homograph/spoofing is out of scope. The library validates syntax and mapping; it does not detect lookalike domains (аpple.com with a Cyrillic а). If you deal with user-supplied brand domains, add a confusables check separately.
  • parse_url() first. This library is for domain names, not URLs. Feeding it a full URL, an email, or a host with a port will fail or, worse, silently validate a wrong substring.
  • Deployment data files. The Punycode/IDNA mapping tables ship as resources inside vendor/. Confirm your deploy pipeline (rsync excludes, Docker COPY filters, --no-dev with custom pruning) does not strip non-PHP files from this package.
  • Release cadence. It is a small, low-traffic package. Pin a version in composer.json, review the changelog on upgrade, and wrap it behind your own normalizer so swapping to symfony/polyfill-intl-idn or true/punycode later is a one-class change.
  • Test fixtures. Use known vectors in tests (täst.de → xn--tst-qla.de, 例え.テスト → xn--r8jz45g.xn--zckzah) to catch accidental behavior changes after upgrades.
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