Search for Tanzania's Skills Development Levy rate and you'll find three different answers: 4.5%, 4%, and 3.5%. All three are published confidently. Only one is current.
The correct figure is 3.5%. The Finance Act 2023 reduced SDL from 4% to 3.5%, effective 1 July 2023, and the Tanzania Revenue Authority's own SDL page states the 3.5% rate. The other figures are what the levy used to be — 4.5% until it dropped, then 4% until 2023 — preserved in blog posts, calculators, and payroll configurations nobody went back to update.
That's the real lesson of Tanzanian payroll, and it applies directly to how you set it up in Odoo: the rates change, so your configuration has to be built to change with them.
The five components, and which side they sit on
Tanzanian payroll has five statutory components. The single most important thing to get right in Odoo isn't the percentage — it's which side of the payslip each one belongs on.
| Component | Rate | Who pays | Affects net pay? |
|---|---|---|---|
| PAYE | Progressive, 0–30% | Employee | Yes — reduces net |
| NSSF (employee) | 10% of gross | Employee | Yes — reduces net |
| NSSF (employer) | 10% of gross | Employer | No — employer cost |
| SDL | 3.5% of gross emoluments | Employer | No — employer cost |
| WCF | 0.5% of wage bill | Employer | No — employer cost |
| HESLB | 15% of basic, where applicable | Employee | Yes — reduces net |
Three of these never touch the employee's take home pay. That's precisely where configurations go wrong.
SDL applies to mainland employers with ten or more employees; in Zanzibar the rate is 5% and the threshold is four employees. The WCF rate above is for private-sector employers. Public-sector employees contribute to PSSSF rather than NSSF, on a different split.
The error that produces a payslip that looks right
In Odoo payroll, every salary rule belongs to a category and the category decides what the rule does to net pay. Deductions reduce it. Company contributions don't.
Put an employer contribution in a deduction category, and it quietly comes out of the employee's pay. Put PAYE in a taxable-salary category instead of a deduction, and it's calculated but never actually subtracted.
The dangerous part is that neither mistake breaks anything visibly. The payslip still totals. The structure still validates. The numbers just aren't true and you find out when an employee queries their pay, or when a filing doesn't reconcile.
We've seen both in real configurations. Also common: a duplicated rule code that silently overwrites the gross calculation, and PAYE computed on basic salary alone while taxable allowances are ignored.
Order of operations matters
The calculation has to run in the right sequence:
- Gross pay — basic salary plus taxable allowances. Most cash allowances (transport, lunch, airtime) are taxable, and belong in gross.
- Employee NSSF — 10% of gross.
- Taxable income — gross less the employee's NSSF contribution.
- PAYE — computed on taxable income using the progressive bands.
- HESLB — where the employee has a loan-repayment obligation.
- Net pay — gross less employee NSSF, PAYE, and HESLB.
- Employer contributions — NSSF employer, SDL and WCF, computed separately and excluded from net.
In Odoo, rule sequence numbers control this order. A PAYE rule that runs before the NSSF rule is computing on the wrong base, however correct its formula looks.
One note: published sources are not fully consistent on whether the employee's NSSF contribution is deducted before PAYE. Standard practice is to deduct it first, and that's how we configure it — but it's exactly the kind of point worth confirming with your tax advisor, because it moves PAYE materially.
The PAYE bands, and a worked example
PAYE uses five monthly bands on taxable income:
- Up to TZS 270,000 — 0%
- TZS 270,001 – 520,000 — 8% of the amount above 270,000
- TZS 520,001 – 760,000 — TZS 20,000 plus 20% of the amount above 520,000
- TZS 760,001 – 1,000,000 — TZS 68,000 plus 25% of the amount above 760,000
- Above TZS 1,000,000 — TZS 128,000 plus 30% of the amount above 1,000,000
The fixed amounts are a useful check. TZS 20,000 is exactly 8% of the 250,000-shilling second band, and each later figure builds on the one before. If a source gives you a different rate for any band, test it against these — the arithmetic has to hold.
Worked example — TZS 1,500,000 gross, all taxable:
| Line | Calculation | Amount (TZS) |
|---|---|---|
| Gross pay | 1,500,000 | |
| NSSF employee | 10% × 1,500,000 | 150,000 |
| Taxable income | 1,500,000 − 150,000 | 1,350,000 |
| PAYE | 128,000 + 30% × 350,000 | 233,000 |
| Net pay | 1,500,000 − 150,000 − 233,000 | 1,117,000 |
| NSSF employer | 10% × 1,500,000 | 150,000 |
| SDL | 3.5% × 1,500,000 | 52,500 |
| WCF | 0.5% × 1,500,000 | 7,500 |
| Total cost to employer | 1,710,000 |
The employee takes home 1,117,000. The employer pays 1,710,000. That TZS 593,000 gap is what a salary actually costs, and it's worth knowing before you quote one.
HESLB is conditional
HESLB loan repayment applies only to employees who have an outstanding student loan — typically 15% of basic salary. It should never be a blanket rule applied to everyone.
The clean way to handle it in Odoo is a conditional salary rule that fires only when a deduction has been recorded for that employee. Staff without a HESLB obligation should see nothing at all on their payslip, not a zero line.
Build rates as parameters, not constants
This is the part that separates a payroll configuration that lasts from one that quietly goes wrong.
Tanzanian rates change through the annual Finance Act, which takes effect on 1 July. SDL has moved three times in recent years. If your SDL rule contains result = categories.GROSS * 0.035, then the next rate change means editing salary rule code — and someone has to remember to do it.
Odoo supports salary rule parameters: named values with effective dates, referenced from rules instead of hard-coded into them. Set the SDL rate as a parameter, and a future change becomes a new dated value rather than a code edit. Historical payslips keep calculating with the rate that applied at the time; new ones pick up the new rate automatically.
It's a small decision at setup that removes an entire category of future error.
Verify against real payslips, not the structure
A salary structure that validates proves only that the formulas run. It doesn't prove they're right.
Before any Tanzanian payroll goes live, compute a handful of real payslips — different salary levels, one with allowances, one with a mid-month start, one with HESLB — and check each line by hand against the bands. If the numbers hold across all of them, the structure is correct. If one doesn't, you've found the problem before a real employee does.
A final practical note
Every figure in this post should be treated the way we'd treat it in a live configuration: verified against the primary source rather than copied from a calculator. For PAYE and SDL that means TRA; for NSSF and WCF, the funds themselves. The whole reason this post exists is that secondary sources go stale — including, eventually, this one.
If you're configuring Tanzanian payroll in Odoo, or want an existing structure checked before your next payroll run, we configure and verify Odoo HR and payroll for Tanzanian employers. Get in touch.