Skip to content

Tax

This guide tells you how the system calculates tax withholding. In the Payroll module, there is a Taxes submodule.

The examples in this guide use Uganda PAYE (Pay As You Earn). For the employee deductions like NSSF that goes with it, see Deductions.


Purpose

This screen sets the amount of tax that taken from each payslip, and the income that the tax is calculated on.


The Two Parts of a Tax Setup

A tax setup has two parts:

  1. A filing status. This record holds the name of the tax and the income brackets that the tax applies to.
  2. A calculation method. This is either a table of tax brackets or a block of custom Python code.

Assign the filing status to each employee contract. See Contracts. If a contract has no filing status, the system calculates no tax for that employee.


Filing Status: the Income Basis

The Based on field of a filing status selects the income that the system calculates the tax on. There are three options.

OptionThe system calculates it as
Basic payThe contract wage for the period, less unpaid leave.
Gross payBasic pay plus all allowances.
Taxable gross payGross pay, less the allowances that have Is taxable off, less all pre-tax deductions.

For Uganda PAYE, select Taxable gross pay. The Uganda term for this figure is chargeable income. Two system controls change it:

  • An allowance that has Is taxable off increases gross pay but does not increase taxable gross pay. Use this setting for an exempt benefit, for example the refund of an actual expense that the employee incurred for the work.
  • A pre-tax deduction decreases taxable gross pay. Uganda gives very few reliefs of this type. Read the warning in Deductions before you make a deduction pre-tax.

Caution: Employee NSSF contributions are not a relief against Uganda PAYE. Do not mark the NSSF deduction as pre-tax. See Deductions.


Case Study: Uganda PAYE

The Uganda Revenue Authority (URA) applies these rates to the monthly chargeable income of a resident individual.

Monthly chargeable income (UGX)Rate on the amount in the bracket
0 to 235,0000%
235,001 to 335,00010%
335,001 to 410,00020%
410,001 to 10,000,00030%
More than 10,000,00040%

Note: Confirm the current rates and the residency rules with the URA and with your tax advisor before you use this setup for real pay. This guide shows how to configure the system. It is not tax advice.

The URA writes the last bracket as 30%, plus an additional 10% on the amount that is more than 10,000,000. The two forms give the same result. A marginal rate of 40% on the top bracket is the correct way to enter the last bracket in the system.

Verification. For a monthly chargeable income of 15,000,000 UGX:

  • URA form: 25,000 + 30% x (15,000,000 - 410,000) + 10% x (15,000,000 - 10,000,000) = 4,902,000
  • The system form: 10,000 + 15,000 + 30% x 9,590,000 + 40% x 5,000,000 = 4,902,000

Annual figures used

The system calculates tax on an annual basis. See How the Calculation Runs. Multiply each monthly URA limit by 12 to get the value to enter.


Method 1: Tax Brackets

Tax brackets are the recommended method for Uganda PAYE. An administrator can edit them at any time, and they need no code.

The system applies the brackets in the normal marginal way. It taxes the part of the income in each bracket at that bracket's rate.

Procedure

  1. Open Payroll > Taxes.
  2. Create a filing status. Give it the name PAYE.
  3. Set Based on to Taxable gross pay.
  4. Leave Python Code off.
  5. Open the new filing status.
  6. Add the five tax brackets from the table below, in order.

The brackets to enter

Min. Income (UGX)Max. Income (UGX)Tax Rate
02,820,0000
2,820,0014,020,00010
4,020,0014,920,00020
4,920,001120,000,00030
120,000,001(leave empty)40

An empty Max. Income field makes the bracket unlimited. Use it for the last bracket only.

Caution: The minimum income of a bracket must be more than the maximum income of the bracket before it. If you enter 2,820,000 as the minimum of the second bracket, the system rejects it. Always add 1 to the previous maximum. The 1 UGX gap changes the tax by less than 1 UGX.

Enter the annual limits, not the monthly limits. If you enter the monthly limits, the system taxes almost all of the income at the top rate.


Method 2: Custom Python Code

A filing status can use Python code in place of tax brackets. Use this method only for rules that brackets cannot express. Uganda PAYE does not need it.

Custom code runs inside the payroll calculation. Limit access to trusted technical staff.

The rules the code must obey

RuleReason
Name the function calculate_taxes.The system looks for this name. It also accepts the earlier name calculate_federal_tax.
Accept the annual income as the first argument.The system supplies the annual figure, not the monthly figure.
Return the annual tax.The system divides the result over the payslip period.
Return a float.The system adds the result to other float amounts. A Decimal return causes a TypeError.
Put every constant and every import inside the function.The system runs the code with separate global and local dictionaries. The function cannot read a name from the module level.

Caution: The system records an error in the log and then sets the tax to zero if the code fails. The payslip shows no error. Always compare the first payslip against a manual calculation.

A print() call in the code has no effect. The system replaces it. An if __name__ == "__main__": block never runs, because __name__ resolves to builtins inside the payroll calculation.

Uganda PAYE as custom code

python
"""Uganda PAYE for a resident individual. URA monthly brackets, made annual."""


def calculate_taxes(yearly_income, **kwargs):
    """Return the annual PAYE on `yearly_income`, in UGX."""
    months = 12

    # (monthly upper limit of the bracket, rate). None means no upper limit.
    monthly_brackets = [
        (235000.0, 0.00),
        (335000.0, 0.10),
        (410000.0, 0.20),
        (10000000.0, 0.30),
        (None, 0.40),
    ]

    income = float(yearly_income)
    if income <= 0:
        return 0.0

    tax = 0.0
    lower = 0.0
    for monthly_upper, rate in monthly_brackets:
        upper = None if monthly_upper is None else monthly_upper * months
        if upper is None or income <= upper:
            tax += (income - lower) * rate
            break
        tax += (upper - lower) * rate
        lower = upper

    return round(tax, 2)

Caution: The system shows the Python Code field only when you create a filing status. The field disappears after you save. To change the code, create a new filing status and move the contracts to it.


How the Calculation Runs

The system does these steps for each payslip:

  1. It finds the income for the payslip period, on the basis that the filing status selects.
  2. It makes the income annual. It divides the income by the number of days in the period, then multiplies by the number of days in the calendar year.
  3. It calculates the annual tax from the brackets or from the custom code.
  4. It divides the annual tax back over the period. It divides by the number of days in the year, then multiplies by the number of days in the period.

The result changes with the length of the month

Step 2 uses the true number of days in the payslip period. A month has 28, 30, or 31 days, so the annual figure is not exactly 12 times the monthly figure. The tax for the period changes by a small amount from month to month.

For a monthly taxable gross pay of 1,700,000 UGX, the correct URA monthly PAYE is 412,000.00 UGX. The system calculates these amounts:

Length of monthPAYE (UGX)Difference (UGX)
31 days410,120.50-1,879.50
30 days413,342.42+1,342.42
28 days419,786.26+7,786.26

The difference is a fixed number of shillings for each length of month. It does not increase with the salary, because it comes from the fixed amounts in the brackets.

The differences cancel over a full calendar year. For the same employee, the 12 payslips give a total of 4,943,999.40 UGX. Twelve URA monthly calculations give 4,944,000.00 UGX. The difference is 0.60 UGX, which is rounding.

The annual total is therefore correct. Only the month-to-month distribution moves. Tell your payroll administrator about this behaviour, because a single payslip does not agree with the URA monthly calculator.


Worked Example

An employee has this monthly pay:

ComponentAmount (UGX)
Basic pay1,200,000.00
Transport allowance (taxable)200,000.00
Housing allowance (taxable)300,000.00
Gross pay1,700,000.00

The employee has the PAYE filing status, with Based on set to Taxable gross pay. The NSSF deduction is post-tax, so it does not decrease the taxable gross pay.

StepAmount (UGX)
Taxable gross pay1,700,000.00
PAYE for June (a 30-day month)413,342.42
Employee NSSF at 5% of gross pay85,000.00
Net pay1,201,657.58

The payslip shows the PAYE amount on a row that has the name of the filing status, PAYE.


What Each User Sees

ActionEmployeeHR / Payroll Administrator
See the tax on their own payslip
Create or change filing statuses and tax brackets

How Visibility Is Decided

  1. Ownership. Each employee sees their own tax amount on their own payslip.
  2. Permissions. To open the Taxes screen, a user must have the payroll.view_filingstatus permission.

Customization Options

  • Filing statuses and their tax brackets are fully company-defined. The system supplies no tax table.
  • The income basis (basic pay, gross pay, or taxable gross pay) is a selection on each filing status.
  • The calculation method is either tax brackets or custom Python code.
  • The name of the filing status becomes the label of the tax row on the payslip.

Good to Know

  • A contract without a filing status gets no tax. The system does not assign a filing status automatically. Assignment is a deliberate step. After you create the PAYE filing status, add it to every contract.
  • Each company keeps its own filing statuses. In a multi-company system, create the PAYE filing status again for each company.
  • The payslip shows the name of the filing status as the tax row. An employee with the PAYE filing status sees a PAYE row. A payslip that the system made before this behaviour existed shows the earlier Taxes label.
  • The system applies one filing status to each contract. To give a different rate to non-resident employees (independent contractors), create a second filing status with its own brackets, then assign it to those contracts.
  • The tax figure is a snapshot. The system stores the calculated amounts on the payslip. A later change to a bracket does not change a payslip that already exists. Generate the payslip again to apply new brackets.
The math, never a mystery.