property.stamp-duty
Stamp duty on a property purchase: SDLT (England, NI), LBTT (Scotland), LTT (Wales), with reliefs and surcharges.
1.0.1 (not the latest) · published 2026-10-03 by charlie · Anterra
Pinned by 51 tests, run in TypeScript, Python and Rust.
Not professional advice. This capability calculates property figures from published rules. It is a software component for developers, not legal or financial 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 conveyancer or tax adviser (SDLT, LBTT, LTT) review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.
What it does
Status: needs review by a conveyancing/tax professional before it is published.
The tax on buying land or property in the UK, which is three different taxes:
For example
stampDuty(price £295,000.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01)→ tax SDLT, schedule standard, slices ×3, surcharge basis points 0%, band tax £4,750.00, supplement basis points 0%, supplement £0.00, total £4,750.00 England, £295,000 from April 2025: £4,750stampDuty(price £2,000,000.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01)→ tax SDLT, schedule standard, slices ×5, surcharge basis points 0%, band tax £153,750.00, supplement basis points 0%, supplement £0.00, total £153,750.00 England, £2,000,000: every band up to 12%stampDuty(price £0.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01)→ tax SDLT, schedule standard, slices , surcharge basis points 0%, band tax £0.00, supplement basis points 0%, supplement £0.00, total £0.00 England, a zero price: no bands, no tax
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 stampDuty(purchase: StampDutyPurchase): StampDutyResult
| purchase | StampDutyPurchase | |
| returns | StampDutyResult |
The types it declares, generated into your project
export type StampDutyRegion = "england-ni" | "scotland" | "wales";
export type PropertyUse = "residential" | "non-residential";
/** One purchase, as the buyer would describe it on the return. */
export interface StampDutyPurchase {
/** the chargeable consideration, in GBP */
readonly price: Money;
/** where the land is: england-ni for SDLT, scotland for LBTT, wales for LTT */
readonly region: StampDutyRegion;
/** non-residential includes mixed use */
readonly propertyUse: PropertyUse;
/** every buyer is a first-time buyer buying their main home (residential only) */
readonly firstTimeBuyer: boolean;
/** the purchase attracts the higher rates or ADS: a buyer will own more than one dwelling and is not replacing a main home */
readonly additionalProperty: boolean;
/** a buyer is not UK resident for SDLT; only England and NI have a surcharge */
readonly nonResident: boolean;
/** usually completion; the date the rates are taken from */
readonly effectiveDate: string;
}
/** One band of the price and the tax on it. */
export interface StampDutySlice {
/** the band's rate including any surcharge added to every band */
readonly basisPoints: number;
/** the part of the price in this band */
readonly taxable: Money;
/** taxable × rate, rounded down to the penny; for display */
readonly tax: Money;
}
/** The tax, how it was reached, and in which schedule. */
export interface StampDutyResult {
/** SDLT, LBTT or LTT */
readonly tax: string;
/** standard, first-time-buyer or higher */
readonly schedule: string;
/** the bands the price reaches, lowest first */
readonly slices: readonly StampDutySlice[];
/** points added to every band (England: higher rates and non-resident) */
readonly surchargeBasisPoints: number;
/** the banded tax, rounded down to the pound */
readonly bandTax: Money;
/** a whole-price supplement's rate (Scotland's ADS), or 0 */
readonly supplementBasisPoints: number;
/** the supplement, rounded down to the pound */
readonly supplement: Money;
/** bandTax + supplement */
readonly total: Money;
}
Your code names it in one line, in the file that uses it
import { stampDuty } from "#fune/property.stamp-duty@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { BANDS, BANDS_HISTORY, BANDS_HORIZON, SURCHARGES, SURCHARGES_HISTORY, SURCHARGES_HORIZON, type StampDutyBand, type StampDutySurcharge } from "./property_stamp_duty_data.ts"; ← this capability’s own data, compiled from data/bands.json into the same file by fune build
import { type StampDutyPurchase, type StampDutyResult, type StampDutySlice } from "./property_stamp_duty_types.ts";
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
const TAX_NAMES: Readonly<Record<string, string>> = { "england-ni": "SDLT", scotland: "LBTT", wales: "LTT" };
// £10bn: price × the highest rate stays an exact JavaScript number.
const MAX_PRICE = 1_000_000_000_000;
// A build installed with history=current keeps only rules still in force, so
// an older date can find nothing even after the horizon check; say why.
function prunedNote(history: string): string {
return history === "full" ? "" : ` (this build was installed with history=${history}; reinstall with history=full for older dates)`;
}
function inForce(row: { validFrom: string; validTo: string | null }, onDate: string): boolean {
return onDate >= row.validFrom && (row.validTo === null || onDate <= row.validTo);
}
function schedule(region: string, use: string, name: string, onDate: string): StampDutyBand[] {
const rows = BANDS.filter((b) => b.region === region && b.propertyUse === use && b.schedule === name && inForce(b, onDate));
rows.sort((a, b) => a.fromMinor - b.fromMinor);
// The bands must run from zero without gaps, or the data is wrong.
let expected: number | null = 0;
for (const row of rows) {
if (expected === null || row.fromMinor !== expected) {
throw new RangeError(`the ${name} bands for ${region} ${use} on ${onDate} do not form a ladder`);
}
expected = row.toMinor;
}
if (rows.length > 0 && expected !== null) {
throw new RangeError(`the ${name} bands for ${region} ${use} on ${onDate} do not form a ladder`);
}
return rows;
}
/**
* Stamp duty on one purchase: SDLT in England and Northern Ireland, LBTT in
* Scotland, LTT in Wales. The price is sliced into the bands in force on the
* effective date; England's higher rates and non-resident surcharge add points
* to every band, Wales's higher rates are a schedule of their own, and
* Scotland's ADS is a separate charge on the whole price. Each tax is rounded
* down to the whole pound once, as the returns require.
*/
export function stampDuty(purchase: StampDutyPurchase): StampDutyResult {
const { price, region, propertyUse: use, firstTimeBuyer, additionalProperty, nonResident, effectiveDate } = purchase;
if (!ISO_DATE.test(effectiveDate)) {
throw new RangeError(`effectiveDate must be an ISO date (YYYY-MM-DD), received "${effectiveDate}"`);
}
const taxName = TAX_NAMES[region];
if (taxName === undefined) {
throw new RangeError(`unknown region "${region}": use england-ni, scotland or wales`);
}
if (use !== "residential" && use !== "non-residential") {
throw new RangeError(`unknown use "${use}": use residential or non-residential`);
}
if (price.currency !== "GBP") {
throw new RangeError(`price must be in GBP, received ${price.currency}`);
}
if (!Number.isInteger(price.minor) || price.minor < 0 || price.minor > MAX_PRICE) {
throw new RangeError(`price must be between 0 and ${MAX_PRICE} minor units, received ${price.minor}`);
}
if (use === "non-residential" && (firstTimeBuyer || additionalProperty)) {
throw new RangeError("firstTimeBuyer and additionalProperty apply only to residential purchases");
}
if (firstTimeBuyer && additionalProperty) {
throw new RangeError("a first-time buyer cannot be buying an additional property");
}
// A pruned build must refuse a date it no longer has the rules for.
for (const [history, horizon] of [[BANDS_HISTORY, BANDS_HORIZON], [SURCHARGES_HISTORY, SURCHARGES_HORIZON]] as const) {
if (history !== "full" && horizon !== null && effectiveDate < horizon) {
throw new RangeError(
`no ${taxName} rates for ${effectiveDate}: this build was installed with history=${history}, so it only carries rules from ${horizon}`,
);
}
}
const surcharges: StampDutySurcharge[] = [];
if (use === "residential") {
if (additionalProperty) {
const row = SURCHARGES.find((s) => s.region === region && s.kind === "additional-property" && inForce(s, effectiveDate));
if (row === undefined) {
throw new RangeError(`no higher rates rule for ${region} on ${effectiveDate}${prunedNote(SURCHARGES_HISTORY)}`);
}
if (price.minor >= row.minimumPriceMinor) surcharges.push(row);
}
if (nonResident) {
const row = SURCHARGES.find((s) => s.region === region && s.kind === "non-resident" && inForce(s, effectiveDate));
if (row !== undefined && price.minor >= row.minimumPriceMinor) surcharges.push(row);
}
}
let scheduleName = "standard";
if (surcharges.some((s) => s.basis === "higher-schedule")) {
scheduleName = "higher";
} else if (firstTimeBuyer) {
const relief = schedule(region, use, "first-time-buyer", effectiveDate);
const cap = relief.length > 0 ? relief[0].priceCapMinor : null;
if (relief.length > 0 && (cap === null || price.minor <= cap)) scheduleName = "first-time-buyer";
}
const bands = schedule(region, use, scheduleName, effectiveDate);
if (bands.length === 0) {
throw new RangeError(`no ${taxName} ${scheduleName} rates for ${region} ${use} on ${effectiveDate}${prunedNote(BANDS_HISTORY)}`);
}
let surchargePoints = 0;
let supplementPoints = 0;
for (const s of surcharges) {
if (s.basis === "each-band") surchargePoints += s.basisPoints;
else if (s.basis === "whole-price") supplementPoints += s.basisPoints;
}
// Everything below is in pence × basis points, exact until the final floor.
const slices: StampDutySlice[] = [];
let exact = 0;
for (const band of bands) {
if (price.minor <= band.fromMinor) break;
const top = band.toMinor === null ? price.minor : Math.min(price.minor, band.toMinor);
const taxable = top - band.fromMinor;
const rate = band.basisPoints + surchargePoints;
exact += taxable * rate;
slices.push({ basisPoints: rate, taxable: money(taxable, "GBP"), tax: money(Math.floor((taxable * rate) / 10000), "GBP") });
}
const toWholePounds = (value: number) => Math.floor(value / 1_000_000) * 100;
const bandTax = toWholePounds(exact);
const supplement = toWholePounds(price.minor * supplementPoints);
return {
tax: taxName,
schedule: scheduleName,
slices,
surchargeBasisPoints: surchargePoints,
bandTax: money(bandTax, "GBP"),
supplementBasisPoints: supplementPoints,
supplement: money(supplement, "GBP"),
total: money(bandTax + supplement, "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 property.stamp-duty
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./property.stamp-duty-1.0.1-typescript.fune, or fetch it from a terminal with fune pull property.stamp-duty@1.0.1:typescript.
The whole function, every language, is one file too: property.stamp-duty-1.0.1.fune, 130,204 bytes, sha256 c5b7dacc19b5e34500470e65f5143c37b58c31208c0822ceb32d5f068acb813a. 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 property.stamp-duty
after — your function gets the result and the arguments, and returns the final result.
// fune: after property.stamp-duty
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 property.stamp-duty
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 property.stamp-duty --steps.
// fune: step property.stamp-duty 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 | |
|---|---|---|---|
| England, £295,000 from April 2025: £4,750 | price £295,000.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | tax SDLT, schedule standard, slices ×3, surcharge basis points 0%, band tax £4,750.00, supplement basis points 0%, supplement £0.00, total £4,750.00 |
| England, £2,000,000: every band up to 12% | price £2,000,000.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | tax SDLT, schedule standard, slices ×5, surcharge basis points 0%, band tax £153,750.00, supplement basis points 0%, supplement £0.00, total £153,750.00 |
| England, a zero price: no bands, no tax | price £0.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | tax SDLT, schedule standard, slices , surcharge basis points 0%, band tax £0.00, supplement basis points 0%, supplement £0.00, total £0.00 |
| England first-time buyer, £500,000: relief up to the limit, £10,000 | price £500,000.00, region england-ni, property use residential, first time buyer true, additional property false, non resident false, effective date 2025-06-01 | → | tax SDLT, schedule first-time-buyer, slices ×2, surcharge basis points 0%, band tax £10,000.00, supplement basis points 0%, supplement £0.00, total £10,000.00 |
| England first-time buyer a penny over £500,000 loses the relief entirely: £15,000 | price £500,000.01, region england-ni, property use residential, first time buyer true, additional property false, non resident false, effective date 2025-06-01 | → | tax SDLT, schedule standard, slices ×3, surcharge basis points 0%, band tax £15,000.00, supplement basis points 0%, supplement £0.00, total £15,000.00 |
| England first-time buyer 1 July 2021: 5% above £300,000 | price £450,000.00, region england-ni, property use residential, first time buyer true, additional property false, non resident false, effective date 2021-07-01 | → | tax SDLT, schedule first-time-buyer, slices ×2, surcharge basis points 0%, band tax £7,500.00, supplement basis points 0%, supplement £0.00, total £7,500.00 |
| England first-time buyer 23 September 2022: nil band £425,000, limit £625,000 | price £625,000.00, region england-ni, property use residential, first time buyer true, additional property false, non resident false, effective date 2022-09-23 | → | tax SDLT, schedule first-time-buyer, slices ×2, surcharge basis points 0%, band tax £10,000.00, supplement basis points 0%, supplement £0.00, total £10,000.00 |
| England first-time buyer 31 March 2025, last day of the £425,000 band | price £450,000.00, region england-ni, property use residential, first time buyer true, additional property false, non resident false, effective date 2025-03-31 | → | tax SDLT, schedule first-time-buyer, slices ×2, surcharge basis points 0%, band tax £1,250.00, supplement basis points 0%, supplement £0.00, total £1,250.00 |
| England first-time buyer 1 April 2025, same price, back to £300,000 | price £450,000.00, region england-ni, property use residential, first time buyer true, additional property false, non resident false, effective date 2025-04-01 | → | tax SDLT, schedule first-time-buyer, slices ×2, surcharge basis points 0%, band tax £7,500.00, supplement basis points 0%, supplement £0.00, total £7,500.00 |
| England additional property from April 2025, £300,000: 5 points on every band, £20,000 | price £300,000.00, region england-ni, property use residential, first time buyer false, additional property true, non resident false, effective date 2025-06-01 | → | tax SDLT, schedule standard, slices ×3, surcharge basis points 5%, band tax £20,000.00, supplement basis points 0%, supplement £0.00, total £20,000.00 |
Show the other 41 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| England additional property on 30 October 2024: 3 points, £11,500 | price £300,000.00, region england-ni, property use residential, first time buyer false, additional property true, non resident false, effective date 2024-10-30 | → | tax SDLT, schedule standard, slices ×2, surcharge basis points 3%, band tax £11,500.00, supplement basis points 0%, supplement £0.00, total £11,500.00 |
| England additional property on 31 October 2024: 5 points, £17,500 | price £300,000.00, region england-ni, property use residential, first time buyer false, additional property true, non resident false, effective date 2024-10-31 | → | tax SDLT, schedule standard, slices ×2, surcharge basis points 5%, band tax £17,500.00, supplement basis points 0%, supplement £0.00, total £17,500.00 |
| England additional property under £40,000: no higher rates | price £39,999.99, region england-ni, property use residential, first time buyer false, additional property true, non resident false, effective date 2025-06-01 | → | tax SDLT, schedule standard, slices ×1, surcharge basis points 0%, band tax £0.00, supplement basis points 0%, supplement £0.00, total £0.00 |
| England additional property at exactly £40,000: higher rates apply, £2,000 | price £40,000.00, region england-ni, property use residential, first time buyer false, additional property true, non resident false, effective date 2025-06-01 | → | tax SDLT, schedule standard, slices ×1, surcharge basis points 5%, band tax £2,000.00, supplement basis points 0%, supplement £0.00, total £2,000.00 |
| England non-resident first-time buyer at £700,000 (the gov.uk worked example): £39,000 | price £700,000.00, region england-ni, property use residential, first time buyer true, additional property false, non resident true, effective date 2025-06-01 | → | tax SDLT, schedule standard, slices ×3, surcharge basis points 2%, band tax £39,000.00, supplement basis points 0%, supplement £0.00, total £39,000.00 |
| England non-resident buying an additional property: both surcharges, 7 points | price £300,000.00, region england-ni, property use residential, first time buyer false, additional property true, non resident true, effective date 2025-06-01 | → | tax SDLT, schedule standard, slices ×3, surcharge basis points 7%, band tax £26,000.00, supplement basis points 0%, supplement £0.00, total £26,000.00 |
| England during the 2021 holiday: a non-resident pays 2% even in the nil band | price £500,000.00, region england-ni, property use residential, first time buyer false, additional property false, non resident true, effective date 2021-04-15 | → | tax SDLT, schedule standard, slices ×1, surcharge basis points 2%, band tax £10,000.00, supplement basis points 0%, supplement £0.00, total £10,000.00 |
| England during the 2021 holiday: first-time buyer relief was replaced by the reduced rates | price £400,000.00, region england-ni, property use residential, first time buyer true, additional property false, non resident false, effective date 2021-05-01 | → | tax SDLT, schedule standard, slices ×1, surcharge basis points 0%, band tax £0.00, supplement basis points 0%, supplement £0.00, total £0.00 |
| England 30 September 2021: nil band £250,000 | price £300,000.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2021-09-30 | → | tax SDLT, schedule standard, slices ×2, surcharge basis points 0%, band tax £2,500.00, supplement basis points 0%, supplement £0.00, total £2,500.00 |
| England 1 October 2021: nil band £125,000 and the 2% band return | price £300,000.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2021-10-01 | → | tax SDLT, schedule standard, slices ×3, surcharge basis points 0%, band tax £5,000.00, supplement basis points 0%, supplement £0.00, total £5,000.00 |
| tax is rounded down to the pound once: £130,049.99 is £100.9998, so £100 | price £130,049.99, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | tax SDLT, schedule standard, slices ×2, surcharge basis points 0%, band tax £100.00, supplement basis points 0%, supplement £0.00, total £100.00 |
| England non-residential, £300,000: £4,500 | price £300,000.00, region england-ni, property use non-residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | tax SDLT, schedule standard, slices ×3, surcharge basis points 0%, band tax £4,500.00, supplement basis points 0%, supplement £0.00, total £4,500.00 |
| England non-residential bought by a non-resident: no surcharge on non-residential | price £300,000.00, region england-ni, property use non-residential, first time buyer false, additional property false, non resident true, effective date 2025-06-01 | → | tax SDLT, schedule standard, slices ×3, surcharge basis points 0%, band tax £4,500.00, supplement basis points 0%, supplement £0.00, total £4,500.00 |
| Scotland, £300,000: £4,600 | price £300,000.00, region scotland, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | tax LBTT, schedule standard, slices ×3, surcharge basis points 0%, band tax £4,600.00, supplement basis points 0%, supplement £0.00, total £4,600.00 |
| Scotland first-time buyer, £300,000: nil band £175,000 saves £600 | price £300,000.00, region scotland, property use residential, first time buyer true, additional property false, non resident false, effective date 2025-06-01 | → | tax LBTT, schedule first-time-buyer, slices ×3, surcharge basis points 0%, band tax £4,000.00, supplement basis points 0%, supplement £0.00, total £4,000.00 |
| Scotland, £800,000: into the 12% band | price £800,000.00, region scotland, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | tax LBTT, schedule standard, slices ×5, surcharge basis points 0%, band tax £54,350.00, supplement basis points 0%, supplement £0.00, total £54,350.00 |
| Scotland ADS at 8% of the whole price from 5 December 2024 | price £300,000.00, region scotland, property use residential, first time buyer false, additional property true, non resident false, effective date 2025-06-01 | → | tax LBTT, schedule standard, slices ×3, surcharge basis points 0%, band tax £4,600.00, supplement basis points 8%, supplement £24,000.00, total £28,600.00 |
| Scotland ADS at 6% on 4 December 2024 | price £300,000.00, region scotland, property use residential, first time buyer false, additional property true, non resident false, effective date 2024-12-04 | → | tax LBTT, schedule standard, slices ×3, surcharge basis points 0%, band tax £4,600.00, supplement basis points 6%, supplement £18,000.00, total £22,600.00 |
| Scotland ADS at 4% on 15 December 2022 | price £300,000.00, region scotland, property use residential, first time buyer false, additional property true, non resident false, effective date 2022-12-15 | → | tax LBTT, schedule standard, slices ×3, surcharge basis points 0%, band tax £4,600.00, supplement basis points 4%, supplement £12,000.00, total £16,600.00 |
| Scotland ADS not due under £40,000 | price £39,999.00, region scotland, property use residential, first time buyer false, additional property true, non resident false, effective date 2025-06-01 | → | tax LBTT, schedule standard, slices ×1, surcharge basis points 0%, band tax £0.00, supplement basis points 0%, supplement £0.00, total £0.00 |
| Scotland LBTT and ADS are each rounded down: £4,600.5625 and £24,000.90 | price £300,011.25, region scotland, property use residential, first time buyer false, additional property true, non resident false, effective date 2025-06-01 | → | tax LBTT, schedule standard, slices ×3, surcharge basis points 0%, band tax £4,600.00, supplement basis points 8%, supplement £24,000.00, total £28,600.00 |
| Scotland has no non-resident surcharge | price £300,000.00, region scotland, property use residential, first time buyer false, additional property false, non resident true, effective date 2025-06-01 | → | tax LBTT, schedule standard, slices ×3, surcharge basis points 0%, band tax £4,600.00, supplement basis points 0%, supplement £0.00, total £4,600.00 |
| Scotland non-residential, £400,000: £8,500 | price £400,000.00, region scotland, property use non-residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | tax LBTT, schedule standard, slices ×3, surcharge basis points 0%, band tax £8,500.00, supplement basis points 0%, supplement £0.00, total £8,500.00 |
| Wales, £300,000 from 10 October 2022: £4,500 | price £300,000.00, region wales, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | tax LTT, schedule standard, slices ×2, surcharge basis points 0%, band tax £4,500.00, supplement basis points 0%, supplement £0.00, total £4,500.00 |
| Wales, £300,000 on 9 October 2022: £4,950 | price £300,000.00, region wales, property use residential, first time buyer false, additional property false, non resident false, effective date 2022-10-09 | → | tax LTT, schedule standard, slices ×3, surcharge basis points 0%, band tax £4,950.00, supplement basis points 0%, supplement £0.00, total £4,950.00 |
| Wales during the 2021 reduction: nil band £250,000 | price £300,000.00, region wales, property use residential, first time buyer false, additional property false, non resident false, effective date 2021-04-01 | → | tax LTT, schedule standard, slices ×2, surcharge basis points 0%, band tax £2,500.00, supplement basis points 0%, supplement £0.00, total £2,500.00 |
| Wales higher rates from 11 December 2024 are their own table: £19,950 | price £300,000.00, region wales, property use residential, first time buyer false, additional property true, non resident false, effective date 2025-06-01 | → | tax LTT, schedule higher, slices ×3, surcharge basis points 0%, band tax £19,950.00, supplement basis points 0%, supplement £0.00, total £19,950.00 |
| Wales higher rates on 10 December 2024: £16,950 | price £300,000.00, region wales, property use residential, first time buyer false, additional property true, non resident false, effective date 2024-12-10 | → | tax LTT, schedule higher, slices ×3, surcharge basis points 0%, band tax £16,950.00, supplement basis points 0%, supplement £0.00, total £16,950.00 |
| Wales higher rates do not apply under £40,000 | price £39,999.00, region wales, property use residential, first time buyer false, additional property true, non resident false, effective date 2025-06-01 | → | tax LTT, schedule standard, slices ×1, surcharge basis points 0%, band tax £0.00, supplement basis points 0%, supplement £0.00, total £0.00 |
| Wales has no first-time buyer relief: the main rates apply | price £200,000.00, region wales, property use residential, first time buyer true, additional property false, non resident false, effective date 2025-06-01 | → | tax LTT, schedule standard, slices ×1, surcharge basis points 0%, band tax £0.00, supplement basis points 0%, supplement £0.00, total £0.00 |
| Wales non-residential, £1,200,000: £49,750 | price £1,200,000.00, region wales, property use non-residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | tax LTT, schedule standard, slices ×4, surcharge basis points 0%, band tax £49,750.00, supplement basis points 0%, supplement £0.00, total £49,750.00 |
| a first-time buyer cannot also be buying an additional property | price £300,000.00, region england-ni, property use residential, first time buyer true, additional property true, non resident false, effective date 2025-06-01 | → | error: a first-time buyer cannot be buying an additional property |
| first-time buyer relief is residential only | price £300,000.00, region england-ni, property use non-residential, first time buyer true, additional property false, non resident false, effective date 2025-06-01 | → | error: apply only to residential purchases |
| England residential before 8 July 2020 is outside the data | price £300,000.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2020-07-07 | → | error: no SDLT standard rates for england-ni residential on 2020-07-07 |
| Scotland residential before 1 April 2021 is outside the data | price £300,000.00, region scotland, property use residential, first time buyer false, additional property false, non resident false, effective date 2021-03-31 | → | error: no LBTT standard rates for scotland residential on 2021-03-31 |
| Northern Ireland is part of england-ni | price £300,000.00, region northern-ireland, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | error: unknown region |
| a price in euros is refused | price €300,000.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | error: price must be in GBP |
| a negative price is refused | price -£0.01, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | error: price must be between 0 and |
| a malformed date is refused | price £300,000.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 1 June 2025 | → | error: effectiveDate must be an ISO date |
| a trailing newline is not part of an ISO date | price £300,000.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date 2025-06-01 | → | error: effectiveDate must be an ISO date |
| non-ASCII digits are not an ISO date | price £300,000.00, region england-ni, property use residential, first time buyer false, additional property false, non resident false, effective date ٢٠٢٥-06-01 | → | error: effectiveDate must be an ISO date |
More from the author
| region | tax | administered by | |---|---|---| | `england-ni` | Stamp Duty Land Tax (SDLT) | HMRC | | `scotland` | Land and Buildings Transaction Tax (LBTT) | Revenue Scotland | | `wales` | Land Transaction Tax (LTT) | Welsh Revenue Authority |
All three are "slice" taxes: the price is cut into bands and each band is taxed at its own rate. `stampDuty` takes one purchase and returns the bands it reaches, the tax on each, the total, and which schedule of rates applied.
## What it covers
- **Residential and non-residential** (non-residential includes mixed use) purchase prices, for every region. - **First-time buyers.** England: relief rates up to a price limit; above the limit the relief is lost entirely and the standard rates apply to the whole price (£500,000 → £10,000; £500,000.01 → £15,000). Scotland: the nil band rises to £175,000, with no price limit. Wales has no first-time buyer relief, so the main rates apply. From 8 July 2020 to 30 June 2021 England's first-time buyer rates were replaced by the reduced standard rates, so the standard schedule applies then. - **Additional dwellings** (`additionalProperty`): England's higher rates add 3 points to every band (5 points from 31 October 2024); Scotland's Additional Dwelling Supplement is a separate charge on the whole price (4%, then 6% from 16 December 2022, 8% from 5 December 2024); Wales's higher residential rates are a separate table with their own bands. None applies below £40,000. - **Non-resident surcharge** (`nonResident`): England and Northern Ireland only, 2 points on every residential band from 1 April 2021, on top of any other rates including the nil band and first-time buyer rates. It does not apply to non-residential purchases, and Scotland and Wales have none, so the flag changes nothing there.
The caller says whether the higher rates apply. Whether a purchase attracts them (replacing a main residence, spouses, inherited shares, the 36-month refund window) is a legal judgement, not arithmetic.
## Rounding
Each tax is rounded down to the whole pound, once, from the exact sum of the bands (HMRC SDLT Manual SDLTM00050: "The amount calculated is round down to the nearest pound"; Revenue Scotland and the Welsh Revenue Authority return guidance: tax entered rounded down to the nearest pound). In Scotland LBTT and ADS are separate lines on the return, each rounded down on its own, so they are here too: `bandTax` and `supplement`, with `total` their sum. The per-band `tax` in `slices` is rounded down to the penny and is for display; it is not what the total is built from.
## Rules as dated data
Every band, relief schedule and surcharge is a dated row in `data/bands.json` and `data/surcharges.json`, taken from the date the rates changed, and the lookup uses `effectiveDate` (normally completion). The data covers every date from 1 April 2021 onwards in every region (and back to 8 July 2020 in England, 27 July 2020 in Wales, 2016 and 2018 for non-residential):
- England residential: 8 July 2020 (nil band £500,000), 1 July 2021 (£250,000), 1 October 2021 (£125,000 and a 2% band), 23 September 2022 (£250,000; first-time buyers £425,000 up to £625,000), 1 April 2025 (back to £125,000; first-time buyers £300,000 up to £500,000). - Scotland residential from 1 April 2021 (unchanged through 2026-27). - Wales main rates: 27 July 2020, 1 July 2021, 10 October 2022; higher rates 1 April 2018, 22 December 2020, 11 December 2024.
A date outside the data is an error, not a guess. A project installed with `history=current` keeps only the rules still in force and refuses an older date with an explanation rather than answering it with today's rates.
## What it does not do
- **Transitional contract rules.** Where a contract exchanged before a change completes after it (England's 3% higher rates for contracts before 31 October 2024; Scotland's 6% ADS for contracts on or before 4 December 2024; Wales's October 2022 and December 2024 rules), the old rates may apply. Pass the date whose rates apply as `effectiveDate`. - Leases (SDLT/LBTT/LTT on the net present value of rent), linked transactions, companies (England's 17% rate for non-natural persons over £500,000), multiple dwellings relief (abolished in England from 1 June 2024), mixed-use apportionment, and other reliefs and exemptions. - ADS on joint purchases or partial interests, and ADS or higher rates repayment claims.
## Sources
England and Northern Ireland (SDLT), fetched September 2026:
- GOV.UK, "Stamp Duty Land Tax: Residential property rates" (current rates, first-time buyers, 5% additional property surcharge, 2% non-resident surcharge): https://www.gov.uk/stamp-duty-land-tax/residential-property-rates - GOV.UK, "Stamp Duty Land Tax rates from 1 December 2003 to 31 March 2025", and the period pages linked from it (8 July 2020 to 30 June 2021; 1 July 2021 to 30 September 2021; 1 October 2021 to 22 September 2022; 23 September 2022 to 30 October 2024; 31 October 2024 to 31 March 2025; 4 December 2014 to 7 July 2020 for the non-residential rates from 17 March 2016 and the 3% higher rates from 1 April 2016): https://www.gov.uk/government/publications/rates-and-allowances-stamp-duty-land-tax - GOV.UK, "Higher rates of Stamp Duty Land Tax" (not below £40,000): https://www.gov.uk/guidance/stamp-duty-land-tax-buying-an-additional-residential-property - GOV.UK, "Rates of Stamp Duty Land Tax for non-UK residents" (£40,000; applies on top of the zero and first-time buyer rates; the £700,000 worked example, £39,000): https://www.gov.uk/guidance/rates-of-stamp-duty-land-tax-for-non-uk-residents - HMRC Stamp Duty Land Tax Manual SDLTM00050 (rounding): https://www.gov.uk/hmrc-internal-manuals/stamp-duty-land-tax-manual/sdltm00050
Scotland (LBTT):
- Revenue Scotland, "Residential property" (rates from 1 April 2021, first-time buyer nil band £175,000): https://revenue.scot/taxes/land-buildings-transaction-tax/residential-property - Revenue Scotland, "Non-residential property" (rates from 25 January 2019): https://revenue.scot/taxes/land-buildings-transaction-tax/non-residential-property - Revenue Scotland, "The Additional Dwelling Supplement (ADS)" (4% from 25 January 2019, 6% from 16 December 2022, 8% from 5 December 2024; on the total price; not below £40,000): https://revenue.scot/taxes/land-buildings-transaction-tax/additional-dwelling-supplement-ads - Revenue Scotland, "About the calculation" (LBTT and ADS each rounded down to the nearest pound): https://revenue.scot/land-buildings-transaction-tax/guidance/how-to/paper-lbtt-return/about-calculation - Scottish Government, "Scottish Budget 2026 to 2027: Scottish tax ready reckoners" (LBTT rates and bands, including ADS, unchanged for 2026-27): https://www.gov.scot/publications/scottish-budget-2026-2027-scottish-tax-ready-reckoners/pages/4/
Wales (LTT):
- Welsh Government, "Land Transaction Tax rates and bands" (main residential rates from 27 July 2020, 1 July 2021 and 10 October 2022; higher residential rates from 1 April 2018, 22 December 2020 and 11 December 2024; non-residential rates from 1 April 2018 and 22 December 2020): https://www.gov.wales/land-transaction-tax-rates-and-bands - Welsh Government, "Higher rates of Land Transaction Tax: overview" (a residential property worth £40,000 or more): https://www.gov.wales/higher-rates-land-transaction-tax-overview - Welsh Government, "Land Transaction Tax return guidance: about the calculation" (amounts rounded down to the nearest pound): https://www.gov.wales/land-transaction-tax-return-guidance-about-calculation-all-transactions-html
1.0.1 fixes Python accepting a trailing newline or non-ASCII digits in effectiveDate; adds tests.
Files
| Path | Bytes |
|---|---|
| README.md | 8,011 |
| data/bands.json | 28,076 |
| data/surcharges.json | 2,029 |
| impl/python.py | 6,774 |
| impl/rust.rs | 9,325 |
| impl/typescript.ts | 6,732 |
| vectors.json | 50,298 |