charity.gasds
Gift Aid Small Donations Scheme top-up for a tax year: the £8,000 limit, the matching rule and community buildings.
1.0.2 · published 2026-10-03 by charlie · Anterra
Pinned by 21 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
The Gift Aid Small Donations Scheme (GASDS) top-up a charity or CASC can claim for one tax year, on small cash and contactless donations it has no Gift Aid declarations for.
The top-up is worked out like Gift Aid (Small Charitable Donations Act 2012 s1(2)): `small donations x R / (100 - R)` at the basic rate R, so £8,000 of small donations earns £2,000 at 20%. It is paid on no more than the **maximum donations limit**, the lower of:
For example
gasds(£10,000.00, £2,000.00, , 2026-06-01)→ eligible donations £8,000.00, top up £2,000.00, maximum donations £8,000.00, specified amount £8,000.00, small donation limit £30.00, used community buildings false £10,000 collected, plenty of Gift Aid: capped at £8,000, a £2,000 top-upgasds(£5,000.00, £100.00, , 2026-06-01)→ eligible donations £1,000.00, top up £250.00, maximum donations £1,000.00, specified amount £8,000.00, small donation limit £30.00, used community buildings false the matching rule: £100 of Gift Aid donations allows £1,000 of small donations, a £250 top-upgasds(£4,000.00, £10,000.00, , 2026-06-01)→ eligible donations £4,000.00, top up £1,000.00, maximum donations £8,000.00, specified amount £8,000.00, small donation limit £30.00, used community buildings false under both limits: the whole £4,000 earns £1,000
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.
pub fn gasds(small_donations: &Money, gift_aid_donations: &Money, community_building_donations: &[Money], on_date: &str) -> GasdsClaim
| small_donations | Money | small cash and contactless donations collected in the tax year, each at or under smallDonationLimit |
| gift_aid_donations | Money | donations in the same tax year the charity has successfully claimed Gift Aid on |
| community_building_donations | Money[] | for a charity running activities in more than one community building: the small donations made in person in each building's local authority area, not double counted; [] otherwise |
| on_date | date | any date in the tax year the donations were collected |
| returns | GasdsClaim |
The type it declares, generated into your project
/// The top-up for the year and the limits it was held to.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GasdsClaim {
/// the small donations the top-up is paid on
pub eligible_donations: Money,
/// eligibleDonations grossed up at the basic rate, like Gift Aid, rounded down to the penny
pub top_up: Money,
/// the lower of 10 times giftAidDonations and specifiedAmount
pub maximum_donations: Money,
/// £8,000, or the sum of the community building amounts when that is more
pub specified_amount: Money,
/// the most one small donation may be: £20, or £30 from 6 April 2019
pub small_donation_limit: Money,
/// the community building amounts gave the higher limit
pub used_community_buildings: bool,
}
Your code names it in one line, in the file that uses it
fune!(charity.gasds@^1); // then call gasds(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::charity_gasds_data::{GasdsLimit, GASDS_LIMITS, GASDS_LIMITS_HISTORY}; ← this capability’s own data, compiled from data/gasds-limits.json into the same file by fune build
use super::charity_gift_aid::gift_aid; ← from charity.gift-aid ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
fn is_iso_date(s: &str) -> bool {
let b = s.as_bytes();
b.len() == 10
&& b.iter().enumerate().all(|(i, c)| if i == 4 || i == 7 { *c == b'-' } else { c.is_ascii_digit() })
}
fn limits_on(on_date: &str) -> &'static GasdsLimit {
for row in GASDS_LIMITS {
if row.valid_from <= on_date && row.valid_to.map_or(true, |to| on_date <= to) {
return row;
}
}
if GASDS_LIMITS_HISTORY != "full" {
if let Some(earliest) = GASDS_LIMITS.iter().map(|r| r.valid_from).min() {
if on_date < earliest {
panic!(
"no GASDS limits on {}: this build was installed with history={}, so it only carries rules from {}. Reinstall with history=full for earlier tax years.",
on_date, GASDS_LIMITS_HISTORY, earliest
);
}
}
}
panic!("no GASDS limits on {}", on_date);
}
fn check_amount(name: &str, amount: &Money) {
if amount.currency != "GBP" {
panic!("GASDS amounts must be in GBP, received {} for {}", amount.currency, name);
}
if amount.minor < 0 {
panic!("{} must not be negative, received {}", name, amount.minor);
}
}
/// The GASDS top-up for one tax year (Small Charitable Donations Act 2012 s1):
/// paid on small donations up to the lower of 10 times the Gift Aid donations
/// claimed in the same year (the matching rule) and the specified amount,
/// which for a charity active in more than one community building may be the
/// sum of each building's local donations, up to £8,000 each (s6).
///
/// # Panics
/// Panics on a non-GBP or negative amount, a malformed date, a date with no
/// limits on file, community buildings before 2017-18, or community building
/// donations that add up to more than the small donations.
pub fn gasds(
small_donations: &Money,
gift_aid_donations: &Money,
community_building_donations: &[Money],
on_date: &str,
) -> GasdsClaim {
check_amount("smallDonations", small_donations);
check_amount("giftAidDonations", gift_aid_donations);
for d in community_building_donations {
check_amount("communityBuildingDonations", d);
}
if !is_iso_date(on_date) {
panic!("onDate must be an ISO date (YYYY-MM-DD), received \"{}\"", on_date);
}
let rule = limits_on(on_date);
if !community_building_donations.is_empty() && !rule.community_buildings {
panic!("community building claims before 6 April 2017 are not supported, received a claim for {}", on_date);
}
let in_buildings: i64 = community_building_donations.iter().map(|d| d.minor).sum();
if in_buildings > small_donations.minor {
panic!(
"communityBuildingDonations total {}, more than smallDonations of {}",
in_buildings, small_donations.minor
);
}
let mut specified = rule.specified_amount;
let mut used_community_buildings = false;
// s6 applies only to a charity active in more than one community building.
if community_building_donations.len() >= 2 {
let buildings: i64 = community_building_donations
.iter()
.map(|d| d.minor.min(rule.specified_amount))
.sum();
if buildings > specified {
specified = buildings;
used_community_buildings = true;
}
}
let maximum = (10 * gift_aid_donations.minor).min(specified);
let eligible = small_donations.minor.min(maximum);
GasdsClaim {
eligible_donations: money(eligible, "GBP"),
top_up: gift_aid(&money(eligible, "GBP"), on_date).tax_reclaimable,
maximum_donations: money(maximum, "GBP"),
specified_amount: money(specified, "GBP"),
small_donation_limit: money(rule.small_donation_limit, "GBP"),
used_community_buildings,
}
}
pub fn gasds_claim_to_value(c: &GasdsClaim) -> Value {
Value::obj(vec![
("eligibleDonations", money_to_value(&c.eligible_donations)),
("topUp", money_to_value(&c.top_up)),
("maximumDonations", money_to_value(&c.maximum_donations)),
("specifiedAmount", money_to_value(&c.specified_amount)),
("smallDonationLimit", money_to_value(&c.small_donation_limit)),
("usedCommunityBuildings", Value::Bool(c.used_community_buildings)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let buildings: Vec<Money> = args[2].as_arr().iter().map(money_from_value).collect();
gasds_claim_to_value(&gasds(
&money_from_value(&args[0]),
&money_from_value(&args[1]),
&buildings,
args[3].as_str(),
))
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the Rust 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. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add charity.gasds
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./charity.gasds-1.0.2-rust.fune, or fetch it from a terminal with fune pull charity.gasds@1.0.2:rust.
The whole function, every language, is one file too: charity.gasds-1.0.2.fune, 30,833 bytes, sha256 e275b9e6f3a916bd36b655b5f4aa92ce758c18247a01e3ea19184c513ac45c77. 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.gasds
after — your function gets the result and the arguments, and returns the final result.
// fune: after charity.gasds
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 charity.gift-aid in charity.gasds
// fune: replace money.amount in charity.gasds
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.gasds --steps.
// fune: step charity.gasds 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 | |
|---|---|---|---|
| £10,000 collected, plenty of Gift Aid: capped at £8,000, a £2,000 top-up | £10,000.00, £2,000.00, , 2026-06-01 | → | eligible donations £8,000.00, top up £2,000.00, maximum donations £8,000.00, specified amount £8,000.00, small donation limit £30.00, used community buildings false |
| the matching rule: £100 of Gift Aid donations allows £1,000 of small donations, a £250 top-up | £5,000.00, £100.00, , 2026-06-01 | → | eligible donations £1,000.00, top up £250.00, maximum donations £1,000.00, specified amount £8,000.00, small donation limit £30.00, used community buildings false |
| under both limits: the whole £4,000 earns £1,000 | £4,000.00, £10,000.00, , 2026-06-01 | → | eligible donations £4,000.00, top up £1,000.00, maximum donations £8,000.00, specified amount £8,000.00, small donation limit £30.00, used community buildings false |
| no Gift Aid claimed in the year: no top-up at all | £4,000.00, £0.00, , 2026-06-01 | → | eligible donations £0.00, top up £0.00, maximum donations £0.00, specified amount £8,000.00, small donation limit £30.00, used community buildings false |
| two community buildings: £8,000 (capped from £9,000) plus £3,000 raises the limit to £11,000 | £20,000.00, £5,000.00, £9,000.00, £3,000.00, 2026-06-01 | → | eligible donations £11,000.00, top up £2,750.00, maximum donations £11,000.00, specified amount £11,000.00, small donation limit £30.00, used community buildings true |
| two community buildings worth less than £8,000 together: the ordinary £8,000 is better | £10,000.00, £10,000.00, £3,000.00, £2,000.00, 2026-06-01 | → | eligible donations £8,000.00, top up £2,000.00, maximum donations £8,000.00, specified amount £8,000.00, small donation limit £30.00, used community buildings false |
| one community building alone does not change the limit | £10,000.00, £10,000.00, £9,000.00, 2026-06-01 | → | eligible donations £8,000.00, top up £2,000.00, maximum donations £8,000.00, specified amount £8,000.00, small donation limit £30.00, used community buildings false |
| community buildings do not lift the matching rule | £20,000.00, £500.00, £9,000.00, £9,000.00, 2026-06-01 | → | eligible donations £5,000.00, top up £1,250.00, maximum donations £5,000.00, specified amount £16,000.00, small donation limit £30.00, used community buildings true |
| £10.01 of small donations: 250.25p rounded down | £10.01, £1,000.00, , 2026-06-01 | → | eligible donations £10.01, top up £2.50, maximum donations £8,000.00, specified amount £8,000.00, small donation limit £30.00, used community buildings false |
| 2015-16: the limit was £5,000 | £10,000.00, £10,000.00, , 2016-04-05 | → | eligible donations £5,000.00, top up £1,250.00, maximum donations £5,000.00, specified amount £5,000.00, small donation limit £20.00, used community buildings false |
Show the other 11 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| 6 April 2016: the limit became £8,000, small donations still £20 | £10,000.00, £10,000.00, , 2016-04-06 | → | eligible donations £8,000.00, top up £2,000.00, maximum donations £8,000.00, specified amount £8,000.00, small donation limit £20.00, used community buildings false |
| 5 April 2019: small donations were £20 or less | £1,000.00, £1,000.00, , 2019-04-05 | → | eligible donations £1,000.00, top up £250.00, maximum donations £8,000.00, specified amount £8,000.00, small donation limit £20.00, used community buildings false |
| 6 April 2019: small donations became £30 or less | £1,000.00, £1,000.00, , 2019-04-06 | → | eligible donations £1,000.00, top up £250.00, maximum donations £8,000.00, specified amount £8,000.00, small donation limit £30.00, used community buildings false |
| community buildings before 2017-18 are an error | £10,000.00, £10,000.00, £5,000.00, £5,000.00, 2016-06-01 | → | error: community building claims before 6 April 2017 are not supported |
| community building donations above the total are an error | £5,000.00, £10,000.00, £4,000.00, £2,000.00, 2026-06-01 | → | error: communityBuildingDonations total 600000, more than smallDonations of 500000 |
| negative small donations are an error | -£0.01, £10,000.00, , 2026-06-01 | → | error: smallDonations must not be negative |
| euros are an error | €10.00, £10,000.00, , 2026-06-01 | → | error: GASDS amounts must be in GBP, received EUR for smallDonations |
| a year before the scheme is an error | £10.00, £10.00, , 2013-04-05 | → | error: no GASDS limits on 2013-04-05 |
| a malformed date is an error | £10.00, £10.00, , 2026 | → | error: onDate must be an ISO date |
| a trailing newline after onDate is not an ISO date | £10.00, £10.00, , 2026-09-16 | → | error: onDate must be an ISO date |
| Arabic-Indic digits in onDate are not an ISO date | £10.00, £10.00, , ٢٠٢٦-09-16 | → | error: onDate must be an ISO date |
More from the author
- 10 times the donations the charity successfully claimed Gift Aid on in the same tax year (the matching rule, s1(4)(a)). No Gift Aid claim, no top-up; - the **specified amount**: £8,000 from 6 April 2016, £5,000 from 6 April 2013.
**Community buildings.** From 2017-18 a charity running charitable activities in more than one community building may use, if it is more, the sum over its buildings of the small donations made in person in each building's local authority area, up to £8,000 per building (s6(2)-(3)). Pass those per-building amounts, with no donation counted twice (s6(3A)); pass `[]` otherwise. With one building, s6 does not apply and the limit is the ordinary one. The matching rule still applies on top. Community building claims for earlier years followed different rules and are an error.
`smallDonationLimit` is reported for checking the donations themselves: a small donation is £30 or less from 6 April 2019 and £20 or less before (Schedule para 1). This capability takes totals, so the caller keeps any gift above that limit out of `smallDonations`.
## Decisions
- The top-up is rounded down to the penny, as in charity.gift-aid, which it reuses for the basic rate. - Out of scope: connected charities sharing one allowance (ss4, 9), the eligibility conditions of s2 (a penalty in this or the last tax year excludes a charity), and the two-year claim deadline.
Note: the task that asked for this capability put the £30 limit at 6 April 2016. The legislation says 6 April 2019 (SI 2019/337); 6 April 2016 is when the specified amount rose to £8,000 (SI 2015/2027).
## Sources
- Small Charitable Donations Act 2012 s1 (top-up, matching rule, specified amount), https://www.legislation.gov.uk/ukpga/2012/23/section/1 , and as it stood on 6 April 2013 (£5,000), https://www.legislation.gov.uk/ukpga/2012/23/section/1/2013-04-06 - s6 (more than one community building), https://www.legislation.gov.uk/ukpga/2012/23/section/6 - Schedule para 1 (£30, previously £20), https://www.legislation.gov.uk/ukpga/2012/23/schedule/paragraph/1 - The Small Charitable Donations Act (Amendment) Order 2019, SI 2019/337 ("increasing the small donation limit from £20 to £30 for gifts made on or after 6th April 2019"), https://www.legislation.gov.uk/uksi/2019/337/made - GOV.UK, "Claim top-up payments for the Gift Aid Small Donations Scheme", https://www.gov.uk/guidance/claiming-a-top-up-payment-on-small-charitable-donations
1.0.1 fixes Python accepting a trailing newline or non-ASCII digits in onDate; adds tests.
## Notices
Contains public sector information licensed under the Open Government Licence v3.0 (https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).
Legislation: Crown copyright and database right.
1.0.2 adds its attribution notices (NOTICE). The code and the tests are unchanged.
Files
| Path | Bytes |
|---|---|
| NOTICE | 226 |
| README.md | 3,327 |
| data/gasds-limits.json | 1,081 |
| impl/python.py | 3,627 |
| impl/rust.rs | 4,841 |
| impl/typescript.ts | 3,521 |
| vectors.json | 8,718 |