Metadata packages in the iTwin.js Library

Packages

The metadata packages implement the abstract concepts of EC in typescript EC overview The API is split into these packages:

  • ecschema-metadata is the basic package that exposes metadata objects.
  • ecschema-editing Since the basic package only implements an API for understanding schemas, this package adds editing on top of it.
  • ecschema-locaters contains classes for locating and loading EC schema files from the file system.

An IModelDb owns a SchemaContext which can be used to access all the meta information stored inside of the imodel.

Example:

// For the sake of this example, we will assume an open IModelDb is provided. See backend docs for details on how to work with imodels. // Get the schema context from the IModelDb const schemaContext = iModel.schemaContext; const key = new SchemaKey("BisCore", 1, 0, 0); const bisCore = await schemaContext.getSchema(key, SchemaMatchType.LatestReadCompatible); if (bisCore === undefined) { throw new IModelError(IModelStatus.NotFound, "BisCore schema not found."); } for (const entity of bisCore.getItems(EntityClass)) { // use the entity class to do something doSomething(entity.name); // Get the properties of the entity const properties = await entity.getProperties(); // Iterate through each property for (const property of properties) { if (property.isPrimitive()) { doSomething(property.name); } } }

The provided SchemaMatchType specifies what schemas are acceptable, for example if the caller only cares that the returned schema is read compatible with the requested version.

Items in a schema

Like schemas are identified by SchemaKey, items inside a schema are identified by SchemaItemKey.

The methods for getting items inside a schema, like SchemaContext.getSchemaItem or Schema.getItem, follow a pattern where you can either get all items, or filter for a specific item type. The latter is done by passing the type of the desired item to the method like with EntityClass in the example in section above.

Supported item types within a schema are:

Each of those classes provides a type guard and assertion so general schema items can be checked for the specific type.

for (const item of bisCore.getItems()) { // item is of type SchemaItem if (EntityClass.isEntityClass(item)) { // item is of type EntityClass // count mixins on the entity class const mixinCount = Array.from(item.getMixinsSync()).length; doSomething(`Entity Class: ${item.name} with ${mixinCount} mixins`); } else if (Unit.isUnit(item)) { const unitSystem = await item.unitSystem; doSomething(`Unit: ${item.name} with unit system ${unitSystem?.name}`); } else if (RelationshipClass.isRelationshipClass(item)) { // item is of type RelationshipClass const sourceRoleLabel = item.source.roleLabel; if (sourceRoleLabel) doSomething(`Relationship Class: ${item.name} with source role label ${sourceRoleLabel}`); // Alternatively, you can use the assertion method, this one throws an error if the item is not a RelationshipClass RelationshipClass.assertIsRelationshipClass(item); } }

Properties in classes

Properties in classes can be accessed from the class itself. See ECClass.getProperty and ECClass.getProperties for more information. The properties by default are a merge of all properties of the class, base classes and mixins. The methods take an optional boolean parameter to only return properties that are defined in the class itself.

const entityClass = await iModel.schemaContext.getSchemaItem("BisCore", "InformationPartitionElement", EntityClass); if (entityClass === undefined) { throw new IModelError(IModelStatus.NotFound, "item not found."); } for (const property of await entityClass.getProperties()) { if (property.isPrimitive()) { doSomething(`Primitive Property: ${property.name}, primitive type: ${property.primitiveType}`); } } // local properties only for (const property of await entityClass.getProperties(true)) { if (property.isStruct()) { doSomething(`Struct Property: ${property.name}, struct type: ${property.structClass.name}`); } } // Get a property by name const singleProperty = await entityClass.getProperty("Description"); if (singleProperty === undefined) { throw new IModelError(IModelStatus.NotFound, "property not found."); } assert.isTrue(singleProperty.isPrimitive());

Custom attributes

Custom attributes are a way to add information to metadata objects like classes and properties. They are defined in the schema using a CustomAttributeClass. See ECClass.getCustomAttributes for more information.

For generic information about custom attributes see ECCustomAttributes.

const key = new SchemaKey("BisCore", 1, 0, 0); const bisCore = await iModel.schemaContext.getSchema(key, SchemaMatchType.LatestReadCompatible); if (bisCore === undefined) { throw new IModelError(IModelStatus.NotFound, "BisCore schema not found."); } for (const item of bisCore.getItems()) { if (!ECClass.isECClass(item)) { continue; } if (item.customAttributes?.has("BisCore.ClassHasHandler")) { doSomething(`Class ${item.name} has BisCore.ClassHasHandler custom attribute`); } // Access data within a custom attribute if (item.customAttributes?.has("ECDbMap.ShareColumns")) { const customAttribute = item.customAttributes.get("ECDbMap.ShareColumns"); if (customAttribute) { const maxSharedColumns = customAttribute.MaxSharedColumnsBeforeOverflow; if (maxSharedColumns && typeof maxSharedColumns === "number") { doSomething(`Class ${item.name} has MaxSharedColumnsBeforeOverflow set to ${maxSharedColumns}`); } } } }

When accessing the custom attributes like in the example above, the information is provided as JSON.

Accessing the information as shown above only yields information from local custom attributes (directly set on the object). To include inherited ones you can use the methods like ECClass.getCustomAttributes or Property.getCustomAttributes. Most of the time we're only interested in local custom attributes, and traversing base objects takes additional time.

Last Updated: 09 May, 2025