How the Canada take-home pay calculator is verified
Inputs
| Input | Field | Type |
|---|---|---|
| Gross annual salary (CAD) | grossAnnual | number |
| Province | province | string |
| Provincial rate if "Other" (e.g. 0.0805) | customProvincialRate | 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) |
|---|---|---|
| Provincial rate | provincialRate | ($s := province; $s = 'ON' ? $const.rateON : $s = 'AB' ? $const.rateAB : $s = 'QC' ? $const.rateQC : $s = 'BC' ? $const.rateBC : customProvincialRate) |
| Federal tax | federalTax | ($t := grossAnnual; $g := (($t < 57375 ? $t : 57375) * 0.145) + (($t > 57375 ? (($t < 114750 ? $t : 114750) - 57375) : 0) * 0.205) + (($t > 114750 ? (($t < 177882 ? $t : 177882) - 114750) : 0) * 0.26) + (($t > 177882 ? (($t < 253414 ? $t : 253414) - 177882) : 0) * 0.29) + (($t > 253414 ? ($t - 253414) : 0) * 0.33); $n := $g - $const.bpa * 0.145; $round($n > 0 ? $n : 0, 2)) |
| Provincial tax | provincialTax | $round(grossAnnual * provincialRate, 2) |
| CPP | cpp | ($base := (grossAnnual < 71300 ? grossAnnual : 71300) - 3500; $base := $base > 0 ? $base : 0; $cpp2 := grossAnnual > 71300 ? ((grossAnnual < 81200 ? grossAnnual : 81200) - 71300) : 0; $round($base * 0.0595 + $cpp2 * 0.04, 2)) |
| EI | ei | $round((grossAnnual < $const.eiCeiling ? grossAnnual : $const.eiCeiling) * $const.eiRate, 2) |
| Net / year | netAnnual | $round(grossAnnual - federalTax - provincialTax - cpp - ei, 2) |
| Net / month | netMonthly | $round(netAnnual / 12, 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 |
|---|---|
bpa | 16129 |
eiRate | 0.0164 |
eiCeiling | 65700 |
rateON | 0.0805 |
rateAB | 0.08 |
rateQC | 0.14 |
rateBC | 0.0506 |
Test vectors (3)
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 — S1 70,000 CAD, Ontario
given grossAnnual | 70000 |
|---|---|
given province | ON |
expect cpp | 3956.75 |
expect ei | 1077.48 |
Case 2 — S3 160,000 CAD
given grossAnnual | 160000 |
|---|---|
given province | ON |
expect cpp | 4430.1 |
expect ei | 1077.48 |
Case 3 — S2 70,000 CAD in Alberta — the lowest provincial rate of the four
given grossAnnual | 70000 |
|---|---|
given province | AB |
expect provincialRate | 0.08 |
expect provincialTax | 5600 |
Sources
- CRA — tax rates · checked 2026-08-02
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 Canada take-home pay calculator
Built with Valem — a live, verifiable calculator you can fork and embed.