OBBBA's New W-2 Fields for Tips and Overtime Create a Payroll Tracking Nightmare—Is Anyone Tracking This in Beancount?

I’ve been updating my Beancount chart of accounts for clients this week, and the new OBBBA payroll reporting requirements are giving me a headache. Figured I can’t be the only one.

What Changed

Starting with 2026 W-2s, the IRS is requiring employers to separately report qualified tips and qualified overtime using new W-2 fields:

  • Box 12, Code TT — Total qualified overtime compensation (only the premium portion, the “half” in time-and-a-half)
  • Box 12, Code TP — Total qualified cash tips reported to employer
  • Box 14b — Treasury Tipped Occupation Code (TTOC) to verify eligibility

These new fields support the “no tax on tips” and “no tax on overtime” provisions. Employees can deduct up to $12,500 ($25,000 MFJ) in qualified overtime and up to $25,000 in qualified tips from taxable income.

The deductions phase out above $150K MAGI ($300K MFJ), so this primarily benefits hourly workers—restaurants, construction, healthcare, hospitality.

My Beancount Problem

For my 20+ small business clients, my payroll journal entries used to look like this:

2026-03-15 * "Payroll - J. Rodriguez"
  Expenses:Payroll:Wages          3,200.00 USD
  Expenses:Payroll:Taxes:FICA       244.80 USD
  Liabilities:Payroll:FedWithholding -384.00 USD
  Liabilities:Payroll:FICA          -244.80 USD
  Assets:Bank:Checking            -2,816.00 USD

Simple. One wages line. But now I need granularity to match the W-2:

2026-03-15 * "Payroll - J. Rodriguez" #payroll
  Expenses:Payroll:Wages:Base         2,400.00 USD
  Expenses:Payroll:Wages:Overtime:Qualified  600.00 USD
  Expenses:Payroll:Wages:Tips:Qualified      200.00 USD
  Expenses:Payroll:Taxes:FICA                244.80 USD
  Liabilities:Payroll:FedWithholding        -384.00 USD
  Liabilities:Payroll:FICA                  -244.80 USD
  Assets:Bank:Checking                    -2,816.00 USD

That’s not terrible for ONE employee. But I have clients in the restaurant industry with 15-30 employees, bi-weekly payroll. That’s 30+ payroll runs per month where I need to correctly split base wages, qualified overtime premium, and qualified tips—and the tips need to be genuinely voluntary (mandatory service charges don’t qualify).

The Importer Challenge

My Python importers pull payroll data from Gusto and ADP CSV exports. Those exports didn’t have separate columns for qualified vs. non-qualified overtime until Q3 2026 (Gusto just shipped the update; ADP still in beta). So for H1 2026, I have to retroactively categorize overtime—cross-referencing time tracking to determine which overtime is FLSA-required vs. voluntary.

Has anyone built a Beancount payroll importer that handles the new W-2 codes? My current approach is ugly:

def classify_overtime(hours_worked, regular_rate, total_ot_pay):
    """Only FLSA-required OT premium qualifies."""
    if hours_worked <= 40:
        return 0  # No FLSA OT
    ot_hours = hours_worked - 40
    # Only the premium portion (0.5x) qualifies, not the full 1.5x
    qualified_ot = ot_hours * (regular_rate * 0.5)
    return min(qualified_ot, total_ot_pay)

Questions for the Community

  1. How granular is your payroll account structure? Do you track by pay component (base, overtime, tips, bonuses) or lump everything into one wages account?

  2. Are you tracking the $12,500 deduction cap? For employees working heavy overtime, they’ll hit the annual limit. Are you building BQL queries to flag when employees approach the cap?

  3. Restaurant/hospitality bookkeepers: How are you separating qualified tips (voluntary) from non-qualified tips (mandatory service charges)? This distinction matters enormously.

  4. For FIRE folks tracking your own pay: If your overtime is now effectively tax-free up to $12,500, does this change your savings rate calculations? Your Roth conversion math?

I’ve been a bookkeeper for 10 years and this is the most impactful payroll reporting change I’ve seen since the ACA employer mandate. The IRS gave penalty relief for 2025 W-2s (no separate reporting required yet), but 2026 is the real deal.

Bob, your pain is valid and this is going to hit harder than most bookkeepers realize. Let me add the IRS compliance layer since I’ve been neck-deep in the OBBBA guidance.

The Qualified vs. Non-Qualified Distinction Is Where Penalties Hide

Here’s what I keep telling my clients: not all overtime is qualified overtime. The deduction ONLY applies to overtime that is required by Section 7 of the Fair Labor Standards Act (29 USC § 207). That means:

Qualified (deductible):

  • FLSA-mandated time-and-a-half for non-exempt employees working over 40 hours/week
  • Only the premium portion (the 0.5x), not the full 1.5x rate

NOT qualified (still fully taxable):

  • Voluntary overtime pay for exempt (salaried) employees
  • State-level overtime rules that exceed FLSA (California’s daily OT, for example)
  • Overtime for employees exempt from FLSA (certain agricultural workers, some transport employees)
  • Bonus pay structured as “overtime” but not FLSA-required

For tips, the trap is even nastier. The IRS specifically says mandatory service charges, auto-gratuities for large parties, and tips forced through a pre-filled payment screen are not voluntary and don’t qualify. I’ve seen restaurants where 40% of what they call “tips” are actually mandatory service charges. Those go in a completely different bucket.

What I’m Building in Beancount

I’ve restructured my tax-prep Beancount templates to include metadata that maps directly to the new W-2 codes:

2026-06-30 * "Payroll - Maria Santos" #payroll
  Expenses:Payroll:Wages:Base            2,200.00 USD
  Expenses:Payroll:Wages:OT:Qualified      375.00 USD
    w2_code: "TT"
  Expenses:Payroll:Wages:OT:NonQualified   125.00 USD
  Expenses:Payroll:Wages:Tips:Qualified     480.00 USD
    w2_code: "TP"
    tip_type: "voluntary"
  Expenses:Payroll:Wages:Tips:ServiceCharge 120.00 USD
    tip_type: "mandatory"
  Expenses:Payroll:Taxes:Employer:FICA      252.45 USD
  Liabilities:Payroll:FedWithholding       -396.00 USD
  Liabilities:Payroll:FICA                 -252.45 USD
  Assets:Bank:Operating                  -2,904.00 USD

The metadata tags (w2_code, tip_type) let me run BQL queries at year-end that directly map to W-2 field population. I can also flag misclassifications early instead of scrambling in January.

The Cap Tracking Query

Bob asked about the $12,500 deduction cap. Here’s the BQL query I use to monitor year-to-date qualified overtime per employee:

SELECT account, sum(position) as total
WHERE account ~ "Wages:OT:Qualified"
  AND year = 2026
GROUP BY account
HAVING sum(number(position)) > 10000

When someone clears $10K, I flag it. The remaining $2,500 in headroom means their withholding projections need adjusting—because once they hit $12,500, the overtime goes back to being fully taxable for the rest of the year. Payroll providers don’t track this automatically yet.

The Updated W-4 Angle

One thing nobody’s talking about: the IRS also updated Form W-4 for 2026. Employees can now enter estimates for qualified tips (Line 1a) and qualified overtime (Line 1b) to reduce withholding during the year. This means lower take-home tax withholding, which is great for employees but creates a reconciliation puzzle for bookkeepers who need to verify the W-4 estimates against actual pay.

If an employee overestimates their qualified overtime on the W-4 and their withholding is too low all year, they’ll owe at tax time. As former IRS, I can tell you: that’s exactly the kind of discrepancy that triggers notice letters.

My recommendation: add a Beancount assertion at each quarter-end that compares YTD qualified overtime against the employee’s W-4 estimate. Catch the gap early, not in January.

Responding to Bob’s question #4 since this directly affects my FIRE tracking.

The Savings Rate Recalculation Is Real

I’m a salaried tech worker but I occasionally pick up freelance data analysis work that generates 1099 income. My wife works in healthcare (non-exempt, heavy overtime). When we ran the numbers on her 2026 pay stubs, the OBBBA overtime deduction changes our household tax picture meaningfully:

Her 2026 projected overtime: ~$18,000 total (she works 48-50 hour weeks regularly)
Qualified portion (0.5x premium only): ~$6,000
Tax savings at 22% marginal rate: ~$1,320/year
Additional FIRE savings if we redirect tax savings: pushes our savings rate from 51.3% to 52.1%

That 0.8% doesn’t sound like much, but compounded over our 8-year FIRE timeline, it’s roughly an extra $14K at FI date. Not nothing.

How I Restructured My Personal Beancount

I track my wife’s pay in our joint Beancount ledger. Here’s how I restructured it:

2026-03-22 * "Hospital System - Paycheck" #payroll #w2
  Income:Spouse:Salary:Base              -3,400.00 USD
  Income:Spouse:Salary:OT:Qualified        -450.00 USD
    deduction_cap_remaining: 12050.00
  Income:Spouse:Salary:OT:NonQualified     -150.00 USD
  Income:Spouse:Salary:ShiftDiff           -200.00 USD
  Expenses:Tax:Federal:Withheld             504.00 USD
  Expenses:Tax:FICA:SocialSecurity          260.40 USD
  Expenses:Tax:FICA:Medicare                 60.90 USD
  Expenses:Tax:State:Withheld               168.00 USD
  Assets:Bank:Joint:Checking              3,206.70 USD

The deduction_cap_remaining metadata is key. I update it each pay period so I can see how much headroom is left before the $12,500 cap kicks in. At her current pace, she’ll hit the cap around late October, meaning November-December overtime goes back to being fully taxable.

The Roth Conversion Wrinkle

Here’s the part that surprised me: the $12,500 overtime deduction reduces your MAGI. For anyone doing backdoor Roth or Roth conversion strategies, this creates a slightly larger “gap” between your income and the phase-out threshold. Not game-changing, but worth running through your Beancount projections.

I built a Python script that reads my Beancount ledger, projects year-end AGI with the overtime deduction, and suggests optimal Roth conversion amounts. Happy to share if anyone wants it.

The Philosophical Question for FIRE Trackers

Should we track the deduction itself or just the net tax impact? I’ve been going back and forth. Currently I have a virtual account Income:Tax:OBBBA:OTDeduction that tracks the cumulative qualified overtime—not as a real cash flow, but as a memo account that feeds into my tax projection script. It feels like overkill, but come tax time I’ll be glad I have the data.

Tina’s point about W-4 estimates is crucial. My wife updated her W-4 to claim the overtime deduction, which increased her per-paycheck take-home by about $50. We’re auto-transferring that $50 straight to our brokerage. That’s the kind of micro-optimization Beancount makes possible—seeing exactly where the money comes from and routing it intentionally.

Great thread. Let me add the CPA practice perspective, because this is creating real advisory opportunities for us—but also real liability exposure if we get it wrong.

The Chart of Accounts Design Decision

Bob’s restructured journal entry is on the right track, but I’d push it further. For my firm’s clients, I’m recommending a chart of accounts that separates not just by pay component, but by qualification status, because that’s what the W-2 cares about:

Expenses:Payroll:{EmployeeName}:Wages:Base
Expenses:Payroll:{EmployeeName}:Wages:OT:FLSA:Premium    ; Box 12 Code TT
Expenses:Payroll:{EmployeeName}:Wages:OT:FLSA:Straight   ; The 1.0x portion of OT (not deductible)
Expenses:Payroll:{EmployeeName}:Wages:OT:Voluntary       ; Non-FLSA OT
Expenses:Payroll:{EmployeeName}:Wages:Tips:Voluntary      ; Box 12 Code TP
Expenses:Payroll:{EmployeeName}:Wages:Tips:ServiceCharge  ; Non-qualifying
Expenses:Payroll:{EmployeeName}:Wages:Tips:AutoGratuity   ; Non-qualifying
Expenses:Payroll:{EmployeeName}:Wages:Bonus
Expenses:Payroll:{EmployeeName}:Wages:ShiftDiff

Yes, this is verbose. Yes, it creates a lot of accounts for a 30-employee restaurant. But here’s why it matters: when the IRS audits a W-2 (and they will audit these new fields aggressively in the first 2-3 years), you need to demonstrate exactly how you determined what was “qualified.” A Beancount ledger with this granularity, backed by Git history showing when each entry was created, is better audit documentation than anything QuickBooks produces.

The Advisory Opportunity

I’ve been framing this to clients as a two-part conversation:

Part 1: Employer compliance. “Your W-2s now need new fields. If you misclassify mandatory service charges as qualified tips, or report state-mandated OT as FLSA-qualified OT, the IRS can penalize you per incorrect W-2. With 25 employees, that adds up fast.”

Part 2: Employee benefit. “Your employees can save $1,000-$3,000 each in federal taxes. If you help them understand and claim this correctly, you become the employer who just gave everyone a raise without spending a dime. That’s a retention tool.”

For my CPA practice, I’m charging a one-time “OBBBA payroll restructuring” engagement—reviewing their payroll classification, updating their chart of accounts, building the Beancount templates, and training their bookkeeper (often Bob-type practitioners) on the new categories. I’ve priced it at $2,500-$5,000 depending on headcount, and three clients have signed this week.

The State Tax Complication Nobody’s Mentioned

Here’s the landmine: the OBBBA deduction is federal only. Most states have not conformed. So in states that don’t conform (like California, New York, New Jersey), you need to track qualified overtime for two purposes:

  1. Federal: Deductible up to $12,500
  2. State: Still fully taxable

This means your Beancount tax projection scripts need to maintain two income calculations—federal AGI (with deduction) and state AGI (without). If you’re running a single BQL query for “total taxable income,” it’s now wrong for at least one jurisdiction.

I’m building a Beancount plugin that reads the w2_code metadata Tina mentioned, and generates both a federal and state tax summary report. Still a work in progress, but the structure is:

def calculate_taxable_income(entries, state="IL"):
    """Calculate taxable income with state conformity check."""
    total_income = sum_income(entries)
    qualified_ot = sum_by_metadata(entries, "w2_code", "TT")
    qualified_tips = sum_by_metadata(entries, "w2_code", "TP")
    
    federal_deduction = min(qualified_ot, 12500) + min(qualified_tips, 25000)
    
    # Check state conformity
    state_conforms = state in ["FL", "TX", "WA", "NV"]  # No income tax states
    state_deduction = federal_deduction if state_conforms else 0
    
    return {
        "federal_agi": total_income - federal_deduction,
        "state_agi": total_income - state_deduction,
    }

This is the kind of thing that makes Beancount genuinely superior to commercial payroll software for power users. QuickBooks can’t do conditional tax calculations based on state conformity. Beancount can, because it’s just Python.

Tina, love the BQL cap tracking query. I’m stealing that for my clients. And Fred, the deduction_cap_remaining metadata approach is clever—I might standardize that across my practice.