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

Nova Vat Validation Laravel Package

napp/nova-vat-validation

A Laravel Nova field for validating VAT numbers, letting you check EU VAT IDs directly in Nova forms and resources. Helps keep company and customer records accurate by flagging invalid or malformed VAT numbers with clear validation feedback.

View on GitHub
Deep Wiki
Context7

Getting Started

Install into an existing Nova app (Nova 4 or 5 — check composer.json constraints first, this package is old and pinned to a specific Nova major):

composer require napp/nova-vat-field

There is no service provider to register manually in a standard Nova install (auto-discovery). If the package ships a config file, publish it and read every key before touching it:

php artisan vendor:publish --provider="Napp\NovaVatField\FieldServiceProvider"

Where to look first, in this order:

  1. README.md — for a 5-star package this is usually the entire documentation surface.
  2. src/ — the field class (likely Vat.php) and any Rules/ directory. This tells you whether validation happens client-side only, via a Nova rule, or against VIES.
  3. composer.json — the require block is the single most important file here. It will show whether it depends on ibericode/vat, dragon-code/vat-calculator, or hand-rolled SOAP against VIES, and which laravel/nova version it permits.
  4. dist/js/ — whether the compiled JS ships with the repo (almost certainly yes, which means you may not be able to rebuild it for Nova 5's Vite toolchain).

First use case: a B2B resource where EU customers enter a VAT number at checkout/signup, and you want the field to (a) normalize the input, (b) check it against VIES, and (c) surface the registered company name back to the admin.

use Napp\NovaVatField\Vat;

public function fields(Request $request)
{
    return [
        ID::make()->sortable(),
        Text::make('Company'),
        Vat::make('Vat Number'),
    ];
}

Load a single edit form, type a known-good number (e.g. a real Belgian BE0...), and watch the network tab. That one test tells you more than the README: you'll see whether it hits VIES directly from the browser (bad — CORS and leaking numbers), proxies through a Nova route, or validates only on submit.

Implementation Patterns

Server-side validation is the source of truth, not the field. Whatever the field does in the browser, mirror it in a FormRequest so the same rule applies to API writes, imports and artisan tinker seeds:

public function rules(): array
{
    return [
        'vat_number' => ['nullable', 'string', 'max:32', new VatNumberRule()],
    ];
}

If the package exposes a rule class, use it; if not, drop in dragon-code/vat-calculator and keep going. Do not let the field be the only gate.

Normalize before you store. VIES rejects separators. Strip spaces, dots and dashes and uppercase in a saving hook on the model, not in the Nova field — this keeps imports and seeders consistent:

protected static function booted(): void
{
    static::saving(function ($model) {
        $model->vat_number = strtoupper(preg_replace('/[^A-Za-z0-9]/', '', $model->vat_number ?? ''));
    });
}

Store the validation result, not just the number. A VAT number that validates today may be deregistered tomorrow, and tax authorities want evidence of what you checked and when. Add vat_validated_at, vat_company_name, and vat_check_payload (raw response) columns and write them from the same code path that runs the check. This is the pattern that actually survives an audit.

Pair it with a country field. The field is EU-only by design. Guard the UI so non-EU customers don't hit a validator that will always fail for them:

Vat::make('Vat Number')
    ->rules(['nullable'])
    ->hideFromIndex()
    ->canSee(fn () => in_array($this->country, EU_COUNTRIES)),

Use fillUsing / resolveUsing to control persistence. If the field returns a structured object (number + company + address) rather than a plain string, fillUsing is where you split it into your columns:

Vat::make('Vat Number')
    ->fillUsing(function ($request, $model, $attribute, $requestAttribute) {
        $value = $request->input($requestAttribute);
        $model->vat_number = $value['number'] ?? null;
        $model->vat_company_name = $value['name'] ?? null;
    });

Re-validate periodically, not on every page load. Add a RevalidateVat Nova action and run it on a schedule (Schedule::command(...)->monthly()). Never validate in the index view — you will fire one VIES call per row.

Gotchas and Tips

VIES is unreliable and this will bite you in production. The EU SOAP service times out, returns MS_MAX_CONCURRENT_REQ, and has maintenance windows. Wrap every call in a timeout and cache the result. A hard failure in the field should resolve to "unknown, retry later", never to "invalid" — otherwise you silently reject paying customers.

Never validate from the browser. If the compiled JS in dist/ calls VIES directly, you'll hit CORS, expose customer VAT numbers to a third party from the client, and have no audit trail. Confirm the request path in the network tab before you ship; if it's client-side, add a Nova route or an observer and treat the field as display-only.

Version lock is the real risk. Five stars and zero dependents means no one is filing bugs. Expect this to be pinned to Nova 3 or 4. If you're on Nova 5 (Vite, no nova.mix.js), the prebuilt dist/js/field.js may not load at all, and rebuilding requires forking the repo and porting the build. Budget for that, or fork on day one and keep it in packages/.

composer.json is the compatibility contract. Check the laravel/nova and php constraints. Nova itself is a private Composer repo, so a missing nova credential is a common first failure, not the package's fault.

VIES does not validate individuals or non-registered sole traders. A legitimate freelancer without a VAT registration will get a false negative. Always allow an admin override field/flag alongside it.

Company names from VIES are localized and dirty — Dutch, French and German variants of the same company, odd casing, abbreviations. Show them in the UI as a hint; don't auto-overwrite your company_name column without a human confirming.

Cache keyed on the normalized number, 24h is plenty:

Cache::remember("vies:{$normalized}", now()->addDay(), fn () => $checker->check($normalized));

VIES explicitly asks integrators not to hammer it, and Nova's "save and continue editing" button will otherwise re-hit the API on every keystroke-triggered save.

Extension points, in order of preference: Nova's own ->rules(), ->resolveUsing(), ->fillUsing(), then a model observer. Only if you genuinely need the JS behaviour should you touch dist/js — at which point you own the fork.

Set a hard sunset date. Given the score and dependent count, plan to replace this with a plain Text field plus a saving observer using dragon-code/vat-calculator within a quarter. That fallback is roughly 30 lines and removes the maintenance question entirely; the only thing you lose is the inline UI feedback.

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.
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
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
cgv/localized-resource