Skip to content

The register

The register

The default sources read their rates, US rules and address boundaries from data.cboxtax.com, a published register of consumption-tax jurisdictions: eleven regimes covering the EU, the US, the UK and the rest of Europe, Canada, Mexico, Latin America, Asia-Pacific, Africa, the Caribbean, the Gulf and the wider Middle East — 293 countries and states, and some 20 000 local authorities beneath them. The engine currently models 52 countries; see Supported jurisdictions for the calculation coverage, and beta status for what the data does and does not guarantee.

It is not fetched while pricing. tax:data:sync compiles a published release into a local store; the engine reads that store and makes no network call at all.

Install it before you price anything

php artisan tax:data:sync

Put it in your deploy alongside php artisan migrate. Until it has run, the engine refuses rather than guessing, and the refusal names this command.

That refusal is deliberate. No rate data ships inside this package — it is MIT and the register is PolyForm Internal Use, and bundling one inside the other would mislabel it — so a fresh install genuinely has nothing. The only unacceptable behaviour would be a plausible number: an invoice priced from a stale snapshot or a guessed standard rate is wrong in a way nobody notices until a return is filed.

The first sync pulls about 6.5 MB over the wire and writes about 63 MB.

Why a local store rather than an API call

Two reasons, and both are load-bearing.

The register publishes several times a day. Four releases landed on 2026-09-15 alone. A rate fetched per request can move under a half-priced order, so which release is live has to be a decision somebody makes rather than a cache expiring.

The US region cannot be decoded in a PHP process. It is 48.8 MB of JSON, which json_decode turns into 315 MB of PHP arrays — it does not blow a generous memory limit, it blows the default one. So the store is not a copy of the API: records are sharded by what a lookup actually names and indexed by jurisdiction, and a lookup reads one index and one record. Pricing a Danish invoice touches 12 KB.

The commands

tax:data:sync Compile a release, activate it and prune according to retention
tax:data:status --offline Active release, configured pin, pricing version and installed scope
tax:data:activate <version> Change the active release; an explicit pricing pin still wins
tax:data:verify [release] Check file sizes and hashes against the local compiled manifest
tax:data:prune --keep=2 Delete old versions

activate is the rollback, and it costs one rename — no download, no network. That matters because a bad release is discovered exactly when you would rather not be downloading another. activate previous takes the one before the live one.

sync --check compares the pricing version with the configured release, or the latest published release when no pin is configured. It exits non-zero when they differ or the required register is missing. --release=latest explicitly checks against the publisher even when pricing is pinned.

verify defaults to the version used for pricing and performs no network request. A missing file, changed content or invalid manifest makes it fail. It checks local integrity against the manifest created during sync; it does not authenticate the publisher or establish that a rate is legally correct.

Configuration and version pins

Publish config/tax.php with php artisan vendor:publish --tag=tax-config.

Config under tax.register Environment Default
url TAX_REGISTER_URL https://data.cboxtax.com
store TAX_REGISTER_STORE storage/app/cbox-tax/register
version TAX_REGISTER_VERSION Active release; sync selects latest
regions TAX_REGISTER_REGIONS All published regions
states TAX_REGISTER_STATES All US states
streets TAX_REGISTER_STREETS No street indexes
boundaries TAX_REGISTER_BOUNDARIES true
keep TAX_REGISTER_KEEP 2

Lists accept PHP arrays or comma-separated environment values. Explicit CLI values replace the corresponding sync defaults. --no-boundaries disables the boundary download for that run.

Set TAX_REGISTER_VERSION=2026.09.16-236 to pin both pricing and the default sync target. A missing pinned release refuses, even if another release is active. Running sync --release=latest or activate changes the active pointer, but does not override a configured pin. status shows both.

Sync prunes automatically using keep; sync --keep=3 overrides it for one run. Pruning always preserves the active release and the configured pin, so it can retain more than the requested count. activate previous selects the most recent installed release older than the active one.

The dataset holds its selected version for the lifetime of the application instance. Restart long-running queue workers and Octane processes after changing the release or pin, and retain releases that are still used by running workers.

Schema compatibility is checked during sync and again before reading an installed store. This reader accepts reviewed schema versions through 1.34.x; a newer minor or major requires a package update. Unknown fields on consumed rules also refuse, so a new condition cannot be dropped while retaining the old boolean or rate decision. Do not strip conditions or edit the schema version to bypass this check. The draft consumer contract describes the proposed extension and the publication barrier needed by older readers.

Take less than everything

'register' => [
    'regions' => ['eu', 'us'],    // only the data regions you need
    'states' => ['TX', 'CA'],     // only the US states you sell into
    'boundaries' => true,
],

Narrowing is safe because a jurisdiction outside what you compiled refuses instead of answering from an absence. A store built --region=eu does not quietly report "no tax in Japan".

The weight is almost entirely American: all ten non-US regimes together come to 4 MB, the US is 38 MB of rates, and Washington alone is 13.4 MB of that. A shop selling only into Texas wants --state=TX.

Street indexes are asked for by name. They are 228 MB across fifteen states against 20 MB for every ZIP index in the register, and they buy one rung of the resolution ladder — a house number instead of a ZIP+4. --streets=KS,WA fetches them for those states; without one, an address there resolves at ZIP+4, which is a visible confidence grade rather than a silent loss.

Rolling back

php artisan tax:data:status
php artisan tax:data:activate 2026.09.15-199

A version stays on disk until pruned, so the previous one is normally right there. status --offline reads only local files. Without that option, status also attempts to report the latest published release; an unreachable publisher does not hide the local installation.

Licence

This package is MIT. The register it compiles is not.

The register is published under the PolyForm Internal Use Licence 1.0.0.

You may use it inside your own organisation for anything, including commercially. Pricing your own sales is exactly the intended use. You may keep copies, read every byte, and check any figure against your own reading of the same statute.

You may not pass the data on: redistribute it, bundle it into something you ship, or offer your customers a rate lookup, an API or a product feature that gives them the rates themselves.

The line is charging your customers tax you computed with it (fine) against telling your customers what the rates are (not covered). The second needs a licence from Cbox — something you buy when you cross the line, not a gate on getting started. There is no key, no signup, and nothing about the register you cannot evaluate before deciding to depend on it.

When the register moves to a schema this package cannot read

tax:data:sync asked for latest installs the newest release this package can read and warns that a newer one exists on a schema it has not been reviewed against — so corrections published on the old schema keep arriving until you upgrade. tax:data:sync --check compares against the same release. A release asked for by name (--release=, or pinned with TAX_REGISTER_VERSION) still refuses: you asked for that one. Nothing is downloaded or replaced for a release that is refused.