Payroll across East Africa: NSSF, NHIF, PAYE and everything after
Kenya alone stacks NSSF, SHIF, PAYE and the housing levy on every payslip, and each changes on its own schedule. Cross the border into Uganda, Tanzania or Rwanda and the rules change again. Here is how to run all of it from one ERPNext.
If your business employs people in more than one East African country, payroll stops being a monthly chore and becomes a compliance surface. Each country runs its own social security scheme, its own health levy, its own income tax bands, and its own filing deadlines, and each of those moves on its own budget cycle. Kenya alone now stacks four separate statutory deductions on a single payslip. Run staff in Uganda, Tanzania, or Rwanda as well, and the number of distinct rules you must keep correct every month multiplies fast.
This guide walks the actual deductions country by country, then shows how we run them from a single ERPNext install using the same country-as-strategy architecture we use across eleven African countries.
Kenya: four deductions on every payslip
Kenya is the most layered payroll in the region, and it is worth understanding in full because the same structure repeats, in lighter form, elsewhere.
- NSSF: the National Social Security Fund is now tiered, with contributions calculated against lower and upper earnings limits that have been rising under the phased implementation of the NSSF Act. Both employee and employer contribute, and the pensionable-earnings ceiling changes as the tiers step up, so last year's figures are not this year's.
- SHIF: the Social Health Insurance Fund replaced NHIF and is levied as a percentage of gross pay rather than the old banded NHIF table. This is a recent, material change: a payroll still computing the old NHIF bands is computing the wrong number.
- PAYE: income tax is charged on graduated bands with personal relief applied, and the bands and relief amounts are set by the Finance Act and revised periodically.
- Affordable Housing Levy: a levy on gross pay, contributed by both employee and employer, introduced as a standing deduction rather than a one-off.
The lesson from Kenya is that statutory payroll is not stable. NHIF became SHIF, NSSF moved to tiers with rising ceilings, and the housing levy appeared as a new line entirely. A payroll built by hardcoding this year's rates will be wrong within a budget cycle. The rates must live in configuration you can update without touching code.
Uganda: leaner, but not trivial
Uganda's statutory stack is shorter but has its own shape. NSSF Uganda takes an employee and an employer contribution as percentages of gross pay, under a different governing act and different rates from Kenya's fund of the same name. The shared name is a trap for anyone who assumes the rules travel across the border. PAYE follows Uganda's own graduated income tax bands, denominated in Ugandan shillings, with its own thresholds and its own tax-free floor. There is also Local Service Tax to account for in the relevant periods. Nothing here maps one-to-one onto Kenya, which is exactly why a single hardcoded 'NSSF' calculation cannot serve both countries.
Tanzania: NSSF, PAYE and the skills levy
Tanzania adds a wrinkle Kenya and Uganda do not emphasise: an employer-borne training levy. The stack is NSSF Tanzania for social security, PAYE on Tanzania's own graduated bands in Tanzanian shillings, and the Skills Development Levy (SDL), which is charged to the employer on the total payroll rather than deducted from the employee. If your payroll model only knows how to deduct from an employee's gross, it cannot represent SDL correctly, because SDL is an employer cost computed on the whole payroll. A serious multi-country model has to handle both employee deductions and employer-side levies as separate categories.
Rwanda: RSSB and community-based health insurance
Rwanda routes social security and pension through the Rwanda Social Security Board (RSSB), covering pension and maternity contributions from both employee and employer, alongside contributions toward community-based health insurance. PAYE is charged on Rwanda's own bands in Rwandan francs. Again the pattern holds: same idea, different institution, different rates, different currency, different filing.
The common shape underneath the differences
Look across all four countries and a structure emerges. Every one of them has a social security contribution, most have a health contribution, all have graduated income tax with reliefs, and some add employer-only levies. What differs is the specific rate, ceiling, band, currency, and institution. The categories are shared; only the parameters vary.
The wrong way to build this is one payroll routine per country, copied and edited, four codebases drifting apart every budget season. The right way is one calculation engine that resolves the country's parameters at runtime and applies the same category logic with different numbers.
One hook, not a fork of HRMS
The first principle is to extend HRMS, never modify it. Frappe HRMS already owns the Salary Slip, its components, and the payroll run. Statutory deductions hook into that lifecycle through a single doc_event on Salary Slip validation. Everything statutory flows from that one entry point.
Because it is a validate hook on the standard Salary Slip, you inherit the entire HRMS payroll engine, leave management, attendance, salary structures, untouched and upgradeable. When HRMS releases a new version, you have nothing to merge. That is what keeps the build maintainable as you add countries.
Country as a strategy, resolved at runtime
Inside that hook, the flow resolves which country's rules apply and delegates to a calculator built for exactly that country. A registry maps each country to its calculator class and its settings DocType. Calculators are lazy-loaded and cached, so the Kenya calculator is only imported and instantiated when a Kenyan slip is processed. Adding a country is a registration, not a rewrite.
Each calculator inherits from an abstract BaseCalculator and implements one method, compute, which returns a dictionary of components to amounts. Four concrete implementations for East Africa live behind that interface today, and the engine never needs to know which one it is talking to. The variation, per-country tax rules, sits behind a stable contract: compute a deduction set for this slip.
What the calculators actually encode
The country-specific logic is more subtle than a flat percentage, which is why it belongs in dedicated, testable classes rather than scattered through salary structure formulas:
- Progressive PAYE bands with cumulative rate application. Each slice of income is taxed at its own rate, not the whole amount at the top rate.
- Tier-based social security with caps. Kenya's NSSF Tier I and Tier II each have ceilings the calculation has to respect.
- Personal relief applied as a tax credit, not a deduction. It reduces tax owed after the bands are applied, which changes the arithmetic.
- Employee versus employer contribution splits. An is_employer_only flag marks contributions that are a company cost rather than an employee deduction, so they never reduce net pay but still show on statutory reports.
The employer-only distinction is the one that quietly produces wrong numbers when teams roll their own payroll. If an employer NSSF contribution is treated as an employee deduction, net pay is understated and the employee is effectively charged for the company's obligation. Model the split explicitly from the start.
Settings and components, kept country-aware
Rates live in data, not code. Each country gets a single settings DocType, Kenya Payroll Settings, Uganda Payroll Settings, Tanzania Payroll Settings, Rwanda Payroll Settings, holding its current rates, plus child tables for the PAYE bands. When a budget changes a band, you edit a record; you do not deploy.
Salary components are namespaced by country so the same chart can carry all of them without collision. The convention is simple: Kenya components carry no suffix (PAYE, NSSF Employee), Uganda components get a UG suffix (PAYE UG, NSSF UG), Tanzania gets TZ, Rwanda gets RW. That small discipline lets a single company run mixed-country payroll while keeping each country's components cleanly filterable.
Rate changes mid-cycle
Statutory rates do not change on your release schedule. A finance act can shift PAYE bands or a levy rate with little notice, sometimes backdated. If you have already generated draft salary slips for the period, they now hold stale numbers.
This is why a bulk recalculation path matters. A well-built payroll layer exposes an API to recompute draft slips for a country and date range after a rate change, rather than forcing payroll staff to delete and regenerate by hand. The simulation API is worth calling out too. Being able to compute net pay for a hypothetical salary in a given country, without creating any document, turns offer-letter modelling and what-if analysis into a single call instead of a throwaway payroll run.
Currency, calendars and filing
Statutory calculation is only half the job. Each country pays in its own currency, so your payroll has to handle KES, UGX, TZS, and RWF cleanly, keep each employee's pay and deductions in their local currency, and still roll up to a group view for management. Filing deadlines differ by country and by scheme, and the outputs each authority expects, contribution schedules, PAYE returns, are country-specific documents, not one universal report.
We treat reporting as a core part of the build, not an afterthought. For each country the system produces the statutory schedules that country's authorities expect, in that country's currency, on that country's format. A payroll that computes the right deduction but cannot produce the filing your local authority accepts has only done half the compliance work.
What to insist on when you build this
Get the architecture right and East African payroll stops being a monthly risk. NSSF, SHIF, PAYE, the housing levy, and their equivalents across the border each become a configured rule the system applies automatically, and a new country becomes a setup task rather than a rebuild. The complexity does not disappear. It moves into configuration, where you can see it, update it, and prove it.
Running staff across more than one East African country on ERPNext, or planning to? We build multi-country statutory payroll across eleven African countries today. Book a discovery call and we will map your countries, components, and statutory reports to a single ERPNext payroll.
Working on something like this?
We ship ERPNext and custom Frappe apps across Kenya, Uganda, Tanzania, Rwanda, Ethiopia, and Somalia. Let's talk through your build.