Build a tiered commission calculator
Apply progressive rates to a Sales column — 5% to £10k, 8% above, 12% above £50k.
Published Nov 26, 2025
Northwind pays its salespeople on a progressive scale: the first slice of sales
earns one rate, the next slice a higher one, and the top slice higher still.
That is the same shape as an income-tax band, and it trips people up the same
way — it is tempting to apply the top rate to the whole figure, which overpays
everyone. Doing it by hand in a spreadsheet means a tangle of nested IFs that
nobody trusts.
This is a custom function. Drop =COMMISSION() next to the sales column and
each salesperson’s payout is worked out tier by tier: 5% on sales up to £10k,
8% on the part between £10k and £50k, and 12% on anything above. Change the
rates in one place and every cell updates.
What you’ll need
- A sheet with a sales figure per salesperson — say their total in column B, with a header in row 1.
- A spare column for the commission result.
- Nothing else. A custom function lives in the bound script and runs as a formula; there is no trigger and no API to set up.
The script
// The commission bands, lowest first. `upTo` is the top of each
// band; `rate` is the rate that applies to the slice inside it.
// Edit these to change the scheme — every cell recalculates.
const TIERS = [
{ upTo: 10_000, rate: 0.05 }, // 5% on the first £10,000
{ upTo: 50_000, rate: 0.08 }, // 8% on £10,000–£50,000
{ upTo: Infinity, rate: 0.12 }, // 12% on everything above £50,000
];
/**
* Calculates progressive commission on a sales amount. Each band's
* rate applies only to the slice of sales that falls inside it.
*
* @param {number} amount The total sales figure.
* @return {number} The commission earned.
* @customfunction
*/
function COMMISSION(amount) {
// Guard: no sales, no commission.
if (!amount || amount <= 0) return 0;
let remaining = amount; // sales not yet allocated to a band
let prev = 0; // the bottom of the current band
let total = 0; // commission accumulated so far
// Walk the bands lowest to highest, taking a slice from each.
for (const tier of TIERS) {
// The slice of sales that sits inside this band.
const slice = Math.max(0, Math.min(tier.upTo - prev, remaining));
// That slice earns this band's rate.
total += slice * tier.rate;
// Move on: the slice is now allocated, the next band starts here.
remaining -= slice;
prev = tier.upTo;
// Stop once all the sales have been allocated.
if (remaining <= 0) break;
}
return total;
}
How it works
TIERSdescribes the scheme as plain data: each band has anupToceiling and theratethat applies to the money inside it. The last band usesInfinityso it catches everything above £50k.COMMISSIONbails out at zero for blank or negative sales, so an empty cell returns0rather than an error.- It walks the bands from the bottom. For each one it works out the slice —
the part of the remaining sales that fits between the previous ceiling and
this one — with
Math.mincapping it at the band width andMath.maxkeeping it non-negative. - That slice is multiplied by the band’s rate and added to the running total.
- The allocated slice is subtracted from
remaining,prevmoves up to this band’s ceiling, and the loop continues. - As soon as every pound of sales has been allocated,
remaininghits zero and the loop stops early — higher bands are never touched.
Example run
On £62,000 of sales the function allocates the figure across all three bands:
| Band | Slice | Rate | Commission |
|---|---|---|---|
| First £10,000 | £10,000 | 5% | £500 |
| £10,000–£50,000 | £40,000 | 8% | £3,200 |
| Above £50,000 | £12,000 | 12% | £1,440 |
| Total | £5,140 |
So =COMMISSION(62000) returns 5140. Note that applying the top 12% to the
whole £62,000 would give £7,440 — the tiering is the difference.
Use it
Type the formula into the first commission cell, pointing at the sales cell:
=COMMISSION(B2)
Then drag it down the column. Each row recalculates instantly, and if you
adjust a rate in TIERS the whole column updates on the next edit.
Watch out for
- Custom functions cannot read Script Properties or make external calls — that
is fine here, but it means the
TIERSrates live in the code. Changing the scheme is a code edit, not a spreadsheet edit. - The bands must be listed lowest first and must not overlap. An out-of-order
or overlapping
TIERSarray will allocate slices wrongly. COMMISSIONtakes a single number. Pass it a range, like=COMMISSION(B2:B9), and it will see an array and return0. Apply it one row at a time.- The result is an unrounded number. If payroll wants pennies exact, wrap the
cell with
=ROUND(COMMISSION(B2), 2). - Recalculation only fires when an input cell changes or the sheet reopens.
Editing
TIERSalone will not refresh existing cells until they are touched.
Related
Build an OKR tracker with progress rollups
Aggregate Northwind's key-result progress into objective-level scores automatically.
Updated Jan 14, 2026
Build a recurring-task generator
Spawn new Northwind task rows on a daily or weekly cadence from a Recurring sheet.
Updated Jan 10, 2026
Build a multi-sheet search-and-jump tool
Find a value across every tab of a workbook and click through to the cell that contains it.
Updated Jan 7, 2026
Auto-rebuild grouped summaries on edit
Refresh pivot-style rollups the instant data changes — no manual recompute.
Updated Jan 4, 2026
Build a data-quality scorecard
Grade any sheet on completeness, validity, and freshness — surface gaps as a single score.
Updated Dec 31, 2025