Develop

Spatial Anchor sample overview

Updated: May 11, 2026

Overview

This sample demonstrates the complete lifecycle of spatial anchors — persistent points in physical space that maintain their position and orientation across sessions. You can study the sample to understand how to create anchors at runtime, save them to local storage, reload them when the app restarts, and delete them when no longer needed. The sample uses passthrough with a controller-based interaction model that includes both anchor placement and raycasting-based selection.

What you will learn

  • Instantiate spatial anchors at runtime using the OVRSpatialAnchor component and poll the creation status until the runtime assigns a UUID
  • Persist anchors to device storage and store their UUIDs for cross-session retrieval
  • Load saved anchors from storage, wait for them to localize in the current tracking space, and bind them to scene GameObjects
  • Erase anchors from persistent storage and remove them from the scene
  • Implement controller-based raycasting for selecting and interacting with placed anchors

Requirements

  • Meta Quest device with spatial anchor support
  • Unity 2022.3 or later
  • Meta XR Core SDK
For platform setup instructions, see Unity development setup.

Get started

Clone or download the Unity-StarterSamples repository and open the project in Unity. Navigate to Assets/StarterSamples/Usage/, open the SpatialAnchor.unity scene, and build and deploy to your Quest device following the repository’s build instructions. When the app launches, you see passthrough enabled with a menu attached to your right controller, starting in Select mode.

Explore the sample

File/SceneWhat it demonstrates
SpatialAnchor.unity
Scene setup with passthrough enabled, controller anchors, and UI components for two-mode interaction (Create/Select)
Scripts/Anchor.cs
Individual anchor behavior: creation lifecycle polling, save/erase/hide operations, per-anchor UI menu, visual feedback, and billboard rendering
Scripts/AnchorUIManager.cs
Mode management (Create/Select), raycasting for anchor selection, anchor placement, main menu navigation, and delegate-based input handling
Scripts/SpatialAnchorLoader.cs
Batch loading of saved anchors by UUID, asynchronous localization, and binding to instantiated GameObjects
Scripts/AnchorUuidStore.cs
PlayerPrefs-based UUID persistence for saving and retrieving anchor identifiers across sessions
Editor/SpatialAnchorLoaderEditor.cs
Custom Inspector button and menu item for clearing all saved anchor UUIDs during development

Runtime behavior

The sample starts in Select mode with a raycast line extending from the right controller. Hovering over an existing anchor highlights it in yellow, and pressing the A button selects the anchor and reveals its per-anchor menu (Save, Hide, Erase). The right thumbstick navigates menus and the right index trigger confirms selections. Pressing the Create Mode button toggles to placement mode, which hides the raycast line and shows a placement preview attached to the controller. Pressing the A button in Create mode instantiates a new anchor at the preview position. Each anchor displays its UUID and tracking status on a billboard panel. The Load Anchors button restores all previously saved anchors from device storage, localizing them and recreating their GameObjects at the original world positions.

Key concepts

Anchor creation and lifecycle

The sample uses the OVRSpatialAnchor component, which is added automatically to the anchor prefab via [RequireComponent(typeof(OVRSpatialAnchor))] in Anchor.cs. When a new anchor is instantiated, the runtime begins creation asynchronously. The sample polls the PendingCreation property in a coroutine until the anchor is fully created, then accesses the runtime-assigned Uuid and displays tracking status. This pattern is shown in Anchor.cs:
while (_spatialAnchor && _spatialAnchor.PendingCreation)
    yield return null;

Async anchor operations

The sample demonstrates two async patterns for anchor persistence. Save and erase operations use ContinueWith with a state parameter, as seen in Anchor.cs:
_spatialAnchor.SaveAnchorAsync().ContinueWith((result, anchor) =>
{
    if (result.Success) anchor.OnSave();
}, this);
Loading anchors uses async/await in SpatialAnchorLoader.cs:
var result = await OVRSpatialAnchor.LoadUnboundAnchorsAsync(uuidBatch, _unboundAnchors);

UUID persistence across sessions

The AnchorUuidStore class provides a simple PlayerPrefs-based storage layer for anchor UUIDs. It stores a count at key “numUuids” and individual UUIDs at keys “uuid0”, “uuid1”, etc. When anchors are saved, their UUIDs are added to this store. When the app restarts, the loader reads all stored UUIDs and passes them to LoadUnboundAnchorsAsync to restore the anchors. The code comments explicitly note this as a sample simplification; production apps should use more robust storage.

Loading saved anchors

The load-localize-bind pattern is the core flow for restoring anchors. The SpatialAnchorLoader loads anchors in batches of 50 UUIDs, as implemented in SpatialAnchorLoader.cs. For each returned UnboundAnchor, the sample checks Localized; if not localized, it calls LocalizeAsync(). Once localized, the sample retrieves the world-space pose with TryGetPose(out pose), instantiates the anchor prefab at that position, and calls BindTo to connect the loaded data to the runtime component:
unboundAnchor.TryGetPose(out var pose);
var spatialAnchor = Instantiate(_anchorPrefab, pose.position, pose.rotation);
unboundAnchor.BindTo(spatialAnchor);

Controller-based interaction

The sample uses Physics raycasting from the right controller anchor to detect and select anchors. In AnchorUIManager.cs, the raycast originates from a tracked device transform and checks for Anchor components on hit colliders. The Physics.Raycast uses infinite range to detect distant anchors, while the line renderer visualizes only the first 10 meters for clarity. When hovering an anchor, the sample calls OnHoverStart() to change the anchor’s emission color to yellow. Selection is triggered by pressing the A button, which invokes a delegate that swaps between PlaceAnchor and SelectAnchor functions depending on the current mode:
if (OVRInput.GetDown(OVRInput.RawButton.A))
    _primaryPressDelegate?.Invoke();

Extend the sample

  • Modify the anchor placement logic to use surface raycasting instead of controller position, enabling users to place anchors on detected walls or floors.
  • Replace the PlayerPrefs storage with a cloud-based UUID store to share anchors across devices.
  • For a sample demonstrating shared spatial anchors across multiple users, see the Shared Spatial Anchors sample.