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 Validation Laravel Package

jschaedl/iban-validation

A Laravel/PHP package for validating International Bank Account Numbers (IBANs). It provides a validator rule and direct IBAN checks to ensure account numbers are correctly formatted and checksum-valid before storage or payment processing.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require jschaedl/iban-validation

No service provider, no config file, no Laravel coupling — it's a plain PHP library with two things you care about:

  • Iban\Validation\Iban — a value object wrapping the raw string (normalization, country code, BBAN accessors).
  • Iban\Validation\Validator — performs the ISO 13616 checks (format, country code, length, BBAN structure, mod-97 checksum) and exposes the reasons it failed.

Smallest useful call:

use Iban\Validation\Iban;
use Iban\Validation\Validator;

$iban      = new Iban('DE89 3704 0044 0532 0130 00');
$validator = new Validator();

if (! $validator->validate($iban)) {
    $reason = $validator->getViolations(); // e.g. checksum / format / unsupported country
}

Where to look first: the Iban class (accessors you'll use for persistence and display) and the country definition table in src/ — that table is the source of truth for which countries and BBAN formats are actually supported. Read it before you promise a country in your product spec.

First use case: validating a user-entered IBAN during payout-method setup or registration. Normalize (strip whitespace, uppercase), construct Iban, validate, then store the normalized string — never the formatted one.

Implementation Patterns

Wrap it in a Laravel validation rule (the only integration you should write). Do not sprinkle new Validator() across controllers; isolate the dependency so you can swap it later.

namespace App\Rules;

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

class ValidIban implements ValidationRule
{
    public function __construct(private ?array $allowedCountries = null) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $validator = new Validator();

        if (! $validator->validate(new Iban((string) $value))) {
            $fail('The :attribute is not a valid IBAN.');

            return;
        }

        if ($this->allowedCountries !== null
            && ! in_array((new Iban((string) $value))->getCountryCode(), $this->allowedCountries, true)) {
            $fail('The :attribute must be from: '.implode(', ', $this->allowedCountries).'.');
        }
    }
}

Normalize in prepareForValidation(), not in the rule. Users paste DE89 3704 0044 0532 0130 00, de89370400440532013000, or with non-breaking spaces from a PDF. Normalize once, up front, and let the rule deal only with correctness:

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

Store normalized, display formatted. Persist DE89370400440532013000 in a varchar(34) column (34 is the ISO max; go to 42 if you ever need to keep the spaced version alongside). Format on output only:

echo chunk_split($account->iban, 4, ' '); // DE89 3704 0044 0532 0130 00

Add an Eloquent cast if you want the object on your models. A custom cast returning Iban gives you $account->iban->getCountryCode() in views, but be aware it changes equality semantics — comparing $model->iban to a string will no longer work. For most apps, keeping a string cast and wrapping in Iban at the edges is cheaper.

Bulk imports (CSV of payouts, supplier onboarding). Validate row-by-row and collect violations keyed by row number rather than failing the whole file. Instantiate one Validator per row (or verify state reset between calls — see gotchas) and pair each failure with getViolations() so the user gets "row 42: checksum invalid" instead of a generic error.

Two-stage validation is mandatory, not optional. The library tells you the IBAN is structurally valid. It cannot tell you the account exists, is open, or belongs to the name you were given. For payouts, follow with a bank-account verification API and treat that as a separate state on your model (iban_valid, iban_verified_at, verification_reference). Never let a boolean from this package gate a money movement on its own.

Country allowlists belong in config, not in the rule constructor call sites: config('payments.iban.allowed_countries'). Some jurisdictions you support commercially may not have a BBAN definition in the library yet — the allowlist is where you discover that mismatch.

Gotchas and Tips

  • Construction ≠ validation. new Iban('garbage') is a dumb wrapper; it will happily hold a string that fails every check. The only thing that means anything is Validator::validate(). Don't write try { new Iban($x) } catch as your validation strategy.
  • Check your validator's violation state. If you reuse a single Validator instance across a loop, confirm against your pinned version that getViolations() is cleared on each validate() call. If you're unsure, instantiate per call — the object is trivial to create.
  • "Country not supported" is a distinct failure from "invalid IBAN." Surface it differently in your logs; it usually means the library's country table predates a country you enabled in the UI, not that the user typed something wrong. Check vendor/jschaedl/iban-validation/src/ for the current list whenever you add a market.
  • Uppercase before validating. Lowercase input is a classic false negative. Normalize at the boundary and store uppercase so WHERE iban = ? lookups actually match.
  • Don't reach for filter_var(). PHP has no built-in IBAN filter; a lot of copied snippets implement a hand-rolled mod-97 and get edge cases wrong. This library already encodes the per-country lengths and BBAN patterns — use it.
  • Column size and indexing. A varchar(255) IBAN column with no index is a common smell. 34 chars, unique index if it's a payout destination, and normalize-on-write so the uniqueness constraint does what you think it does.
  • Version pinning matters more than usual here. The country/format table changes between releases (last release Dec 2025). Pin a minor and read the changelog on upgrade — a BBAN regex change can flip previously-valid stored data to invalid, which will surface as failed re-validation jobs.
  • Extension point: the country definitions are data, not code. If you need a missing country or a corrected pattern, contribute upstream rather than forking — a local patch to the BBAN table is exactly the kind of thing that silently rots.
  • Testing: build a fixture set per country from the official SWIFT IBAN registry examples, including at least one case where the length is right but the checksum is wrong, and one where the country is unsupported. That's where regressions actually show up.

Assessment: at ~100 stars and a few hundred lines, this is a low-risk, narrow dependency — it does IBAN structure validation and nothing else. Keep it behind your own ValidIban rule so the blast radius of an abandoned or breaking release is one class, and pair it with a real account-verification service before any money moves.

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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log