fleet.company-car-bik Unreviewed
UK company car benefit in kind: appropriate percentage by CO2 and electric range, car and fuel benefit for a tax year.
1.0.2 · published 2026-10-03 by charlie · Anterra
Pinned by 37 tests, run in TypeScript, Python and Rust.
Unreviewed. This capability’s implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified payroll or benefits-in-kind specialist has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.
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
company_car_bik(£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.00company_car_bik(£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 multipliercompany_car_bik(£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.
def company_car_bik(list_price: Money, capital_contributions: Money, co2: int, electric_range_miles: int, diesel_supplement: bool, first_registered: str, on_date: str, unavailable_days: int, private_use_contributions: Money, free_fuel: bool) -> CompanyCarBik
| list_price | Money | list price of the car plus taxable accessories, in GBP |
| capital_contributions | 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) |
| electric_range_miles | int | electric range in miles, rounded up to a whole number; 0 when the car has none |
| diesel_supplement | bool | true for a car propelled solely by diesel that does not meet Euro 6d (RDE2) |
| first_registered | date | date of first registration; before 6 April 2020 the NEDC table applies in 2021-22 |
| on_date | date | any date in the tax year to calculate |
| unavailable_days | int | days in the tax year the car was not available (30 or more consecutive days only) |
| private_use_contributions | Money | what the employee was required to pay, and paid, for private use in the year |
| free_fuel | bool | true when the employer provides fuel for private use |
| returns | CompanyCarBik |
The type it declares, generated into your project
@dataclass(frozen=True)
class CompanyCarBik:
"""The benefit for one car and one tax year, with the figures it was worked from."""
#: HMRC's label, e.g. 2025/26
tax_year: str
#: 365 or 366
days_in_year: int
#: days in the year less unavailableDays
days_available: int
#: list price less the capital contributions that count
price: Money
#: the appropriate percentage, 2500 = 25%, after any diesel supplement
appropriate_basis_points: int
#: cash equivalent of the car, after unavailability and private use payments
car_benefit: Money
#: cash equivalent of free fuel, zero without it
fuel_benefit: Money
#: car benefit plus fuel benefit
total: Money
Your code names it in one line, in the file that uses it
from fune.fleet.company_car_bik import company_car_bik # 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 re
from typing import NoReturn, Optional
from .dates_days_between import days_between ← from dates.days-between ^1.0.0 · built alongside by fune
from .dates_uk_tax_year import uk_tax_year ← from dates.uk-tax-year ^1.0.0 · built alongside by fune
from .fleet_company_car_bik_data import BIK_BANDS, BIK_BANDS_HISTORY, BIK_LIMITS, BIK_LIMITS_HISTORY, FUEL_MULTIPLIERS, FUEL_MULTIPLIERS_HISTORY ← this capability’s own data, compiled from data/bik-bands.json into the same file by fune build
from .fleet_company_car_bik_types import CompanyCarBik
from .math_round_div import round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .money_add import add_money ← from money.add ^1.0.0 · built alongside by fune
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
ISO_DATE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}")
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.
WLTP_FROM = "2020-04-06"
def _in_force(row, on_date: str) -> bool:
return row.valid_from <= on_date and (row.valid_to is None or on_date <= row.valid_to)
def _missing(what: str, label: str, rows, history: str, on_date: str) -> NoReturn:
# A history=current build has dropped old rows; say so rather than
# answering an old tax year with today's figures.
if history != "full":
earliest: Optional[str] = None
for row in rows:
if earliest is None or row.valid_from < earliest:
earliest = row.valid_from
if earliest is not None and on_date < earliest:
raise ValueError(
"no %s for tax year %s: this build was installed with history=%s, "
"so it only carries figures from %s. Reinstall with history=full for earlier years."
% (what, label, history, earliest)
)
raise ValueError("no %s for tax year %s" % (what, label))
def _check_gbp(name: str, amount: Money) -> None:
if amount.currency != "GBP":
raise ValueError("company car benefit is worked in GBP; %s is in %s" % (name, amount.currency))
if amount.minor < 0:
raise ValueError("%s must not be negative, received %d" % (name, amount.minor))
def _check_whole(name: str, value, unit: str) -> None:
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
raise ValueError("%s must be a whole number of %s, not negative, received %s" % (name, unit, value))
def _check_date(name: str, value) -> None:
if not isinstance(value, str) or not ISO_DATE.fullmatch(value):
raise ValueError('%s must be an ISO date (YYYY-MM-DD), received "%s"' % (name, value))
def _proportion(minor: int, basis_points: int, days_available: int, days_in_year: int) -> int:
# 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.
product = minor * basis_points * days_available
# Python would carry on, but TypeScript cannot, and all three must agree.
if product > MAX_SAFE:
raise ValueError("the benefit calculation exceeds 2^53 - 1; the list price is too large")
return round_div(product, 10000 * days_in_year, "down")
def company_car_bik(
list_price: Money,
capital_contributions: Money,
co2: int,
electric_range_miles: int,
diesel_supplement: bool,
first_registered: str,
on_date: str,
unavailable_days: int,
private_use_contributions: Money,
free_fuel: bool,
) -> CompanyCarBik:
"""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.
"""
_check_gbp("listPrice", list_price)
_check_gbp("capitalContributions", capital_contributions)
_check_gbp("privateUseContributions", private_use_contributions)
_check_whole("co2", co2, "g/km")
_check_whole("electricRangeMiles", electric_range_miles, "miles")
_check_date("firstRegistered", first_registered)
_check_date("onDate", on_date)
if diesel_supplement and co2 == 0:
raise ValueError("a zero-emission car cannot take the diesel supplement")
year = uk_tax_year(on_date)
if first_registered > year.end:
raise ValueError("firstRegistered (%s) is after the end of tax year %s" % (first_registered, year.label))
days_in_year = days_between(year.start, year.end) + 1
if (
isinstance(unavailable_days, bool)
or not isinstance(unavailable_days, int)
or unavailable_days < 0
or unavailable_days > days_in_year
):
raise ValueError(
"unavailableDays must be a whole number from 0 to %d, received %s" % (days_in_year, unavailable_days)
)
days_available = days_in_year - unavailable_days
registration = "before-2020-04-06" if first_registered < WLTP_FROM else "from-2020-04-06"
def applies(row) -> bool:
return row.registration == "any" or row.registration == registration
limit = next((row for row in BIK_LIMITS if applies(row) and _in_force(row, on_date)), None)
if limit is None:
_missing("company car benefit rates", year.label, BIK_LIMITS, BIK_LIMITS_HISTORY, on_date)
if co2 >= 75:
# s139(3): one point per whole 5 g/km above 75, up to the year's maximum.
basis_points = min(limit.from75_basis_points + 100 * ((co2 - 75) // 5), limit.maximum_basis_points)
else:
band = next(
(
row
for row in BIK_BANDS
if applies(row)
and _in_force(row, on_date)
and row.co2_from <= co2 <= row.co2_to
and row.range_from <= electric_range_miles
and (row.range_to is None or electric_range_miles <= row.range_to)
),
None,
)
if band is None:
_missing("company car benefit band", year.label, BIK_BANDS, BIK_BANDS_HISTORY, on_date)
basis_points = band.basis_points
if diesel_supplement:
basis_points = min(basis_points + limit.diesel_supplement_basis_points, limit.maximum_basis_points)
counted = min(capital_contributions.minor, limit.capital_contribution_cap_minor)
price = money(max(list_price.minor - counted, 0), "GBP")
provisional = _proportion(price.minor, basis_points, days_available, days_in_year)
car_benefit = money(max(provisional - private_use_contributions.minor, 0), "GBP")
fuel_benefit = money(0, "GBP")
# s149(4): electricity, and energy for a car that cannot emit CO2, is not fuel.
if free_fuel and co2 > 0:
multiplier = next((row for row in FUEL_MULTIPLIERS if _in_force(row, on_date)), None)
if multiplier is None:
_missing("car fuel benefit multiplier", year.label, FUEL_MULTIPLIERS, FUEL_MULTIPLIERS_HISTORY, on_date)
fuel_benefit = money(_proportion(multiplier.pounds * 100, basis_points, days_available, days_in_year), "GBP")
return CompanyCarBik(
tax_year=year.label,
days_in_year=days_in_year,
days_available=days_available,
price=price,
appropriate_basis_points=basis_points,
car_benefit=car_benefit,
fuel_benefit=fuel_benefit,
total=add_money(car_benefit, fuel_benefit),
)Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 5 dependencies, pins them in fune.lock, downloads only the Python 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 Python implementation. Install it without the registry with fune add ./fleet.company-car-bik-1.0.2-python.fune, or fetch it from a terminal with fune pull fleet.company-car-bik@1.0.2:python.
The whole function, every language, is one file too: fleet.company-car-bik-1.0.2.fune, 80,480 bytes, sha256 7e493f52d22a4498ea6cd4387b67b820b51da25c702c6184ec830b306bda0ff1. 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 27 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 |
| a onDate with a trailing newline is refused | £30,000.00, £0.00, 120, 0, false, 2023-03-01, 2026-09-16 , 0, £0.00, false | → | error: onDate must be an ISO date (YYYY-MM-DD), received "2026-09-16 " |
| a onDate in Arabic-Indic digits is refused | £30,000.00, £0.00, 120, 0, false, 2023-03-01, ٢٠٢٦-٠٩-١٦, 0, £0.00, false | → | error: onDate must be an ISO date (YYYY-MM-DD), received "٢٠٢٦-٠٩-١٦" |
| a firstRegistered with a trailing newline is refused | £30,000.00, £0.00, 120, 0, false, 2023-03-01 , 2025-09-01, 0, £0.00, false | → | error: firstRegistered must be an ISO date (YYYY-MM-DD), received "2023-03-01 " |
| a firstRegistered in Arabic-Indic digits is refused | £30,000.00, £0.00, 120, 0, false, ٢٠٢٦-٠٩-١٦, 2025-09-01, 0, £0.00, false | → | error: firstRegistered must be an ISO date (YYYY-MM-DD), received "٢٠٢٦-٠٩-١٦" |
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
1.0.1 fixes Python accepting a trailing newline or non-ASCII digits in firstRegistered and onDate; adds tests.
## Before you rely on this
**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 above, 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.
**Unreviewed.** This capability's implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified payroll or benefits-in-kind specialist has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.
## 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 marks it unreviewed and adds its attribution notices (NOTICE). The code and the tests are unchanged.
Files
| Path | Bytes |
|---|---|
| NOTICE | 234 |
| README.md | 7,700 |
| data/bik-bands.json | 17,562 |
| data/bik-limits.json | 1,795 |
| data/fuel-multipliers.json | 999 |
| impl/python.py | 7,316 |
| impl/rust.rs | 9,926 |
| impl/typescript.ts | 6,940 |
| vectors.json | 16,555 |