"We Just Need to Handle All 50 States"
Every payroll engineering team starts the same way. Federal taxes? Got it — IRS Publication 15-T, seven brackets, standard deduction. State taxes? More work, but manageable — download each state's withholding guide, implement 43 different bracket systems (plus the 7 states with no income tax and the 2 that only tax dividends/interest).
Then someone asks: "What about local taxes?"
And that's when the project timeline doubles.
The Scope of the Problem
Here are the numbers that most product teams don't see coming:
- 4,246 local jurisdictions levy some form of payroll tax
- 17 states have cities, counties, or school districts with local income taxes
- Pennsylvania alone has 2,573 municipalities, each with its own Earned Income Tax rate
- Ohio has 528+ municipalities across three different collection systems
- Kentucky has 120 counties plus 33+ cities, and the taxes stack
For a payroll system that claims to handle "all US jurisdictions," the local layer is 98% of the jurisdictions and 80% of the implementation complexity.
Why Teams Underestimate Local Taxes
It's Not Just Different Rates
If local taxes were simply "look up a rate and apply it," they'd be straightforward. The real complexity comes from the rules:
Stacking: In Kentucky, an employee owes both city OLT and county OLT — they stack. In Ohio, city income taxes don't stack the same way. In Pennsylvania, the rules are different again.
Credits: Ohio municipalities give credits for taxes paid to the work city against the residence city's tax. The credit limit varies by city. You need to calculate: (1) tax owed to work city, (2) credit available in residence city, (3) net tax owed to residence city.
Resident vs. non-resident rates: Many jurisdictions charge different rates depending on whether the employee lives or works there. Louisville charges 2.20% to residents but 1.45% to non-residents.
Collection systems: Ohio has three separate systems (RITA, CCA, self-administered) with different registration, filing, and payment requirements. Your payroll system needs to know which system each municipality uses.
The Data Maintenance Problem
Federal tax brackets change once a year (January 1). State rates change once a year. But local rates can change:
- January 1 for most annual rate changes
- After any election for Ohio school district levies (ballot measures)
- Mid-year when a city council votes to change rates
- When municipalities merge, split, or annex territory (changing boundaries)
Symmetry employs ~68 people and dedicates a meaningful portion to monitoring and maintaining rate data across 7,040+ jurisdictions. It's a full-time operation.
The Geocoding Problem
Here's the problem that kills most DIY approaches: a street address determines the tax jurisdiction, not a ZIP code.
ZIP codes were designed for mail delivery, not tax boundaries. A single ZIP code routinely spans multiple municipalities. If you use ZIP codes to determine local tax rates, you will calculate the wrong tax for some employees.
The correct approach requires either:
- Geocoding the address to coordinates, then checking which municipal boundary contains those coordinates
- Using Pennsylvania PSD codes (for PA), which map addresses to specific tax collection districts
- Querying a jurisdiction lookup API that handles the mapping for you
The Build vs. Buy Decision
What "Building It Yourself" Actually Looks Like
Teams that decide to build their own local tax system typically go through this progression:
Month 1: "We'll just add a table of city tax rates." Create a spreadsheet with the 50 biggest cities.
Month 3: "We need more cities." Discover Ohio has 528 municipalities. Start scraping RITA's website.
Month 6: "Pennsylvania has HOW many municipalities?" Discover 2,573 PA municipalities, each needing a PSD code. Try to find an authoritative data source.
Month 9: "Kentucky taxes stack?!" Discover that your data model assumed one local tax rate per location. Refactor the entire withholding calculation.
Month 12: "This is now a full-time job." Hire someone to monitor rate changes across 4,246 jurisdictions.
Symmetry's marketing claims that building in-house payroll compliance costs over $4 million. That's probably inflated, but the ongoing maintenance cost of $1-2M/year is realistic for a team monitoring and updating rates across all US jurisdictions.
What an API Looks Like
// One call. All jurisdictions. Current rates.
const response = await fetch(
'https://payroll-tax-api-9f4b18020da9.herokuapp.com/v1/rates/lookup' +
'?workState=OH&workCity=Columbus&payDate=2026-04-15' +
'&filingStatus=single&grossWages=5000&payPeriod=biweekly',
{ headers: { 'Authorization': 'Bearer ptx_free_your_key' } }
);
const { taxes, meta } = await response.json();
// Returns: federal brackets, FICA, FUTA, OH state income,
// Columbus city income tax — all in ~12ms
No spreadsheets. No scraping. No geocoding. No monitoring rate changes.
The 2024-2026 Landscape
The multi-state payroll landscape continued to evolve:
Remote work increased local tax complexity. Post-pandemic remote work policies mean employees increasingly work from home in different jurisdictions than their office. Ohio's Cleveland CWT (Central Withholding Tax) controversy highlighted the tension — should the work city still collect income tax when the employee works from home in a different city?
More jurisdictions, not fewer. The trend is toward more local taxation, not less. As state and local governments face budget pressures, local income taxes are an attractive revenue source. New jurisdictions continue to be created.
Rate changes accelerated. Between 2024 and 2026:
- Indiana had 6 counties change rates for 2026 (Carroll, Grant, Greene, Howard, Shelby, Union)
- Kentucky added new city OLT jurisdictions
- Ohio RITA municipalities published updated rate tables each year
- The Social Security wage base increased from $168,600 (2024) to $176,100 (2025) to $184,500 (2026)
What Good Multi-State Payroll Architecture Looks Like
If you're building (or rebuilding) a multi-state payroll system, here's the architecture that works:
1. Model Taxes as a Stack, Not a Single Rate
Every employee potentially owes multiple layers of tax. Your data model should be:
Employee Pay Period
├── Federal Income Tax (graduated)
├── Social Security (6.2%, capped)
├── Medicare (1.45%, + 0.9% surtax)
├── FUTA (employer, capped)
├── State Income Tax (graduated or flat)
├── State Unemployment (employer, experience-rated)
├── State Disability (if applicable)
├── City Income Tax (work city)
├── City Income Tax (residence city, net of credit)
├── County Tax (if applicable, e.g., KY OLT)
└── School District Tax (if applicable, e.g., OH)
2. Always Query by Pay Date
Rates have effective dates. Never cache "the current rate" — always query based on the actual pay date. A paycheck covering December 28 - January 10 might span two different rate years.
3. Separate Rate Data from Calculation Logic
Get your rate data from an authoritative source (an API), then implement your own withholding calculations. This separation means:
- Rate updates don't require code changes
- Calculation logic can be unit tested against known scenarios
- You can switch rate data providers without rewriting calculation code
4. Plan for the Long Tail
The top 50 cities cover maybe 60% of your employees. The next 500 cover another 30%. The last 2,800 cover the remaining 10%. But that 10% generates 90% of the support tickets, because it's the edge cases that break.
Start Simple, Scale Later
You don't need to cover every jurisdiction on day one. A practical rollout:
- Phase 1: Federal + all 50 states (covers ~85% of payroll needs)
- Phase 2: Major local jurisdictions — NYC, Philadelphia, Columbus, Cleveland (covers the biggest cities)
- Phase 3: Full local coverage — PA, OH, KY, IN, MI, MD, and all states with local taxes (covers 4,246 jurisdictions including 713 school districts)
Or skip the phased build and use an API that already covers 4,246 jurisdictions. Free tier gets you started in 30 seconds.