German Income Tax Calculation in JavaScript
German income tax is famously complicated, and for good reason: it is not a simple set of brackets. Germany uses a progressive formula with a tax-free allowance, two curved progression zones, and then flat marginal rates. If you build payroll or salary tools for the German market, a bracket lookup table will give you wrong answers. You need the actual formula.
Try the numbers yourself with the free Salary Calculator Germany on freekalc.com — enter a gross salary and see the estimated net pay after tax and social contributions.
How the German system is structured
For a single filer in 2026, the structure looks like this:
- Basic allowance (Grundfreibetrag): income up to €12,096 is tax-free
- Progression zone 1: €12,097 to €17,779 — marginal rate rises smoothly from 0% to about 24%
- Progression zone 2: €17,780 to €68,480 — marginal rate rises smoothly from about 24% to 42%
- 42% zone: €68,481 to €277,825 — flat 42% marginal rate
- 45% zone (Reichensteuer): above €277,825 — flat 45% marginal rate
The key word is smoothly. Inside the two progression zones, the marginal rate is not a step — it is a quadratic curve. Your tax is the area under that curve, which is why the official formula uses squared terms. This is the part most hobby calculators get wrong by substituting stepped brackets.
On top of income tax come two further charges that developers often forget:
- Solidarity surcharge (Solidaritätszuschlag): 5.5% of the income tax, but only once income tax exceeds a threshold — most middle earners now pay nothing at all
- Church tax (Kirchensteuer): 8% or 9% of income tax depending on the federal state, but only if you are a registered church member
Social contributions (health, pension, unemployment, long-term care insurance) are separate again, and are usually calculated by the employer. This article focuses on the income-tax formula itself, which is the hard part to get right.
The official formula
The German tax code publishes the formula in terms of taxable income z, expressed in euros. The progression zones use a scaled variable so the quadratic coefficients stay manageable. Here is the standard formulation for a single filer, using the 2026 thresholds:
// 2026 thresholds for single filers (Grundtabelle) const ZONES = { allowance: 12096, zone1End: 17779, zone2End: 68480, topRateEnd: 277825, }; function incomeTax(taxableIncome) { const zvE = Math.floor(taxableIncome); if (zvE <= ZONES.allowance) return 0; let tax; if (zvE <= ZONES.zone1End) { // Progression zone 1: marginal rate climbs 0% -> ~24% const y = (zvE - ZONES.allowance) / 10000; tax = (922.98 * y + 1400) * y; } else if (zvE <= ZONES.zone2End) { // Progression zone 2: marginal rate climbs ~24% -> 42% const z = (zvE - ZONES.zone1End) / 10000; tax = (181.86 * z + 2397) * z + 1015.13; } else if (zvE <= ZONES.topRateEnd) { // Flat 42% zone tax = 0.42 * zvE - 10602.13; } else { // Flat 45% zone tax = 0.45 * zvE - 18936.88; } return Math.floor(tax); } // Examples (single filer, taxable income = gross minus deductions) console.log(incomeTax(30000)); // ~ 4863 console.log(incomeTax(60000)); // ~ 14473 console.log(incomeTax(100000)); // ~ 31398 A few implementation notes. First, taxable income is not gross salary: standard deductions, work-related expenses, and social contributions reduce it before this formula is applied, so a real payroll tool needs a deductions layer in front of this function. Second, the result is floored to whole euros, matching the official calculation. Third, married couples filing jointly use the splitting method (Ehegattensplitting): halve the joint income, run the formula, and double the result — which usually produces a lower bill than two single calculations.
Adding the surcharges
Once you have the base income tax, the surcharges are straightforward:
function totalTaxWithSurcharges(taxableIncome, { churchMember = false, churchRate = 0.09 } = {}) { const tax = incomeTax(taxableIncome); // Solidarity surcharge: 5.5% of income tax, with a generous exemption // threshold — simplified here; most middle earners owe nothing. const soliFreeZone = 18130; // single filer, approximate 2026 threshold const soli = tax > soliFreeZone ? Math.min(0.055 * tax, 0.119 * (tax - soliFreeZone)) : 0; const churchTax = churchMember ? churchRate * tax : 0; return { incomeTax: tax, solidaritySurcharge: Math.floor(soli), churchTax: Math.floor(churchTax), total: tax + Math.floor(soli) + Math.floor(churchTax), }; } console.log(totalTaxWithSurcharges(60000, { churchMember: false })); // { incomeTax: 14473, solidaritySurcharge: 0, churchTax: 0, total: 14473 } Note the solidarity surcharge cap: it is phased in gradually rather than switching on at full rate, which is why the Math.min with the 11.9% phase-in slope matters. A calculator that applies a flat 5.5% above the threshold will overstate the surcharge for earners just above it.
Practical edge cases
Tax classes (Steuerklassen). Monthly payroll withholding uses tax classes I–VI, which change the monthly deduction but not the final annual bill. Class III/V combinations for married couples, or class VI for a second job, can make monthly payslips look very different from the annual calculation. Do not confuse a withholding figure with the final tax.
The annual thresholds move. The basic allowance and zone boundaries are adjusted almost every year for inflation. Hard-coding them is the most common bug in salary tools. Keep them in a versioned config keyed by tax year, exactly as in the ZONES object above, and update them each January from the official BMF tables.
Capital income is separate. Investment income is generally taxed at a flat 25% withholding rate (Abgeltungsteuer) plus surcharges, not through this formula. Mixing salary and capital income into one calculation is a category error.
Mini-jobs and midi-jobs. Earnings up to €556 per month (mini-job) are largely exempt from employee income tax and social contributions; the €556–€2,000 midi-job zone phases contributions in gradually. A general salary calculator that ignores these bands will mislead low earners.
Why the formula matters
Stepped brackets are tempting because they are easy to code, but for German salaries they produce errors of hundreds of euros in the progression zones — exactly where most employees sit. The quadratic formula is not much harder to implement, and it is the only approach that matches official payslips. Build the formula once, parameterise it by tax year, layer deductions in front and surcharges behind, and you have a salary engine you can trust.
To sanity-check your implementation, run a few known salaries through the free Salary Calculator Germany and compare the net figures against your own code's output. If they agree within a few euros, your formula layer is sound — and any remaining difference will almost certainly be in the deductions layer, not the tax maths.



