Providers

Providers are the runtime components that supply format and unit definitions to formatters and parsers. Understanding the different provider types and how to register them is essential for setting up quantity formatting in iTwin.js applications.

Understanding Providers

UnitsProvider

The UnitsProvider interface is central to unit management in iTwin.js. It provides methods to:

Units Provider Concept

A units provider acts as a registry and converter for units. When you need to format or parse a quantity value, the provider:

  1. Locates the source unit (e.g., meters for persistence)
  2. Locates the target unit(s) (e.g., feet and inches for display)
  3. Provides conversion factors between these units
  4. Validates unit compatibility (ensures units are in the same phenomenon)

BasicUnitsProvider

BasicUnitsProvider is a standalone provider that contains common units needed for basic quantity formatting. It's used as the default provider in IModelApp.quantityFormatter when no iModel is open.

Characteristics:

  • No dependencies on iModels or schemas
  • Contains units for: length, angle, area, volume, time
  • Sufficient for applications without schema-specific units
  • Lightweight and fast

When to use:

  • Applications without iModel dependencies, enabled by default
  • Simple formatting scenarios
  • Fallback when schema loading fails

SchemaUnitProvider

SchemaUnitProvider loads unit definitions from EC schemas stored in iModels. It provides access to the extensive Units schema as well as custom units defined in domain schemas.

Characteristics:

  • Requires access to ECSchemas via SchemaContext, commonly through iModels
  • Accesses units through SchemaContext
  • Supports custom domain-specific units
  • More comprehensive than BasicUnitsProvider

When to use:

  • Applications working with iModels
  • Need for domain-specific units (civil, structural, etc.)
  • When unit definitions must match schema specifications

FormatsProvider

A FormatsProvider supplies format definitions for KindOfQuantities. The FormatDefinition interface extends FormatProps to help identify formats.

SchemaFormatsProvider

SchemaFormatsProvider retrieves formats from EC schemas using a SchemaContext. It requires a UnitSystemKey to filter formats according to the current unit system.

Characteristics:

  • Loads formats from KindOfQuantity definitions in schemas
  • Filters formats by unit system (metric, imperial, usCustomary, usSurvey)
  • Throws error for invalid EC full names
  • Read-only format provider

Example: Simple Formatting

Formatting with SchemaFormatsProvider
const formatsProvider = new SchemaFormatsProvider(schemaContext, "metric"); const unitsProvider = new SchemaUnitProvider(schemaContext); const persistenceUnit = await unitsProvider.findUnitByName("Units.M"); // or unitsProvider.findUnit("m"); // No unit system was provided, and no format was found in the cache so the method will return the first presentation format for the KoQ, which uses KM. const formatProps = await formatsProvider.getFormat("AecUnits.LENGTH"); const format = await Format.createFromJSON("testFormat", unitsProvider, formatProps!); const formatSpec = await FormatterSpec.create("TestSpec", format, unitsProvider, persistenceUnit); const result = formatSpec.applyFormatting(50); // The persistence unit is meters, so this input value is 50 m. // result in formatted value of 50 m

Example: Parsing

Parsing with SchemaFormatsProvider
const formatsProvider = new SchemaFormatsProvider(schemaContext, "metric"); const unitsProvider = new SchemaUnitProvider(schemaContext); const persistenceUnit = await unitsProvider.findUnitByName("Units.M"); // or unitsProvider.findUnit("m"); const formatProps = await formatsProvider.getFormat("AecUnits.LENGTH_LONG"); const format = await Format.createFromJSON("testFormat", unitsProvider, formatProps!); const parserSpec = await ParserSpec.create(format, unitsProvider, persistenceUnit); const result = parserSpec.parseToQuantityValue("50 km"); // result.value 50000 (value in meters)

Example: Unit System Override

When retrieving a format from a schema, you might want to ensure the format matches your current unit system. You can pass the unit system on initialization or change it afterward:

Formatting with Unit System Override
const formatsProvider = new SchemaFormatsProvider(schemaContext, "metric"); const unitsProvider = new SchemaUnitProvider(schemaContext); const persistenceUnit = await unitsProvider.findUnitByName("Units.M"); // or unitsProvider.findUnit("m"); formatsProvider.unitSystem = "imperial"; // This will cause the method to return the first presentation format for the KoQ that uses imperial units. const formatProps = await formatsProvider.getFormat("AecUnits.LENGTH_LONG"); const format = await Format.createFromJSON("testFormat", unitsProvider, formatProps!); const formatSpec = await FormatterSpec.create("TestSpec", format, unitsProvider, persistenceUnit); const result = formatSpec.applyFormatting(50); // The persistence unit is meters, so this input value is 50 m. // result in formatted value of 164'0 1/2"

Example: Retrieving KindOfQuantity and Persistence Unit

When you only have a KindOfQuantity name, you can use a SchemaContext to find the schema item and access its persistence unit:

Using SchemaContext to get KindOfQuantity and persistence unit
const formatsProvider = new SchemaFormatsProvider(schemaContext, "metric"); const unitsProvider = new SchemaUnitProvider(schemaContext); const kindOfQuantityName = "AecUnits.LENGTH"; // Get the format definition const formatDef = await formatsProvider.getFormat(kindOfQuantityName); if (!formatDef) throw new Error(`Format not found for ${kindOfQuantityName}`); const kindOfQuantity = await schemaContext.getSchemaItem(kindOfQuantityName, KindOfQuantity); if (!kindOfQuantity) throw new Error(`KindOfQuantity not found for ${kindOfQuantityName}`); const persistenceUnit = kindOfQuantity.persistenceUnit; if (!persistenceUnit) throw new Error(`Persistence unit not found for ${kindOfQuantityName}`); const persistenceUnitProps = await unitsProvider.findUnitByName(persistenceUnit.fullName); const format = await Format.createFromJSON(formatDef.name ?? "", unitsProvider, formatDef); const formatterSpec = await FormatterSpec.create( formatDef.name ?? "", format, unitsProvider, // Use a schema units provider persistenceUnitProps ); const _formattedValue = formatterSpec.applyFormatting(123.45);

MutableFormatsProvider

MutableFormatsProvider extends the read-only FormatsProvider by allowing formats to be added or removed at runtime.

Characteristics:

  • Supports dynamic format management
  • Can add custom formats not in schemas
  • Can override schema-defined formats
  • Fires onFormatsChanged event when formats are modified

Example: Implementation

Example MutableFormatsProvider implementation
/** * Implements a formats provider with a cache, to allow adding/removing formats at runtime. */ class ExampleFormatProvider implements MutableFormatsProvider { private _cache: Map<string, FormatDefinition> = new Map(); public onFormatsChanged = new BeEvent<(args: FormatsChangedArgs) => void>(); public async getFormat(name: string): Promise<FormatDefinition | undefined> { return this._cache.get(name); } public async addFormat(name: string, format: FormatDefinition): Promise<void> { this._cache.set(name, format); this.onFormatsChanged.raiseEvent({ formatsChanged: [name]}); } public async removeFormat(name: string): Promise<void> { this._cache.delete(name); this.onFormatsChanged.raiseEvent({ formatsChanged: [name]}); } }

Example: Adding Formats

Adding formats to MutableFormatsProvider
const formatsProvider = new ExampleFormatProvider(); const format: FormatDefinition = { label: "NewFormat", type: "Fractional", precision: 8, formatTraits: ["keepSingleZero", "showUnitLabel"], uomSeparator: "", }; await formatsProvider.addFormat("DefaultToolsUnits.LENGTH", format); const retrievedFormat = await formatsProvider.getFormat("DefaultToolsUnits.LENGTH"); // retrievedFormat is the format we just added.

FormatSetFormatsProvider

FormatSetFormatsProvider manages formats within a FormatSet. This provider automatically updates the underlying format set when formats are added or removed, making it ideal for applications that need to persist format changes.

Key Features:

  • String Reference Resolution: Automatically resolves string references to their target FormatDefinition. When a format references another via string (e.g., "DefaultToolsUnits.LENGTH": "CivilUnits.LENGTH"), the provider resolves and returns the actual FormatDefinition.
  • Chain Resolution: Supports chains of references with circular reference detection.
  • Cascade Notifications: When adding or removing a format, the onFormatsChanged event includes not only the modified format but also all formats that reference it (directly or indirectly).
  • Fallback Provider: String references can resolve through an optional fallback provider if the target format isn't found in the format set.

Example: FormatSet with String References

Using FormatSetFormatsProvider with string references
const unitsProvider = new SchemaUnitProvider(schemaContext); const persistenceUnit = await unitsProvider.findUnitByName("Units.M"); // Create a format set with a base format and string references const formatSet = { name: "MyFormatSet", label: "My Custom Formats", unitSystem: "metric" as const, formats: { // Base format definition "CivilUnits.LENGTH": { composite: { includeZero: true, spacer: " ", units: [{ label: "m", name: "Units.M" }] }, formatTraits: ["keepSingleZero", "showUnitLabel"], precision: 2, type: "Decimal" } as FormatDefinition, // DISTANCE references LENGTH via string "DefaultToolsUnits.LENGTH": "CivilUnits.LENGTH", } }; // Create the provider const formatsProvider = new FormatSetFormatsProvider({ formatSet }); // Getting AecUnits.LENGTH resolves to the RoadRailUnits.LENGTH format definition const lengthFormat = await formatsProvider.getFormat("DefaultToolsUnits.LENGTH"); const format = await Format.createFromJSON("length", unitsProvider, lengthFormat!); const formatSpec = await FormatterSpec.create("LengthSpec", format, unitsProvider, persistenceUnit); const result = formatSpec.applyFormatting(42.567); // result is "42.57 m"

AlternateUnitLabelsProvider

AlternateUnitLabelsProvider allows specifying alternate labels for units during parsing. This is useful for:

  • Supporting common abbreviations (e.g., "ft" and "foot" for feet)
  • Enabling easier keyboard input (e.g., "^" for degrees "°")
  • Accommodating regional variations in unit labels

Registering Providers in iTwin Applications

This section covers how to register and configure providers in your iTwin application. Proper provider registration ensures consistent quantity formatting across your application.

Registering UnitsProvider

Manual Registration

You can manually register a SchemaUnitProvider when opening an iModel:

Manual SchemaUnitProvider registration
/** Register SchemaUnitProvider when IModelConnection is opened */ export async function registerSchemaUnitProvider(iModelConnection: IModelConnection) { const schemaLocater = new ECSchemaRpcLocater(iModelConnection); await IModelApp.quantityFormatter.setUnitsProvider(new SchemaUnitProvider(schemaLocater)); }

Automatic Registration on IModelConnection Open

The recommended approach is to automatically register the provider when any IModelConnection opens:

Automatic registration via IModelConnection.onOpen
/** Register SchemaUnitProvider automatically when IModelConnection opens */ export function setupIModelConnectionListener() { IModelConnection.onOpen.addListener(async (iModelConnection: IModelConnection) => { const schemaLocater = new ECSchemaRpcLocater(iModelConnection); await IModelApp.quantityFormatter.setUnitsProvider(new SchemaUnitProvider(schemaLocater)); }); }

If errors occur while configuring the units provider, they are caught within the QuantityFormatter.setUnitsProvider method, and the code reverts back to BasicUnitsProvider.

Registering FormatsProvider

Using SchemaFormatsProvider

Register a SchemaFormatsProvider to load formats from iModel schemas:

Registering SchemaFormatsProvider on IModelConnection open
const removeIModelConnectionListener = IModelConnection.onOpen.addListener((iModel: IModelConnection) => { if (iModel.isBlankConnection()) return; // Don't register on blank connections. const schemaFormatsProvider = new SchemaFormatsProvider(iModel.schemaContext, IModelApp.quantityFormatter.activeUnitSystem); const removeUnitSystemListener = IModelApp.quantityFormatter.onActiveFormattingUnitSystemChanged.addListener((args) => { schemaFormatsProvider.unitSystem = args.system; }); iModel.onClose.addOnce(() => { removeUnitSystemListener(); }); IModelApp.formatsProvider = schemaFormatsProvider; }); IModelConnection.onClose.addOnce(() => { removeIModelConnectionListener(); IModelApp.resetFormatsProvider(); });

Using FormatSetFormatsProvider

For applications that persist user format preferences:

// Load FormatSet from application settings const formatSet: FormatSet = await loadUserFormatPreferences(); // Create provider with optional fallback const fallbackProvider = new SchemaFormatsProvider(schemaContext); const formatSetProvider = new FormatSetFormatsProvider(formatSet, fallbackProvider); // Register with IModelApp IModelApp.formatsProvider = formatSetProvider; // Listen for changes to update persistence formatSetProvider.onFormatsChanged.addListener((formats) => { // Save updated format set to user preferences saveUserFormatPreferences(formatSet); });

Adding Alternate Unit Labels

Add alternate unit labels for easier input during parsing:

Adding alternate unit labels
/** Add alternate unit labels for easier input during parsing */ export function addAlternateUnitLabels() { // Use "^" as an alternate label for degrees IModelApp.quantityFormatter.addAlternateLabels("Units.ARC_DEG", "^"); // Add alternate labels for feet IModelApp.quantityFormatter.addAlternateLabels("Units.FT", "feet", "foot"); }

Configuring Unit System

Set the active unit system for the QuantityFormatter:

Configuring unit system
/** Configure the unit system for the QuantityFormatter */ export async function configureUnitSystem() { // Set the active unit system (metric, imperial, usCustomary, or usSurvey) await IModelApp.quantityFormatter.setActiveUnitSystem("metric"); }

See Also

Last Updated: 23 January, 2026