I wanted a simple table: 50 US states, one row each, "how much tax do you pay when you buy a car". The first draft had two columns, state and rate. It was wrong for roughly half the states, not because the numbers were off, but because the columns were.
This post covers the schema I ended up with and the rules that forced each column. Every row is checked against the state's revenue department or DMV page, and the source URL is kept in the row.
1. The tax isn't always a "sales tax"
Of 50 states, 22 either have no state sales tax on cars or call the tax on a car purchase something other than a plain "sales tax". A few examples from the table:
| State | What it's called | Rate |
|---|---|---|
| Georgia | Title ad valorem tax (no sales tax) | 7% of fair market value, once at titling |
| North Carolina | Highway use tax | 3% |
| Delaware | Document fee (no sales tax) | 5.25% |
| Iowa | Fee for new registration (no sales tax) | 5% |
| South Carolina | Infrastructure maintenance fee | 5%, capped at $500 |
| Oklahoma | Excise tax + sales tax | 3.25% + 1.25% |
| Kentucky | Motor vehicle usage tax | 6% |
This matters beyond naming. These are often separate taxes with their own base and their own exemptions, so a general sales tax rate table (the kind built for retail) gives the wrong answer for a car. Georgia is the clearest case: its general sales tax doesn't apply to the vehicle purchase at all; the 7% TAVT replaces it.
So the first column became tax — the name the state uses — next to state_rate.
2. "Is there local tax?" is a separate yes/no
22 states add a county or city rate on top of the state rate. That's a boolean in the table (local_tax), and the state rate alone is shown as the floor, not the answer.
Which local rate applies is a rule of its own. In most of these states it's the rate of the address where the car is registered — Missouri, Nebraska, New York, Ohio and Wyoming all say so in some form. Nevada is the other way around: the rate of the county where the sale happens. Kansas uses the higher of the dealer's and your home rate. I kept those rules in a free-text note rather than inventing an enum for them, because there are almost as many variants as states.
3. Trade-in credit needs six values, not two
My first instinct was trade_in_lowers_tax: boolean. The data disagreed:
| Value | States | Meaning |
|---|---|---|
yes | 35 | Tax on price minus trade-in |
no | CA, OK, VA | No credit; you pay tax on the full price |
dealer | GA, ID, IL, MA | Credit only when a dealer takes the trade-in in the same deal |
new | OH | Credit only on a new vehicle |
capped | MI | Credit limited ($12,000 in 2026) |
| (empty) | AK, HI, MT, NH, OR, SC | No state tax to reduce, or the rule doesn't apply |
On a $40,000 car with a $15,000 trade-in, the difference between yes and no at a 6% rate is $900. That's the single most common reason two "car tax calculators" disagree.
4. Caps and thresholds live inside the rate
Several states have a percentage that only applies to part of the price:
- Florida: the county surtax (up to 2%) applies to the first $5,000 only.
- Tennessee: local tax applies only to the first $1,600, plus a 2.75% state tax on the next $1,600.
- South Carolina: 5%, but never more than $500.
- Connecticut: 6.35%, or 7.75% on vehicles over $50,000.
- Arkansas: used vehicles under $4,000 are exempt; $4,000–$9,999 pay a lower 3.5% state rate.
A rate column that says "Florida 6%–8%" is technically true and practically misleading — on a $30,000 car the county part adds at most $100. In the table these rules go into the note; in the calculator each one is code.
5. The taxable amount isn't always the price
Some states tax a valuation, not what you paid, at least for private sales:
- Texas: private sales are taxed on at least 80% of the standard presumptive value.
- Massachusetts: private sales on the higher of the price or the NADA trade-in value.
- Vermont: the higher of the price or the J.D. Power clean trade-in value.
- New Mexico: used cars on the higher of the price or 80% of NADA value.
- Delaware: the higher of the price or the NADA average trade-in value.
Writing a low number on the bill of sale doesn't help in these states, and any estimate built only on the price undercounts.
6. Rates change more often than you'd think
Since the start of 2025 alone: Louisiana's state rate rose to 5% (2025), Maryland's excise went from 6% to 6.5% (July 1, 2025), Delaware's document fee rose to 5.25% (October 1, 2025), and Washington added a 0.5% motor vehicle sales tax on January 1, 2026. Every row has an as_of date, and the table as a whole carries the date it was last checked. A rate without a date is a guess about the present.
The final schema
state, code, tax, state_rate, local_tax, trade_in_lowers_tax, note, as_of Plus a source URL per row (in the JSON; the CSV keeps it short). Eight columns, one of which is free text, which feels unsatisfying from a modeling point of view. But every attempt to normalize the notes into more enums produced columns with one or two states each. For a reference table, a precise sentence beats a sparse column.
What I'd tell someone building the same thing
- Start from each state's official page, not from an aggregator. The general retail rate tables are the wrong source for vehicles in at least a third of states.
- Model the trade-in rule and the taxable base explicitly; that's where estimates diverge most.
- Show the state rate as a floor when local tax exists, and say which address decides the local rate.
- Date every row.
The table, with a source link in every row, is on carsalestax.com; the CSV is linked from the site's methodology page.


