@itwin/scenes-client
v0.18.0
Published
Client-side code for managing scenes
Keywords
Readme
@itwin/scenes-client
About
This package provides a TypeScript client and types for interacting with the Scenes API.
Installation
npm install @itwin/scenes-client
# or
pnpm add @itwin/scenes-clientUsage
Basic Client Setup
import { SceneClient } from "@itwin/scenes-client";
const client = new SceneClient(async () => "<itwin_platform_auth_token>");Working with Scenes
Get a Scene
import { OrderByProperties } from "@itwin/scenes-client";
const sceneResponse = await client.getScene({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
orderBy: OrderByProperties.NAME, // Optional property to order scene data by
});
console.log(sceneResponse.scene);
/*
{
id: "<scene_id>",
displayName: "My Scene",
iTwinId: "<itwin_id>",
visibility: "iTwin",
createdById: "<creator_id>",
lastModifiedById: "<editor_id>",
creationTime: "2025-01-01T10:00:00.000Z",
lastModified: "2025-01-01T10:01:00.000Z",
sceneData: { objects: [...] },
isPartial: false
}
*/Get List of Scenes for an iTwin
import { SceneMinimal } from "@itwin/scenes-client";
// Get a single page of scenes (for UI pagination)
const listResponse = await client.getScenes({
iTwinId: "<itwin_id>",
top: 10, // Optional, defaults to 100
skip: 5, // Optional, defaults to 0
});
console.log(`Found ${listResponse.scenes.length} scenes on this page.`);
listResponse.scenes.forEach((scene: SceneMinimal) => {
console.log(`${scene.displayName} (${scene.id})`);
});Get All Scenes with Iterator
import { SceneMinimal } from "@itwin/scenes-client";
// Get all scenes using async iterator
const allScenesIterator = await client.getAllScenes({
iTwinId: "<itwin_id>",
});
let totalScenes = 0;
for await (const page of allScenesIterator) {
console.log(`Processing ${page.scenes.length} scenes...`);
page.scenes.forEach((scene: SceneMinimal) => {
console.log(`${scene.displayName}`);
totalScenes++;
});
}
console.log(`Processed ${totalScenes} total scenes`);Create a Scene
const createResponse = await client.postScene({
iTwinId: "<itwin_id>",
scene: {
displayName: "Construction Site Overview",
visibility: "private",
sceneData: {
objects: [
/** (optional) objects to create */
],
},
},
});
console.log(`Created scene: ${createResponse.scene!.displayName}`);Update a Scene's metadata
const updateResponse = await client.patchScene({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
scene: {
displayName: "Updated Scene Name",
},
});
console.log(`Updated scene: ${updateResponse.scene!.displayName}`);Delete a Scene
await client.deleteScene({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
});
console.log("Scene deleted successfully");Working with Scene Objects
Get a Scene Object
const objectResponse = await client.getObject({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
objectId: "<object_id>",
});
console.log(objectResponse.object);
/*
{
id: "<object_id>",
sceneId: "<scene_id>",
displayName: "My View3d Object",
kind: "View3d",
version: "1.0.0",
data: { ... },
createdById: "<creator_id>",
lastModifiedById: "<editor_id>",
creationTime: "2025-01-01T10:00:00.000Z",
lastModified: "2025-01-01T10:01:00.000Z"
}
*/Get List of Objects in a Scene
import { OrderByProperties, SceneObject } from "@itwin/scenes-client";
// Get a single page of objects (for UI pagination)
const listResponse = await client.getObjects({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
orderBy: OrderByProperties.NAME, // Optional property to order results by
top: 10, // Optional, defaults to 100
skip: 5, // Optional, defaults to 0
});
console.log(`Found ${listResponse.objects.length} objects on this page.`);
listResponse.objects.forEach((object: SceneObject) => {
console.log(`${object.displayName} (${object.id})`);
});Get All Objects with Iterator
import { SceneObject } from "@itwin/scenes-client";
// Get all objects in a scene using async iterator
const allObjectsIterator = await client.getAllObjects({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
});
let totalObjects = 0;
for await (const page of allObjectsIterator) {
console.log(`Processing ${page.objects.length} objects...`);
page.objects.forEach((object: SceneObject) => {
console.log(`${object.id}`);
totalObjects++;
});
}
console.log(`Processed ${totalObjects} total objects`);Create Scene Objects
import {
SceneObject,
iModelVisibilityCreate,
LayerCreate,
RepositoryCreate,
RepositoryResourceCreate,
View3dCreate,
} from "@itwin/scenes-client";
// Create objects with strongly typed interfaces
// Note: LayerCreate is an alias for StandardObjectCreate<"Layer", "1.0.0">
const layer: LayerCreate = {
id: "<layer_id>", // Optional, will be auto-generated if not provided
kind: "Layer",
version: "1.0.0",
displayName: "Buildings", // Optional
visible: true, // Optional, sets initial visibility state
data: {},
};
// Note: RepositoryResourceCreate is an alias for StandardObjectCreate<"RepositoryResource", "1.0.0">
const baseMapResource: RepositoryResourceCreate = {
kind: "RepositoryResource",
version: "1.0.0",
displayName: "Cesium Base Map Imagery", // Optional
parentId: "<layer_id>", // Organize under the layer
visible: true, // Optional, sets initial visibility state
displayOrder: 0, // Optional, sets the rendering order
data: {
class: "Cesium",
repositoryId: "cesium",
id: "<cesium_imagery_id>",
},
};
const iModelResource: RepositoryResourceCreate = {
id: "<imodel_object_id>", // Optional
kind: "RepositoryResource",
version: "1.0.0",
displayName: "Main Building Model", // Optional
parentId: "<layer_id>", // Organize under the layer
visible: true, // Optional, sets initial visibility state
data: {
iTwinId: "<itwin_id>",
class: "iModels",
repositoryId: "imodels",
id: "<imodel_id>",
version: "<imodel_changeset_id>", // Optional, if omitted latest should be used
},
};
// Note: iModelVisibilityCreate is an alias for ResourceStylingObjectCreate<"iModelVisibility", "1.0.0">
const iModelStyling: iModelVisibilityCreate = {
kind: "iModelVisibility",
version: "1.0.0",
displayName: "Hide Building Elements",
relatedId: "<imodel_object_id>", // References the iModel resource to style
data: {
categories: {
shownList: "",
hiddenList: "+300000000A0+ED1+3*2+4+D+3*2+8+4*3+3*5+2+3*4+4+3*2+4*2+3*3+5+4+5+4+8+3*2+5+4+7F",
},
models: {
shownList: "",
hiddenList: "+20000000002",
},
perModelCategoryVisibility: [
{ modelId: "0x20000000079", categoryId: "0x2000000003e", visible: false },
],
alwaysDrawn: "+1*4+4*3",
neverDrawn: "+21234567890+10000000000*2+20000000000",
},
};
// Note: View3dCreate is an alias for StandardObjectCreate<"View3d", "1.0.0">
const view3d: View3dCreate = {
kind: "View3d",
version: "1.0.0",
displayName: "Aerial View", // Optional
data: {
position: { x: -50.0, y: 75.0, z: 150.0 },
direction: { x: 0.2, y: 0.2, z: -0.96 },
isOrthographic: false,
up: { x: 0, y: 1, z: 0 },
aspectRatio: 1.33,
near: 1,
far: 1000,
ecefTransform: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
},
};
// Note: RepositoryCreate is an alias for StandardObjectCreate<"Repository", "1.0.0">
const formsRepository: RepositoryCreate = {
kind: "Repository",
version: "1.0.0",
displayName: "iTwin A Forms", // Optional
data: {
iTwinId: "<itwin_id>",
repositoryId: "forms",
class: "Forms",
},
};
// Create objects in bulk
const createResponse = await client.postObjects({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
objects: [layer, view3d, baseMapResource, iModelResource, iModelStyling, formsRepository],
});
console.log(`Created ${createResponse.objects.length} objects:`);
createResponse.objects.forEach((obj: SceneObject) => {
console.log(`Created ${obj.kind} object: ${obj.displayName} (${obj.id})`);
});Update Scene Objects
import {
SceneObject,
SceneObjectUpdate,
SceneObjectOperation,
OperationType,
} from "@itwin/scenes-client";
// Update data for a single object with type safety
const objectUpdate: SceneObjectUpdate<"GoogleTilesStyling", "1.0.0"> = {
displayName: "Updated Global Styling Options",
data: {
// Fully typed - IntelliSense shows available properties
quality: 0.30000001192092896,
adjustment: [1.309999942779541, 72.0326, -75.6275],
},
};
const updateResponse = await client.patchObject({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
objectId: "<object_id>",
object: objectUpdate,
});
console.log(`Updated object: ${updateResponse.object.displayName}`);
// Update many scene objects with atomic operations
// All operations execute in provided order
const operations: SceneObjectOperation[] = [
// 1: Add a new Layer object
{
op: OperationType.CREATE,
payload: {
id: "<new_layer_id>", // Optional, will be auto-generated if not provided
kind: "Layer",
version: "1.0.0",
displayName: "New Construction Phase", // Optional
visible: true, // Optional, sets initial visibility state
data: {},
},
},
//2: Move existing object to the new layer
{
op: OperationType.UPDATE,
id: "<existing_object_id>",
payload: { parentId: "<new_layer_id>" },
},
// 3: Remove old Layer
{ op: OperationType.DELETE, id: "<old_layer_id>" },
// 4-6: Reorder scene objects
{ op: OperationType.UPDATE, id: "<object_id_1>", payload: { order: 1 } },
{ op: OperationType.UPDATE, id: "<object_id_2>", payload: { order: 2 } },
{ op: OperationType.UPDATE, id: "<object_id_3>", payload: { order: 3 } },
// 7-8: Set rendering order
{ op: OperationType.UPDATE, id: "<object_id_3>", payload: { displayOrder: 1 } },
{ op: OperationType.UPDATE, id: "<object_id_1>", payload: { displayOrder: 0 } },
];
const bulkUpdateResponse = await client.patchObjectsOperations({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
operations, // Maximum 100 operations per request
});
bulkUpdateResponse.objects.forEach((obj: SceneObject) => {
console.log(`Created or updated ${obj.kind} object: ${obj.displayName} (${obj.id})`);
});Delete Scene Objects
// Delete a single object by id
await client.deleteObject({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
objectId: "<object_id>",
});
// Delete objects in bulk
await client.deleteObjects({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
objectIds: ["<object_id_1>", "<object_id_2>", "<object_id_3>"],
});Working with Tags
Tags are iTwin-scoped labels that can be created independently and then applied to scenes. Scenes expose their tags as an array of TagMinimal objects (id and displayName). Assigning or replacing a scene's tags is done via the tagIds field when creating or updating a scene.
Create a Tag
import { Tag } from "@itwin/scenes-client";
const createTagResponse = await client.postTag({
iTwinId: "<itwin_id>",
tag: {
id: "<optional_tag_id>", // Optional, will be auto-generated if not provided
displayName: "Structural",
},
});
const tag: Tag = createTagResponse.tag;
console.log(`Created tag: ${tag.displayName} (${tag.id})`);Get a Tag
const tagResponse = await client.getTag({
iTwinId: "<itwin_id>",
tagId: "<tag_id>",
});
console.log(tagResponse.tag);
/*
{
id: "<tag_id>",
displayName: "Structural",
iTwinId: "<itwin_id>",
createdById: "<creator_id>",
lastModifiedById: "<editor_id>",
creationTime: "2025-01-01T10:00:00.000Z",
lastModified: "2025-01-01T10:01:00.000Z"
}
*/Get List of Tags for an iTwin
// Get a single page of tags (for UI pagination)
const listResponse = await client.getTags({
iTwinId: "<itwin_id>",
top: 10, // Optional, defaults to 100
skip: 0, // Optional, defaults to 0
});
console.log(`Found ${listResponse.tags.length} tags on this page.`);
listResponse.tags.forEach((tag) => {
console.log(`${tag.displayName} (${tag.id})`);
});Get All Tags with Iterator
// Get all tags using async iterator
const allTagsIterator = await client.getAllTags({
iTwinId: "<itwin_id>",
});
let totalTags = 0;
for await (const page of allTagsIterator) {
page.tags.forEach((tag) => {
console.log(`${tag.displayName} (${tag.id})`);
totalTags++;
});
}
console.log(`Processed ${totalTags} total tags`);Update a Tag
const updateTagResponse = await client.patchTag({
iTwinId: "<itwin_id>",
tagId: "<tag_id>",
tag: {
displayName: "Structural",
},
});
console.log(`Updated tag: ${updateTagResponse.tag.displayName}`);Delete a Tag
await client.deleteTag({
iTwinId: "<itwin_id>",
tagId: "<tag_id>",
});
console.log("Tag deleted successfully");Assign Tags to a Scene
Pass tagIds when creating a scene to apply existing tags to it:
const createResponse = await client.postScene({
iTwinId: "<itwin_id>",
scene: {
displayName: "Tagged Scene",
tagIds: ["<tag_id_1>", "<tag_id_2>"],
sceneData: {
objects: [
/** (optional) full list of scene objects */
],
},
},
});
console.log(`Scene tags:`, createResponse.scene!.tags);
// [{ id: "<tag_id_1>", displayName: "Structural" }, { id: "<tag_id_2>", displayName: "Civil" }]Update a Scene's Tags
Use patchScene with tagIds to replace the full set of tags on an existing scene. Provide an empty array to remove all tags:
// Replace the scene's tags with a new set
const updateResponse = await client.patchScene({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
scene: {
tagIds: ["<tag_id_3>"], // Replaces all existing tags
},
});
console.log(`Updated scene tags:`, updateResponse.scene!.tags);
// [{ id: "<tag_id_3>", displayName: "Site Perimeter" }]
// Remove all tags from a scene
await client.patchScene({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
scene: {
tagIds: [],
},
});Working with Scene Shares
Scene sharing lets you grant unauthenticated, read-only access to a scene and the resources it references (iModels, Reality Data, GIS layers, etc.). Use the returned shareKey as HTTP Basic credentials for iTwin Platform APIs (e.g. Authorization: Basic <shareKey>).
A common pattern is to distribute a shareable URL that includes the share key in the fragment, for example:
https://app.com/iTwins/<itwin_id>/scenes/<scene_id>#shareKey=<share_key>.
⚠️ Treat
shareKeyvalues as secrets. Anyone who obtains one can read the scene and its referenced resources until the share expires or is revoked. Avoid logging them or including in client telemetry.
Create a Share
import { SceneShare } from "@itwin/scenes-client";
// Share with a custom expiration
const shareResponse = await client.postSceneShare({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
share: {
expiration: "2027-01-01T00:00:00.000Z",
},
});
const share: SceneShare = shareResponse.share;
console.log(`Share ${share.id} expires at ${share.expiration}`);Get a Share
const shareResponse = await client.getSceneShare({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
shareId: "<share_id>",
});
const share: SceneShare = shareResponse.share;
console.log(`Share ${share.id} expires at ${share.expiration}`);List All Shares for a Scene
const listResponse = await client.getAllSceneShares({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
});
console.log(`Found ${listResponse.shares.length} active shares.`);
listResponse.shares.forEach((share) => {
console.log(`${share.id} expires at ${share.expiration}`);
});Revoke a Share
Revocation is permanent; any future requests using the associated shareKey will be rejected.
await client.revokeSceneShare({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
shareId: "<share_id>",
});
console.log("Share revoked successfully");Consume a Shared Scene
You can configure a SceneClient instance for public, unauthenticated access to a shared scene — no sign-in required. Because the getAccessToken callback provides the full Authorization header value, simply have it return Basic <shareKey> instead of a Bearer token, and use the client as you normally would.
Note: Only read endpoints are permitted with the shareKey; write requests are rejected.
import { SceneClient } from "@itwin/scenes-client";
const shareKey = "<share_key>"; // from a previous postSceneShare or a shareable link
const sharedClient = new SceneClient(async () => `Basic ${shareKey}`);
// Read-only access to the shared scene
const sceneResponse = await sharedClient.getScene({
iTwinId: "<itwin_id>",
sceneId: "<scene_id>",
});Type Safety
This client provides strongly typed interfaces for all scene object operations, giving you compile-time validation:
import { StandardObjectCreate, ResourceStylingObjectCreate } from "@itwin/scenes-client";
// Each schema has its own typed interface
const camera: StandardObjectCreate<"CameraAnimation", "1.0.0"> = {
kind: "CameraAnimation",
version: "1.0.0",
displayName: "Site Walkthrough",
data: {
input: [0, 5, 10, 15], // TypeScript knows this should be number[]
output: [
{
camera: {
position: { x: 0, y: 0, z: 100 },
direction: { x: 0, y: 1, z: 0 },
up: { x: 0, y: 0, z: 1 },
// IntelliSense shows all available camera properties
},
},
],
},
};
// Resource styling objects automatically require relatedId
const styling: ResourceStylingObjectCreate<"iModelVisibility", "1.0.0"> = {
kind: "iModelVisibility",
version: "1.0.0",
relatedId: "<repository_object_id>", // TypeScript enforces this field
data: {
// IntelliSense shows iModelVisibility specific properties
categories: { ... },
models: { ... },
},
};Error Handling
import { SceneApiError } from "@itwin/scenes-client";
try {
const scene = await client.getScene({
iTwinId: "<itwin_id>",
sceneId: "<invalid_scene_id>",
});
} catch (error) {
if (error instanceof SceneApiError) {
console.error(`Error getting scene: ${error.status} - ${error.message}`);
console.error(`Details ${error.details}`);
}
}Issues
Please report bugs, feature requests, or questions using the GitHub Issues page.
Copyright © Bentley Systems, Incorporated. All rights reserved. See LICENSE.md for license terms and full copyright notice.
