charity.payroll-giving
UK Payroll Giving for one pay period: the donation comes off pay before PAYE, and the tax it saves the employee.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 12 tests, run in TypeScript, Python and Rust.
Not professional advice. This capability calculates tax figures from published rules. It is a software component for developers, not tax advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed in its README, and have a payroll specialist review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.
What it does
Payroll Giving (Give As You Earn) for one pay period. The employer deducts the donation from pay **before income tax** (ITEPA 2003 s713) but **after National Insurance**, and passes it to a Payroll Giving agency. The relief is simply the PAYE the lower pay saves, so this runs payroll.income-tax twice, with and without the donation, and reports the difference.
That is deliberately not `donation x marginal rate`. PAYE works on taxable pay rounded down to whole pounds, so the saving each period comes in whole multiples of the rate on £1: a £5 donation at basic rate saves exactly £1, a 50p donation saves nothing that month, and a donation that straddles a band boundary gets each rate on its own part. The PAYE is the real deduction the payroll will make, cumulative codes, Scottish and Welsh bands, K codes and all.
For example
payrollGiving(1257L, monthly, 1, £3,000.00, £100.00, £0.00, £0.00, 2026-04-25)→ taxable pay £2,900.00, tax without giving £390.20, tax with giving £370.20, tax saved £20.00, net cost £80.00 basic rate: £100 given from £3,000 saves £20 of PAYE, costing £80payrollGiving(1257L, monthly, 1, £3,000.00, £5.00, £0.00, £0.00, 2026-04-25)→ taxable pay £2,995.00, tax without giving £390.20, tax with giving £389.20, tax saved £1.00, net cost £4.00 £5 given saves exactly £1payrollGiving(1257L, monthly, 1, £3,000.00, £0.50, £0.00, £0.00, 2026-04-25)→ taxable pay £2,999.50, tax without giving £390.20, tax with giving £390.20, tax saved £0.00, net cost £0.50 50p given saves nothing: PAYE rounds taxable pay down to whole pounds (naive 20% says 10p)
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
export function payrollGiving(taxCode: string, frequency: PayFrequency, period: number, pay: Money, donation: Money, previousPayToDate: Money, previousTaxToDate: Money, payDate: string): PayrollGiving
| taxCode | string | as issued by HMRC, e.g. 1257L or S1257L |
| frequency | PayFrequency | |
| period | int | tax week or month of the payment, from payroll.tax-period |
| pay | Money | taxable pay for the period before the donation, after any net pay pension deduction |
| donation | Money | the Payroll Giving deduction this period |
| previousPayToDate | Money | taxable pay already paid this tax year, after earlier Payroll Giving deductions |
| previousTaxToDate | Money | tax already deducted this tax year |
| payDate | date | decides the tax year's bands |
| returns | PayrollGiving |
The type it declares, generated into your project
/** The period's PAYE with and without the donation. */
export interface PayrollGiving {
/** pay less the donation: what PAYE is worked out on */
readonly taxablePay: Money;
/** PAYE this period had there been no donation */
readonly taxWithoutGiving: Money;
/** PAYE this period with the donation deducted first */
readonly taxWithGiving: Money;
/** the relief: taxWithoutGiving less taxWithGiving */
readonly taxSaved: Money;
/** donation less taxSaved: what the gift costs the employee */
readonly netCost: Money;
}
Your code names it in one line, in the file that uses it
import { payrollGiving } from "#fune/charity.payroll-giving@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { type Money, money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { incomeTax } from "./payroll_income_tax.ts"; ← from payroll.income-tax ^1.0.0 · built alongside by fune
import { type PayFrequency } from "./payroll_tax_period_types.ts";
import { type PayrollGiving } from "./charity_payroll_giving_types.ts";
/**
* Payroll Giving (ITEPA 2003 s713): the donation is deducted from pay before
* PAYE is worked out, so the relief is whatever PAYE the lower pay saves, by
* the same tax table routines the payroll uses. That is not simply the
* donation times the marginal rate: PAYE rounds taxable pay down to whole
* pounds, so a 50p donation can save nothing and a £5 one exactly £1.
*/
export function payrollGiving(
taxCode: string,
frequency: PayFrequency,
period: number,
pay: Money,
donation: Money,
previousPayToDate: Money,
previousTaxToDate: Money,
payDate: string
): PayrollGiving {
if (donation.currency !== "GBP") {
throw new RangeError(`payroll amounts must be in GBP, received ${donation.currency} for donation`);
}
if (donation.minor < 0) throw new RangeError(`donation must not be negative, received ${donation.minor}`);
if (donation.minor > pay.minor) {
throw new RangeError(`donation of ${donation.minor} is more than pay of ${pay.minor}`);
}
const taxablePay = money(pay.minor - donation.minor, "GBP");
const without = incomeTax(taxCode, frequency, period, pay, previousPayToDate, previousTaxToDate, payDate).tax.minor;
const withGiving = incomeTax(taxCode, frequency, period, taxablePay, previousPayToDate, previousTaxToDate, payDate).tax.minor;
return {
taxablePay,
taxWithoutGiving: money(without, "GBP"),
taxWithGiving: money(withGiving, "GBP"),
taxSaved: money(without - withGiving, "GBP"),
netCost: money(donation.minor - (without - withGiving), "GBP"),
};
}Install
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 3 dependencies, pins them in fune.lock, downloads only the TypeScript package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:
fune add charity.payroll-giving
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./charity.payroll-giving-1.0.0-typescript.fune, or fetch it from a terminal with fune pull charity.payroll-giving@1.0.0:typescript.
The whole function, every language, is one file too: charity.payroll-giving-1.0.0.fune, 17,773 bytes, sha256 6b0c4d385fdbed84474164b57fe3c3f23e9d39a2757335f3e7ea1641e055b2ce. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
// fune: before charity.payroll-giving
after — your function gets the result and the arguments, and returns the final result.
// fune: after charity.payroll-giving
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
// fune: replace money.amount in charity.payroll-giving
// fune: replace payroll.income-tax in charity.payroll-giving
// fune: replace payroll.tax-period in charity.payroll-giving
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show charity.payroll-giving --steps.
// fune: step charity.payroll-giving after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| basic rate: £100 given from £3,000 saves £20 of PAYE, costing £80 | 1257L, monthly, 1, £3,000.00, £100.00, £0.00, £0.00, 2026-04-25 | → | taxable pay £2,900.00, tax without giving £390.20, tax with giving £370.20, tax saved £20.00, net cost £80.00 |
| £5 given saves exactly £1 | 1257L, monthly, 1, £3,000.00, £5.00, £0.00, £0.00, 2026-04-25 | → | taxable pay £2,995.00, tax without giving £390.20, tax with giving £389.20, tax saved £1.00, net cost £4.00 |
| 50p given saves nothing: PAYE rounds taxable pay down to whole pounds (naive 20% says 10p) | 1257L, monthly, 1, £3,000.00, £0.50, £0.00, £0.00, 2026-04-25 | → | taxable pay £2,999.50, tax without giving £390.20, tax with giving £390.20, tax saved £0.00, net cost £0.50 |
| higher rate: £200 given from £5,000 saves £80 | 1257L, monthly, 1, £5,000.00, £200.00, £0.00, £0.00, 2026-04-25 | → | taxable pay £4,800.00, tax without giving £952.06, tax with giving £872.06, tax saved £80.00, net cost £120.00 |
| Scottish intermediate rate: £100 saves £21 | S1257L, monthly, 1, £3,000.00, £100.00, £0.00, £0.00, 2026-04-25 | → | taxable pay £2,900.00, tax without giving £392.27, tax with giving £371.27, tax saved £21.00, net cost £79.00 |
| cumulative month 6: £100 saves £20 against tax to date | 1257L, monthly, 6, £3,000.00, £100.00, £15,000.00, £1,951.60, 2026-09-25 | → | taxable pay £2,900.00, tax without giving £390.40, tax with giving £370.40, tax saved £20.00, net cost £80.00 |
| no donation: nothing saved | 1257L, monthly, 1, £3,000.00, £0.00, £0.00, £0.00, 2026-04-25 | → | taxable pay £3,000.00, tax without giving £390.20, tax with giving £390.20, tax saved £0.00, net cost £0.00 |
| pay under the allowance: the donation saves no tax at all | 1257L, monthly, 1, £1,000.00, £1,000.00, £0.00, £0.00, 2026-04-25 | → | taxable pay £0.00, tax without giving £0.00, tax with giving £0.00, tax saved £0.00, net cost £1,000.00 |
| a donation above pay is an error | 1257L, monthly, 1, £100.00, £100.01, £0.00, £0.00, 2026-04-25 | → | error: donation of 10001 is more than pay of 10000 |
| a negative donation is an error | 1257L, monthly, 1, £100.00, -£0.01, £0.00, £0.00, 2026-04-25 | → | error: donation must not be negative |
Show the other 2 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| a euro donation is an error | 1257L, monthly, 1, £100.00, €1.00, £0.00, £0.00, 2026-04-25 | → | error: payroll amounts must be in GBP, received EUR for donation |
| a bad period is refused by payroll.income-tax | 1257L, monthly, 13, £3,000.00, £1.00, £0.00, £0.00, 2026-04-25 | → | error: period 13 is not a tax period for monthly pay |
More from the author
- `pay` is the period's taxable pay before the donation (after any net pay pension deduction); `previousPayToDate` should already be net of earlier Payroll Giving deductions, as it is on the payroll record. - National Insurance is not affected by the donation and is not computed here (payroll.national-insurance on the full pay). - A charity cannot claim Gift Aid on Payroll Giving donations (s416(4) Condition C excludes them), and agency fees are the caller's to handle. - The tax years are those payroll.income-tax has bands for (2023-24 on).
## Sources
- Income Tax (Earnings and Pensions) Act 2003 s713, https://www.legislation.gov.uk/ukpga/2003/1/section/713 - GOV.UK, "Payroll Giving" ("taken from your employees' pay before tax but after National Insurance"; "You can't claim Gift Aid on Payroll Giving donations"), https://www.gov.uk/payroll-giving - Income Tax Act 2007 s416(4), https://www.legislation.gov.uk/ukpga/2007/3/section/416
Files
| Path | Bytes |
|---|---|
| README.md | 1,812 |
| impl/python.py | 1,740 |
| impl/rust.rs | 2,674 |
| impl/typescript.ts | 1,792 |
| vectors.json | 5,382 |