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

Iban Bundle Laravel Package

makinacorpus/iban-bundle

Symfony bundle that integrates International Bank Account Number (IBAN) validation and formatting into your application, helping you check user-supplied bank details in forms and reuse IBAN handling across services and templates.

View on GitHub
Deep Wiki
Context7

Getting Started

Start here: the mismatch. makinacorpus/iban-bundle is a Symfony bundle — it registers a Form\Type and a Validator\Constraints pair through Symfony's DI container and bundle extension. There is no composer require path that gets you working behaviour inside a Laravel request lifecycle. The realistic first step is to treat the repository as a reference implementation, not a dependency:

git clone https://github.com/makinacorpus/iban-bundle /tmp/iban-bundle

Read, in this order:

  1. src/Validator/Constraints/ — the constraint + validator class. This is the only part that contains domain logic; everything else is Symfony plumbing.
  2. src/Form/Type/ — the IbanType form type. This is the part you will not port; Laravel has Blade/Inertia, not Symfony Forms.
  3. composer.json — note the symfony/form / symfony/validator constraints and the PHP floor. This is your compatibility gate and, almost certainly, your reason not to vendor it.
  4. Whatever it delegates to (often a makinacorpus/iban-style library) — that delegate is where the actual mod-97 and country-length table live.

Your first use case — an IBAN field on a payout/vendor-onboarding form. The Laravel-day-to-day version of this is three small pieces:

// app/Rules/Iban.php
namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class Iban implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $normalized = strtoupper(preg_replace('/\s+/', '', (string) $value));

        if (! preg_match('/^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/', $normalized)) {
            $fail('The :attribute is not a well-formed IBAN.');
            return;
        }

        if (! app(\App\Support\IbanValidator::class)->checksumOk($normalized)) {
            $fail('The :attribute has an invalid checksum.');
        }
    }
}

Then in the form request:

public function rules(): array
{
    return [
        'iban' => ['required', 'string', 'max:34', new \App\Rules\Iban],
    ];
}

protected function prepareForValidation(): void
{
    $this->merge(['iban' => strtoupper(preg_replace('/\s+/', '', (string) $this->iban))]);
}

If you'd rather not write the country table, back IbanValidator with a maintained standalone library (php-iban is the usual choice) and keep the rule thin — that is the "buy the validation, build the integration" split.

Implementation Patterns

Constraint-style reuse, the Laravel way. The Symfony bundle's value is that its constraint fires for forms, DTOs, and API payloads alike. In Laravel the equivalent is a Rule object used everywhere you validate — form requests, Validator::make in a job, or a Livewire/Inertia endpoint. Resist the urge to inline a regex in each controller; one rule class is the whole point.

Normalize at the boundary, validate the normalized value. IBANs arrive as fr76 3000 6000 0112 3456 7890 189, FR76-3000-..., or with a non-breaking space. Normalize (strip non-alphanumerics, uppercase) in prepareForValidation or in a value-object cast, then validate that string, then persist that string. Never persist the raw input — it breaks unique indexes, lookups, and later BIC derivation.

Value object + cast for anything that leaves the request layer.

final class Iban
{
    public function __construct(public readonly string $value)
    {
        if (! (new \App\Support\IbanValidator)->isValid($value)) {
            throw new \InvalidArgumentException("Invalid IBAN: {$value}");
        }
    }
    public function masked(): string { return substr($this->value, 0, 4).str_repeat('*', 12).substr($this->value, -4); }
    public function __toString(): string { return $this->value; }
}

Wire it with an Eloquent cast so $vendor->iban is always a validated, normalized object. This is the pattern that pays off daily: masking for display, equality checks for duplicate-account detection, and one choke point for future BIC derivation.

Where else this fires. Seller payouts, supplier master data, expense reimbursement, SEPA direct-debit mandates, invoicing "bank details on file." All of them are the same rule + the same normalizer. Build it once, then a Blade component:

<x-input name="iban" :value="old('iban')" placeholder="FR76 3000 6000 0112 3456 7890 189" />

Testing is where the pattern earns trust. Table-driven tests over known-valid and known-invalid per-country values (the IBAN registry and the standard test IBANs are your fixtures). Assert both "rejected" and "accepted" outcomes — a validator that accepts nothing passes half your tests.

Gotchas and Tips

Don't try to install it "just to see." The bundle pins Symfony component versions that will fight Laravel's internally-pinned Symfony minors. You'll get --ignore-platform-reqs territory, or a container that half-boots. Read the source instead; the domain logic is a few dozen lines.

Format vs checksum are not the same check. Regex-only validation accepts FR7630006000011234567890189 and its transposed cousins. You want mod-97 (and per-country length where you have it). Most production incidents here are "the form accepted it, the payout bounced."

Stale country tables are the silent failure mode. Whether you copy the bundle's table or import one, it drifts as countries amend formats. Isolate the table behind an interface and pin it with a test fixture so an upgrade to your IBAN library shows up as a failing test, not a support ticket.

Spaces are not always spaces. preg_replace('/\s+/', '', ...) misses non-breaking spaces (\x{00A0}) and narrow no-break spaces; use preg_replace('/[^A-Za-z0-9]/', '', ...) instead. Also uppercase before validation, not after — lowercase input will fail the [A-Z]{2} country check otherwise.

Don't lowercase-check the checksum. Case folding can affect the alphabetic portion of the BBAN. Normalize to uppercase once, then everything downstream is uppercase.

Client-side hints are a UX nicety, server-side rule is the contract. You can group digits in the input as the user types, but never trust it. Also: don't use Livewire/Inertia client validation as the only gate on a payments path.

Store normalized, display masked. varchar(34) normalized column, iban_hash (hash('sha256', $iban)) with a unique index if you need duplicate detection, and a masked() display for anything user-facing — support staff should see FR76**********0189, not the full account.

Extension points, if you ever do vendor a slice. Take only the validator file (with attribution and a pinned commit) and keep the country table in a separate, data-driven class. Everything Symfony-specific — form type, DI extension, Twig theme — has no Laravel counterpart and should be left behind.

No built-in iban rule in Laravel. There is no Validator::extend-free option; don't paper over it with an inline regex in three controllers, because the fourth one will drift. One rule class, one normalizer, one value object — and the "Symfony bundle we can't use" is permanently off your supply chain.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource