Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 

Repository files navigation

HW Preservation Toolkit

Preserve your Horizon Worlds creations before VR access ends June 15, 2026.

A community-built set of tools to extract and preserve Meta Horizon Worlds projects as standard 3D formats (glTF, FBX, JSON) that can be opened in Blender, Unity, Godot, or any other 3D tool.


⚠ Deadline

VR access ends June 15, 2026. After that date the Horizon Worlds app is removed from Quest and you can no longer visit worlds, run scripts, or recover any data from the platform. Do not wait.


Files

File Purpose
hw_extractor.py Python CLI — packages Desktop Editor project assets and generates a glTF scene file
hw_scene_exporter.ts TypeScript script — paste into a live Horizon World to capture the scene graph
hw_reconstruct.py Blender Python script — merges scene.gltf layout with your original FBX files
requirements.txt Python dependencies (all optional, graceful fallback)

Confirmed Working Method

This is the workflow that has been tested and confirmed working as of March 2026.

What you need

  • Meta Horizon Worlds Desktop Editor installed on Windows
  • Python 3.10+ installed
  • The world you want to preserve opened and loaded in the Desktop Editor

Step 1 — Install Python dependencies (optional but recommended)

pip install pygltflib Pillow rich

These are all optional. The tool works without them but rich gives nicer output and pygltflib enables proper glTF validation.


Step 2 — Set up the scene exporter script

The Desktop Editor does not store scene layout data locally — it lives on Meta's servers. You need to run a TypeScript script inside your world to capture it while the platform is still live.

  1. Open your world in the Desktop Editor
  2. In the Assets panel, right-click → New Script
  3. Name it SceneExporter
  4. Open it in VS Code (it should open automatically)
  5. Delete everything in the file and paste in the entire contents of hw_scene_exporter.ts
  6. Save the file

Step 3 — Parent all objects to one empty

This is the critical step for flat worlds (no nested hierarchy).

The scene exporter captures objects by walking the parent/child hierarchy. If your objects are all sitting flat at the same level with no nesting, the script can only see the one object it is attached to. The fix:

  1. In the Desktop Editor's scene hierarchy panel, press Ctrl+A to select all objects
  2. Create a new Empty object (Add → Empty or equivalent in the editor)
  3. With all your world objects selected, parent them to the empty (drag them onto the empty in the hierarchy panel)
  4. All your objects are now children of that one empty

If your world already has objects nested inside parents, skip this step — the exporter will find them automatically by walking up to the root.


Step 4 — Attach and run the exporter

  1. Drag the SceneExporter script onto the empty you created in Step 3 (attach it to just the one empty, not to every individual object)
  2. Hit the Play button in the Desktop Editor
  3. Open the console panel (tab at the bottom of the editor)
  4. Wait a few seconds — you will see log messages starting with [HWExporter]
  5. Look for:
    [HWExporter] === SCENE DUMP START (X chunks) ===
    
  6. Copy everything from the opening { to the closing } of the JSON output (the stack trace lines after each log message are normal, ignore them)
  7. Paste into a text editor and save the file as scene_dump.json

A successful export looks like this:

[HWExporter] Collected 10 entities
[HWExporter] Export complete: 10 entities captured
[HWExporter] Copy JSON above, save as scene_dump.json

Step 5 — Run the extractor

python hw_extractor.py --scene-json scene_dump.json

This produces a timestamped folder like ./hw_exports/World_20260318_013000/ containing:

File/Folder Contents
scene.gltf Full scene with all object positions, rotations, and scales
scene_manifest.json Complete inventory of everything captured
README.md Per-world import instructions
mesh/ Any FBX/glTF assets found in the local project folder
texture/ Any texture files found in the local project folder
script/ Any TypeScript scripts found in the local project folder
primitive_meshes/ OBJ stand-ins for any detected Meta primitive shapes

Step 6 — Validate the export

python hw_extractor.py --validate ./hw_exports/World_20260318_013000

Checks the export folder and reports what is present and what is missing before you delete anything from your machine.


Step 7 — Open in Blender ✓ (confirmed working)

  1. Open Blender
  2. File → Import → glTF 2.0 (.glb/.gltf)
  3. Select your scene.gltf
  4. All captured objects appear as empties at their correct world positions, rotations, and scales

Your scene layout is now preserved in Blender. Object names, positions, rotations, and scales are all correct.

Asset re-integration (linking your original FBX files back to the correct positions) is handled by hw_reconstruct.py — see the section below once you have confirmed your scene.gltf opens correctly.


Repeat for each world

Run Steps 3–6 for each world you want to preserve. Open the next world in the Desktop Editor and repeat from Step 3. You do not need to reinstall or reconfigure anything between worlds.


Optional: ZIP for archiving

Add --zip to create a single archive per world:

python hw_extractor.py --scene-json scene_dump.json --zip

Known Limitations (for now)

Item Status
Scene layout (positions, rotations, scales) ✅ Fully working
Object names ✅ Captured when names are set in the editor
TypeScript world scripts ✅ Preserved if local project files exist
Custom imported FBX files ✅ Preserved if local project files exist
Textures ✅ Preserved if local project files exist
Meta built-in asset library meshes ❌ Not exportable — server-side, proprietary
Meta AI-generated meshes ❌ Not exportable — server-side
Material and shader parameters ❌ Must recreate manually
Baked lighting ❌ Not exportable
Physics settings ⚠ Check TypeScript scripts for values

API Notes (for developers)

The following was discovered by reverse-engineering the horizon/core TypeScript API during development. None of this appears to be officially documented anywhere.

Entity handles are lazy wrappers. The Entity type is { getter, _cache }. Call .get() on it to resolve the actual underlying data object. Every property on an entity — name, children, parent, transform — is also a lazy handle that requires .get() to unwrap.

World is a function constructor, not an instance. It exposes only onUpdate and onPrePhysicsUpdate as own keys. There is no entity registry accessible through the World object.

children only works with hierarchy. Calling .get() on the children handle returns an array, but for flat worlds (no nesting) that array is always empty. The solution is to parent all objects to a single empty before running the exporter.

Confirmed working imports from horizon/core: Component, Entity, Player, PropTypes, World, Vec3, CodeBlockEvents

Quat is not exported by horizon/core. Use plain objects for quaternion data.

PropTypes syntax: use { type: PropTypes.Boolean, default: true }, not PropTypes.Boolean(true).


Contributing

This is a community preservation project. Pull requests and issue reports welcome, especially:

  • Testing on different world types and editor versions
  • Worlds with complex nested hierarchies
  • Asset re-integration results with hw_reconstruct.py
  • Godot import instructions
  • Better name resolution for worlds where names are not capturing

If something does not work, open an issue with your full console output.


License

MIT — do whatever you want with it. Make it better and preserve those worlds!


Not affiliated with Meta Platforms Inc. Built out of spite, because creators' work deserves to survive corporate pivots.

About

This is a tool that I made to help abandoned Meta Horizon Creators reclaim their hard work.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages