convert-units: chained unit conversion with tree-shakeable measure definitions
An elegant way to convert quantities between different units.
At a glance
- What is it?
- convert-units is a TypeScript utility for converting quantities between units, built around a configureMeasurements closure so you can ship only the measures you use. The default install is still a 3.0 beta, and the toBest helper the documentation calls subjective is the part most likely to surprise you.
- Who is it for?
- convert-units suits a TypeScript application that needs reliable unit conversion across a handful of measures and wants the unused measure definitions stripped from its bundle, since the configureMeasurements closure and the per-measure import paths make that a one line change.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A closure that holds your measure directory and hands back a convert function
The API shape of convert-units is unusual enough to be worth describing before anything else. You do not import a convert function. You import a factory called configureMeasurements, pass it a directory of measures, and it returns a convert function bound to exactly those measures.
// `allMeasures` includes all the measures packaged with this library
import configureMeasurements from 'convert-units';
import allMeasures from 'convert-units/definitions/all';
const convert = configureMeasurements(allMeasures);Everything else follows from that choice. The measures, the units, the systems and the conversion factors all live in the directory you pass in, and the convert function is just a view over that directory.
The reason to structure it this way is bundle size. If you import allMeasures, every packaged measure comes with it. If you import only the measures you use, the rest can be dropped by a bundler such as webpack or rollup.
import configureMeasurements from 'convert-units';
import volume from 'convert-units/definitions/volume';
/*
`configureMeasurements` is a closure that accepts a directory
of measures and returns a factory function (`convert`) that uses
only those measures.
*/
const convert = configureMeasurements({
volume,
mass,
length,
});The manifest backs this up. The package exports a wildcard subpath for definitions, with separate import and require conditions pointing at ESM and CommonJS builds of the same files, and the main entry points at lib/esm/index.js and lib/cjs/index.js. The project is written in TypeScript, and the compile script builds three targets: the main config, a CommonJS config and a UMD config.
One structural note: the directory in the snippet above names volume, mass and length, but only volume is imported in the code shown. In the README's own listing the missing imports are left implicit, so treat that snippet as a shape rather than a copy-paste ready file.
Conversion is from, to, and a hard stop between different measures
Once you have a convert function, the chain is short. You pass a value, name the unit you are converting from, and name the unit you are converting to.
convert(1).from('l').to('ml');
// 1000Crossing between systems is handled by the library rather than by you. A pound to kilogram conversion does not need a special call.
convert(1).from('lb').to('kg');
// 0.4536... (tested to 4 significant figures)What is not handled is a conversion between two different measures. That is an error, and it is an error by design rather than a silent wrong answer.
convert(1).from('oz').to('fl-oz');
// throws -- you can't go from mass to volume!That guard is the feature worth noting. A unit conversion library that lets you convert ounces of weight into fluid ounces without complaint will eventually hand you a number that looks reasonable and is meaningless. convert-units refuses instead.
The unit abbreviations follow a convention that mixes common short forms with a few distinctive ones. The volume measure uses ml, l, tsp, Tbs, fl-oz, cup, pnt, qt and gal. Mass uses mcg, mg, g, kg, oz, lb, mt and t. Length uses mm, cm, m, in, ft-us, ft and mi. The presence of both ft and ft-us, and of the prefixed fl-oz, is a hint that the packaged definitions distinguish units that other libraries treat as the same.
toBest picks a display unit, and the documentation calls it subjective for a reason
toBest is the method that converts a value into a unit a human would want to read, and it comes with an explicit warning: it is subjective and does not work for all measures.
The default rule is the smallest value above 1.
convert(12000).from('mm').toBest();
// { val: 12, unit: 'm', ... }The fallback behaviour matters. If no better value is found, the original unit is returned, and zero is treated as a special case that also returns the origin unit.
convert(1).from('mm').toBest();
// { val: 1, unit: 'mm', ... }
convert(0).from('mm').toBest();
// { val: 0, unit: 'mm', ... }The rule can be tuned. Excluding units changes the answer, and the cut-off number changes which unit qualifies as good enough.
convert(12000).from('mm').toBest({ exclude: ['m'] });
// { val: 1200, unit: 'cm', ... } (the smallest unit excluding meters)convert(900).from('mm').toBest({ cutOffNumber: 10 });
// { val: 90, unit: 'cm', ... } (the smallest unit with a value equal to or above 10)
convert(1000).from('mm').toBest({ cutOffNumber: 10 });
// { val: 100, unit: 'cm', plural: 'Centimeters' } (the smallest unit with a value equal to or above 10)The system option overrides which system the result is chosen from. By default the system of the origin unit is used.
convert(254).from('mm').toBest({ system: 'imperial' }); // ('mm' is metric)
// { val: 10, unit: 'in', plural: 'Inches' } // ('in' is imperial)The negative case is spelled out in the documentation too: if the value is negative, the best is the largest number below -1. The returned object also carries a plural field with the human readable unit name, so the output is presentable without a separate lookup.
Where this gets risky is exactly where the documentation warns. A measure without a sensible hierarchy of units has no valid answer for the algorithm to pick, and the library will not warn you when it falls back.
Introspection calls let you build pickers without hardcoding unit lists
A unit library is only useful if you can ask it what it knows. convert-units exposes that through several introspection calls, and together they cover the cases where an application needs to build a UI rather than perform a single conversion.
measures() lists the configured measures.
convert().measures();
// [ 'length', 'mass', 'volume', ... ]possibilities() works at three levels. Called on a unit, it lists everything that unit can be converted to.
convert().from('l').possibilities();
// [ 'ml', 'l', 'tsp', 'Tbs', 'fl-oz', 'cup', 'pnt', 'qt', 'gal' ]
convert().from('kg').possibilities();
// [ 'mcg', 'mg', 'g', 'kg', 'oz', 'lb' ]Called with a measure name, it lists the units in that measure, and called with no argument it lists every configured unit.
convert().possibilities('mass');
// [ 'mcg', 'mg', 'g', 'kg', 'oz', 'lb', 'mt', 't' ]For richer metadata there is describe(), which returns the abbreviation, the measure, the system, and the singular and plural names, and list(), which returns the same structure for every unit or for one measure.
convert().describe('kg');
/*
{
abbr: 'kg',
measure: 'mass',
system: 'metric',
singular: 'Kilogram',
plural: 'Kilograms',
}
*/convert().list('mass');
/*
[{
abbr: 'kg',
measure: 'mass',
system: 'metric',
singular: 'Kilogram',
plural: 'Kilograms',
}, ...]
*/This is where the measure directory design pays off twice. The introspection calls reflect exactly the measures you configured, so a picker built from possibilities() can never offer a unit the application did not import, and a library that only wants length and mass will not advertise the rest.
A custom measure needs a system, a base unit, and a to_anchor factor
Custom measures are the part of the API that rewards reading carefully, because the structure encodes the conversion mathematics.
A measure starts as a plain object whose key is the measure name.
const measure = {
customMeasure: {},
};Next come the systems. A system is a collection of related units, and the documentation names metric, imperial, SI, bits and bytes as examples, while making clear you do not have to use one of them. The example defines three systems named A, B and C.
const measure = {
customMeasure: {
systems: {
A: {},
B: {},
C: {},
}
},
};Then each system needs a base unit. The base unit is the one used to convert between systems, and every other unit in that system is configured to convert directly to it.
const measure = {
customMeasure: {
systems: {
A: {
a: { // the name of the unit (commonly an abbreviation)
name: { // human friendly names for the unit
singular: 'a',
plural: 'as',
},
}
},
// ignoring C & B for now
}
},
};Finally each unit needs a to_anchor property, which holds the factor needed to reach the base unit from another unit in the system. Base units use 1, and the documentation explains why: if you convert 5 a to a the result should be 5 a, because the to_anchor value is multiplied by the value being converted from.
const measure = {
customMeasure: {
systems: {
A: {
a: {
name: {
singular: 'a',
plural: 'as',
},
to_anchor: 1,
}
},The pattern to take away is that a measure is systems, a system is units, and a unit is a name plus a to_anchor factor relative to that system's base unit. Everything the library computes follows from those factors, so a wrong factor produces a wrong conversion everywhere rather than in one place.
Where convert-units is the wrong tool
The first wrong-tool case is a codebase that needs stable releases. Every published version listed is a beta: v3.0.0-beta.8 from 2025-07-07, v3.0.0-beta.7 from 2024-08-28 and v3.0.0-beta.6 from 2024-01-25. The manifest version matches, at 3.0.0-beta.8. A project with a policy against prerelease dependencies has no stable line to pin to here, and the beta channel is explicitly installable with npm install convert-units@beta, which means the prerelease can reach an install by accident if the version range is not pinned carefully.
The second case is code that needs unit conversion with dimensional analysis, uncertainty propagation or currency-like rates that change over time. convert-units converts within a measure and across systems using fixed anchor factors. There is no place in the documented API for an exchange rate, a tolerance or a unit that depends on runtime conditions.
The third case is a UI that must pick a display unit automatically across many measures. The documentation's own warning that toBest does not work for all measures, and its fallback to the origin unit, means a generic format helper built on toBest will produce inconsistent output between measures, and it will do so silently.
On licensing, the licence is reported as a custom notice that GitHub cannot classify, so the LICENSE file at the repository root is the authority and an automated allowlist will not give you a clean answer. On upkeep, the project is genuinely active, with the last push on 2026-10-18, and the repository carries a jest test setup with a coverage script, a publint check that validates the published package layout, and separate ESM, CommonJS and UMD compile targets, which is the tooling you would expect from a package that has been through a major version rewrite.
Editorial conclusion
convert-units suits a TypeScript application that needs reliable unit conversion across a handful of measures and wants the unused measure definitions stripped from its bundle, since the configureMeasurements closure and the per-measure import paths make that a one line change. It is the wrong choice for a project pinned to stable releases, because the published versions are all 3.0.0 beta builds and the latest tag is v3.0.0-beta.8, and it is the wrong choice when you need the best human-readable unit automatically, because the documentation itself warns that toBest does not work for all measures. Before adopting, install convert-units@beta deliberately rather than by accident, confirm the licence in the LICENSE file since it is not a standard identifier, and check convert().possibilities() output for the measure you rely on.
Frequently asked questions
How do I install convert-units?
The stable channel is npm install convert-units --save. Beta builds are also published and can be installed with npm install convert-units@beta --save. The manifest version is 3.0.0-beta.8, so the current release line is a prerelease.
How do I keep unused measures out of my bundle?
Import only the measures you need from convert-units/definitions and pass them to configureMeasurements instead of importing convert-units/definitions/all. The documentation notes that this allows for smaller packages when using a bundler like webpack or rollup.
What happens if I convert between two different measures?
The call throws an error. The documentation gives the example of converting from oz to fl-oz and states that you cannot go from mass to volume, so the library refuses the conversion rather than returning a meaningless number.
What does toBest do and when should I avoid it?
toBest returns the value expressed in the smallest unit that yields a number above 1, falling back to the origin unit when no better value exists and when the value is zero. The documentation states plainly that the method is subjective and does not work for all measures, so avoid it for generic formatting across many measures.
How do I define a custom measure in convert-units?
Create a plain object whose key is the measure name, define systems inside it, give each system a base unit with a name and a to_anchor value of 1, and give every other unit a to_anchor factor expressing its ratio to that base unit. The library computes all conversions from those factors.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/convert-units-convert-units)Was this useful?