charity.gift-aid-eligibility
Whether a UK donation qualifies for Gift Aid: declaration, enough tax paid, and the donor benefit limits.
1.0.1 (not the latest) · published 2026-10-03 by charlie · Anterra
Pinned by 25 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 charity tax specialist review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.
What it does
Whether one donation qualifies for Gift Aid, and every reason it does not. It checks the three things a charity can check at the point of a gift:
1. **A Gift Aid declaration** covers the gift (ITA 2007 s416(1)(b)). 2. **The donor pays enough tax.** A donor who pays less income tax and capital gains tax in the year than their Gift Aid gifts reclaim is liable to pay the difference (s424). The charity cannot work this out, so it is a flag the caller supplies, usually from what the donor confirmed on the declaration. 3. **The donor benefit limits** (s418): the benefits the donor, or a person connected with them, receives because of the gift.
For example
giftAidEligibility(£100.00, £25.00, £0.00, true, true, 2026-05-01)→ eligible true, reasons , benefit limit £25.00, annual benefit limit £2,500.00 £100 with a £25 benefit is exactly at 25% and qualifiesgiftAidEligibility(£100.00, £25.01, £0.00, true, true, 2026-05-01)→ eligible false, reasons benefit-over-limit, benefit limit £25.00, annual benefit limit £2,500.00 £100 with a £25.01 benefit exceeds 25%giftAidEligibility(£1.01, £0.25, £0.00, true, true, 2026-05-01)→ eligible true, reasons , benefit limit £0.25, annual benefit limit £2,500.00 £1.01: a 25p benefit is under the unrounded limit of 25.25p
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 giftAidEligibility(donation: Money, benefitValue: Money, priorBenefitsThisYear: Money, hasDeclaration: boolean, donorPaysEnoughTax: boolean, onDate: string): GiftAidEligibility
| donation | Money | the gift, in GBP |
| benefitValue | Money | value of benefits the donor or a connected person gets because of this gift; 0 if none |
| priorBenefitsThisYear | Money | benefits from earlier qualifying gifts by this donor to this charity in the same tax year |
| hasDeclaration | bool | a Gift Aid declaration covers this gift |
| donorPaysEnoughTax | bool | the donor pays at least as much income or capital gains tax as all their Gift Aid gifts reclaim |
| onDate | date | the date of the gift; decides which limits apply |
| returns | GiftAidEligibility |
The type it declares, generated into your project
/** Whether the gift qualifies, every reason it does not, and the limits it was tested against. */
export interface GiftAidEligibility {
readonly eligible: boolean;
/** in this order, when they apply: no-declaration, insufficient-tax, benefit-over-limit, annual-benefits-over-limit */
readonly reasons: readonly string[];
/** most this gift's benefits may be worth, rounded down to the penny */
readonly benefitLimit: Money;
/** most all benefits from this donor's gifts to the charity in the tax year may be worth */
readonly annualBenefitLimit: Money;
}
Your code names it in one line, in the file that uses it
import { giftAidEligibility } from "#fune/charity.gift-aid-eligibility@^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 { type GiftAidEligibility } from "./charity_gift_aid_eligibility_types.ts";
import { type BenefitLimit, BENEFIT_LIMITS, BENEFIT_LIMITS_HISTORY } from "./charity_gift_aid_eligibility_data.ts"; ← this capability’s own data, compiled from data/benefit-limits.json into the same file by fune build
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
function limitsOn(onDate: string): BenefitLimit {
const row = BENEFIT_LIMITS.find((r) => r.validFrom <= onDate && (r.validTo === null || onDate <= r.validTo));
if (row !== undefined) return row;
if (BENEFIT_LIMITS_HISTORY !== "full" && BENEFIT_LIMITS.length > 0) {
const earliest = BENEFIT_LIMITS.map((r) => r.validFrom).reduce((a, b) => (b < a ? b : a));
if (onDate < earliest) {
throw new RangeError(
`no Gift Aid benefit limits on ${onDate}: this build was installed with history=${BENEFIT_LIMITS_HISTORY}, so it only carries rules from ${earliest}. Reinstall with history=full for earlier tax years.`
);
}
}
throw new RangeError(`no Gift Aid benefit limits on ${onDate}`);
}
function checkGbp(name: string, amount: Money): void {
if (amount.currency !== "GBP") {
throw new RangeError(`Gift Aid amounts must be in GBP, received ${amount.currency} for ${name}`);
}
}
/**
* Whether a donation qualifies for Gift Aid (ITA 2007 ss416-418). The benefit
* test is done on the exact limit, scaled by 10000 so that 25% or 5% of an odd
* number of pence is never rounded before the comparison: a benefit of 25p on
* a £1.01 gift (limit 25.25p) passes, where a rounded-down limit would fail it.
*/
export function giftAidEligibility(
donation: Money,
benefitValue: Money,
priorBenefitsThisYear: Money,
hasDeclaration: boolean,
donorPaysEnoughTax: boolean,
onDate: string
): GiftAidEligibility {
checkGbp("donation", donation);
checkGbp("benefitValue", benefitValue);
checkGbp("priorBenefitsThisYear", priorBenefitsThisYear);
if (donation.minor <= 0) throw new RangeError(`donation must be positive, received ${donation.minor}`);
if (benefitValue.minor < 0) throw new RangeError(`benefitValue must not be negative, received ${benefitValue.minor}`);
if (priorBenefitsThisYear.minor < 0) {
throw new RangeError(`priorBenefitsThisYear must not be negative, received ${priorBenefitsThisYear.minor}`);
}
if (!ISO_DATE.test(onDate)) throw new RangeError(`onDate must be an ISO date (YYYY-MM-DD), received "${onDate}"`);
const rule = limitsOn(onDate);
// The variable limit, in pence x 10000.
let scaledLimit: number;
if (donation.minor <= rule.smallGiftUpTo) {
scaledLimit = donation.minor * rule.smallGiftBasisPoints;
} else if (rule.middleGiftUpTo !== null && rule.middleGiftFixed !== null && donation.minor <= rule.middleGiftUpTo) {
scaledLimit = rule.middleGiftFixed * 10000;
} else {
scaledLimit = rule.excessFixed * 10000 + (donation.minor - rule.excessFrom) * rule.excessBasisPoints;
}
const reasons: string[] = [];
if (!hasDeclaration) reasons.push("no-declaration");
if (!donorPaysEnoughTax) reasons.push("insufficient-tax");
// "exceeds" and "is more than": a benefit exactly at a limit is allowed.
if (benefitValue.minor * 10000 > scaledLimit) reasons.push("benefit-over-limit");
if (benefitValue.minor + priorBenefitsThisYear.minor > rule.annualLimit) reasons.push("annual-benefits-over-limit");
return {
eligible: reasons.length === 0,
reasons,
benefitLimit: money(Math.floor(scaledLimit / 10000), "GBP"),
annualBenefitLimit: money(rule.annualLimit, "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 1 dependency, 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.gift-aid-eligibility
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./charity.gift-aid-eligibility-1.0.1-typescript.fune, or fetch it from a terminal with fune pull charity.gift-aid-eligibility@1.0.1:typescript.
The whole function, every language, is one file too: charity.gift-aid-eligibility-1.0.1.fune, 29,993 bytes, sha256 0bc66abcd11938618923032f62de56a88fca162d7f8cba6fe68820a524ad7a63. 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.gift-aid-eligibility
after — your function gets the result and the arguments, and returns the final result.
// fune: after charity.gift-aid-eligibility
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.gift-aid-eligibility
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.gift-aid-eligibility --steps.
// fune: step charity.gift-aid-eligibility 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 | |
|---|---|---|---|
| £100 with a £25 benefit is exactly at 25% and qualifies | £100.00, £25.00, £0.00, true, true, 2026-05-01 | → | eligible true, reasons , benefit limit £25.00, annual benefit limit £2,500.00 |
| £100 with a £25.01 benefit exceeds 25% | £100.00, £25.01, £0.00, true, true, 2026-05-01 | → | eligible false, reasons benefit-over-limit, benefit limit £25.00, annual benefit limit £2,500.00 |
| £1.01: a 25p benefit is under the unrounded limit of 25.25p | £1.01, £0.25, £0.00, true, true, 2026-05-01 | → | eligible true, reasons , benefit limit £0.25, annual benefit limit £2,500.00 |
| £1.01: a 26p benefit is over 25.25p | £1.01, £0.26, £0.00, true, true, 2026-05-01 | → | eligible false, reasons benefit-over-limit, benefit limit £0.25, annual benefit limit £2,500.00 |
| £2,000 since 2019: £25 plus 5% of £1,900 is £120, and £120 qualifies | £2,000.00, £120.00, £0.00, true, true, 2026-05-01 | → | eligible true, reasons , benefit limit £120.00, annual benefit limit £2,500.00 |
| £2,000 since 2019: £120.01 is over the limit | £2,000.00, £120.01, £0.00, true, true, 2026-05-01 | → | eligible false, reasons benefit-over-limit, benefit limit £120.00, annual benefit limit £2,500.00 |
| £50,000: the per-gift limit is £2,520 but a £2,500.01 benefit breaks the £2,500 annual cap | £50,000.00, £2,500.01, £0.00, true, true, 2026-05-01 | → | eligible false, reasons annual-benefits-over-limit, benefit limit £2,520.00, annual benefit limit £2,500.00 |
| £50,000 with a £2,500 benefit is exactly at the annual cap and qualifies | £50,000.00, £2,500.00, £0.00, true, true, 2026-05-01 | → | eligible true, reasons , benefit limit £2,520.00, annual benefit limit £2,500.00 |
| earlier benefits this tax year count towards the £2,500 | £2,000.00, £100.01, £2,400.00, true, true, 2026-05-01 | → | eligible false, reasons annual-benefits-over-limit, benefit limit £120.00, annual benefit limit £2,500.00 |
| no declaration and not enough tax: both reasons, in order | £10.00, £0.00, £0.00, false, false, 2026-05-01 | → | eligible false, reasons no-declaration, insufficient-tax, benefit limit £2.50, annual benefit limit £2,500.00 |
Show the other 15 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| every reason at once | £100.00, £2,500.01, £0.00, false, false, 2026-05-01 | → | eligible false, reasons no-declaration, insufficient-tax, benefit-over-limit, annual-benefits-over-limit, benefit limit £25.00, annual benefit limit £2,500.00 |
| before 6 April 2019 a £500 gift had a flat £25 limit, so a £40 benefit failed | £500.00, £40.00, £0.00, true, true, 2019-04-05 | → | eligible false, reasons benefit-over-limit, benefit limit £25.00, annual benefit limit £2,500.00 |
| from 6 April 2019 the same £500 gift allows £25 plus 5% of £400, £45 | £500.00, £40.00, £0.00, true, true, 2019-04-06 | → | eligible true, reasons , benefit limit £45.00, annual benefit limit £2,500.00 |
| before 2019 a gift over £1,000 allowed 5% of the whole gift: £100 on £2,000 | £2,000.00, £100.00, £0.00, true, true, 2018-06-01 | → | eligible true, reasons , benefit limit £100.00, annual benefit limit £2,500.00 |
| before 2019 exactly £1,000 was still in the £25 band | £1,000.00, £25.01, £0.00, true, true, 2018-06-01 | → | eligible false, reasons benefit-over-limit, benefit limit £25.00, annual benefit limit £2,500.00 |
| 2010-11: the annual limit was £500 | £2,000.00, £50.01, £450.00, true, true, 2011-04-05 | → | eligible false, reasons annual-benefits-over-limit, benefit limit £100.00, annual benefit limit £500.00 |
| 6 April 2011: the annual limit became £2,500 | £2,000.00, £50.01, £450.00, true, true, 2011-04-06 | → | eligible true, reasons , benefit limit £100.00, annual benefit limit £2,500.00 |
| a zero donation is an error | £0.00, £0.00, £0.00, true, true, 2026-05-01 | → | error: donation must be positive |
| a euro donation is an error | €10.00, £0.00, £0.00, true, true, 2026-05-01 | → | error: Gift Aid amounts must be in GBP, received EUR for donation |
| a negative benefit is an error | £10.00, -£0.01, £0.00, true, true, 2026-05-01 | → | error: benefitValue must not be negative |
| negative prior benefits are an error | £10.00, £0.00, -£0.01, true, true, 2026-05-01 | → | error: priorBenefitsThisYear must not be negative |
| a date before the rules on file is an error | £10.00, £0.00, £0.00, true, true, 2007-04-05 | → | error: no Gift Aid benefit limits on 2007-04-05 |
| a malformed date is an error | £10.00, £0.00, £0.00, true, true, 2026-5-1 | → | error: onDate must be an ISO date |
| a trailing newline after onDate is not an ISO date | £10.00, £0.00, £0.00, true, true, 2026-09-16 | → | error: onDate must be an ISO date |
| Arabic-Indic digits in onDate are not an ISO date | £10.00, £0.00, £0.00, true, true, ٢٠٢٦-09-16 | → | error: onDate must be an ISO date |
More from the author
The benefit limits, for gifts on or after 6 April 2019:
| gift | the benefits may be worth up to | |---|---| | £100 or less | 25% of the gift | | more than £100 | £25 plus 5% of the amount over £100 | | all gifts to this charity in the tax year | £2,500 in total |
Earlier rules are dated data: from 6 April 2007, 25% up to £100, a flat £25 on gifts up to £1,000 and 5% of the whole gift above that, with a £500 annual total, which became £2,500 from 6 April 2011. A £500 gift in March 2019 could carry a £25 benefit; the same gift in April 2019 can carry £45.
## Decisions
- **The comparison is exact.** The statute says a benefit breaches the limit if it "exceeds" it, and the annual total if it "is more than" £2,500, so a benefit exactly at a limit qualifies. The limit is compared unrounded: 25% of £1.01 is 25.25p, so a 25p benefit passes and 26p fails. `benefitLimit` is reported rounded down to the penny, for display. - **Every reason is returned**, in a fixed order (`no-declaration`, `insufficient-tax`, `benefit-over-limit`, `annual-benefits-over-limit`), so a donor record can be fixed in one pass. - `priorBenefitsThisYear` is the benefits from this donor's earlier *qualifying* gifts to this charity in the same tax year (s418(4)); the caller keeps that running total. - Out of scope: valuing a benefit, the special cases of ss419-421 (right of admission to heritage property, publications, and so on), and the other qualifying conditions of s416 (a payment of money, not repayable, not payroll giving, not deductible from income).
## Sources
- Income Tax Act 2007 ss416-418 and s424, https://www.legislation.gov.uk/ukpga/2007/3/part/8/chapter/2 - Finance Act 2007 s60 (5% above £1,000 and £500 annual limit, gifts from 6 April 2007), https://www.legislation.gov.uk/ukpga/2007/11/section/60 - Finance Act 2011 s41 (annual limit £2,500, gifts from 6 April 2011), https://www.legislation.gov.uk/ukpga/2011/11/section/41 - Finance Act 2019 s40 (£25 plus 5% of the excess, gifts from 6 April 2019), https://www.legislation.gov.uk/ukpga/2019/1/section/40 - GOV.UK, "Gift Aid: what donations charities and CASCs can claim on", https://www.gov.uk/guidance/gift-aid-what-donations-charities-and-cascs-can-claim-on
1.0.1 fixes Python accepting a trailing newline or non-ASCII digits in onDate; adds tests.
Files
| Path | Bytes |
|---|---|
| README.md | 3,052 |
| data/benefit-limits.json | 1,222 |
| impl/python.py | 3,575 |
| impl/rust.rs | 4,547 |
| impl/typescript.ts | 3,509 |
| vectors.json | 8,636 |