Search by

beziworld / laravel-polish-validation

kamcw

Laravel validation rules for Polish identifiers: PESEL, NIP, REGON, KRS, IBAN/NRB, ID card, postal code and phone numbers.

Package info

github.com/Beziworld/laravel-polish-validation

Homepage

Issues

pkg:composer/beziworld/laravel-polish-validation

Statistics

Installs: 1

Dependents: 0

Suggesters: 1

Stars: 0

v1.0.0 2026-11-04 13:34 UTC

This package is auto-updated.

Last update: 2026-11-04 16:36:40 UTC


README

tests Latest Version License

Laravel validation rules and helpers for Polish identifiers: PESEL, NIP, REGON, KRS, IBAN/NRB, identity card, postal code and phone number. Checksums are verified, not only the format.

Polska wersja dokumentacji

Installation

composer require beziworld/laravel-polish-validation
php artisan vendor:publish --tag=polish-validation-translations   # optional, to customise messages

Messages are included in English and Polish and follow the application locale.

String rules

$request->validate([
    'pesel'       => ['required', 'pesel'],
    'nip'         => ['required', 'nip'],            // accepts 123-456-32-18, 1234563218, PL1234563218
    'regon'       => ['nullable', 'regon'],          // 9 or 14 digits
    'krs'         => ['nullable', 'krs'],
    'iban'        => ['required', 'iban'],           // any IBAN or a 26-digit Polish NRB
    'id_card'     => ['required', 'pl_id_card'],     // e.g. ABA300000
    'postal_code' => ['required', 'pl_postal_code'], // 00-950
    'phone'       => ['required', 'pl_phone'],       // 500100200, +48 500 100 200, 0048500100200
]);

Rule objects with options

use Beziworld\PolishValidation\Enums\Gender;
use Beziworld\PolishValidation\Rules\Iban;
use Beziworld\PolishValidation\Rules\Nip;
use Beziworld\PolishValidation\Rules\Pesel;
use Beziworld\PolishValidation\Rules\PostalCode;

$request->validate([
    'pesel'   => ['required', (new Pesel())->adult()],                 // at least 18 years old today
    'mother'  => ['required', (new Pesel())->gender(Gender::Female)],
    'nip'     => ['required', (new Nip())->withoutPrefix()],           // reject "PL1234563218"
    'account' => ['required', (new Iban())->countries('PL', 'DE')->withoutNrb()],
    'zip'     => ['required', (new PostalCode())->lenient()],          // also accept "00950"
]);

Helpers outside validation

use Beziworld\PolishValidation\Validators\Iban;
use Beziworld\PolishValidation\Validators\Nip;
use Beziworld\PolishValidation\Validators\Pesel;
use Beziworld\PolishValidation\Validators\PhoneNumber;
use Beziworld\PolishValidation\Validators\PostalCode;

$details = Pesel::parse('44051401359');   // throws InvalidIdentifier when invalid; Pesel::details() returns null
$details->birthDate;                       // DateTimeImmutable 1944-05-14
$details->gender;                          // Gender::Male
$details->age();                           // 82
$details->isAdult();                       // true

Nip::normalize('PL 123-456-32-18');        // "1234563218"
Nip::format('1234563218');                 // "123-456-32-18"
Iban::format('61109010140000071219812874');// "PL61 1090 1014 0000 0712 1981 2874"
PhoneNumber::toE164('500 100 200');        // "+48500100200"
PostalCode::normalize('00950');            // "00-950"

Usage examples

Registration form with automatic data from PESEL

class RegisterPatientRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'pesel' => ['required', 'pesel', 'unique:patients,pesel'],
            'phone' => ['required', 'pl_phone'],
            'postal_code' => ['required', 'pl_postal_code'],
        ];
    }

    protected function passedValidation(): void
    {
        $details = Pesel::parse($this->input('pesel'));

        $this->merge([
            'birth_date' => $details->birthDate->format('Y-m-d'),
            'gender' => $details->gender->value,
            'phone' => PhoneNumber::toE164($this->input('phone')),
        ]);
    }
}

B2B checkout

$request->validate([
    'company.nip' => ['required_if:invoice,1', 'nip'],
    'company.regon' => ['nullable', 'regon'],
    'refund_account' => ['nullable', (new Iban())->countries('PL')],
]);

$order->company_nip = Nip::normalize($request->input('company.nip'));

Combine it with beziworld/laravel-company-data to fetch the company name and address from GUS by NIP and to check the account on the VAT white list.

Algorithms

Identifier Check
PESEL 11 digits, weights 1-3-7-9, valid date of birth (1800-2299)
NIP 10 digits, weights 6-5-7-2-3-4-5-6-7, modulo 11
REGON 9 digits (weights 8-9-2-3-4-5-6-7) or 14 digits (parent REGON + weights 2-4-8-5-0-9-7-3-6-1-2-4-8)
KRS 10 digits (no check digit exists)
IBAN country length table + ISO 7064 MOD 97-10
Identity card 3 letters + 6 digits, weights 7-3-1-9-7-3-1-7-3

Development

composer install
composer ci   # pint --test, phpstan (level 8), pest

Author

Created and maintained by Kamil Ćwiertnia, beziworld.eu.

Need a custom Laravel project or an integration with Polish services? Get in touch through beziworld.eu.

Other packages by the author

License

MIT. See LICENSE.md.