worksome/company-info
Laravel package for looking up company details by VAT number. It offers a unified API across multiple data providers like VIES and Virk, returning consistent DTOs for names, addresses, and registration info so you can easily swap or combine sources.
worksome/company-info is a thin Laravel wrapper around public company registries (VIES for EU VAT numbers, national registries such as Denmark's CVR, etc.). You give it a country + registration/VAT number, it gives you back a normalized company record. It is not a CRM or an enrichment service — it does one lookup, synchronously, per call.
First steps:
composer require worksome/company-info — on Laravel 13 the floor is now ^1.5 (v1.5.0 is purely an L13 compatibility bump over 1.4.0; no API changes).php artisan vendor:publish --provider="Worksome\CompanyInfo\CompanyInfoServiceProvider" (or the company-info tag), then open config/company-info.php. This is the file that matters — it defines which drivers are available and which credentials/headers each one needs. Some registries (e.g. CVR) require a descriptive User-Agent; VIES needs nothing but is rate-limited and flaky..env and .env.example. Because config is cached in production, never call env() outside the config file.php artisan tinker:use Worksome\CompanyInfo\Facades\CompanyInfo;
$company = CompanyInfo::lookup('DK', '12345678');
$company->name; // "Worksome ApS"
$company->address; // street, postcode, city
Best first use case: a "enter your CVR/VAT number" field on a billing/onboarding form that autofills company name and address. Start with a single driver and a single country; add breadth later. Do the actual lookup in a queued job or an axios call to your own endpoint — never inline in a page render, because registry APIs regularly take 2–10 seconds.
Wrap the facade in your own action. The facade is convenient but leaks a third-party API surface across your codebase. Put a single LookupCompany action behind an interface, inject it, and have it return your own DTO (or the package DTO if you're happy to couple). Everything else — caching, retries, fallbacks, logging — lives in one place.
Normalize before you call. Registries are unforgiving about formatting. Strip spaces/dashes, uppercase, and split the country prefix off a VAT number (DK12345678 → country DK, number 12345678). Validate the shape locally first (DK CVR = 8 digits, etc.) so you don't burn a network call on input you can reject yourself.
Cache aggressively. Company registration data changes on the order of years, not minutes. Cache::remember("company-info:{$country}:{$number}", now()->addDays(7), fn () => ...) cuts the vast majority of calls and removes the registry as a runtime dependency for repeat visitors. Cache the successful result only, and keep the failure cache short (a few minutes) if you cache failures at all.
Fallbacks, not a single point of failure. National registries are faster and return richer data; VIES is EU-wide but slow and frequently degraded. Try the national driver first, fall back to VIES when it's configured for that country, and record which source answered. The package's driver abstraction makes this a loop over config, not a rewrite.
Queue the lookup. A LookupCompanyInformation job that fills in the company fields on your Customer/BillingDetails model, with a status column (pending/verified/unverified) so the UI can show progress. This is also what makes the feature testable and retryable.
Persist raw alongside normalized. Store the mapped fields (name, address, vat_number, verified_at) and the raw payload in a JSON column. When a registry changes its response shape, or when finance asks "what did we see in March", you'll have the answer.
Front-end integration. A tiny POST /companies/lookup controller returning { name, address, city, postal_code }, called from Alpine/Livewire/Inertia on blur of the VAT field, with debounce. Keep the controller dumb; all logic lives in the action.
Testing. Bind a fake implementation of your interface in the container, or use the package's faking mechanism / Http::fake() for the underlying client. CI should never touch a real registry — tests become slow and non-deterministic, and some drivers rate-limit by IP.
CompanyNotFoundException, RegistryUnavailableException) at the action boundary so callers don't import vendor exception classes.User-Agent and credentials. Some registries reject requests with a generic or missing User-Agent, and the error surfaces as a confusing 4xx. Set a descriptive one (app name + contact URL) in the config.dk or Denmark will fail in unhelpful ways.tinker and not in production, it's almost always php artisan config:cache having captured stale or missing env values.RateLimiter / Bus::batch with a delay) rather than firing hundreds of lookups.^1.5 if you're on Laravel 13. Nothing in 1.5.0 changes behaviour for existing 1.4.x users, so an upgrade during an L12 → L13 migration is low-risk — but that's an infrastructure release, not a reason to adopt the package.How can I help you explore Laravel packages today?