ibanchecker.cash
Developer ResourcesJune 3, 2026 · 8 min read

IBAN Validation in PHP: Vanilla Implementation, php-iban Package, and Laravel Integration

Three production-ready PHP approaches: a zero-dependency MOD-97 function, the globalcitizen/php-iban Composer package, and REST API integration with a Laravel validator rule.

Share

IBAN validation in PHP can be done in three ways: a vanilla PHP function using the MOD-97 algorithm, the globalcitizen/php-iban Composer package for full structural validation, or the ibanchecker.cash REST API when you also need bank name and BIC data. This guide covers all three with production-ready code and a Laravel validator integration.

Option 1 — Vanilla PHP: MOD-97 Implementation

If you cannot install Composer packages or want zero dependencies, implement MOD-97 directly. The algorithm is four steps: strip spaces, rearrange, convert letters to numbers, compute mod 97.

<?php

function validateIban(string $iban): bool
{
    $iban = strtoupper(str_replace(' ', '', $iban));

    if (strlen($iban) < 15 || strlen($iban) > 34) {
        return false;
    }

    $rearranged = substr($iban, 4) . substr($iban, 0, 4);

    $numeric = '';
    foreach (str_split($rearranged) as $char) {
        $numeric .= ctype_alpha($char)
            ? (string)(ord($char) - 55)
            : $char;
    }

    // Process in chunks to avoid float precision loss
    $remainder = 0;
    foreach (str_split($numeric) as $digit) {
        $remainder = ($remainder * 10 + (int)$digit) % 97;
    }

    return $remainder === 1;
}

var_dump(validateIban('GB29 NWBK 6016 1331 9268 19')); // bool(true)
var_dump(validateIban('GB29 NWBK 6016 1331 9268 20')); // bool(false)
var_dump(validateIban('DE89 3704 0044 0532 0130 00')); // bool(true)

The chunk loop keeps the running remainder as a small integer on every iteration, avoiding the floating-point precision loss that would occur if you tried to compute intval($numeric) % 97 directly on a 30+ digit string.

Option 2 — php-iban Composer Package

The globalcitizen/php-iban package adds country-specific length validation and BBAN format checks on top of MOD-97. Install via Composer:

composer require globalcitizen/php-iban
<?php

require 'vendor/autoload.php';

$iban = 'GB29NWBK60161331926819';

if (verify_iban($iban)) {
    echo "Valid\n";
    echo "Country: " . iban_get_info($iban, 'country') . "\n";
    echo "BBAN: "    . iban_get_info($iban, 'bban')    . "\n";
} else {
    echo "Invalid: " . iban_validation_failure($iban) . "\n";
}

iban_validation_failure() returns a human-readable string explaining exactly why the IBAN failed — wrong length, bad check digits, unknown country code — useful for surfacing specific errors to users rather than a generic "invalid" message.

Option 3 — ibanchecker.cash REST API (with Bank Data)

Neither vanilla PHP nor php-iban can tell you the bank name or BIC code. For that, call the ibanchecker.cash API. It returns validity, bank name, BIC, country, and SEPA status in a single JSON response.

<?php

function checkIbanApi(string $iban, string $apiKey = ''): array
{
    $ch = curl_init('https://ibanchecker.cash/api/v1/validate');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            $apiKey ? "Authorization: Bearer {$apiKey}" : '',
        ],
        CURLOPT_POSTFIELDS => json_encode(['iban' => $iban]),
    ]);
    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode($response, true) ?? [];
}

$result = checkIbanApi('DE89 3704 0044 0532 0130 00');

if ($result['valid'] ?? false) {
    echo "Bank: "    . $result['bankName'] . "\n";
    echo "BIC: "     . $result['bic']      . "\n";
    echo "SEPA: "    . ($result['sepa'] ? 'yes' : 'no') . "\n";
} else {
    echo "Invalid IBAN\n";
}

Get a free API key from the API documentation page. Free tier keys do not require a credit card and cover up to 1,000 requests per month.

Laravel Validator Integration

To validate IBANs inside Laravel form validation, register a custom rule in a service provider or directly in a controller. The cleanest approach is an invokable rule class:

<?php
// app/Rules/ValidIban.php

namespace App\Rules;

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

class ValidIban implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (!$this->checkMod97($value)) {
            $fail("The :attribute is not a valid IBAN.");
        }
    }

    private function checkMod97(string $iban): bool
    {
        $iban = strtoupper(str_replace(' ', '', $iban));
        if (strlen($iban) < 15 || strlen($iban) > 34) return false;
        $rearranged = substr($iban, 4) . substr($iban, 0, 4);
        $numeric = '';
        foreach (str_split($rearranged) as $char) {
            $numeric .= ctype_alpha($char) ? (string)(ord($char) - 55) : $char;
        }
        $remainder = 0;
        foreach (str_split($numeric) as $digit) {
            $remainder = ($remainder * 10 + (int)$digit) % 97;
        }
        return $remainder === 1;
    }
}


// In a controller or Form Request:
$request->validate([
    'iban' => ['required', 'string', new \App\Rules\ValidIban],
]);

For a Laravel project that also needs bank name lookup, replace the internalcheckMod97 call with an HTTP request to the ibanchecker.cash API and cache the result in Redis or the Laravel cache store keyed by the normalised IBAN.

Error Handling and Edge Cases

When validating IBANs in production PHP code, account for these inputs:

  • Spaces and dashes: strip both before validation — users copy IBANs with mixed separators from PDF statements.
  • Lowercase input: always uppercase before checking — some banking software outputs lowercase country codes.
  • Non-ASCII characters: reject inputs containing characters outside A–Z and 0–9 after normalisation; they will break the numeric conversion step.
  • NULL and empty string: guard with a null check before passing to the validator — PHP's strlen(null) emits a deprecation notice in PHP 8.1+.
  • Country-specific letter IBANs: French account numbers (numéro de compte) may contain letters — your regex pre-filter must allow alphanumeric BBANs, not just numeric ones.

Testing Your PHP IBAN Validator

<?php
// PHPUnit test

use PHPUnit\Framework\TestCase;

class IbanValidatorTest extends TestCase
{
    public function validIbans(): array
    {
        return [
            ['GB29 NWBK 6016 1331 9268 19'],
            ['DE89 3704 0044 0532 0130 00'],
            ['FR76 1450 8059 9521 1642 5957 022'],
        ];
    }

    public function invalidIbans(): array
    {
        return [
            ['GB29 NWBK 6016 1331 9268 20'], // wrong check digit
            ['DE89 3704 0044 0532 0130'],     // too short
            ['XX89 0000 0000 0000 0000 00'],  // unknown country
        ];
    }

    /** @dataProvider validIbans */
    public function testValidIbans(string $iban): void
    {
        $this->assertTrue(validateIban($iban));
    }

    /** @dataProvider invalidIbans */
    public function testInvalidIbans(string $iban): void
    {
        $this->assertFalse(validateIban($iban));
    }
}

For more test vectors, the IBAN regex patterns guide lists valid and invalid examples for DE, GB, FR, and NL that you can use as parametrized test inputs.

Last updated: June 2026

Validate an IBAN instantly

Free IBAN checker — MOD-97 verification, bank lookup, and SEPA status across 84 countries.

Open IBAN Checker →

Related Articles