facebook/iwsdk-migrate-0-5
Migrate an existing IWSDK 0.4.x application to IWSDK 0.5.0. Use when upgrading across the 0.5 boundary, replacing Meta Spatial Editor or GLXF, removing retired Vite plugins, moving UIKitML to runtime-loaded assets, adopting native scene JSON, or resolving 0.5 migration errors.
npx skills add https://github.com/facebook/immersive-web-sdk --skill iwsdk-migrate-0-5
Upgrade an existing IWSDK application without losing behavior or authored
content. This is a migration, not a rewrite: preserve working runtime logic,
move only static composition into the native scene format, and prove parity in
the live runtime.
User context is in $ARGUMENTS.
This skill is specifically for 0.4.x -> 0.5.0. First inspect the installed
versions in package.json and the lockfile.
0.5.x, diagnose the reported issue withoutreplaying the migration.
0.4.x, apply the intervening release migrations first.0.5.x, use the skill for that release boundary instead.The public comparison baseline for this guide is IWSDK 0.4.2.
git status before editing. Preserve all existing work; never reset,clean, or overwrite unrelated changes.
sources. Use the project's established version-control workflow; do not
commit unless the user authorized commits.
after all of its imports, config hooks, scripts, and generated outputs have
been replaced.
the app compiles. IWSDK 0.5 has no GLXF runtime fallback; reproduce and
verify the authored hierarchy in native scene JSON.
composition, component values, transforms, lights, panels, and player-space
attachments—not runtime-dependent entity counts or game logic.
| 0.4.x surface | 0.5.0 replacement |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| GLXF levels and @iwsdk/glxf | Native scene selected by iwsdk.config.json (or explicit low-level WorldOptions.level) |
| Meta Spatial Editor and @iwsdk/vite-plugin-metaspatial | IWSDK managed editor in @iwsdk/vite-plugin-dev |
| @iwsdk/vite-plugin-uikitml generated JSON | Runtime parsing of source .uikitml files from public/ui/ |
| @iwsdk/vite-plugin-gltf-optimizer | Pre-optimized source assets or the normal IWSDK glTF asset pipeline |
| vite-plugin-mkcert | Cached, untrusted HTTPS certificate generated by iwsdkDev() |
| PanelUI.config: './ui/panel.json' | UIKitML manifest asset using a BASE_URL-safe source .uikitml URL |
| PanelUI.maxWidth / maxHeight | Entity transform scale plus the document's intrinsic dimensions |
| features.spatialUI.kits | features.spatialUI.kit and optional componentSets |
| GLXFComponentRegistry | defineComponents([...]) plus native scene component props |
| LevelGLXFImporter / LevelEntityCreator | World.loadLevel() for native scenes / World.createTransformEntity() for dynamic objects |
| iwsdkDev({ assetManifest, componentManifest, emulator }) | iwsdk.config.json plus bare iwsdkDev() |
| iwsdkDev({ ai, workspace }) | Launch-time iwsdk dev up session flags |
| Remote Chef starter recipes / @iwsdk/starter-assets | Common source, scenes, and guidance embedded in @iwsdk/create |
| xr.features.lightEstimation | Authored light components and IBLTexture/IBLGradient |
| render.defaultLighting and implicit gradients | Explicit dome and IBL components on each native scene root |
| Static entities created in the startup callback | Asset/component manifests plus native scene nodes |
| Ad hoc custom-component registration | defineComponents([...]), shared by runtime and editor |
The Interactable compatibility alias still exists, but new and migrated code
should use RayInteractable.
Determine the package manager from the lockfile, then collect:
@iwsdk/* dependency and its installed version;glxf, metaspatial, compileUIKit,vite-plugin-uikitml, vite-plugin-gltf-optimizer, mkcert, or
IWSDK_DISABLE_MKCERT;
World.create, World.loadLevel, PanelUI, ScreenSpace,Visibility, render.defaultLighting, and features.spatialUI use;
.glxf, .uikitml, generated UI JSON, Meta Spatial project files, andgenerated glTF folders;
manipulate those entities.
Classify each static object as one of:
asset: glTF, UIKitML, or a parentless procedural Object3D prototype;scene node: a stable id, transform, asset reference, and component values;player-space child: content attached to player, camera/head, target-ray, orgrip space;
dynamic: keep in code because runtime state determines its existence.Record the inventory in the migration report. This is the parity checklist.
Update every IWSDK package already used by the application to 0.5.0. Keep
IWSDK packages on one version; do not mix 0.4.x and 0.5.x packages.
Remove these retired packages when present:
@iwsdk/glxf
@iwsdk/vite-plugin-gltf-optimizer
@iwsdk/vite-plugin-metaspatial
@iwsdk/vite-plugin-uikitml
vite-plugin-mkcert
Do not add @iwsdk/scene-composition merely because it is new. It is already a
core dependency; add it directly only if application code imports its document,
validation, or composition APIs.
After editing package.json, use the project's package manager to update the
lockfile and installation. Do not delete the lockfile as a shortcut.
Remove imports and plugin entries for mkcert, Meta Spatial, UIKitML compilation,
and the glTF optimizer. A migrated config should follow this shape:
import { iwsdkDev } from '@iwsdk/vite-plugin-dev';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [iwsdkDev()],
server: { host: '0.0.0.0', open: false },
});
Important behavior:
iwsdk.config.json as the committed project authority. It selects thescene plus extensionless assets.module and components.module paths and
stores serializable world/emulator options.
headed/headless launch, open behavior, and screenshot size with iwsdk dev
flags rather than committed Vite options.
server.open false soit does not create a second unmanaged tab.
installing a local CA. Managed Playwright accepts it automatically; a
physical headset shows the expected certificate warning.
iwsdkDev({ https: false }) only when HTTP is intentional. A customserver.https certificate takes precedence.
the editor can load them in its own realm.
Remove obsolete generated-output ignore rules only after confirming nothing
else creates those directories. Common stale paths are generated UIKit JSON,
Meta Spatial GLXF output, and plugin-generated glTF folders.
Move the asset catalog into a dedicated module:
// src/assets.ts
import { AssetType, defineAssets } from '@iwsdk/core';
const publicAssetUrl = (path: string) =>
`${import.meta.env.BASE_URL}${path.replace(/^\/+/u, '')}`;
export default defineAssets({
environment: {
name: 'Environment',
type: AssetType.GLTF,
url: publicAssetUrl('models/environment.glb'),
},
'settings-panel': {
name: 'Settings Panel',
type: AssetType.UIKitML,
url: publicAssetUrl('ui/settings.uikitml'),
},
});
An asset manifest may also contain parentless procedural Object3D prototypes.
Do not place a prototype in the Three scene or parent it before registration.
Declare application components separately:
// src/components.ts
import { defineComponents } from '@iwsdk/core';
import { MyBehavior } from './components/my-behavior.js';
export default defineComponents([MyBehavior]);
Point the project authority at both extensionless modules:
{
"$schema": "./node_modules/@iwsdk/core/dist/schemas/iwsdk-project.v1.schema.json",
"version": "iwsdk.project.v1",
"scene": "./public/scenes/main.iwsdk.scene.json",
"assets": { "module": "./src/assets" },
"components": { "module": "./src/components" },
"world": {
"xr": { "mode": "vr", "offer": "always" },
"features": { "spatialUI": true }
},
"dev": { "emulator": { "device": "metaQuest3" } }
}
The Vite virtual module supplies the same exact exports to World.create():
import { World } from '@iwsdk/core';
import projectOptions from 'virtual:iwsdk-project';
const world = await World.create(container, projectOptions);
Do not also import the manifests into index.ts or configure them in
iwsdkDev(). iwsdk.config.json is the single runtime/editor authority.
Delete GLXF registry setup and field mappers. Native scene component objects
store ordinary props keyed by the component id, and the shared component
manifest supplies their schemas to both runtime and editor.
Create public/scenes/main.iwsdk.scene.json. A minimal asset-backed panel and
model look like this:
{
"version": "iwsdk.scene.v1",
"units": "meters",
"components": {},
"resources": {},
"nodes": [
{
"id": "environment",
"content": { "type": "asset", "asset": "environment" },
"transform": { "position": [0, 0, 0] },
"components": { "LocomotionEnvironment": {} }
},
{
"id": "settings-panel",
"content": { "type": "asset", "asset": "settings-panel" },
"transform": {
"position": [0, 1.5, -2],
"scale": 0.4
},
"components": {
"RayInteractable": {},
"ScreenSpace": {
"top": "20px",
"left": "20px",
"width": "25vw",
"height": "40vh"
}
}
}
]
}
Migration rules:
id is the stable runtime/editor identity. Preserve meaningful uniqueidentifiers and use them from code; do not locate authored objects by array
position or display name.
src/assets.ts.code.
children. Use a node parent of typeplayer-space for content attached to player, camera, head, target-ray,
or grip spaces.
player.transform. Trackedhead/controller transforms are runtime-owned and overridden by XR tracking.
visible: false for authored initial visibility. Visibility andTransform are intrinsic editor properties, not ordinary add-component UI.
environment object. Use DomeGradient/DomeTexture for the visible
background and IBLGradient/IBLTexture for image-based lighting. There is
no separate AR background policy; immersive AR remains transparent.
render.defaultLighting. IWSDK no longer injects either environmentcomponent. Author both gradient components for the former default look, or
omit either independently when the scene intentionally has no background or
no image-based lighting.
hemisphere, directional, point, spot, or rect-area lights.
There is no supported 0.5 runtime path for .glxf. For a Meta Spatial/GLXF
project, use the old scene and screenshots as the visual reference, recreate
its static hierarchy in native scene JSON, then compare multiple editor and
runtime views before deleting legacy sources. If parity cannot be established,
stop and report that migration as incomplete.
Code-created dynamic entities can stay in code. It is valid to migrate the
static shell first and leave gameplay spawning, effects, and variable-count
objects in systems.
Move source UIKitML files into public/ui/ and delete the generated intermediate
JSON once no code references it. Change panel URLs from .json to .uikitml.
For editor-authored panels, prefer an AssetType.UIKitML manifest entry and an
asset-backed scene node, as shown above. PanelUI remains a compatibility path
for code-created panels, but it is hidden from generic editor authoring.
Remove maxWidth and maxHeight from PanelUI; 0.5 no longer performs a
second fit. The UIKitML document owns intrinsic dimensions and the entity
transform owns world scale. Give ScreenSpace explicit CSS dimensions rather
than relying on auto.
Replace spatial-UI kit configuration:
features: {
spatialUI: {
kit: 'horizon',
componentSets: [],
},
}
The default kit is horizon. UIKitML can load TTF fonts declared with
@font-face, including remote HTTPS URLs. Keep font loading CORS-compatible and
verify text after the document reports stable layout; do not add arbitrary
frame-count sleeps.
Locate and manipulate an authored panel by stable scene and element ids:
import { UIKitMLAsset } from '@iwsdk/core';
const panel = world.requireSceneObject<UIKitMLAsset>('settings-panel');
const saveButton = panel.requireElementById('save-button');
saveButton.addEventListener('click', onSave);
Do not traverse the entire Three scene looking for an anonymous
UIKitDocument, and do not key logic off generated JSON paths.
Audit these cases even when TypeScript compiles:
createTransformEntity() now gives every transform entity intrinsicVisibility. If old code adds Visibility itself, change it to set the
existing value or assign entity.object3D.visible.
RayInteractable over the deprecated Interactable alias.xr.features.lightEstimation; IWSDK 0.5 no longer requests theunsupported feature. Replace its visual role with authored lights and IBL.
while retaining IBL. Re-test any app that previously expected a virtual AR
background.
AssetManager.getGLTF() returns a fresh clone by default. Use{ shared: true } only when shared mutable state is intentional.
ScreenSpace width/height: 'auto' warns and falls back to viewportsizing. Author explicit dimensions and test browser resize plus XR exit.
kits, migrate them tocomponentSets; select the built-in collection with kit.
object plumbing with world.getSceneObject, requireSceneObject,
getSceneEntity, or requireSceneEntity and stable node ids.
listeners. World.destroy() is now available for hot reload, tests, and
multi-world hosts.
Run the project's normal formatter, typecheck, tests, and production build.
Then verify the actual app:
npx iwsdk dev status --json to reportbrowserConnected: true and browserCommandReady: true.
components, entity references, and file paths.
panels, player-space children, and representative camera views with the
pre-migration inventory.
screenshots are runtime-only by design.
controls. Confirm screen-space panels return after XR exit.
corners, and perceived scale.
accept its self-signed-certificate warning, and enter XR.
Do not regenerate IWSDK's library reference corpus as part of an application
migration.
Return a concise report containing:
After migration is green, consider these additions separately: signal helpers
re-exported from @iwsdk/core, authored light components, raw XR frame/session
and hit-test helpers, World.destroy(), World.loadSceneDocument(), renderable
asset instantiation, player-space authoring, and managed scene review tools.
Do not mix these refactors into the compatibility pass unless the user asks.
Take facebook/iwsdk-migrate-0-5 from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npx.
Without those the skill loads but fails at the first command.