thiagocfn/inscricaoestadual
Validate, format, and mask Brazilian state registration numbers (Inscrição Estadual) for all 27 states. Provides per-state validation rules, formatting helpers, and Laravel validation integration, making it easy to verify IE numbers and detect their issuing state in forms and APIs.
The package is a plain PHP library (thiagocfn/inscricaoestadual) with no Laravel-specific service provider, facade, or config file. Installation is a single Composer require:
composer require thiagocfn/inscricaoestadual
The entry point is the Thiagocfn\InscricaoEstadual\InscricaoEstadual class. Everything you need lives behind a single validation call that takes the registration number and the state (UF) it belongs to:
use Thiagocfn\InscricaoEstadual\InscricaoEstadual;
$validator = new InscricaoEstadual();
$validator->validate('110042490114', 'SP'); // true/false
Look first at the package's per-state classes (one validator per UF is what gives the library its coverage — 27 states). The README is minimal; the real contract is "pass raw digits + UF, get a boolean."
Your first real use case in a Laravel app is almost always form validation: a customer registration or checkout form where the user types an IE alongside a UF. Wire the validator into a FormRequest or custom rule (see below) and you have the whole day-one workflow.
Wrap the call in a Laravel rule so it composes with $request->validate() and Validator::extend:
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Thiagocfn\InscricaoEstadual\InscricaoEstadual;
class InscricaoEstadualRule implements Rule
{
public function __construct(private string $uf) {}
public function passes($attribute, $value): bool
{
$digits = preg_replace('/\D+/', '', (string) $value);
if ($digits === '') {
return true; // let `required` handle emptiness
}
return (new InscricaoEstadual())->validate($digits, $this->uf);
}
public function message(): string
{
return 'A inscrição estadual informada não é válida para o estado selecionado.';
}
}
The IE validator depends on the UF, so pull it from the same request and pass it into the rule. Validate order matters: ensure the UF is one of the 27 accepted values before validating the IE.
public function rules(): array
{
return [
'uf' => ['required', Rule::in(['AC','AL','AP','AM','BA','CE','DF','ES','GO','MA','MT','MS','MG','PA','PB','PR','PE','PI','RJ','RN','RS','RO','RR','SC','SP','SE','TO'])],
'inscricao_estadual' => ['nullable', new InscricaoEstadualRule($this->input('uf'))],
];
}
Many Brazilian e-commerce payloads carry the two-digit IBGE state code. Maintain a tiny map (35 => 'SP', 33 => 'RJ', 31 => 'MG', ...) and normalize before calling the validator. This keeps your API contract numeric while the library stays UF-based.
For persistence, strip masks in a mutator and validate at the boundary (rules), not the model:
protected function inscricaoEstadual(): Attribute
{
return Attribute::make(
set: fn ($v) => $v === null ? null : preg_replace('/\D+/', '', $v),
get: fn ($v) => $v, // format with a presenter if UI needs the mask
);
}
Keep storage as raw digits; apply the state-specific mask only when serializing (SP 000.000.000.000, RJ 00.000.00-0, MG 000.000.000/0000, etc.). This avoids re-validating masked input later and keeps DB indexing clean.
On CSV import of suppliers/customers, run the validator inside a foreach with per-row error capture. The validator is stateless and cheap, so you can validate tens of thousands of rows without a service container or caching layer. Collect rows that fail and return them for manual review rather than aborting the import.
preg_replace('/\D+/', '', $value) before calling validate(). Leading zeros matter (e.g., SP and SE), so never cast to int.'SP', not 'sp'. Normalize with strtoupper() on input; the per-state lookup is typically exact-match, so lowercase will fail or throw.App\Rules namespace and inject the validator, or just new InscricaoEstadual() where needed. Do not search for a config file — there isn't one.validate() returns true/false (or null/false for empty input in some builds). All user-facing messaging must be built in your rule. This is by design, not a limitation to fight.composer update."thiagocfn/inscricaoestadual": "^1.0") and wrap it behind your own rule class so a fork or replacement (e.g., laravellegends/pt-br-validator or a custom implementation) is a one-file swap if the package goes stale.[uf, validSample, invalidSample] triples and assert both directions. This catches the most common bug: a validator that accepts too much (e.g., passing all-same-digit strings for states where the checksum should reject them).How can I help you explore Laravel packages today?