<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eric Osterberg</title>
    <description>The latest articles on DEV Community by Eric Osterberg (@ejosterberg).</description>
    <link>https://dev.to/ejosterberg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3913123%2Fff6e4948-cdc4-4764-bbbf-d688c755c7c5.png</url>
      <title>DEV Community: Eric Osterberg</title>
      <link>https://dev.to/ejosterberg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ejosterberg"/>
    <language>en</language>
    <item>
      <title>Replace TaxJar with self-hosted OpenSalesTax in WooCommerce — free, ~30 min setup</title>
      <dc:creator>Eric Osterberg</dc:creator>
      <pubDate>Tue, 05 May 2026 13:54:37 +0000</pubDate>
      <link>https://dev.to/ejosterberg/replace-taxjar-with-self-hosted-opensalestax-in-woocommerce-free-30-min-setup-2k1k</link>
      <guid>https://dev.to/ejosterberg/replace-taxjar-with-self-hosted-opensalestax-in-woocommerce-free-30-min-setup-2k1k</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;If you run a US-shipping WooCommerce store, your options for accurate destination-based sales tax are roughly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Pricing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WooCommerce Tax (Jetpack)&lt;/td&gt;
&lt;td&gt;Free up to 200 transactions/month, paid above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TaxJar&lt;/td&gt;
&lt;td&gt;From $19/mo + transaction fees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avalara AvaTax&lt;/td&gt;
&lt;td&gt;Enterprise pricing (usually starts in the thousands/yr)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stripe Tax&lt;/td&gt;
&lt;td&gt;0.5% per transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual rate tables&lt;/td&gt;
&lt;td&gt;Free but you maintain ~50 jurisdictions' worth of rules&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

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

&lt;p&gt;Both are Apache 2.0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Engine: &lt;a href="https://github.com/ejosterberg/open-sales-tax" rel="noopener noreferrer"&gt;https://github.com/ejosterberg/open-sales-tax&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;WC plugin: &lt;a href="https://github.com/ejosterberg/opensalestax-woocommerce" rel="noopener noreferrer"&gt;https://github.com/ejosterberg/opensalestax-woocommerce&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup (~30 min)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Stand up the engine (5 min)&lt;/span&gt;
git clone https://github.com/ejosterberg/open-sales-tax
&lt;span class="nb"&gt;cd &lt;/span&gt;open-sales-tax
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
curl http://localhost:8080/v1/health
&lt;span class="c"&gt;# → {"status":"ok","version":"0.39.0","database_connected":true}&lt;/span&gt;

&lt;span class="c"&gt;# 2. Install the plugin&lt;/span&gt;
composer require ejosterberg/opensalestax-woocommerce
&lt;span class="c"&gt;# (or git clone into wp-content/plugins/ and run composer install --no-dev)&lt;/span&gt;

&lt;span class="c"&gt;# 3. Activate + configure&lt;/span&gt;
wp plugin activate opensalestax-woocommerce
wp option update opensalestax_base_url &lt;span class="s2"&gt;"http://your-engine:8080"&lt;/span&gt;
wp opensalestax test-connection
&lt;span class="c"&gt;# → Success: Engine v0.39.0 is ok — DB up&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Verifying it works
&lt;/h2&gt;

&lt;p&gt;Drop a $100 product into a cart with a Minneapolis MN shipping address (ZIP 55401):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Subtotal: $100.00
Total tax: $9.03
Tax lines (1):
  - code=US-OPENSALESTAX-1, label=OpenSalesTax, amount=$9.03
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the order in the admin and you'll see the full per-jurisdiction breakdown:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Jurisdiction&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;th&gt;Tax&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;state&lt;/td&gt;
&lt;td&gt;Minnesota&lt;/td&gt;
&lt;td&gt;6.875%&lt;/td&gt;
&lt;td&gt;$6.88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;county&lt;/td&gt;
&lt;td&gt;Hennepin County&lt;/td&gt;
&lt;td&gt;0.150%&lt;/td&gt;
&lt;td&gt;$0.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;city&lt;/td&gt;
&lt;td&gt;Minneapolis&lt;/td&gt;
&lt;td&gt;0.500%&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;district&lt;/td&gt;
&lt;td&gt;Hennepin County Transit Sales Tax&lt;/td&gt;
&lt;td&gt;0.500%&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;district&lt;/td&gt;
&lt;td&gt;Metro Area Transportation Sales Tax&lt;/td&gt;
&lt;td&gt;0.750%&lt;/td&gt;
&lt;td&gt;$0.75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;district&lt;/td&gt;
&lt;td&gt;Metro Area Sales and Use Tax for Housing&lt;/td&gt;
&lt;td&gt;0.250%&lt;/td&gt;
&lt;td&gt;$0.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9.025%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$9.03&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the box (v0.4.0)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Core (v0.1):&lt;/strong&gt;&lt;/p&gt;

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

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

&lt;p&gt;&lt;strong&gt;v0.3 — Operational visibility:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-order jurisdiction breakdown&lt;/strong&gt; — 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&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status dashboard widget&lt;/strong&gt; — 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&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recent-calculations debug log&lt;/strong&gt; — 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&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin-UI tax-class mapper&lt;/strong&gt; — replaces the CLI-only configuration with a UI right in the settings page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;v0.4 — WooCommerce Subscriptions:&lt;/strong&gt; 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.)&lt;/p&gt;

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

&lt;h2&gt;
  
  
  What's intentionally NOT in scope
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  Honest disclaimer
&lt;/h2&gt;

&lt;p&gt;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.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Self-hosted&lt;/th&gt;
&lt;th&gt;Per-jurisdiction breakdown&lt;/th&gt;
&lt;th&gt;WC Subs auto-recalc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stripe Tax&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.5%/txn&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TaxJar API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;from ~$0.50/txn&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Avalara AvaTax&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;enterprise&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;WC Tax (Jetpack)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0 / paid above&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenSalesTax + this plugin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0 software&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;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 &lt;a href="https://github.com/ejosterberg/opensalestax-woocommerce/issues" rel="noopener noreferrer"&gt;https://github.com/ejosterberg/opensalestax-woocommerce/issues&lt;/a&gt; — the plugin needs real-merchant feedback on:&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;composer require ejosterberg/opensalestax-woocommerce&lt;/code&gt; to get started.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
