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

Companies House Laravel Laravel Package

juststeveking/companies-house-laravel

A Laravel client for the UK Companies House API, giving you a clean, fluent interface to look up company profiles, officers, filing history, and related records. Configuration is handled through a simple config file, with support for API authentication and typical Laravel conventions.

View on GitHub
Deep Wiki
Context7

Getting Started

Read composer.json before you run composer require. The last tagged release is September 2021, so the framework and PHP constraints almost certainly cap at Laravel 8/9 and PHP 7.4/8.0. If your app is on a current Laravel line, decide up front whether you are forking, vendoring, or reimplementing against Http:: — do not paper over it with --ignore-platform-reqs, because you will be the only person who ever notices it break.

composer require juststeveking/companies-house-laravel
php artisan vendor:publish --provider="JustSteveKing\CompaniesHouse\CompaniesHouseServiceProvider"

Publish tags are inconsistent across small packages; if --tag= doesn't resolve, publish by provider (as above) and confirm the config file lands in config/companies-house.php. Set the key in .env — register a free application at the Companies House developer hub to get it — then php artisan config:cache and verify the key still resolves (config-cached apps that read env() directly outside config files are the classic first-hour bug here).

Get the API key, then spend twenty minutes reading src/. The whole package is a thin layer over Laravel's HTTP client: a facade, a config file, an options/request builder, and a handful of endpoint methods. You need to know the exact method names, whether responses are arrays or DTOs, and how errors surface. Don't guess — grep:

grep -rn "class .*Facade" vendor/juststeveking/companies-house-laravel/src
grep -rn "function " vendor/juststeveking/companies-house-laravel/src | head -50

First use case: validate-and-enrich at B2B signup. A user types a company number, you confirm the entity is real and active, and you pre-fill the registered name and address. That single flow exercises search, profile, and registered-address endpoints and is the highest-leverage thing this package enables. Timebox it to half a day and record exact response shapes and status codes — the release notes are one line, so every behaviour claim is a hypothesis until you hit it.

Implementation Patterns

Never let the facade escape the adapter. Put it behind your own interface so controllers, Blade, and jobs are insulated from a 2021 dependency you may replace in an afternoon:

interface CompanyRegistry
{
    public function lookup(string $number): CompanyLookup;
}
final class CompaniesHouseRegistry implements CompanyRegistry
{
    public function lookup(string $number): CompanyLookup
    {
        try {
            $company = CompaniesHouse::company($number); // confirm name in src
        } catch (NotFound $e) {          // map to the real exception types
            return CompanyLookup::notFound($number);
        } catch (ConnectionException|RequestException $e) {
            report($e);
            return CompanyLookup::unavailable($number);
        }

        return CompanyLookup::fromApi($number, $company);
    }
}

Model the outcome as a small enum-like result, not a boolean. Verified, NotFound, Dissolved, InLiquidation, Unavailable. "Company not found" is a legitimate product answer — collapsing it into the same branch as "API is down" produces the worst possible UX (a real company rejected because the registry had a blip).

Validate the format offline first. Companies House numbers are eight characters: either eight digits, or a two-letter prefix plus six digits (SC Scotland, NI Northern Ireland, OC/SO LLPs, FC overseas). Reject bad formats with a Laravel rule before spending a request, then only call the API for format-valid numbers. Whether the package's validator is offline or API-dependent is a spike question — if it's API-dependent, don't put it in a form request.

Cache by company number with an hours-scale TTL. Profiles change on the order of days, and your signup funnel will hammer the same handful of numbers:

$lookup = Cache::remember(
    "companies-house:company:{$number}",
    now()->addHours(12),
    fn () => $registry->lookup($number),
);

Key on the normalised (uppercased, trimmed) number, and make the TTL configurable so support can flush it when a customer insists their status changed.

Never block the request path on it. A signup or onboarding action should dispatch a job (->afterCommit()) that does the lookup and writes your own companies snapshot table — company_number, name, status, checked_at. You own persistence; the package owns none, ships no migrations, and models nothing in Eloquent. Keeping your own row is what lets you render "last verified 3 days ago" instead of pretending the data is live.

Test with Http::fake(), because there is almost certainly no fake mode in the package. It's built on the Laravel HTTP client, so fakes at the HTTP layer work — but assert on the URL pattern, not just a blanket Http::fake(), or you'll silently pass when the package calls the wrong endpoint:

Http::fake(['api.company-information.service.gov.uk/*' => Http::response([...], 200)]);
Http::fake(['api.company-information.service.gov.uk/*' => Http::response([], 404)]);

Cover 200, 404, 401, 429, and 500. If the package builds URLs itself, a fake that never matches will fail loudly with Http::preventStrayRequests() — enable that in tests.

Pagination is yours. Search, officers, and filing history accept start_index and items_per_page (capped at 100 upstream). If the wrapper doesn't expose them, drop to the underlying request builder or add a thin method; without it any list view is silently truncated at the first page, which is the kind of bug that ships because nobody notices the missing tail.

Gotchas and Tips

  • Zero dependents and one maintainer means you are the QA. Pin the exact version in composer.json (not ^), commit composer.lock, and read the diff of any patch bump. MIT licensing means forking or copying the two service classes into app/Support is a legitimate, low-drama move — treat replacement as an afternoon, not a migration.
  • 401 vs 404 vs 429 must be distinguishable. A 401 means your key is wrong or not config-cached (a deploy problem). A 404 is a product result. A 429 means you are over the per-window quota (on the order of 600 requests per five minutes per application) and should be backing off, not retrying immediately. Map each to a distinct outcome and alert only on the ones that are your fault.
  • Retries and backoff belong to you. Wrap the call in Http::retry(3, 500, throw: false) in your adapter, or configure the underlying client if the package exposes it. Retrying a 404 is pointless; retrying a 429 without jitter makes the quota problem worse.
  • Set a short timeout. Laravel's HTTP client default is generous; a signup flow should fail fast. Explicitly set a few seconds in your adapter config, and handle ConnectionException as Unavailable rather than letting a raw exception 500 the page.
  • Trailing slash on the base URL. These thin wrappers usually concatenate base_url . '/search/companies'. A published config value ending in / produces //search and a 404 that looks like a data problem. Check the concat code and trim.
  • Responses may be arrays, not typed DTOs. Use data_get() and null-coalesce rather than assuming keys. The resigned_on field added in 2.0.4 is a pass-through from the upstream officer payload — it is not evidence of governance history, and it only appears on officers who have resigned.
  • Officer names are personal data under UK GDPR, and Companies House imposes terms on caching, re-display, and attribution. Get a legal read on retention and UI copy before shipping officer lists to customers, and put an attribution/"data sourced from Companies House" line in the UI. A package with no security policy won't survive procurement; you need your own position regardless of library.
  • Grep-test the boundary. Add a CI check that JustSteveKing\CompaniesHouse appears only in app/Support/CompaniesHouse/. The whole value of the adapter is that removing it later is a one-directory delete plus a container rebind.
  • Debugging tip: during the spike, log the raw response body and status in your adapter at debug level. You will otherwise be reasoning about an undocumented API through an undocumented wrapper at the same time.
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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log