Refactor an existing project

Authoring a scene from nothing is the easy half. The harder half is the project that already exists: a main.tscn that grew a HUD inside it, a script folder that wants reorganising, a node whose name is wrong everywhere. Every one of those edits has a way to go silently wrong, because a .tscn refers to things by path and by id, and moving a file with mv leaves both dangling.

The rule for this whole page: validate after every step, and run the game at the end. A refactor that validates clean at each step and runs clean at the end is a refactor you can commit.

Learn the scene first

godot-cli scene node list scenes/main.tscn --json
godot-cli scene inspect scenes/main.tscn --json
godot-cli scene connection list scenes/main.tscn --json
godot-cli scene refs scenes/main.tscn --project-root . --json

node list gives the tree with viewport paths, inspect the sections and their ids, connection list the signal wiring, and refs every external path with whether it resolves. Together they are the picture you need before touching anything.

Pull a subtree into its own scene

This is the editor's Save Branch as Scene, and it is the most common refactor in a project that grew:

godot-cli scene extract scenes/main.tscn /root/Main/HUD \
  --output ui/hud.tscn --catalog-id ui/hud --project-root . --json

The subtree moves to the new file with its properties, its unique ids, the resources it uses, and the connections that live entirely inside it, all rewritten relative to the new root. The source scene gets an instance in its place, so the tree looks the same when you open it. --catalog-id registers the new scene in the component catalog at the same time, which is what makes an agent instance it next time instead of rebuilding it.

Two things come back in messages and both deserve reading:

text a connection was re-pointed at the new scene's root, which now needs `func _on_close_pressed()` in a script on res://ui/hud.tscn

That gives a self-contained component. If you would rather the parent stay in charge, --editable leaves the instance open so you can connect straight to a node inside it afterwards. - Script references. Lines in your GDScript that reach into the moved subtree by path, like $Main/HUD/Score, are listed with their file and line. They still parse; they just resolve to nothing now.

The catalog entry can be written in the same call, so a component pulled out of an existing scene arrives as complete as one written deliberately:

godot-cli scene extract scenes/main.tscn /root/Main/HUD --output ui/hud.tscn \
  --catalog-id ui/hud --summary "Player HUD with health and score" \
  --tags ui,hud --when-to-use "Any in-game screen" \
  --when-not-to-use "Menus; use ui/menu" --project-root .

Move a file without breaking it

Never mv a file a scene references. project move moves the file and rewrites every res:// reference to it across the project:

godot-cli project move --project-root . \
  --from scripts/player.gd --to scripts/hero.gd --json

It takes the .uid sidecar with the file and reports each scene it edited. Godot's UID cache still maps the old path afterwards, so validation reports uid_path_mismatch until an import refreshes it — --import does that as part of the move, which is what makes "validate after every edit" hold for this step:

godot-cli project move --project-root . \
  --from scripts/player.gd --to scripts/hero.gd --import --rename-ids --json

--rename-ids re-seeds the ext_resource id from the new file name, so a Script_player id stops naming a file called hero.gd. Godot's own 1_ab12c ids name no file and are left alone.

If a component folder moved and its catalog entry no longer points at the scene, catalog relink finds it again and repairs the manifest.

Rename and reparent

godot-cli scene node rename scenes/main.tscn /root/Main/Box --name Panel --project-root .
godot-cli scene node reparent scenes/main.tscn /root/Main/Panel --parent /root/Main/HUD --project-root .

Both rewrite the parent= attributes of every descendant, and reparent keeps the node's unique id. What neither can do is fix a script that says $Main/Box, so grep for the old name afterwards — the same class of breakage scene extract reports for you.

Take a snapshot you can roll back to

Any scene apply can record the inverse of what it did:

godot-cli scene apply scenes/main.tscn --patch refactor.json --project-root . \
  --write-undo-patch undo.json --auto-snapshot --json

--write-undo-patch gives you a patch that reverses the change, and --auto-snapshot copies the file before writing. If an op fails, nothing is written at all — the whole document is applied in one pass or not at all.

Check the result

godot-cli scene validate scenes/main.tscn --project-root . --json
godot-cli scene validate ui/hud.tscn --project-root . --json
godot-cli scene diff before.tscn scenes/main.tscn --properties --json
godot-cli project run --project-root . --json

validate catches dangling references, duplicate ids, a property whose value is the wrong type for its class, and a connection to a signal the emitting class does not have. diff shows what actually changed, property by property. The run is what proves the refactor did not change behaviour: same frame, same log, no errors.

Verify a change by running the game covers that last step in full, including clicking a button to prove its wiring still works after the move.