5.15.0 Change Notes
Backend
Faster asynchronous ECSQL paging and result conversion
Asynchronous ECSQL readers can now resume unfinished concurrent-query statements between contiguous batches, avoiding repeated scans through preceding rows and repeated sorting. A continuation briefly waits for a busy owning worker before falling back to another available worker. Reuse is temporary and bounded; expiration, cache eviction, or an observed committed data change returns paging to the existing LIMIT/OFFSET path.
Concurrent queries also reduce repeated metadata lookups and temporary JSON allocations when converting supported scalar and composite results. Point-coordinate reuse and more efficient ID formatting reduce per-row work without changing the documented result formats.
Reuse is automatic for eligible worker queries in WAL databases. Primary-connection queries, databases not using WAL journal mode (even when opened read-only), and connections with attached data databases continue re-executing each batch. Nondeterministic expressions may be evaluated once for a retained execution instead of once per batch, so use deterministic queries and ordering rather than depending on batch boundaries for re-evaluation. Retained statements can hold sorter resources and delay WAL checkpoints until released.
See Asynchronous paging for behavior and resource considerations.
Vertical CRS discovery
The new beta getAvailableVerticalCoordinateReferenceSystems function returns an array of available vertical coordinate reference systems. Results can be filtered by geographic point or extent and by unit name. Unlike the similar getAvailableCoordinateReferenceSystems function, this function is not async.
Opt-in fallback for missing navigation relationship class ids
Added ECSQLOPTIONS NAV_REL_CLASSID_FALLBACK for legacy navigation properties that contain an Id but no RelECClassId. When enabled, end-table relationship queries and ECVLib.Relations() report the relationship declared by the navigation property. Existing behavior is unchanged when the option is omitted, and directly selecting the navigation property's RelECClassId still returns its stored NULL value.
The option adds compatibility predicates that can result in less efficient query plans, so applications should enable it only for queries that need to read affected legacy data. ECVLib.Relations() also requires ENABLE_EXPERIMENTAL_FEATURES.
The ECSQL version was bumped to 2.0.4.2.
Pre-push validation
The new TxnManager.onPrePushValidation asynchronous event allows listeners to be notified just before a changeset is pushed to iModel Hub. The event allows last-minute edits to the iModel as well as the possibility to forbid the push to proceed.
Stricter briefcase file deletion
NativeApp.deleteBriefcase now only deletes files that are inside the briefcase cache directory or that are valid iModels. If the supplied path refers to an existing file that is neither, it throws an IModelError with IModelStatus.BadRequest and deletes nothing. If the path does not exist and is outside the briefcase cache, the call does nothing.
BriefcaseManager.deleteBriefcaseFiles no longer deletes every file in the same directory whose name starts with the briefcase's file name. Only files named <briefcase file name>-* or <briefcase file name>.* (e.g. -wal, -shm, -locks, .Tiles) are deleted along with the briefcase.
IdSet accepts ids with the high bit set
The ECSQL IdSet virtual table rejected valid ids at or above 0x8000000000000000. Element ids reserved by schema synchronization (0xfffff6…) are in that range, as are ids created by any briefcase whose id is 0x800000 or higher (element ids store the 24-bit briefcase id in their top bits). Queries such as SELECT id FROM IdSet(?) failed with IdSet vtab: The element with index N is invalid. When the set was given as hex strings in a JSON array and was therefore accepted, WHERE id = ? and WHERE id IN (...) filters on IdSet still returned no rows for those ids. IdSet now returns these ids whether they are bound with QueryBinder.bindIdSet, ECSqlBinder.bindIdSet, or passed as a JSON array.
Because SQLite stores ids as signed 64-bit integers, a negative integer in an IdSet JSON array is now read as the id with the same bits. For example, IdSet('[-1]') returns 0xffffffffffffffff instead of failing. 0, non-integer numbers, and negative numbers written as strings are still rejected. JavaScript numbers cannot represent ids above 2^53 exactly, so pass ids as hex strings or bind them with bindIdSet rather than building a JSON array from JavaScript numbers.
Native ChangeUnifier
The new @beta ChangeUnifier merges the per-table rows of ChangesetReaders into complete EC instances in native code. Memory stays bounded because merged data beyond a memory budget is spilled to temporary files, and propNames keeps only the properties you need. See ChangeUnifier — native merging with bounded memory for usage and how it differs from PartialChangeUnifier.
Batched relationship lookups
The new @beta Relationships.queryInstancesProps and Relationships.queryInstances methods look up many relationships of the same class with at most two queries. Each criterion is either a relationship id or a source and target id pair, and both kinds can be mixed. The returned array has one entry per criterion, in the same order, with undefined where no relationship was found. The results match calling Relationships.tryGetInstanceProps or Relationships.tryGetInstance for each criterion.
By default, the queries run on the primary connection, so unsaved changes are visible. Pass { usePrimaryConn: false } to use a concurrent query connection, which only sees saved changes.
Common
Step-interpolated render schedule keyframes no longer apply one keyframe late
Querying a RenderSchedule.Timeline at a time that exactly matches one of its keyframes returned the preceding keyframe's value when that preceding keyframe used RenderSchedule.Interpolation.Step. It now returns the matched keyframe's own value. This affects RenderSchedule.Timeline.getVisibility, RenderSchedule.Timeline.getColor, RenderSchedule.Timeline.getAnimationTransform, and RenderSchedule.Timeline.getCuttingPlane.
For a step visibility timeline with keyframes 100 -> 80, 200 -> 50, and 300 -> 0, querying at time 200 previously returned 80 and now returns 50.
The change only affects queries at a time exactly equal to a keyframe's time, only for keyframes other than the first and last, and only when the preceding keyframe uses RenderSchedule.Interpolation.Step.
BeAsyncEvent
BeAsyncEvent is similar to BeEvent in that it allows any number of listeners to be notified when something happens. The difference is that, in the case of BeAsyncEvent, the listener is expected to return a Promise. Only after the returned Promise settles is the listener considered to have finished handling the event.
Persisted entity properties
The new Persisted type represents entity properties with a required ID, and isPersisted checks whether arbitrary entity properties have a valid ID. Model and element property APIs that return persisted entities now expose this stronger type, so callers can remove unnecessary ID checks and non-null assertions from those results.
Schemas
SchemaView: property overrides use metadata of the overridden property
In SchemaView, when a class overrides a property without specifying a category or KindOfQuantity, category and kindOfQuantity on SchemaView.Property now return the values of the overridden property, matching the rest of the EC APIs. The new isEffectivelyHidden also considers HiddenProperty on overridden properties. Label, description and priority still come from the overriding declaration.
Classes using a mixin that overrides a property of its base mixin now get the overriding declaration.
Quantity
Built-in length ratio units for drawing scales
The built-in unit set in @itwin/core-quantity now follows BIS Units schema 01.00.12, which adds three LENGTH_RATIO units: Units.DECIMAL_LENGTH_RATIO, Units.M_PER_M_LENGTH_RATIO (label m:m), and Units.IN_PER_FT_LENGTH_RATIO (label in:ft). These are intended for persisting drawing and sheet scales as paper length divided by model length, so a 1:100 scale is stored as 0.01 and a 1/4" = 1' scale is stored as 1/48 in m:m (or 0.25 in in:ft).
UnitConversions can convert between these units, and getDefaultPersistenceUnit now accepts Phenomena.LENGTH_RATIO and returns Units.M_PER_M_LENGTH_RATIO. Previously LENGTH_RATIO was excluded from that helper because no built-in default existed.
Async formats provider setter
IModelApp.setFormatsProvider replaces the formats provider, optionally sets the active unit system through SetFormatsProviderOptions.unitSystem, and returns a promise that resolves after the QuantityFormatter has rebuilt its formatting and parsing caches. Use it instead of assigning IModelApp.formatsProvider when you need to know that formatting reflects the new provider.
- The provider and unit system take effect immediately:
IModelApp.formatsProviderlookups, includinggetFormatSync, use the new provider, andIModelApp.quantityFormatter.activeUnitSystemreports the new unit system before the promise settles. - If a later
setFormatsProvidercall is made before the reload finishes, the earlier promise rejects. The latest provider wins. - If the reload fails, the promise rejects and the new provider stays installed. The formatter remains usable, but some cached formats may still come from the previous provider.
- The promise also rejects if the application shuts down before the reload finishes.
Assigning IModelApp.formatsProvider still works and still starts the same reload, but it gives you nothing to await.
Platform support
Electron 45 support
In addition to already supported Electron versions, iTwin.js now supports Electron 45.
Breaking changes
Extensible sub-model class validation
Validation of which model classes may sub-model an element has moved from the native library to Element.onSubModelInsert.
The built-in rules remain:
- Sheet accepts SheetModel and its subclasses.
- Drawing accepts DrawingModel and its subclasses.
- TemplateRecipe2d accepts DrawingModel and its subclasses.
- SectionDrawing accepts DrawingModel, GraphicalModel3d, and their subclasses.
Domain element classes implementing bis:ISubModeledElement may restrict their permitted sub-model classes by overriding the protected static Element.allowedSubModelClasses getter. The default value, undefined, permits any model class. A model class derived from an allowed class is also accepted, including a generated JavaScript class for an unregistered EC subclass.
The following behavior changes apply:
- Inserting a disallowed model now throws IModelError with IModelStatus.WrongModel. The corresponding native checks previously returned
IModelStatus.BadElement. - An override of Element.onSubModelInsert must call
super.onSubModelInsert(arg)to retain inherited validation. An override that does not callsuperreplaces that validation. - Missing modeled elements and elements that do not implement
bis:ISubModeledElementcontinue to be rejected by the native library.
This functionality requires the matching @bentley/imodeljs-native version distributed with @itwin/core-backend. Combining a native version containing the relaxed checks with an older @itwin/core-backend removes the built-in Sheet and Drawing restrictions.
Mobile RPC authentication
Mobile RPC WebSocket connections now require a random per-launch token, preventing other applications on the device from accessing the backend through loopback. Connections without the token are rejected before RPC processing or pending-message delivery.
Upgrade the mobile native runtime together with both the frontend and backend copies of @itwin/core-mobile. The secured transport cannot use older native runtimes that do not deliver the token, and older frontends cannot connect to the secured backend. There is no unauthenticated fallback. Updated native runtimes remain compatible with older frontend/backend pairs.
No Mobile SDK changes are required when using the existing native host startup paths. On iOS, register the WebView with IModelJsHost before loading the frontend so the native host can inject the token at document start. On Android, IModelJsHost.loadEntryPoint supplies the token in the frontend URL fragment. Both native hosts supply the token when reconnecting after background suspension.
Custom native hosts must deliver the backend token to the trusted frontend out of band, using window.__iTwinJsRpcToken or the rpcToken URL fragment parameter, and include it as the second argument to window._imodeljs_rpc_reconnect. Do not obtain the token over the unauthenticated socket or include it in logs.
Non-finite geographic conversion results
The GCS-based single-point and batch conversion methods on IModelConnection now reject non-finite returned coordinates with GeoServiceStatus.CSMapError, even if the conversion reports success. Previously, coordinate normalization could turn an invalid ordinate into zero or return an infinite value. Finite successful results and the existing handling of failure statuses are unchanged. OPC point-cloud placement now warns and uses zero vertical correction when its computed correction is non-finite.
Handle these rejected promises instead of relying on normalized invalid elevations. Applications that deliberately use partial conversion results should use GeoConverter, inspect each PointWithStatus, and validate the ordinates they need. Status 26 (GeoCoordStatus.VerticalDatumConvertError) permits horizontal use but does not provide a reliable converted elevation, even when Z is finite. Connection convenience methods continue to reject this status; OPC's warning and zero-correction fallback is approximate display placement, not successful vertical conversion.
After fixing unavailable grid resources, do not reuse a retained converter's cached failure. Finish outstanding requests and obtain a fresh converter from GeoServices.getConverter to retry.
Preserving geographic coordinate systems during property updates
EditTxn.updateIModelProps, EditTxn.updateProjectExtents, and EditTxn.updateEcefLocation no longer replace an unchanged geographic coordinate system. Unrelated property writes preserve the complete stored CRS definition without re-resolving its dictionary identifiers, including when its workspace dictionary is unavailable. Intentionally changed CRS definitions continue to be persisted.
Do not rely on repeated property updates to re-resolve an unchanged CRS against a changed dictionary. Explicitly set a changed CRS definition when replacing the stored CRS is intended. Abandoning edits, undo, and redo now reload the cached geographic coordinate system from the database; explicitly reapply an abandoned CRS edit if it should be saved later. See Updating iModel properties.
CRS-restoration notifications reject transaction writes, including implicit writes, so listeners cannot write abandoned edits back to the database. Defer follow-up edits until restoration completes.
Bulk Element Deletion API changes
EditTxn.deleteElements (beta) is now all-or-nothing. Previously the call deleted whatever it could and returned PartialSuccess with the ids it had to skip. Now, if any element in the expanded delete set (requested elements plus their children and sub-model contents) is still referenced from outside the set, nothing is deleted and the status is ConstraintViolation. The validation runs before anything is modified, so a rejected call leaves the iModel and the current txn untouched.
Changes to the beta types:
BulkDeleteElementsStatus.PartialSuccesshas been removed. The enum is nowSuccess(0),ConstraintViolation(1) andDeletionFailed(2). Code that handledPartialSuccessshould handleConstraintViolationinstead - and note that in that case nothing was deleted.- New BulkDeleteElementsResult.failures: a per-element report. Each BulkDeleteFailure carries the element id, a BulkDeleteFailureReason (
ReferencedByForeignKey,DefinitionInUse,ProtectedElementorReadOnlyDomain). Only the direct violators are reported. Because the whole expanded set is validated, a reported element may be a child or sub-model member that was not in theidsyou passed in; its requested ancestor is not listed separately. For example, requesting[parent]whenparent's child is the CodeScope of an element outside the set reports the child, notparent. BulkDeleteElementsResult.failedIdsis deprecated in favor offailures. Its contents have changed:- On
ConstraintViolationit holds the unique element ids appearing infailures. As described above, these may include cascaded descendants that were not in the requestedids, sofailedIdsis no longer guaranteed to be a subset of the input. It is never empty onConstraintViolation, so existingfailedIds.size > 0checks still detect a rejected delete. However, it no longer lists everything left undeleted: for input[blocked, independent], both remain undeleted whilefailedIdsreports onlyblocked. Callers that filter this list to existing elements and throw still detect rejection, but their error reports omit other undeleted elements. - On
DeletionFailedit holds every requested id, matching the previous behavior for this status, so callers that only checkfailedIds.size > 0still detect the failure. The iModel may be partially modified in this case and the caller must abandon the current txn. - On
Successit is empty.
- On
BulkDeleteElementsArgs.skipFKConstraintValidationsis deprecated and ignored. Skipping the validation pass could leave the iModel with dangling references (e.g. a deleted GeometryPart still used by a geometry stream), so that behavior was removed. The option object is still accepted so existing calls keep compiling.
If you relied on the previous best-effort behavior, inspect failures and, for each reported element, either include or update the elements that reference it, or remove it from ids together with every requested element it was cascaded from; then call deleteElements again. Retries may reveal further violators. Callers that ignored the result, or treated a non-Success status as non-fatal, now delete nothing instead of the valid subset.
See Bulk Element Deletion for full details.
Last Updated: 05 November, 2026