How the Hong Kong salaries tax calculator is verified
Inputs
| Input | Field | Type |
|---|---|---|
| Gross annual salary (HKD) | grossAnnual | number |
| Allowance | household | string |
| Children you claim for | children | number |
The formula
Each derived value below is computed by the Valem engine from the expression shown, in this order. Nothing else runs — there is no hidden code path.
| Derived value | Field | Formula (JSONata) |
|---|---|---|
| Employee MPF (5%, capped) | mpfEmployee | ($m := $n(grossAnnual) / 12; $round(($m < $const.mpfMinRelevantMonthly ? 0 : ($m < $const.mpfMaxRelevantMonthly ? $m : $const.mpfMaxRelevantMonthly) * $const.mpfRate) * 12, 2)) |
| Mpf Deduction | mpfDeduction | mpfEmployee < $const.mpfDeductionCap ? mpfEmployee : $const.mpfDeductionCap |
| Net total income (after the MPF deduction) | incomeAfterDeductions | ($v := $n(grossAnnual) - mpfDeduction; $v < 0 ? 0 : $v) |
| Allowances | allowances | (household = 'marriedSoleEarner' ? $const.marriedAllowance : $const.basicAllowance) + $floor($n(children)) * $const.childAllowance |
| Net chargeable income | chargeableIncome | ($v := incomeAfterDeductions - allowances; $v < 0 ? 0 : $v) |
| Tax at progressive rates | taxProgressive | $round($bandedCharge(chargeableIncome, [{"upTo": $const.bandWidth, "rate": $const.rateBand1}, {"upTo": $const.bandWidth * 2, "rate": $const.rateBand2}, {"upTo": $const.bandWidth * 3, "rate": $const.rateBand3}, {"upTo": $const.bandWidth * 4, "rate": $const.rateBand4}, {"upTo": null, "rate": $const.rateTop}]), 2) |
| Tax at standard rate | taxStandard | $round($bandedCharge(incomeAfterDeductions, [{"upTo": $const.standardTierThreshold, "rate": $const.standardRate}, {"upTo": null, "rate": $const.standardRateUpper}]), 2) |
| Salaries tax (the lower) | salariesTax | taxProgressive < taxStandard ? taxProgressive : taxStandard |
| Net / month | netMonthly | $round(($n(grossAnnual) - mpfEmployee - salariesTax) / 12, 2) |
| Net / year (after MPF & salaries tax) | netAnnual | $round($n(grossAnnual) - mpfEmployee - salariesTax, 2) |
Rates and thresholds
The expressions above read these as $const. They are the figures the annual refresh replaces, so they live as data rather than being written into the formulas — which is what makes a year-on-year change a one-line diff instead of an edit to arithmetic.
| Constant | Value |
|---|---|
basicAllowance | 145000 |
marriedAllowance | 290000 |
childAllowance | 140000 |
bandWidth | 50000 |
rateBand1 | 0.02 |
rateBand2 | 0.06 |
rateBand3 | 0.1 |
rateBand4 | 0.14 |
rateTop | 0.17 |
standardRate | 0.15 |
standardRateUpper | 0.16 |
standardTierThreshold | 5000000 |
mpfRate | 0.05 |
mpfMinRelevantMonthly | 7100 |
mpfMaxRelevantMonthly | 30000 |
mpfDeductionCap | 18000 |
Test vectors (6)
Every case is executed against this model on each build, by the Java engine and independently by a JavaScript one. A mismatch of a single cent fails the build, so the page cannot ship advertising a figure the model no longer produces.
Case 1 — H1 IRD pam39e Q2: $480,000 with the basic allowance — $35,890 at progressive rates
given grossAnnual | 480000 |
|---|---|
given household | single |
given children | 0 |
expect mpfEmployee | 18000 |
expect incomeAfterDeductions | 462000 |
expect chargeableIncome | 317000 |
expect taxProgressive | 35890 |
expect taxStandard | 69300 |
expect salariesTax | 35890 |
expect netAnnual | 426110 |
Case 2 — H2 IRD pam39e Q5: same income, married person’s allowance — $12,080
given grossAnnual | 480000 |
|---|---|
given household | marriedSoleEarner |
given children | 0 |
expect chargeableIncome | 172000 |
expect salariesTax | 12080 |
Case 3 — H3 one child allowance
given grossAnnual | 480000 |
|---|---|
given household | single |
given children | 1 |
expect chargeableIncome | 177000 |
expect salariesTax | 12780 |
Case 4 — H4 $20,000 a month: MPF $1,000 a month, tax $2,980
given grossAnnual | 240000 |
|---|---|
given household | single |
given children | 0 |
expect mpfEmployee | 12000 |
expect chargeableIncome | 83000 |
expect salariesTax | 2980 |
expect netAnnual | 225020 |
Case 5 — H5 below the $7,100 minimum relevant income: no employee MPF and no tax
given grossAnnual | 84000 |
|---|---|
given household | single |
given children | 0 |
expect mpfEmployee | 0 |
expect salariesTax | 0 |
expect netAnnual | 84000 |
Case 6 — H6 $6m: the two-tiered standard rate is lower than progressive
given grossAnnual | 6000000 |
|---|---|
given household | single |
given children | 0 |
expect taxProgressive | 974290 |
expect taxStandard | 907120 |
expect salariesTax | 907120 |
expect netAnnual | 5074880 |
Sources
- Inland Revenue Department, A guide to Salaries Tax (pam39e) — progressive and two-tiered standard rates, the 2026/27 basic and married person’s allowances and the MPF deduction, with worked 2026/27 examples · checked 2026-10-12
- Inland Revenue Department, 2026-27 Budget — Tax Measures (FAQ): allowances from 2026/27, including the child allowance of $140,000 for each of the 1st to 9th child · checked 2026-10-12
- MPFA, Mandatory contributions — employees: 5% of relevant income between the minimum and maximum relevant income levels · checked 2026-10-12
Check it yourself — and reuse it
The whole model as JSON — inputs, outputs, formulas, vectors and sources in one re-runnable document.
This model is published under CC BY 4.0: use it, adapt it, build on it, with attribution. The licence covers the model — the expression of each rule and the arrangement of the catalog. The rates and thresholds are law, and nobody licenses those; the sources above are where they come from.
← Back to the Hong Kong salaries tax calculator
Built with Valem — a live, verifiable calculator you can fork and embed.