Dump a Unity scene hierarchy to a text file

Walk the scene’s root GameObjects, build an indented string as you recurse, and write it with File.WriteAllText from an editor script — no Play mode needed.

Applies to Unity 2021.3 LTS through Unity 6.0 LTS

Unity has no built-in “export hierarchy” command, and the scene file on disk is not the answer either — a .unity file is YAML full of file IDs, not names. The whole job is about forty lines of editor script.

Dump the open scene to a text file

Editor/HierarchyDump.cs
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;

/// <summary>
/// Dumps the open scene's hierarchy to a text file. Editor-only: put this in an
/// Editor/ folder. No Play mode, no runtime code in your build.
/// </summary>
public static class HierarchyDump
{
    [MenuItem("Tools/Dump Scene Hierarchy")]
    static void Dump()
    {
        Scene scene = SceneManager.GetActiveScene();

        var text = new StringBuilder();
        text.AppendLine(scene.name);

        GameObject[] roots = scene.GetRootGameObjects();
        for (int i = 0; i < roots.Length; i++)
        {
            Append(roots[i].transform, text, "", i == roots.Length - 1);
        }

        string path = EditorUtility.SaveFilePanel(
            "Dump scene hierarchy", "", $"{scene.name}-hierarchy", "txt");
        if (string.IsNullOrEmpty(path))
        {
            return;
        }

        File.WriteAllText(path, text.ToString());
        Debug.Log($"Wrote {roots.Length} roots to {path}");
    }

    static void Append(Transform transform, StringBuilder text, string prefix, bool isLast)
    {
        text.Append(prefix)
            .Append(isLast ? "└── " : "├── ")
            .AppendLine(transform.name);

        string childPrefix = prefix + (isLast ? "    " : "│   ");
        for (int i = 0; i < transform.childCount; i++)
        {
            Append(transform.GetChild(i), text, childPrefix, i == transform.childCount - 1);
        }
    }
}

Tools > Dump Scene Hierarchy. Editor-only — nothing here ships in your build.

Two details in there are the difference between a script that works once and one you keep. EditorUtility.SaveFilePanel puts the file wherever you are actually going to look for it, rather than in the project root; and passing the isLast flag down the recursion is what lets the connector characters close correctly, which is what makes the output readable at depth 8.

On a real scene the output has this shape. The excerpt below is a verbatim slice of a real export from a 1,413-object demo scene, not a hand-typed illustration — and it already carries each object's tag and component list, which the next section adds:

Output StonebridgeDemo-hierarchy.txt
StonebridgeDemo
├── Main Camera <Tag: MainCamera> [Transform, AudioListener, Camera]
├── GlobalLighting [Transform]
│   ├── SunLight [Transform, Light]
│   ├── FillLight [Transform, Light]
│   └── AmbientLight [Transform, Light]
├── GroundPlane [Transform, MeshCollider, MeshFilter, MeshRenderer]
├── EventSystem [Transform, EventSystem, StandaloneInputModule]
├── StonebridgeManager [Transform, SampleGameManager]
│   ├── TimeOfDay [Transform]
│   ├── WeatherState [Transform]
│   └── TownBell [Transform, AudioSource]
…

Adding the components

A hierarchy without component names answers “what is in this scene” but not “why is this object here”. GetComponents<Component>() gives you them — with one landmine:

Editor/HierarchyDump.cs (excerpt)
static void AppendComponents(Transform transform, StringBuilder text)
{
    var names = new List<string>();

    foreach (Component component in transform.GetComponents<Component>())
    {
        // GetComponents returns a NULL entry for a MonoBehaviour whose script is
        // missing. Dereferencing it here is how a scene dump crashes on the one
        // scene you most wanted dumped.
        names.Add(component == null ? "<Missing Script>" : component.GetType().Name);
    }

    text.Append(" [").Append(string.Join(", ", names)).Append(']');
}

A missing MonoBehaviour script comes back as a null entry, not as a skipped one.

That null is not an edge case you will never hit. Any scene that has outlived a refactor or a package upgrade has missing scripts in it, and they cluster in exactly the scenes people most want dumped. Naming them <Missing Script> in the output turns the dump into the fastest way to find them.

Dump to the console, or to the clipboard

The file is one destination; often you want the text in a bug report or a chat message instead.

Destinations
// Console: fine for one subtree, wrong for a whole scene. The Editor console
// stores and renders every entry, and a few thousand lines of text in a single
// entry is neither readable nor selectable.
Debug.Log(text.ToString());

// Clipboard: what you actually want when the destination is a bug report or a
// chat message. Works from any Editor script.
EditorGUIUtility.systemCopyBuffer = text.ToString();

// One subtree only — Debug.Log with a context object highlights the GameObject
// in the Hierarchy window when you click the log entry.
Debug.Log(SubtreeAsText(transform), transform.gameObject);

EditorGUIUtility.systemCopyBuffer is the one people miss, and it is usually the one they wanted.

Debug.Log is the right destination for a single subtree and the wrong one for a whole scene: the Editor console keeps every entry and renders it in a list row, so a few thousand lines in one entry is neither readable nor selectable. Passing the GameObject as the second argument is worth it though — clicking the log entry then pings that object in the Hierarchy window.

JSON, and why JsonUtility will not do it

The obvious move is a [Serializable] node class holding a List<Node> of itself, handed to JsonUtility.ToJson. It compiles, it runs, and it silently gives you the wrong answer: Unity's serializer stops at a nesting depth of seven and warns “Serialization depth limit 7 exceeded… There may be an object composition cycle in one or more of your serialized classes.” A scene tree is precisely that shape, and any scene deeper than seven levels comes out truncated.

Editor/HierarchyDump.Json.cs (excerpt)
// JsonUtility cannot do this, and the failure is quiet.
//
// Unity's serializer stops at a nesting depth of 7 and warns
// "Serialization depth limit 7 exceeded... There may be an object composition
// cycle", so a node type that holds a List<Node> of itself silently truncates
// at depth 7. A scene tree is exactly that shape.
//
// Write the JSON yourself. It is twenty lines, and it is the only version that
// is correct at depth 20.

static void AppendJson(Transform transform, StringBuilder text, string indent)
{
    text.Append(indent).Append("{\n");
    text.Append(indent).Append("  \"name\": ").Append(Quote(transform.name));

    if (transform.childCount > 0)
    {
        text.Append(",\n").Append(indent).Append("  \"children\": [\n");
        for (int i = 0; i < transform.childCount; i++)
        {
            AppendJson(transform.GetChild(i), text, indent + "    ");
            text.Append(i == transform.childCount - 1 ? "\n" : ",\n");
        }
        text.Append(indent).Append("  ]");
    }

    text.Append('\n').Append(indent).Append('}');
}

/// <summary>RFC 8259 string escaping. Object names contain quotes and backslashes.</summary>
static string Quote(string value)
{
    var quoted = new StringBuilder("\"");
    foreach (char c in value)
    {
        switch (c)
        {
            case '"':  quoted.Append("\\\""); break;
            case '\\': quoted.Append("\\\\"); break;
            case '\n': quoted.Append("\\n");  break;
            case '\r': quoted.Append("\\r");  break;
            case '\t': quoted.Append("\\t");  break;
            default:
                if (c < ' ')
                {
                    quoted.Append("\\u").Append(((int)c).ToString("x4"));
                }
                else
                {
                    quoted.Append(c);
                }
                break;
        }
    }
    return quoted.Append('"').ToString();
}

Twenty lines, correct at depth 20. The escaping is not optional — GameObject names contain quotes.

And a real export in that shape, tags and components included, again verbatim:

Output StonebridgeDemo.json
{
  "name": "StonebridgeDemo",
  "components": [],
  "children": [
    {
      "name": "Main Camera",
      "tag": "MainCamera",
      "components": [
        {
          "type": "Transform"
        },
        {
          "type": "AudioListener"
        },
        {
          "type": "Camera"
        }
      ]
    },
    {
      "name": "GlobalLighting",
      "components": [
        {
          "type": "Transform"
        }
      ],
      "children": [
        …

Markdown and YAML

Same walk, different Append calls. Markdown is the format to reach for when the destination is a pull request or a design doc, because it renders as a nested list wherever you paste it. YAML is the one that diffs best in version control — indentation-only changes when a subtree moves, rather than JSON's brace-and-comma churn.

Output StonebridgeDemo.md
# Hierarchy

## **StonebridgeDemo**
  - **Main Camera** <Tag: MainCamera>
    - **Components:** `Transform`, `AudioListener`, `Camera`
  - **GlobalLighting**
    - **Components:** `Transform`
    - **SunLight**
      - **Components:** `Transform`, `Light`
    - **FillLight**
      - **Components:** `Transform`, `Light`
    - **AmbientLight**
      - **Components:** `Transform`, `Light`
  …
Output StonebridgeDemo.yaml
---
- name: StonebridgeDemo
  children:
    - name: Main Camera
      tag: MainCamera
      components:
        - type: Transform
        - type: AudioListener
        - type: Camera
    - name: GlobalLighting
      components:
        - type: Transform
      children:
        - name: SunLight
          components:
            - type: Transform
            - type: Light
        - name: FillLight
          components:
            - type: Transform
            - type: Light
    …

Also called

The same job goes by a dozen names — dump scene hierarchy, export the scene tree, print the hierarchy to console, log all gameobjects, save the scene structure to txt, serialize the transform hierarchy to json, write the scene graph to a file. Console, clipboard, .txt, .json, .md and .csv are all just different StringBuilder endings on the one recursive walk above.

Reaching the objects in the first place

This page assumes you can already get at every object you want to dump. If your walk is quietly skipping the disabled half of the scene, that is a separate and very common problem: get all GameObjects in a Unity scene, including inactive ones.

Hierarchy Exporter

The same dump in four formats, stable enough to diff.

The four exports on this page came out of Hierarchy Exporter. It walks a scene or prefab with tags, prefab sources, missing scripts and components included, previews the result before anything is written, and exports it as a tree, JSON, YAML or Markdown, to a file or the clipboard. The output is deterministic, so a diff between two exports shows only what actually changed in the scene.

The Hierarchy Exporter window previewing a Tree-format export of a 1,413-object scene, each object followed by its tag, prefab source and component list
The tree export from this page as it previews before anything is written — tags, prefab sources and components included, Export and Copy to Clipboard one click away.