Compiling and nodemaps for Roblox✎
How a graph becomes a file, and a file becomes an instance in Studio.
Verified
This page is about graphs whose Target is Roblox. The bar along the top of the canvas says which, and a new graph takes the project's. For Lune, the answer is a different one and it is on Compiling and nodemaps for Lune.
A graph is a .nodescript under .roswaal/scripts. Compiling it writes a .luau file to the same place under src, and Rojo syncs that into Studio. Roswaal never talks to Studio itself.
Folders carry across: .roswaal/scripts/ReplicatedStorage/Shared/Greeter.nodescript writes src/ReplicatedStorage/Shared/Greeter.luau. Both directories are project settings.
Nodemap basics
A .nodemap says where your files land in the DataModel, and is edited in Roswaal rather than as JSON by hand. Here is one built from the start:
This map
Instance
Globs under this instance's path that Rojo should skip, e.g. shared/** — how you stop a nested mapping syncing twice.
Project file
Written to default.project.json when this map is compiled.
Passed to Rojo as globIgnorePaths, unanchored.
{ "name": "Game", "tree": { "$className": "DataModel", "ServerScriptService": { "Source": { "$path": "src" } } }}
This map
Instance
Globs under this instance's path that Rojo should skip, e.g. shared/** — how you stop a nested mapping syncing twice.
Project file
Written to default.project.json when this map is compiled.
Passed to Rojo as globIgnorePaths, unanchored.
{ "name": "Game", "tree": { "$className": "DataModel", "ServerScriptService": { "Source": { "$path": "src" } } }}
This map
Instance
Globs under this instance's path that Rojo should skip, e.g. shared/** — how you stop a nested mapping syncing twice.
Project file
Written to default.project.json when this map is compiled.
Passed to Rojo as globIgnorePaths, unanchored.
{ "name": "Game", "tree": { "$className": "DataModel", "ServerScriptService": { "Source": { "$path": "src" } }, "ReplicatedStorage": {} }}
This map
Instance
Globs under this instance's path that Rojo should skip, e.g. shared/** — how you stop a nested mapping syncing twice.
Project file
Written to default.project.json when this map is compiled.
Passed to Rojo as globIgnorePaths, unanchored.
{ "name": "Game", "tree": { "$className": "DataModel", "ServerScriptService": { "Source": { "$path": "src" } }, "ReplicatedStorage": { "Folder": { "$className": "Folder" } } }}
This map
Instance
Globs under this instance's path that Rojo should skip, e.g. shared/** — how you stop a nested mapping syncing twice.
Project file
Written to default.project.json when this map is compiled.
Passed to Rojo as globIgnorePaths, unanchored.
{ "name": "Game", "tree": { "$className": "DataModel", "ServerScriptService": { "Source": { "$path": "src" } }, "ReplicatedStorage": { "Shared": { "$className": "Folder" } } }}
This map
Instance
Globs under this instance's path that Rojo should skip, e.g. shared/** — how you stop a nested mapping syncing twice.
Project file
Written to default.project.json when this map is compiled.
Passed to Rojo as globIgnorePaths, unanchored.
{ "name": "Game", "tree": { "$className": "DataModel", "ServerScriptService": { "Source": { "$path": "src" } }, "ReplicatedStorage": { "Shared": { "$path": "src/Shared" } } }}
- Press New node map on the top row, or right-click a folder in the Project panel, and name the map.
- It opens in a tab of its own: the tree on the left, the Inspector on the right. A new map starts with ServerScriptService, filled from
src. - With DataModel selected, Add service… adds a service under it. Pick ReplicatedStorage.
- Select ReplicatedStorage and press Add folder.
- Name the folder in Name.
- Set Path to the folder on disk that fills it. A folder under
srcthat is not there yet is made when the map is written. - The project file shows what the map writes, with the selected row's lines lit.
- Press Write project file, or
Ctrl+S. In Dynamic mode, saving the map writes it.
What a graph compiles to
The file is named after the graph, and its ending comes from the script kind, chosen in the Inspector with nothing selected. The ending is how Rojo knows which class of instance to make.
| Kind | File | In Studio |
|---|---|---|
| Script | Greeter.server.luau | A Script, running on the server |
| LocalScript | Greeter.client.luau | A LocalScript, running on a player's machine |
| ModuleScript | Greeter.luau | A ModuleScript, run by whatever requires it |
Compiling
| What it compiles | |
|---|---|
Compile script, or Ctrl + S | The open graph |
| Compile project | Every graph, then every node map |
| Compile: Dynamic | Each graph as you edit it, and any that change on disk — after a git pull, say |
roswaal compile | Everything, or the one graph or map you give it |
roswaal watch | The same, without the editor |
A graph with errors writes nothing; warnings do not stop it. With format on and StyLua on your PATH, the file is formatted as it is written. If StyLua refuses it, it is written unformatted and the compile says why.
In either mode, an open graph whose file changes on disk takes the new version. If it has edits that are not saved yet, you are asked which to keep.
Generated files
A generated file starts with a header naming its graph and a hash of what was written. Roswaal will not overwrite a file whose hash no longer matches — one edited by hand — or a file it did not write.
Danger
roswaal compile --force. The hand edit is lost.Moving and deleting graphs
Rename a graph, move it or change its kind, and its next compile removes the file it used to write.
A generated file whose graph is gone is stale, and Rojo goes on syncing it. The compile results list stale files with a remove link. From the command line, roswaal prune lists them and roswaal prune --yes removes them.
Building a node map
A .nodemap says where your files land in the DataModel, and compiles to a Rojo project file — default.project.json unless you change it. You edit the tree in Roswaal rather than the JSON by hand.
This map
Instance
Globs under this instance's path that Rojo should skip, e.g. shared/** — how you stop a nested mapping syncing twice.
Project file
Written to default.project.json when this map is compiled.
Passed to Rojo as globIgnorePaths, unanchored.
{ "name": "Tycoon", "tree": { "$className": "DataModel", "ReplicatedStorage": { "Shared": { "$path": "src/Shared" } }, "ServerScriptService": { "Server": { "$path": "src/Server" } } }}
- The treeEvery instance the map describes. Pick one and the Inspector follows.
- DescribesWhether this is a DataModel for Roblox or a filesystem for Lune. It decides what the rest of the panel is.
- NameThe instance's name in the DataModel.
- ClassBlank on a service, because Rojo infers it from the key and rejects it being said twice.
- PathThe folder or file on disk whose contents fill this instance.
- Ignore unknownRojo leaves alone anything in Studio that it did not put there.
- Ignore pathsGlobs under this instance's path that Rojo should skip.
- Add folder, Add service, DeleteBuild the tree. Services can only be added to the root.
- OutputWhere the project file is written, relative to the project root.
- Project-wide ignoresPassed to Rojo as globIgnorePaths, unanchored.
- The project fileWhat the map compiles to. Selecting a row lights the lines that row writes.
Two things in that file are worth naming, because both are Roswaal leaving something out on purpose. A service carries no $className, because Rojo already knows what ReplicatedStorage is and saying it again is something Rojo rejects. And Shared is a Folder in the tree with no $className in the file, because a path pointing at a directory already implies one.
Select an instance to edit it. Drag the line between the tree and the Inspector to resize them.
| Field | What it does |
|---|---|
| Name | The instance's name in the DataModel |
| Class | Blank for a service, because Rojo already knows what ServerScriptService is. Otherwise Folder, Model, Configuration, ScreenGui, Part or Tool |
| Path | The folder or file on disk that fills the instance. A folder under src is made when the map is written, with its mirror under .roswaal/scripts for the graphs; anything else is marked when Roswaal cannot find it |
| Ignore unknown | Rojo leaves alone anything in Studio that it did not put there |
| Ignore paths | Files under the path that Rojo should skip. Start one with / to write it from the project root |
Add folder and Add service build the tree. Under Project file, Output is where the file is written and Project-wide ignores go to Rojo as written. The JSON it will write is shown underneath, with anything wrong — an instance with no name, or two with the same one.
Info
roswaal compile also make the folders it syncs, and move a folder whose path you changed to the new path, with its graphs. A project file Roswaal did not write needs --force.Warning
Requiring a module
A map is also how Roswaal knows where a file ends up. Drag a graph or a .luau from the project tree onto the canvas, and it offers Require Module with the path filled in — src/ReplicatedStorage/Shared/Greeter.luau becomes ReplicatedStorage.Shared.Greeter — or Instance for a reference to it. If no map covers the file, it says so.
An existing game
A Rojo project or a place you already have becomes a Roswaal project on Places and Rojo projects, which also covers browsing the place and writing scripts back into it. Packages are on Wally packages.
Last reviewed 11 September 2026 by @neopolitans