Part of our Compliance & Regulation series
Read the complete guideThe Programmablaufplan (PAP) Lohnsteuer 2026 is the official algorithm published by Germany's Federal Ministry of Finance (Bundesministerium der Finanzen, BMF) that every payroll system must follow to calculate wage tax (Lohnsteuer), solidarity surcharge (Solidaritätszuschlag), and the church-tax assessment base. It is published both as a human-readable flowchart/pseudocode PDF and as a machine-readable XML file, and the BMF operates an official online calculation service developers can use as a reference oracle for testing. If you are implementing German payroll — in Odoo or any other system — the PAP is not a guideline; it is the binding specification, and your output must match it to the cent.
This guide explains what the PAP actually is, how the XML pseudocode format works, what changes between tax years, how to validate your implementation against the BMF's own service and test data, and how the pieces map onto Odoo's German payroll setup. We implement and customize Odoo for German businesses, so the pitfalls listed here are ones we have hit or audited in real payroll projects.
Key Takeaways
- The PAP is the legally binding calculation specification for German wage tax — published annually by the BMF as a flowchart/pseudocode PDF plus a machine-readable XML version on bmf-steuerrechner.de
- The XML encodes the entire algorithm as typed variables (inputs like STKL, RE4, LZZ; outputs like LSTLZZ, SOLZLZZ, BK) and execution nodes — it is designed for code generation, not hand-porting
- All monetary arithmetic is defined in cents with exact, prescribed rounding at specific steps — using floating-point math is the classic way implementations drift by one cent and fail certification
- 2026 carries updated parameters versus 2025 (basic allowance, child allowance, and related thresholds rise under already-legislated tax relief) — always take the exact constants from the published 2026 PAP, never from news articles
- The BMF provides an official online wage-tax calculator with a developer-accessible interface plus published test-case tables — your implementation should reproduce every test row exactly
- The PAP covers tax only: Lohnsteuer, Solidaritätszuschlag, and the church-tax base (BK). Social-insurance contributions follow separate annually-updated Rechengrößen and are out of scope
- Mid-year revised PAPs happen — a compliant payroll engine needs versioned calculators selectable by payment date, not a single hardcoded algorithm
- In Odoo, the PAP belongs in a dedicated, versioned calculation module called from salary rules — with employee tax attributes (Steuerklasse, Kinderfreibeträge, church tax) sourced from ELStAM data on the employee/contract record
What the Programmablaufplan actually is
Germany does not let payroll vendors interpret tax law individually. Under § 39b of the income tax act (Einkommensteuergesetz, EStG), the BMF publishes a Programmablaufplan für die maschinelle Berechnung der Lohnsteuer — literally a "program flow chart for the machine calculation of wage tax" — for each tax year. Every employer or payroll product calculating Lohnsteuer by machine must produce results consistent with it.
Three properties matter to a developer:
- It is an algorithm, not a table. The PAP defines named input variables, internal variables, constants, and a step-by-step computation — including the income-tax tariff formula, the Vorsorgepauschale (a notional social-insurance allowance deducted within the tax calculation), allowances by tax class, and the solidarity-surcharge thresholds.
- It is exact. Amounts flow through the algorithm in euro cents as integer/decimal values, with rounding prescribed at specific steps (where to truncate, where to round, and to which unit). Two implementations that follow the PAP must agree to the cent.
- It is versioned by year — and sometimes within a year. When tax parameters change retroactively or mid-year (which has happened repeatedly in recent years, for example when allowances are raised by late-passing legislation), the BMF publishes a geänderter Programmablaufplan (revised PAP) with its own validity window. A payroll engine must select the correct PAP version based on the wage-payment date.
The PAP family typically includes the main annual PAP for regular payroll runs and a manual-calculation variant; the machine PAP is the one you implement.
What the PAP covers — and what it does not
| In scope (PAP outputs) | Out of scope (separate rules) |
|---|---|
| Lohnsteuer for the payment period (LSTLZZ) | Social-insurance contributions (pension, health, care, unemployment) |
| Solidaritätszuschlag (SOLZLZZ) | Contribution ceilings (Beitragsbemessungsgrenzen) and Rechengrößen |
| Church-tax assessment base (BK) | The church-tax rate itself (8% or 9% depending on the federal state — applied by your system, not the PAP) |
| Tax on other/one-off payments (sonstige Bezüge) and multi-year compensation (Fünftelregelung inputs) | ELStAM data retrieval (the procedure that tells you an employee's tax attributes) |
| Faktorverfahren for tax-class IV couples (factor input f) | Wage-tax filing/remittance (Lohnsteuer-Anmeldung via ELSTER) |
This boundary is the first compliance pitfall: a "German payroll implementation" needs the PAP plus the social-insurance calculation plus the reporting procedures. The PAP is the tax brain only.
The XML pseudocode format, explained for implementers
Alongside the PDF, the BMF publishes the PAP as an XML file on its developer pages (the Lohnsteuerrechner service at bmf-steuerrechner.de hosts the machine-readable artifacts). The XML is not a data file — it is a serialized program, structured roughly like this (illustrative, simplified):
<PAP name="Lohnsteuer2026" version="1.0">
<VARIABLES>
<INPUTS>
<INPUT name="STKL" type="int"/> <!-- Steuerklasse 1-6 -->
<INPUT name="RE4" type="BigDecimal"/> <!-- wage for the period, in CENTS -->
<INPUT name="LZZ" type="int"/> <!-- period: 1=year 2=month 3=week 4=day -->
<INPUT name="ZKF" type="BigDecimal"/> <!-- child allowance count, halves allowed -->
<INPUT name="R" type="int"/> <!-- church-tax liability flag -->
<INPUT name="KVZ" type="BigDecimal"/> <!-- health-insurance add-on rate -->
<!-- ... roughly two dozen inputs in total ... -->
</INPUTS>
<OUTPUTS>
<OUTPUT name="LSTLZZ" type="BigDecimal"/> <!-- wage tax, cents -->
<OUTPUT name="SOLZLZZ" type="BigDecimal"/> <!-- solidarity surcharge, cents -->
<OUTPUT name="BK" type="BigDecimal"/> <!-- church-tax base, cents -->
</OUTPUTS>
<INTERNALS> ... </INTERNALS>
</VARIABLES>
<CONSTANTS> ... tariff constants, allowance amounts ... </CONSTANTS>
<METHODS>
<MAIN>
<EVAL exec="MPARA()"/>
<EVAL exec="MRE4JL()"/>
<IF expr="STKL < 5">
<THEN> ... </THEN>
<ELSE> ... </ELSE>
</IF>
</MAIN>
<METHOD name="MPARA"> ... </METHOD>
</METHODS>
</PAP>
Key implications:
- It is built for code generation. The element set (variables, constants, methods, EVAL/IF/THEN/ELSE execution nodes with expressions) maps mechanically to any imperative language. The established pattern in the German payroll ecosystem is a generator that emits a calculator class per PAP year — there are mature open-source generators and pre-generated libraries for Java, C#, PHP, Python, and JavaScript. We strongly recommend generating rather than hand-porting: hand-ported PAPs are where one-cent bugs live.
- Variable names are the documentation. STKL is the tax class, RE4 the period wage in cents, JFREIB/JHINZU annual allowance/add-back amounts from the employee's ELStAM record, PKV/KRV/PVS flags describing the employee's insurance situation, AF/F the Faktorverfahren flag and factor. Keep the cryptic names in your code — renaming them breaks the audit trail back to the official document.
- Decimal semantics are part of the spec. The pseudocode assumes exact decimal arithmetic (BigDecimal in the original Java idiom) with explicit scale and rounding-mode operations. In Python use
decimal.Decimal; in JavaScript use an arbitrary-precision decimal library. IEEE-754 floats will pass most test cases and then fail a handful — the worst kind of bug.
What changes from 2025 to 2026
Each annual PAP carries the year's updated parameters. For 2026, the headline movements were already legislated in the tax-relief packages passed in late 2024: the basic allowance (Grundfreibetrag) rises again (from EUR 12,096 in 2025 to EUR 12,348 in 2026 under that legislation), the child allowance (Kinderfreibetrag) increases correspondingly, and dependent values — the tariff zone boundaries and the solidarity-surcharge exemption threshold (Freigrenze) — shift with them. Contribution-related inputs that feed the Vorsorgepauschale (such as the average health-insurance add-on rate and the contribution ceilings used inside the tax calculation) are also updated annually.
Two practitioner warnings:
- Take constants only from the published PAP itself. Press summaries round numbers and omit interactions; late amendments happen. The XML's constants block is the single source of truth for the year, and a revised mid-year PAP supersedes it from its stated effective date.
- Version your calculator by validity window, not by calendar year. Recent years have repeatedly required two PAPs per year (an initial one and a revised one effective from a mid-year month, with retroactive correction permitted via the payroll). Your engine should resolve "payment date → PAP version" through configuration.
Testing against the BMF's official service and test data
You never have to guess whether your implementation is correct. Two official instruments exist:
1. The BMF online calculator and its developer interface. The BMF operates its wage- and income-tax calculator at bmf-steuerrechner.de. Beyond the browser UI, the service exposes a parameterized interface that accepts the PAP input variables (STKL, LZZ, RE4 in cents, and so on) for a given PAP version and returns the computed outputs (LSTLZZ, SOLZLZZ, BK) as XML. Access conditions have changed over time — check the current terms on the site — but the pattern for implementers is stable: treat the BMF service as a reference oracle, fire your own test matrix at both your engine and the service, and diff the results. Do this at integration-test time, not per payroll run; production payroll must not depend on an external service being up.
2. The published test-case tables. Each PAP release comes with official test data: tables of input combinations (across tax classes, periods, wage levels, child allowances, and insurance situations) with the expected outputs. A compliant implementation reproduces every row exactly — to the cent. Wire these tables into your CI suite as golden tests. When the 2026 PAP (or a revision) lands, regenerate the calculator, swap in the new test tables, and your pipeline tells you within minutes whether you are compliant.
A minimal sanity check we run on every engagement, in pseudocode:
for each row in official_test_table_2026:
result = pap2026.calculate(row.inputs)
assert result.LSTLZZ == row.expected.LSTLZZ # exact, in cents
assert result.SOLZLZZ == row.expected.SOLZLZZ
assert result.BK == row.expected.BK
If even one assertion is off by a cent, do not patch the output — find the rounding step you got wrong. One-cent diffs are almost always a misplaced truncation or a float that leaked into the pipeline.
Mapping the PAP to Odoo payroll (l10n_de)
Odoo's payroll framework computes payslips through salary rules organized in a salary structure, reading from the contract, the employee record, and input lines. Germany's localization layers German specifics on top. Whether you use Odoo's own German payroll localization, a partner localization, or a custom build, the architecture that works is the same:
| PAP concept | Where it lives in Odoo |
|---|---|
| Input STKL (tax class) | Employee record — German payroll setups add a Steuerklasse field, sourced from ELStAM |
| ZKF (child allowance count) | Employee record (note: halves like 0.5 and 1.5 are valid — use a decimal field, not an integer) |
| R / church-tax attribute | Employee record (denomination from ELStAM); the 8%/9% state rate applied on BK in a separate rule |
| RE4 (period wage, cents) | Computed from the contract wage plus taxable input lines on the payslip — converted to cents before entering the PAP |
| LZZ (payment period) | Derived from the payslip period (monthly payroll → LZZ 2) |
| KVZ, PKV, KRV, PVS, PVZ (insurance flags/rates) | Contract-level insurance configuration |
| Outputs LSTLZZ / SOLZLZZ / BK | Three salary rules that call the PAP calculator and post the results (converted back from cents) |
The design rules we hold to in Odoo customization projects for German clients:
- Isolate the PAP in its own module/class, one per version, with a resolver that picks the version from the payslip's payment date. Salary rules call
compute_lohnsteuer(payslip)— they never contain tariff math inline. Inline Python in salary-rule definitions is untestable and unauditable for something this sensitive. - Generate the calculator from the official XML and commit both the generated code and the source XML. Your auditor — and the next developer — can then verify provenance.
- Keep cents end-to-end inside the calculator. Convert Odoo's float currency values to
Decimalcents at the boundary, never inside. - Golden-test in CI with the official test tables, as above, plus a handful of your client's real anonymized payslips cross-checked against the BMF reference service.
And a scope warning we give every client: passing the PAP makes your tax calculation correct. Full German payroll operation additionally involves ELStAM retrieval for employee tax attributes, social-insurance calculation and electronic reporting (DEÜV), and wage-tax filing via ELSTER — procedures with their own certification and communication requirements that no PAP implementation covers. Treat "we implemented the PAP" as one milestone of roughly three, and validate the overall setup with a German tax advisor (Steuerberater) before the first live payroll run. For the broader context of running a German entity on Odoo — GoBD, e-invoicing, DATEV export — see our Odoo ERP guide for German businesses.
Frequently Asked Questions
What is the BMF Programmablaufplan Lohnsteuer?
It is the official, legally binding algorithm published annually by Germany's Federal Ministry of Finance for the machine calculation of wage tax, solidarity surcharge, and the church-tax assessment base under § 39b EStG. It is released as a flowchart/pseudocode PDF and as a machine-readable XML file, and every payroll system in Germany must produce results consistent with it to the cent.
Where do I get the PAP 2026 XML file?
The BMF publishes the machine-readable PAP alongside its official wage-tax calculator on the bmf-steuerrechner.de developer pages, with the announcement of each year's PAP appearing on bundesfinanzministerium.de. Download the XML for the exact version and validity window you need — and watch for revised mid-year PAPs (geänderte Programmablaufpläne), which supersede the initial release from their stated effective date.
Is there an official API to calculate German wage tax?
The BMF's online calculator at bmf-steuerrechner.de exposes a parameterized developer interface that accepts the PAP input variables and returns the computed outputs as XML. It is intended as a reference and test oracle, not as a production dependency — use it to validate your own implementation in integration tests, and check the current access terms on the site, since they have changed over time.
What inputs does the PAP wage-tax calculation need?
The core inputs are STKL (tax class 1–6), LZZ (payment period: year, month, week, or day), RE4 (the period's taxable wage in euro cents), ZKF (number of child allowances, halves allowed), church-tax liability, allowance amounts from the employee's ELStAM record, and a set of insurance flags and rates (statutory vs private health insurance, care-insurance specifics, the health-insurance add-on rate) that feed the Vorsorgepauschale. The main outputs are LSTLZZ (wage tax), SOLZLZZ (solidarity surcharge), and BK (church-tax base), all in cents.
Does Odoo support the German Programmablaufplan out of the box?
Odoo provides a payroll framework and German localization content, but in our project experience any production German payroll on Odoo needs careful localization work: a versioned PAP calculator module called from salary rules, employee fields for ELStAM attributes (Steuerklasse, child allowances, denomination), correct cent-based decimal arithmetic, and golden tests against the official BMF test tables. Social-insurance calculation and electronic reporting (DEÜV, ELSTER) are separate workstreams on top of the PAP.
Why does my implementation differ from the official calculator by one cent?
Almost always rounding: the PAP prescribes exact decimal arithmetic in cents with truncation or rounding at specific algorithm steps, and an implementation that uses binary floating-point or rounds at the wrong step will match most inputs but drift by a cent on edge cases. Use a decimal type end-to-end, follow the prescribed scale operations literally, and locate the divergence by comparing intermediate variables against the pseudocode rather than patching the final output.
Written by
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Transform Your Business with Odoo ERP
Expert Odoo implementation, customization, and support to streamline your operations.
Related Articles
How Much Does a CRM System Cost in 2026? Real Pricing From 40+ Implementations
Real CRM pricing from 40+ implementations: license costs per user, implementation fees, hidden costs, and 3-year TCO for Odoo, HubSpot, Salesforce, and more.
eMAG Odoo Integration: Connect Romania's Largest Marketplace to Your ERP (Orders, Stock, e-Factura)
Connect eMAG Marketplace to Odoo ERP: offer and order sync, AWB shipping, returns, stock and price updates, plus Romanian e-Factura compliance for sellers.
EOQ, Safety Stock, and Reorder Point: Formulas, Worked Examples, and Odoo Setup
EOQ, safety stock, and reorder point explained with plain formulas, worked numeric examples, service-level z-score tables, and Odoo reordering rule setup.
More from Compliance & Regulation
ERP for Clothing & Fashion Brands: Size-Color Matrix, Seasonal Planning, and Compliance (2026 Guide)
How fashion and clothing brands choose an ERP in 2026: size-color matrix variants, seasonal planning, GoBD and DATEV compliance, vendor comparison, and costs.
ERPNext HR & Payroll in 2026: Setup, Salary Structures, and Multi-Country Compliance
Step-by-step ERPNext HR and payroll setup for 2026: HRMS app install, salary structures, payroll entry runs, income tax slabs, multi-country compliance.
GoHighLevel A2P 10DLC Compliance in 2026: Registration, Fees, and Fixing Blocked SMS
Complete GoHighLevel A2P 10DLC guide for 2026: brand and campaign registration steps, carrier fees, common rejection reasons, and how to fix filtered SMS.
GxP Validation for ERP Systems: What Your 2026 Validation RFP Must Require (CSV, IQ/OQ/PQ, Audit Trails)
What a GxP ERP validation RFP must require in 2026: CSV and CSA scope, 21 CFR Part 11, EU Annex 11, IQ/OQ/PQ deliverables, audit trails, and GAMP 5 risk.
OpenClaw Security Model, Data Residency, SOC 2 and ISO 27001
OpenClaw security architecture: tenant isolation, encryption, secret management, audit logs, data residency, SOC 2, ISO 27001, GDPR, HIPAA fitness.
Cybersecurity for E-commerce: Protect Your Business in 2026
Complete ecommerce cybersecurity guide for 2026. PCI DSS 4.0, WAF setup, bot protection, payment fraud prevention, security headers, and incident response.