lens-studio-scripting
Lens Studio Scripting — Reference Guide
Lens Studio TypeScript components are classes that extend BaseScriptComponent and are decorated with @component. This guide covers the patterns you'll use in every script you write.
Component Anatomy
import { SomeModule } from 'SpectaclesInteractionKit.lspkg/SomeModule'
@component
export class MyComponent extends BaseScriptComponent {
// --- Inspector-exposed inputs ---
@input
@hint('Drag a scene object here')
targetObject: SceneObject
@input
speed: number = 1.0
@input
@allowUndefined // makes the field optional in the inspector
optionalAudio: AudioComponent
@input
@label('Display Name') // rename the field label in the inspector
internalProp: number = 0
// --- Private state ---
private elapsedTime: number = 0
// --- Lifecycle ---
onAwake(): void {
// Called once at construction time. Set up events here.
this.createEvent('OnStartEvent').bind(() => this.onStart())
this.createEvent('UpdateEvent').bind(() => this.onUpdate())
}
private onStart(): void {
// Called once the scene is fully loaded.
// Reference other components here rather than in onAwake.
if (!this.targetObject) {
print('[MyComponent] ERROR: targetObject not assigned')
return
}
}
private onUpdate(): void {
// Called every frame.
this.elapsedTime += getDeltaTime()
}
onDestroy(): void {
// Called when the scene object this component belongs to is destroyed.
// Use to unsubscribe events, clean up sessions, etc.
}
}
Lifecycle order reference
| Event name | When it fires | Typical use |
|---|---|---|
onAwake |
Component constructs | Wire up event listeners |
OnStartEvent |
Scene finishes loading | Access other components |
UpdateEvent |
Every rendered frame | Per-frame logic |
DelayedCallbackEvent |
After N seconds | Timers, deferred actions |
TurnOnEvent |
Object becomes enabled (.enabled = true) |
React to visibility on |
TurnOffEvent |
Object becomes disabled (.enabled = false) |
React to visibility off |
onDestroy |
Scene object is destroyed | Clean up resources |
Decorator Reference
| Decorator | Effect |
|---|---|
@component |
Registers the class as a Lens Studio component |
@input |
Exposes the property in the Lens Studio Inspector |
@hint('text') |
Adds a tooltip to the inspector field |
@allowUndefined |
Prevents validation errors for optional inputs |
@label('Display Name') |
Renames the field label shown in the inspector |
@serializeField |
Persists a property value across hot-reloads in the editor (dev-time only) |
Input arrays
To expose a list of assets or objects in the inspector:
@input
myObjects: SceneObject[] // shown as a resizable list in the inspector
@input
audioTracks: AudioTrackAsset[]
Accessing Other Components
On the same scene object
const audio = this.sceneObject.getComponent('Component.AudioComponent')
const meshVisual = this.sceneObject.getComponent('Component.RenderMeshVisual')
On a child scene object
const child = this.sceneObject.getChild(0) // by index
const comp = child.getComponent('Component.AudioComponent')
Accessing a custom TypeScript component on another object
Method A — @input (preferred)
@input
otherComponent: ScriptComponent // assign in inspector
// then cast:
const typed = otherComponent as unknown as MyOtherComponent
Method B — TypeScript-to-TypeScript import
import { TSComponentA } from './TSComponentA'
// Then get the component and cast:
const comp = this.sceneObject.getComponent(TSComponentA.getTypeName()) as unknown as TSComponentA
Scene Object Queries
// Find by name across the whole scene (recursive)
const obj = scene.getRootObject(0).findChild('TargetObject', true)
// Iterate all root objects (phone lenses often have one root)
const rootCount = scene.getRootObjectsCount()
for (let i = 0; i < rootCount; i++) {
const root = scene.getRootObject(i)
}
// Find by name (built-in alternative)
const obj = scene.findByName('TargetObject')
// Iterate children
const count = parent.getChildrenCount()
for (let i = 0; i < count; i++) {
const child = parent.getChild(i)
}
// Create a new empty scene object
const newObj = scene.createSceneObject('NewObject')
newObj.setParent(this.sceneObject)
Prefab Instantiation
@input prefab: ObjectPrefab
// Synchronous instantiation
const instance = this.prefab.instantiate(parentSceneObject) // null = root
instance.name = 'SpawnedItem'
instance.getTransform().setWorldPosition(spawnPos)
// Async instantiation (non-blocking, large prefabs)
this.prefab.instantiateAsync(parentSceneObject).then((instance) => {
instance.getTransform().setWorldPosition(spawnPos)
})
// Destroy an instance
instance.destroy()
DelayedCallbackEvent (Timers)
// One-shot delay
const delayedEvent = this.createEvent('DelayedCallbackEvent')
delayedEvent.bind(() => {
print('2 seconds elapsed')
doSomething()
})
delayedEvent.reset(2) // seconds
// Repeating timer: call reset() again at the end of the callback
delayedEvent.bind(() => {
tick()
delayedEvent.reset(1) // re-fire after 1 second
})
delayedEvent.reset(1)
// Cancel a scheduled event
delayedEvent.enabled = false
Logging
print (basic)
print('Simple message: ' + value)
print(`Template literal: ${object.name}`)
NativeLogger (prefixed, structured)
import NativeLogger from 'SpectaclesInteractionKit.lspkg/Utils/NativeLogger'
const log = new NativeLogger('MyComponent') // prefix shown in console
log.d('Debug message') // debug
log.i('Info message') // info
log.w('Warning message') // warning
log.e('Error message') // error
NativeLogger messages can be filtered in the Lens Studio console by prefix, which makes debugging multi-component scenes much easier.
Enabling / Disabling Scene Objects and Components
// Show / hide a whole object and all its children
sceneObject.enabled = false
// Disable only a component without hiding the object
meshVisual.enabled = false
// Toggle
sceneObject.enabled = !sceneObject.enabled
TurnOnEvent fires after enabled is set to true; TurnOffEvent fires after enabled is set to false. Both fire on the object itself, not on children.
Custom Events
Lens Studio allows you to create named custom events and dispatch/receive them across scripts:
// Dispatching a custom event
const event = this.createEvent('CustomTrigger')
event.bind(() => this.onCustomTrigger())
// Sending a custom event to another script
// (use a shared EventWrapper / callback pattern instead of global events)
type OnScoreUpdate = (score: number) => void
class ScoreManager extends BaseScriptComponent {
private listeners: OnScoreUpdate[] = []
addScoreListener(fn: OnScoreUpdate): void {
this.listeners.push(fn)
}
updateScore(score: number): void {
this.listeners.forEach(fn => fn(score))
}
}
Note: Lens Studio does not have a global event bus — prefer callback arrays or direct
@inputwiring for cross-component communication.
Common Gotchas
- Never call
getComponentinonAwake— the scene may not be fully loaded yet. UseOnStartEvent. @inputarrays need a matching type annotation and are assigned from the Inspector list.nullvsundefined: Lens Studio usesnullmore thanundefined; check withisNull(val)orval !== null.getDeltaTime()returns frame delta in seconds — always use it for frame-rate-independent motion.thisinside callbacks: if using a plainfunction() {}callback (not an arrow function),thiswill be wrong. Either use arrow functions or assignconst self = thisbefore the callback.- Destroying objects mid-update can cause frame errors — defer with a
DelayedCallbackEventset to 0 delay if needed. - Component caching: call
getComponentonce inOnStartEventand store the result; calling it every frame is expensive. onDestroyfires on the scene object being destroyed, not when only a component is removed; if you need component-level cleanup, useTurnOffEventor a manual teardown method.- Script initialization order: components on the same frame initialize roughly in scene-hierarchy order. If two components in the same frame need each other in
onAwake, useOnStartEventinstead. @serializeFieldonly persists values in the Lens Studio editor (useful during development); it does not persist values on-device at runtime — usepersistentStorageSystemfor on-device persistence.
More from rolandsmeenk/lensstudioagents
spectacles-lens-essentials
Reference guide for foundational Lens Studio patterns on Spectacles — covering the GestureModule (pinch down/up/strength, targeting, grab, phone-in-hand with correct TypeScript API), SIK components (PinchButton, DragInteractable, GrabInteractable, ScrollView), hand-tracking gestures, physics bodies/colliders/callbacks (including audio-on-collision), LSTween animation (position/scale/rotation/color tweens), prefab instantiation at runtime, materials (clone-before-modify), spatial anchors, on-device persistent storage (putString/getFloat), spatial images, and the Path Pioneer raycasting pattern. Use this skill for any Spectacles lens that needs interaction, motion, animation, physics, audio, or persistent local storage — including Essentials, Throw Lab, Spatial Persistence, Spatial Image Gallery, Path Pioneer, Public Speaker, Voice Playback, Material Library, and DJ Specs samples.
9lens-studio-world-query
Reference guide for world understanding and scoring in Lens Studio — covering WorldQueryModule HitTestSession (HitTestSessionOptions.filter for jitter smoothing, semantic surface classification for floor/wall/ceiling/table detection, null result handling, per-frame performance), SIK InteractionManager targeting interactor ray pattern, Physics.createGlobalProbe().rayCast for scene-collider hits with collision layer filtering, aligning objects to surface normals using quat.lookAt, and the LeaderboardModule (create/retrieve with TTL and OrderingType, submitScore, getLeaderboardInfo with UsersType.Global/Friends). Use this skill when detecting real floors/walls/tables to place AR content, raycasting for hover or interaction against scene objects, or adding a global in-lens leaderboard — differentiates from spectacles-lens-essentials (physics/SIK) and from spectacles-cloud (Supabase persistence).
8spectacles-cloud
Reference guide for Snap Cloud (Supabase-powered backend) in Spectacles lenses — covering Fetch API setup (requires Internet Access capability in Project Settings), Postgres REST queries with the anon key, Row Level Security policies, Realtime WebSocket subscriptions with correct postgres_changes event format and reconnect-on-sleep patterns, cloud storage uploads of base64 images captured by Spectacles, serverless Edge Functions, and companion web dashboard architecture. Use this skill whenever a lens needs persistent cloud data, needs to share data with a web app in real time, uploads captured images to a bucket, or calls a cloud function — covering Snap Cloud and World Kindness Day samples. Use spectacles-networking for plain REST calls to non-Snap backends, and spectacles-connected-lenses for in-session multiplayer state.
7lens-studio-materials-shaders
Reference guide for materials and shaders in Lens Studio — covering runtime material property changes (clone-before-modify, mainPass.baseColor, mainPass.opacity, mainPass.baseTex), blend modes (Normal/Alpha/Add/Screen/Multiply), depth and cull settings (depthTest, depthWrite, twoSided, cullMode), render order, material variants, assigning textures and render targets, reading and writing RenderTarget textures for post-processing, the graph-based Material Editor node system, custom shader graph nodes, and common shader pitfalls. Use this skill for any lens that needs to change material colors or textures at runtime, implement custom visual effects with shaders, set up post-processing render pipelines, chain render targets, or debug material/blend-mode issues — covering MaterialEditor, Drawing, and HairSimulation examples.
6spectacles-networking
Reference guide for the Lens Studio Fetch API and WebView component in Spectacles lenses — covering InternetModule (Lens Studio 5.9+), Fetch API via internetModule.fetch(Request) with bytes/text/json response handling, performHttpRequest, Internet Access capability, GET/POST requests, custom headers, Bearer auth, polling, timeouts, CORS/HTTPS, WebSocket and RemoteMediaModule for media from URLs, and bidirectional WebView messaging. Use this skill for any lens that calls a REST API, polls a JSON endpoint, loads remote images, embeds a webpage, or talks to a custom backend — including the Fetch sample. Use spectacles-ai for LLM/RSG calls, or spectacles-cloud for Supabase/Snap Cloud integration.
6spectacles-connected-lenses
Reference guide for real-time multiplayer AR on Spectacles using Connected Lenses and Spectacles Sync Kit — covering session creation/joining with joinOrCreateSession (including 'already-in-session' error handling), TransformSyncComponent for position/rotation replication, RealtimeStore for shared key-value state (max 512 bytes per key), NetworkEventSystem for one-shot broadcast events, EntityOwnership for physics authority, Lens Cloud for persistent cross-session data, and patterns for turn-based (Tic Tac Toe) and real-time physics (Air Hockey). Also covers late-joiner state sync, transform drift mitigation, and store size limits. Use this skill whenever multiple Spectacles users need to share AR objects or state — covering Tic Tac Toe, Air Hockey, Laser Pointer, High Five, Shared Sync Controls, Spectacles Sync Kit, and Think Out Loud samples.
5