DEV Community

Eric Osterberg
Eric Osterberg

Posted on

Replace TaxJar with self-hosted OpenSalesTax in WooCommerce — free, ~30 min setup

I just open-sourced a WooCommerce plugin that replaces TaxJar / Avalara / WooCommerce Tax (Jetpack) with a free, self-hostable US sales-tax engine. Here's the pitch and how to install it.

The problem

If you run a US-shipping WooCommerce store, your options for accurate destination-based sales tax are roughly:

Service Pricing
WooCommerce Tax (Jetpack) Free up to 200 transactions/month, paid above
TaxJar From $19/mo + transaction fees
Avalara AvaTax Enterprise pricing (usually starts in the thousands/yr)
Stripe Tax 0.5% per transaction
Manual rate tables Free but you maintain ~50 jurisdictions' worth of rules

Above ~200 transactions/month or ~$50K/yr in revenue, the math gets uncomfortable fast. At $1M ARR, Stripe Tax alone costs ~$5K/yr — and that's just the Stripe portion.

The fix

Self-host the math. OpenSalesTax is an open-source US sales-tax calculation engine — Docker container, REST API, ~52 jurisdictions, real per-state taxability rules. OpenSalesTax for WooCommerce is the plugin that wires it into WC's checkout flow.

Both are Apache 2.0:

Setup (~30 min)

# 1. Stand up the engine (5 min)
git clone https://github.com/ejosterberg/open-sales-tax
cd open-sales-tax
docker compose up -d
curl http://localhost:8080/v1/health
# → {"status":"ok","version":"0.39.0","database_connected":true}

# 2. Install the plugin
composer require ejosterberg/opensalestax-woocommerce
# (or git clone into wp-content/plugins/ and run composer install --no-dev)

# 3. Activate + configure
wp plugin activate opensalestax-woocommerce
wp option update opensalestax_base_url "http://your-engine:8080"
wp opensalestax test-connection
# → Success: Engine v0.39.0 is ok — DB up
Enter fullscreen mode Exit fullscreen mode

That's it. The next cart that ships to a US address gets destination-based sales tax computed via the engine.

Verifying it works

Drop a $100 product into a cart with a Minneapolis MN shipping address (ZIP 55401):

Subtotal: $100.00
Total tax: $9.03
Tax lines (1):
  - code=US-OPENSALESTAX-1, label=OpenSalesTax, amount=$9.03
Enter fullscreen mode Exit fullscreen mode

Open the order in the admin and you'll see the full per-jurisdiction breakdown:

Type Jurisdiction Rate Tax
state Minnesota 6.875% $6.88
county Hennepin County 0.150% $0.15
city Minneapolis 0.500% $0.50
district Hennepin County Transit Sales Tax 0.500% $0.50
district Metro Area Transportation Sales Tax 0.750% $0.75
district Metro Area Sales and Use Tax for Housing 0.250% $0.25
9.025% $9.03

That's the correct combined rate for Minneapolis. TaxJar / Avalara show one flat number; OpenSalesTax shows you exactly where every penny went — useful for audit reconciliation.

What's in the box (v0.4.0)

Core (v0.1):

  • woocommerce_calc_tax filter integration (works with classic checkout AND Cart/Checkout Blocks via the Store API)
  • Settings page under WC → Settings → Tax → OpenSalesTax
  • "Test connection" button
  • Tax-exempt customers honored via WC()->customer->is_vat_exempt()
  • WP transient caching (default 60-min TTL, configurable)
  • Configurable error fallback: block (no tax line) or zero ($0 + log)

v0.2 — Tax classes: Custom WC tax-class → OST category mapping. Map clothing, groceries, or any custom class to the right OST category (or mark non-taxable). Built-in defaults still apply for standard / reduced-rate / zero-rate. Configure via the admin UI (v0.3.3) or WP-CLI.

v0.3 — Operational visibility:

  • Per-order jurisdiction breakdown — every order stores the engine's full state/county/city/district split as meta and renders the table above on the WC admin order-edit page
  • Status dashboard widget — WP-admin home page shows engine reachability, version, and today's order count with breakdown captured. 60s transient cache so it never hammers the engine
  • Recent-calculations debug log — opt-in 50-entry ring buffer captures every cart calc (cache-hit / engine-call / error) with timing, ZIP, category, amount, tax. Useful for "why is this rate $8.25 when it should be $9.03?" debugging
  • Admin-UI tax-class mapper — replaces the CLI-only configuration with a UI right in the settings page

v0.4 — WooCommerce Subscriptions: If you run WC Subscriptions, every renewal order gets a fresh tax recalculation against today's rates instead of inheriting the parent sub's stale tax line. State law changes mid-subscription? You stay accurate. (Auto-detected; no-op if WC Subscriptions isn't installed.)

Quality bar: PHPStan level=max clean, 98 unit tests + integration test on a real WP+WooCom install, security review (docs/SECURITY-REVIEW.md), Apache 2.0, DCO sign-off.

What's intentionally NOT in scope

  • Tax filing / remittance — calculation only, by design. The merchant remits.
  • Multi-currency carts — engine is USD-only; non-USD throws.
  • Stripe Connect / multi-vendor marketplace tax allocation — not yet.
  • Per-product custom tax-code overrides — use the tax-class mapper for category-level distinctions.

Honest disclaimer

Tax calculations are provided as-is. The merchant is solely responsible for tax-collection accuracy and remittance to the appropriate jurisdictions. Verify against your state Department of Revenue before remitting. (This isn't lawyer-speak — it's the genuine constraint of any sales-tax integration; OpenSalesTax computes the math, you remit.)

Compare

Cost Self-hosted Per-jurisdiction breakdown WC Subs auto-recalc
Stripe Tax 0.5%/txn
TaxJar API from ~$0.50/txn partial
Avalara AvaTax enterprise partial
WC Tax (Jetpack) $0 / paid above
OpenSalesTax + this plugin $0 software

Try it

If you run a US-shipping WooCommerce store and you're hitting the WC Tax (Jetpack) 200-transaction limit OR paying TaxJar, give it a try. File issues at https://github.com/ejosterberg/opensalestax-woocommerce/issues — the plugin needs real-merchant feedback on:

  • Address scenarios where customers ship to less-common ZIPs (rural / overseas-territory edges)
  • WC Subscriptions integration — verify the renewal recalc against your real subscription flow
  • Anywhere the per-jurisdiction breakdown disagrees with what your state DOR expects

composer require ejosterberg/opensalestax-woocommerce to get started.

Top comments (0)