fleet.company-car-bik
UK company car benefit in kind: appropriate percentage by CO2 and electric range, car and fuel benefit for a tax year.
1.0.0 (not the latest) · published 2026-10-03 by charlie · Anterra
Pinned by 33 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 or benefits-in-kind specialist 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 tax professional before it is published.
The taxable benefit in kind of a company car for one UK tax year, and of free fuel for it: what goes on a P11D or is payrolled. It follows the method in ITEPA 2003 s121:
For example
companyCarBik(£30,000.00, £0.00, 120, 0, false, 2023-03-01, 2025-09-01, 0, £0.00, false)→ tax year 2025/26, days in year 365, days available 365, price £30,000.00, appropriate basis points 30%, car benefit £9,000.00, fuel benefit £0.00, total £9,000.00 petrol car at 120 g/km in 2025-26 is 30% of 30,000.00companyCarBik(£30,000.00, £0.00, 120, 0, false, 2023-03-01, 2025-09-01, 0, £0.00, true)→ tax year 2025/26, days in year 365, days available 365, price £30,000.00, appropriate basis points 30%, car benefit £9,000.00, fuel benefit £8,460.00, total £17,460.00 free fuel adds 30% of the 28,200 multipliercompanyCarBik(£40,000.00, £0.00, 0, 250, false, 2024-01-10, 2025-09-01, 0, £0.00, true)→ tax year 2025/26, days in year 365, days available 365, price £40,000.00, appropriate basis points 3%, car benefit £1,200.00, fuel benefit £0.00, total £1,200.00 electric car at 3% in 2025-26, and electricity is not fuel so no fuel benefit
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 companyCarBik(listPrice: Money, capitalContributions: Money, co2: number, electricRangeMiles: number, dieselSupplement: boolean, firstRegistered: string, onDate: string, unavailableDays: number, privateUseContributions: Money, freeFuel: boolean): CompanyCarBik
| listPrice | Money | list price of the car plus taxable accessories, in GBP |
| capitalContributions | Money | what the employee paid towards the car or accessories; only the first 5,000.00 counts |
| co2 | int | the car's CO2 emissions figure in g/km, whole number (0 for zero emission) |
| electricRangeMiles | int | electric range in miles, rounded up to a whole number; 0 when the car has none |
| dieselSupplement | bool | true for a car propelled solely by diesel that does not meet Euro 6d (RDE2) |
| firstRegistered | date | date of first registration; before 6 April 2020 the NEDC table applies in 2021-22 |
| onDate | date | any date in the tax year to calculate |
| unavailableDays | int | days in the tax year the car was not available (30 or more consecutive days only) |
| privateUseContributions | Money | what the employee was required to pay, and paid, for private use in the year |
| freeFuel | bool | true when the employer provides fuel for private use |
| returns | CompanyCarBik |
The type it declares, generated into your project
/** The benefit for one car and one tax year, with the figures it was worked from. */
export interface CompanyCarBik {
/** HMRC's label, e.g. 2025/26 */
readonly taxYear: string;
/** 365 or 366 */
readonly daysInYear: number;
/** days in the year less unavailableDays */
readonly daysAvailable: number;
/** list price less the capital contributions that count */
readonly price: Money;
/** the appropriate percentage, 2500 = 25%, after any diesel supplement */
readonly appropriateBasisPoints: number;
/** cash equivalent of the car, after unavailability and private use payments */
readonly carBenefit: Money;
/** cash equivalent of free fuel, zero without it */
readonly fuelBenefit: Money;
/** car benefit plus fuel benefit */
readonly total: Money;
}
Your code names it in one line, in the file that uses it
import { companyCarBik } from "#fune/fleet.company-car-bik@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { daysBetween } from "./dates_days_between.ts"; ← from dates.days-between ^1.0.0 · built alongside by fune
import { ukTaxYear } from "./dates_uk_tax_year.ts"; ← from dates.uk-tax-year ^1.0.0 · built alongside by fune
import { roundDiv } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { addMoney } from "./money_add.ts"; ← from money.add ^1.0.0 · built alongside by fune
import { type Money, money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { BIK_BANDS, BIK_BANDS_HISTORY, BIK_LIMITS, BIK_LIMITS_HISTORY, FUEL_MULTIPLIERS, FUEL_MULTIPLIERS_HISTORY } from "./fleet_company_car_bik_data.ts"; ← this capability’s own data, compiled from data/bik-bands.json into the same file by fune build
import { type CompanyCarBik } from "./fleet_company_car_bik_types.ts";
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
const MAX_SAFE = 9007199254740991;
// Cars first registered before this date keep their NEDC figures, and in
// 2021-22 their own (one point higher) column of the table.
const WLTP_FROM = "2020-04-06";
interface Dated {
validFrom: string;
validTo: string | null;
}
function inForce(row: Dated, onDate: string): boolean {
return row.validFrom <= onDate && (row.validTo === null || onDate <= row.validTo);
}
// A history=current build has dropped old rows; say so rather than answering
// an old tax year with today's figures.
function missing(what: string, label: string, rows: readonly Dated[], history: string, onDate: string): never {
if (history !== "full") {
let earliest: string | null = null;
for (const row of rows) if (earliest === null || row.validFrom < earliest) earliest = row.validFrom;
if (earliest !== null && onDate < earliest) {
throw new RangeError(
`no ${what} for tax year ${label}: this build was installed with history=${history}, ` +
`so it only carries figures from ${earliest}. Reinstall with history=full for earlier years.`
);
}
}
throw new RangeError(`no ${what} for tax year ${label}`);
}
function checkGbp(name: string, amount: Money): void {
if (amount.currency !== "GBP") {
throw new RangeError(`company car benefit is worked in GBP; ${name} is in ${amount.currency}`);
}
if (amount.minor < 0) {
throw new RangeError(`${name} must not be negative, received ${amount.minor}`);
}
}
function checkWhole(name: string, value: number, unit: string): void {
if (!Number.isInteger(value) || value < 0) {
throw new RangeError(`${name} must be a whole number of ${unit}, not negative, received ${value}`);
}
}
function checkDate(name: string, value: string): void {
if (!ISO_DATE.test(value)) {
throw new RangeError(`${name} must be an ISO date (YYYY-MM-DD), received "${value}"`);
}
}
// price x basis points x days available / (10000 x days in year), rounded down
// to the penny: one rounding, and never more benefit than the law charges.
function proportion(minor: number, basisPoints: number, daysAvailable: number, daysInYear: number): number {
const product = minor * basisPoints * daysAvailable;
if (product > MAX_SAFE) {
throw new RangeError("the benefit calculation exceeds 2^53 - 1; the list price is too large");
}
return roundDiv(product, 10000 * daysInYear, "down");
}
/**
* The taxable benefit of a company car for one UK tax year (ITEPA 2003 s121).
*
* Price less capital contributions (at most 5,000.00), times the appropriate
* percentage for the car's CO2 figure and electric range in that year, cut
* pro rata for days the car was unavailable, less what the employee paid for
* private use. Free fuel adds the year's fuel benefit multiplier times the same
* percentage, cut for the same days.
*/
export function companyCarBik(
listPrice: Money,
capitalContributions: Money,
co2: number,
electricRangeMiles: number,
dieselSupplement: boolean,
firstRegistered: string,
onDate: string,
unavailableDays: number,
privateUseContributions: Money,
freeFuel: boolean
): CompanyCarBik {
checkGbp("listPrice", listPrice);
checkGbp("capitalContributions", capitalContributions);
checkGbp("privateUseContributions", privateUseContributions);
checkWhole("co2", co2, "g/km");
checkWhole("electricRangeMiles", electricRangeMiles, "miles");
checkDate("firstRegistered", firstRegistered);
checkDate("onDate", onDate);
if (dieselSupplement && co2 === 0) {
throw new RangeError("a zero-emission car cannot take the diesel supplement");
}
const year = ukTaxYear(onDate);
if (firstRegistered > year.end) {
throw new RangeError(`firstRegistered (${firstRegistered}) is after the end of tax year ${year.label}`);
}
const daysInYear = daysBetween(year.start, year.end) + 1;
if (!Number.isInteger(unavailableDays) || unavailableDays < 0 || unavailableDays > daysInYear) {
throw new RangeError(`unavailableDays must be a whole number from 0 to ${daysInYear}, received ${unavailableDays}`);
}
const daysAvailable = daysInYear - unavailableDays;
const registration = firstRegistered < WLTP_FROM ? "before-2020-04-06" : "from-2020-04-06";
const applies = (row: { registration: string }) => row.registration === "any" || row.registration === registration;
const limit = BIK_LIMITS.find((row) => applies(row) && inForce(row, onDate));
if (limit === undefined) missing("company car benefit rates", year.label, BIK_LIMITS, BIK_LIMITS_HISTORY, onDate);
let basisPoints: number;
if (co2 >= 75) {
// s139(3): one point per whole 5 g/km above 75, up to the year's maximum.
basisPoints = Math.min(limit.from75BasisPoints + 100 * Math.floor((co2 - 75) / 5), limit.maximumBasisPoints);
} else {
const band = BIK_BANDS.find(
(row) =>
applies(row) &&
inForce(row, onDate) &&
row.co2From <= co2 &&
co2 <= row.co2To &&
row.rangeFrom <= electricRangeMiles &&
(row.rangeTo === null || electricRangeMiles <= row.rangeTo)
);
if (band === undefined) missing("company car benefit band", year.label, BIK_BANDS, BIK_BANDS_HISTORY, onDate);
basisPoints = band.basisPoints;
}
if (dieselSupplement) {
basisPoints = Math.min(basisPoints + limit.dieselSupplementBasisPoints, limit.maximumBasisPoints);
}
const counted = Math.min(capitalContributions.minor, limit.capitalContributionCapMinor);
const price = money(Math.max(listPrice.minor - counted, 0), "GBP");
const provisional = proportion(price.minor, basisPoints, daysAvailable, daysInYear);
const carBenefit = money(Math.max(provisional - privateUseContributions.minor, 0), "GBP");
let fuelBenefit = money(0, "GBP");
// s149(4): electricity, and energy for a car that cannot emit CO2, is not fuel.
if (freeFuel && co2 > 0) {
const multiplier = FUEL_MULTIPLIERS.find((row) => inForce(row, onDate));
if (multiplier === undefined) {
missing("car fuel benefit multiplier", year.label, FUEL_MULTIPLIERS, FUEL_MULTIPLIERS_HISTORY, onDate);
}
fuelBenefit = money(proportion(multiplier.pounds * 100, basisPoints, daysAvailable, daysInYear), "GBP");
}
return {
taxYear: year.label,
daysInYear,
daysAvailable,
price,
appropriateBasisPoints: basisPoints,
carBenefit,
fuelBenefit,
total: addMoney(carBenefit, fuelBenefit),
};
}Install
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 5 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 fleet.company-car-bik
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./fleet.company-car-bik-1.0.0-typescript.fune, or fetch it from a terminal with fune pull fleet.company-car-bik@1.0.0:typescript.
The whole function, every language, is one file too: fleet.company-car-bik-1.0.0.fune, 77,201 bytes, sha256 f46eed0571d51a4cb6c090d3e55436d3bac3f446f14f74bc68b9484a5f01a977. 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 fleet.company-car-bik
after — your function gets the result and the arguments, and returns the final result.
// fune: after fleet.company-car-bik
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 dates.days-between in fleet.company-car-bik
// fune: replace dates.uk-tax-year in fleet.company-car-bik
// fune: replace math.round-div in fleet.company-car-bik
// fune: replace money.add in fleet.company-car-bik
// fune: replace money.amount in fleet.company-car-bik
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 fleet.company-car-bik --steps.
// fune: step fleet.company-car-bik 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 | |
|---|---|---|---|
| petrol car at 120 g/km in 2025-26 is 30% of 30,000.00 | £30,000.00, £0.00, 120, 0, false, 2023-03-01, 2025-09-01, 0, £0.00, false | → | tax year 2025/26, days in year 365, days available 365, price £30,000.00, appropriate basis points 30%, car benefit £9,000.00, fuel benefit £0.00, total £9,000.00 |
| free fuel adds 30% of the 28,200 multiplier | £30,000.00, £0.00, 120, 0, false, 2023-03-01, 2025-09-01, 0, £0.00, true | → | tax year 2025/26, days in year 365, days available 365, price £30,000.00, appropriate basis points 30%, car benefit £9,000.00, fuel benefit £8,460.00, total £17,460.00 |
| electric car at 3% in 2025-26, and electricity is not fuel so no fuel benefit | £40,000.00, £0.00, 0, 250, false, 2024-01-10, 2025-09-01, 0, £0.00, true | → | tax year 2025/26, days in year 365, days available 365, price £40,000.00, appropriate basis points 3%, car benefit £1,200.00, fuel benefit £0.00, total £1,200.00 |
| plug-in hybrid at 30 g/km with a 45 mile range is 10% in 2026-27 | £45,000.00, £0.00, 30, 45, false, 2025-06-01, 2026-06-01, 0, £0.00, false | → | tax year 2026/27, days in year 365, days available 365, price £45,000.00, appropriate basis points 10%, car benefit £4,500.00, fuel benefit £0.00, total £4,500.00 |
| the same hybrid is 18% in 2028-29, when range stops mattering | £45,000.00, £0.00, 30, 45, false, 2025-06-01, 2028-05-01, 0, £0.00, false | → | tax year 2028/29, days in year 365, days available 365, price £45,000.00, appropriate basis points 18%, car benefit £8,100.00, fuel benefit £0.00, total £8,100.00 |
| 2021-22, car first registered before 6 April 2020 uses the NEDC column: 100 g/km is 25% | £25,000.00, £0.00, 100, 0, false, 2019-05-01, 2021-10-01, 0, £0.00, false | → | tax year 2021/22, days in year 365, days available 365, price £25,000.00, appropriate basis points 25%, car benefit £6,250.00, fuel benefit £0.00, total £6,250.00 |
| 2021-22, first registered on 6 April 2020 uses the WLTP column: 24% | £25,000.00, £0.00, 100, 0, false, 2020-04-06, 2021-10-01, 0, £0.00, false | → | tax year 2021/22, days in year 365, days available 365, price £25,000.00, appropriate basis points 24%, car benefit £6,000.00, fuel benefit £0.00, total £6,000.00 |
| 2021-22, first registered on 5 April 2020 is still NEDC | £25,000.00, £0.00, 100, 0, false, 2020-04-05, 2021-10-01, 0, £0.00, false | → | tax year 2021/22, days in year 365, days available 365, price £25,000.00, appropriate basis points 25%, car benefit £6,250.00, fuel benefit £0.00, total £6,250.00 |
| 2023-24 has 366 days: a diesel unavailable 61 days pays for 305/366 of 35% | £30,000.00, £0.00, 130, 0, true, 2019-01-01, 2023-11-15, 61, £0.00, false | → | tax year 2023/24, days in year 366, days available 305, price £30,000.00, appropriate basis points 35%, car benefit £8,750.00, fuel benefit £0.00, total £8,750.00 |
| the diesel supplement stops at the 37% maximum | £20,000.00, £0.00, 158, 0, true, 2017-01-01, 2024-12-01, 0, £0.00, false | → | tax year 2024/25, days in year 365, days available 365, price £20,000.00, appropriate basis points 37%, car benefit £7,400.00, fuel benefit £0.00, total £7,400.00 |
Show the other 23 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| capital contributions count only up to 5,000.00; private use payments come off after | £30,000.00, £8,000.00, 0, 300, false, 2024-01-01, 2025-09-01, 0, £200.00, false | → | tax year 2025/26, days in year 365, days available 365, price £25,000.00, appropriate basis points 3%, car benefit £550.00, fuel benefit £0.00, total £550.00 |
| private use payments above the benefit leave it at nil, not negative | £20,000.00, £0.00, 0, 300, false, 2024-01-01, 2025-09-01, 0, £1,000.00, false | → | tax year 2025/26, days in year 365, days available 365, price £20,000.00, appropriate basis points 3%, car benefit £0.00, fuel benefit £0.00, total £0.00 |
| unavailability rounds the car benefit down to the penny; the fuel benefit is 265/365 of 34% of 29,200 | £33,333.33, £0.00, 142, 0, false, 2022-01-01, 2026-08-01, 100, £0.00, true | → | tax year 2026/27, days in year 365, days available 265, price £33,333.33, appropriate basis points 34%, car benefit £8,228.30, fuel benefit £7,208.00, total £15,436.30 |
| 2026-27: 77 g/km is still 21%, the same as 70-74 g/km | £10,000.00, £0.00, 77, 0, false, 2022-01-01, 2026-08-01, 0, £0.00, false | → | tax year 2026/27, days in year 365, days available 365, price £10,000.00, appropriate basis points 21%, car benefit £2,100.00, fuel benefit £0.00, total £2,100.00 |
| 2026-27: 74 g/km is 21% | £10,000.00, £0.00, 74, 0, false, 2022-01-01, 2026-08-01, 0, £0.00, false | → | tax year 2026/27, days in year 365, days available 365, price £10,000.00, appropriate basis points 21%, car benefit £2,100.00, fuel benefit £0.00, total £2,100.00 |
| 2025-26: 50 g/km with a 130 mile range is 3% | £10,000.00, £0.00, 50, 130, false, 2024-01-01, 2025-09-01, 0, £0.00, false | → | tax year 2025/26, days in year 365, days available 365, price £10,000.00, appropriate basis points 3%, car benefit £300.00, fuel benefit £0.00, total £300.00 |
| 2025-26: 50 g/km with a 129 mile range is 6% | £10,000.00, £0.00, 50, 129, false, 2024-01-01, 2025-09-01, 0, £0.00, false | → | tax year 2025/26, days in year 365, days available 365, price £10,000.00, appropriate basis points 6%, car benefit £600.00, fuel benefit £0.00, total £600.00 |
| 2025-26: 51 g/km is 16% whatever the range | £10,000.00, £0.00, 51, 130, false, 2024-01-01, 2025-09-01, 0, £0.00, false | → | tax year 2025/26, days in year 365, days available 365, price £10,000.00, appropriate basis points 16%, car benefit £1,600.00, fuel benefit £0.00, total £1,600.00 |
| 2029-30 maximum is 39% | £10,000.00, £0.00, 250, 0, false, 2027-01-01, 2029-09-01, 0, £0.00, false | → | tax year 2029/30, days in year 365, days available 365, price £10,000.00, appropriate basis points 39%, car benefit £3,900.00, fuel benefit £0.00, total £3,900.00 |
| 2029-30 zero emission is 9% | £50,000.00, £0.00, 0, 300, false, 2027-01-01, 2029-09-01, 0, £0.00, false | → | tax year 2029/30, days in year 365, days available 365, price £50,000.00, appropriate basis points 9%, car benefit £4,500.00, fuel benefit £0.00, total £4,500.00 |
| 5 April 2026 is still 2025-26: 3% for a zero-emission car | £10,000.00, £0.00, 0, 0, false, 2024-01-01, 2026-04-05, 0, £0.00, false | → | tax year 2025/26, days in year 365, days available 365, price £10,000.00, appropriate basis points 3%, car benefit £300.00, fuel benefit £0.00, total £300.00 |
| 6 April 2026 starts 2026-27: 4% | £10,000.00, £0.00, 0, 0, false, 2024-01-01, 2026-04-06, 0, £0.00, false | → | tax year 2026/27, days in year 365, days available 365, price £10,000.00, appropriate basis points 4%, car benefit £400.00, fuel benefit £0.00, total £400.00 |
| a car unavailable all year has no benefit | £30,000.00, £0.00, 120, 0, false, 2023-03-01, 2025-09-01, 365, £0.00, true | → | tax year 2025/26, days in year 365, days available 0, price £30,000.00, appropriate basis points 30%, car benefit £0.00, fuel benefit £0.00, total £0.00 |
| no fuel benefit multiplier is published yet for 2027-28 | £30,000.00, £0.00, 120, 0, false, 2023-03-01, 2027-09-01, 0, £0.00, true | → | error: no car fuel benefit multiplier for tax year 2027/28 |
| 2020-21 is before the tables this carries | £30,000.00, £0.00, 120, 0, false, 2019-01-01, 2020-09-01, 0, £0.00, false | → | error: no company car benefit rates for tax year 2020/21 |
| only GBP | $30,000.00, £0.00, 120, 0, false, 2023-03-01, 2025-09-01, 0, £0.00, false | → | error: company car benefit is worked in GBP |
| a negative list price is an error | -£0.01, £0.00, 120, 0, false, 2023-03-01, 2025-09-01, 0, £0.00, false | → | error: listPrice must not be negative |
| more unavailable days than the year has | £30,000.00, £0.00, 120, 0, false, 2023-03-01, 2025-09-01, 366, £0.00, false | → | error: unavailableDays must be a whole number from 0 to 365 |
| a fractional CO2 figure must be rounded down by the caller | £30,000.00, £0.00, 120.5, 0, false, 2023-03-01, 2025-09-01, 0, £0.00, false | → | error: co2 must be a whole number of g/km |
| a negative electric range | £30,000.00, £0.00, 120, -1, false, 2023-03-01, 2025-09-01, 0, £0.00, false | → | error: electricRangeMiles must be a whole number of miles |
| a zero-emission car has no diesel supplement | £30,000.00, £0.00, 0, 0, true, 2023-03-01, 2025-09-01, 0, £0.00, false | → | error: a zero-emission car cannot take the diesel supplement |
| a malformed date | £30,000.00, £0.00, 120, 0, false, 2023-03-01, 1/9/2025, 0, £0.00, false | → | error: onDate must be an ISO date (YYYY-MM-DD) |
| a car first registered after the tax year | £30,000.00, £0.00, 120, 0, false, 2026-05-01, 2025-09-01, 0, £0.00, false | → | error: is after the end of tax year 2025/26 |
More from the author
1. the price: list price plus taxable accessories (`listPrice`), less the employee's capital contributions, of which only the first 5,000.00 counts (s132); 2. times the appropriate percentage for the car's CO2 figure and, for 1-50 g/km, its electric range, in that tax year (s139), plus the 4% diesel supplement where it applies (s141); 3. cut for the days the car was unavailable: price x percentage x (days in year - unavailable days) / days in year (s143); 4. less what the employee was required to pay, and paid, for private use, never below nil (s144).
Free fuel (s149-150) is the year's car fuel benefit multiplier times the same appropriate percentage, cut for the same unavailable days. It is not reduced by private use payments. Electricity is not fuel (s149(4)), so a zero-emission car has no fuel benefit even when `freeFuel` is true.
`taxYear`, `daysInYear` (366 in a year containing 29 February, such as 2023-24 and 2027-28) and the percentage come back with the answer, so a payroll line can show its working.
## What the caller decides
- `co2` is the whole-number g/km on the registration documents: the NEDC figure for a car first registered before 6 April 2020, WLTP otherwise. s139(2) rounds a fractional figure down; do that before calling. - `electricRangeMiles` is rounded up to a whole mile (s139(2)(b)). It only matters for 1-50 g/km up to 2027-28; from 2028-29 every 1-50 g/km car has one percentage. - `dieselSupplement` is true for a car propelled solely by diesel that is not certified to Euro 6d (RDE2). Hybrids are not "solely by diesel". The supplement never takes the percentage past the year's maximum. - `firstRegistered` picks the table column. Only 2021-22 has different percentages for cars first registered before 6 April 2020 (NEDC, one point higher). It must not be after the tax year. - `unavailableDays` are the days before the car was first available, after it was last available, and in any spell of 30 or more consecutive days when it was not available. Shorter gaps do not count; this function trusts the count.
## Rounding
Each benefit is one exact division, rounded down to the penny, so neither is ever more than the statutory figure. The P11D takes whole pounds; converting is left to the caller. The same rounding is used for 2021-22 and 2029-30.
## What it does not do
Cars without a CO2 figure (s140), cars first registered before 1998 (s142), shared cars, a replacement car (s145), classic cars valued at market value, optional remuneration arrangements (salary sacrifice, s121A), fuel withdrawn part way through the year (s152), and the easement for plug-in hybrids approved under Euro 6e-bis from 1 January 2025 (EIM24710). Vans are a different charge.
## Rates
Three tables in `data/`, each with dated rows, looked up on `onDate`:
- `bik-bands.json`: the percentages below 75 g/km, including 0 g/km and the 1-50 g/km electric range bands, by tax year and registration column. - `bik-limits.json`: the percentage at 75 g/km, the maximum, the 4% diesel supplement and the 5,000.00 capital contribution cap. From 75 g/km the percentage rises one point per whole 5 g/km (s139(3)). Note that since 2025-26 the 70-74 and 75-79 g/km bands share a percentage (21% in 2026-27), so "one point per 5 g/km from 55" gets 75-79 wrong. - `fuel-multipliers.json`: the car fuel benefit multiplier, 24,600 (2021-22), 25,300, 27,800, 27,800, 28,200 and 29,200 (2026-27). The multiplier for 2027-28 and later is set each Budget and is not published yet, so free fuel in those years is an error rather than a guess; 2027-28 onward percentages are legislated, so the car benefit itself is answered.
| tax year | 0 g/km | 1-50 g/km by range (130+, 70-129, 40-69, 30-39, <30) | 51-54 | at 75 g/km | max | |---|---|---|---|---|---| | 2021-22 WLTP | 1% | 1, 4, 7, 11, 13% | 14% | 19% | 37% | | 2021-22 NEDC | 1% | 2, 5, 8, 12, 14% | 15% | 20% | 37% | | 2022-23 to 2024-25 | 2% | 2, 5, 8, 12, 14% | 15% | 20% | 37% | | 2025-26 | 3% | 3, 6, 9, 13, 15% | 16% | 21% | 37% | | 2026-27 | 4% | 4, 7, 10, 14, 16% | 17% | 21% | 37% | | 2027-28 | 5% | 5, 8, 11, 15, 17% | 18% | 21% | 37% | | 2028-29 | 7% | 18% | 19% | 22% | 38% | | 2029-30 on | 9% | 19% | 20% | 23% | 39% |
Years before 2021-22 are not carried and are an error. The tables declare effective columns, so a `history=current` build refuses an earlier tax year instead of answering it with today's figures. When a rate changes, a new version of this capability carries it.
## Sources
- HMRC Employment Income Manual, EIM24705, "Car benefit calculation: step 5: appropriate percentage: tax years 2015 to 2016 onwards: ready reckoner for type A cars" (tables 2020-21 to 2026-27, NEDC and WLTP columns, the diesel supplement and Euro 6d): https://www.gov.uk/hmrc-internal-manuals/employment-income-manual/eim24705 - Finance Act 2023 s11 (tables for 2025-26, 2026-27 and 2027-28, and 21% at 75 g/km): https://www.legislation.gov.uk/ukpga/2023/1/section/11/enacted - Finance Act 2025 s5 and s6 (2028-29 and 2029-30, maximum 38% and 39%): https://www.legislation.gov.uk/ukpga/2025/8/section/5/enacted and https://www.legislation.gov.uk/ukpga/2025/8/section/6/enacted - HMRC policy paper, "Taxation of company cars: The appropriate percentage for tax years 2028 to 2029 and 2029 to 2030" (30 October 2024): https://www.gov.uk/government/publications/income-tax-company-car-tax-rates-2028-to-2030 - ITEPA 2003 s132 (capital contributions, 5,000), s139, s141 (diesel), s143 (unavailability), s144 (private use payments), s149 (fuel; electricity is not fuel): https://www.legislation.gov.uk/ukpga/2003/1/part/3/chapter/6 - HMRC, "Travel - mileage and fuel rates and allowances" (car fuel benefit multiplier by tax year): https://www.gov.uk/government/publications/rates-and-allowances-travel-mileage-and-fuel-allowances/travel-mileage-and-fuel-rates-and-allowances
Files
| Path | Bytes |
|---|---|
| README.md | 6,200 |
| data/bik-bands.json | 17,562 |
| data/bik-limits.json | 1,795 |
| data/fuel-multipliers.json | 999 |
| impl/python.py | 7,305 |
| impl/rust.rs | 9,926 |
| impl/typescript.ts | 6,940 |
| vectors.json | 15,241 |