Skip to main content

ugaroll

A standalone Uganda payroll engine for Python. Handles PAYE, NSSF, Local Service Tax (LST), payslips, and paysheets — all with zero dependencies.

Features

  • basic, gross, or net driven payroll requests
  • Built-in preset categories: overtime, bonus, transport, SACCO, housing benefit, gratuity, and more
  • Simplified from_hrm() factory for quick category creation with just taxable, nssfable, and nature
  • Percentage-based line items (% of basic or % of gross)
  • Batch payroll processing with aggregate totals
  • Resident, non-resident, secondary-employment, and secondary-above-10M tax modes
  • Configurable PAYE, NSSF, and LST policies
  • HTML payslip and paysheet rendering out of the box

Installation

pip install ugaroll

Quick Example — Single Payslip

from uganda_payroll import (
    EmployerProfile,
    EmployeeProfile,
    EmploymentTaxMode,
    InputMode,
    PayrollCalculator,
    PayrollLineItem,
    PayrollPeriod,
    PayrollRequest,
    PaymentCategory,
    render_payslip_html,
)

calculator = PayrollCalculator()

# Use preset categories — no need to configure effects manually
overtime = PaymentCategory.overtime()
sacco = PaymentCategory.sacco_deduction()

request = PayrollRequest(
    employer=EmployerProfile(name="Nkozi Farms Ltd", tin="1005827461"),
    employee=EmployeeProfile(
        full_name="Grace Atuhaire",
        staff_number="7023",
        tin="1028463915",
        nssf_number="8842100557031",
        department="Logistics",
        position="Fleet Coordinator",
        employment_tax_mode=EmploymentTaxMode.PRIMARY_RESIDENT,
    ),
    period=PayrollPeriod.from_month(year=2026, month=3),
    input_mode=InputMode.BASIC_PAY,
    target_amount=2_450_000,
    items=[
        PayrollLineItem(category=overtime, amount=185_000),
        PayrollLineItem(category=sacco, amount=50_000),
    ],
)

result = calculator.calculate(request)
payslip_html = render_payslip_html(result)

print(f"Gross:  {result.gross_earnings:,}")
print(f"PAYE:   {result.total_paye:,}")
print(f"NSSF:   {result.nssf_employee:,}")
print(f"Net:    {result.net_pay:,}")

# Save payslip to file
with open("payslip_grace.html", "w") as f:
    f.write(payslip_html)

Paysheet — Multiple Employees

from uganda_payroll import (
    AmountMode,
    EmployerProfile,
    EmployeeProfile,
    InputMode,
    PaymentCategory,
    PayrollBatchRequest,
    PayrollCalculator,
    PayrollLineItem,
    PayrollPeriod,
    PayrollRequest,
    render_paysheet_html,
)

calculator = PayrollCalculator()
employer = EmployerProfile(name="Nkozi Farms Ltd", tin="1005827461")
period = PayrollPeriod.from_month(2026, 3)

bonus = PaymentCategory.bonus()
transport = PaymentCategory.transport()
overtime = PaymentCategory.overtime()

requests = [
    PayrollRequest(
        employer=employer,
        employee=EmployeeProfile(
            full_name="Grace Atuhaire", staff_number="7023",
            tin="1028463915", department="Logistics",
        ),
        period=period,
        input_mode=InputMode.BASIC_PAY,
        target_amount=2_450_000,
        items=[PayrollLineItem(category=overtime, amount=185_000)],
    ),
    PayrollRequest(
        employer=employer,
        employee=EmployeeProfile(
            full_name="Kenneth Obol", staff_number="7041",
            tin="1033918204", department="Engineering",
        ),
        period=period,
        input_mode=InputMode.BASIC_PAY,
        target_amount=4_100_000,
        items=[
            PayrollLineItem(category=bonus, amount=300_000),
            PayrollLineItem(category=transport, amount=120_000),
        ],
    ),
    PayrollRequest(
        employer=employer,
        employee=EmployeeProfile(
            full_name="Esther Nambooze", staff_number="7055",
            tin="1041672583", department="Finance",
        ),
        period=period,
        input_mode=InputMode.BASIC_PAY,
        target_amount=1_800_000,
        items=[
            # 5% of basic as overtime
            PayrollLineItem(
                category=overtime, amount=5,
                amount_mode=AmountMode.PERCENTAGE_OF_BASIC,
            ),
        ],
    ),
]

# Calculate individually and render a paysheet
results = [calculator.calculate(r) for r in requests]
paysheet_html = render_paysheet_html(results, title="March 2026 Paysheet — Nkozi Farms Ltd")

with open("paysheet_march.html", "w") as f:
    f.write(paysheet_html)

# Or use batch processing for aggregate totals
batch = PayrollBatchRequest(
    employer=employer,
    period=period,
    employee_requests=tuple(requests),
)
batch_result = calculator.calculate_batch(batch)

print(f"Total Net Pay:       {batch_result.total_net_pay:,}")
print(f"Total PAYE:          {batch_result.total_paye:,}")
print(f"Total NSSF (EE):     {batch_result.total_nssf_employee:,}")
print(f"Total NSSF (ER):     {batch_result.total_nssf_employer:,}")
print(f"Total Employer Cost: {batch_result.total_employer_cost:,}")

Preset Categories

Create common Ugandan payroll categories in one line:

from uganda_payroll import PaymentCategory

PaymentCategory.overtime()            # Taxable + NSSF
PaymentCategory.bonus()               # Taxable + NSSF
PaymentCategory.transport()           # Non-taxable, no NSSF
PaymentCategory.housing_benefit()     # Taxable memo (non-cash benefit)
PaymentCategory.sacco_deduction()     # Post-tax deduction
PaymentCategory.salary_advance()      # Post-tax deduction
PaymentCategory.payroll_correction()  # Reduces PAYE base, not NSSF
PaymentCategory.medical_insurance()   # Employer contribution
PaymentCategory.gratuity()            # Post-tax addition
PaymentCategory.pension_deduction()   # Pre-tax, reduces PAYE base
PaymentCategory.notice_pay()          # Taxable + NSSF
PaymentCategory.leave_compensation()  # Taxable + NSSF

All presets accept optional code and name overrides:

PaymentCategory.overtime(code="ot_weekend", name="Weekend Overtime")

Simplified Category Creation

If you don't need full control over every effect flag, use from_hrm() to create categories with just three fields:

from uganda_payroll import PaymentCategory

# A non-taxable meal allowance
meal = PaymentCategory.from_hrm(
    code="meal",
    name="Meal Allowance",
    nature="Increment",
    taxable=False,
    nssfable=False,
)

# A taxable, NSSF-attracting commission
commission = PaymentCategory.from_hrm(
    code="comm",
    name="Sales Commission",
    nature="Increment",
    taxable=True,
    nssfable=True,
)

Supported nature values: "Increment", "Deduction", "Increment2", "Do nothing", "Bonus"

Notes on Defaults

  • Resident monthly PAYE bands match current URA schedules
  • Secondary employment: 30% flat, or 40% if all employments exceed UGX 10M
  • Non-resident rates are configurable; the default uses 10% for the first bracket
  • LST is configurable — the default mirrors common "Chop Once" / "Chop Four times" patterns
  • NSSF: 5% employee, 10% employer on total employment income (basic + all qualifying allowances)

Metadata

Release files for ugaroll 0.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ugaroll 0.2.2
File Size Uploaded Provenance Actions
ugaroll-0.2.2.tar.gz 17.3 kB Without provenance Details for ugaroll-0.2.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ugaroll 0.2.2
File Interpreter ABI Platform Actions
ugaroll-0.2.2-py3-none-any.whl Python 3 none any Details for ugaroll-0.2.2-py3-none-any.whl

Total release size: 32.1 kB

Release files / ugaroll-0.2.2.tar.gz

Download URL ugaroll-0.2.2.tar.gz
Size 17.3 kB
Tags Source
SHA-256 checksum
How to use checksums
9047246968ba5ff5ddc8b41b862a1a24d711d68d350a3fe60fecdddaf4354732
BLAKE2b-256 checksum
How to use checksums
59214b3c9880d0bf8ed19a7149dd79fdb66ecaa6f2a2f230b6a38fc7e0f40aea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release files / ugaroll-0.2.2-py3-none-any.whl

Download URL ugaroll-0.2.2-py3-none-any.whl
Size 14.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4944918cf3d1a6075fe59483fa05b8681fa06b4ca65223c768fb2fb5681006c9
BLAKE2b-256 checksum
How to use checksums
3b8991e8a843058c8c931864536ad70091b1852ba6877d0cfdf372ebc4871adc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Security for ugaroll 0.2.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page