Rotree watches your source and generates default.project.json for Rojo. You organize code by feature (modules/player/) and name files by role (player.service.ts); Rotree routes each file into the right Roblox service — with no client/server/shared folders to maintain. Works with both Luau and roblox-ts.
The CLI command is rotree on either channel.
Rokit — recommended for Luau projects:
[tools]
rotree = "notmatical/rotree@0.2.0"npm — recommended for roblox-ts (or anyone with Node/Bun). Add it as a dev dependency, or run it directly:
bun add -d @matical/rotree # or: npm install -D @matical/rotree
bunx @matical/rotree --watch # or: npx @matical/rotree --watchrotree --init # writes a .rotree.json
rotree --watch # regenerate default.project.json on every changeFor roblox-ts, run Rotree next to the compiler (via concurrently):
"scripts": {
"dev": "concurrently \"rotree -w\" \"rbxtsc -w\" \"rojo serve\""
}You never create client/server/shared folders. Instead you name a file for what it is, and its realm falls out automatically:
| You write | Because it's a… | Lands in |
|---|---|---|
player.service.ts |
service | server (ServerScriptService) |
input.controller.ts |
controller | client (StarterPlayerScripts) |
hud.tsx / button.component.tsx |
UI | client |
player.ts, dmg.const.ts, net.remote.ts |
anything else | shared (ReplicatedStorage) |
Two rules to remember: service → server, controller/component/.tsx → client, everything else → shared.
Two explicit escapes you touch rarely:
- Entry points that run →
main.server.ts/main.client.ts(these become Script / LocalScript). - A whole folder at once → drop an empty
.server/.client/.sharedmarker file in it (great forui/or vendored libraries).
Organize by feature, and Rotree mirrors it into the tree:
src/
main.server.ts → ServerScriptService/main
main.client.ts → StarterPlayerScripts/main
modules/player/
player.service.ts → ServerScriptService/modules/player/player.service
player-data.ts → ReplicatedStorage/modules/player/player-data
ui/
.client (marker: everything under ui/ is client)
app.tsx → StarterPlayerScripts/ui/app
vendor/
.server (marker: server-only library)
profile-store.luau → ServerScriptService/vendor/profile-store
Why not
.server.tsfor a service? In roblox-ts,.server.ts/.client.tscompile to a Script/LocalScript (an entry point that runs) — you can'timportfrom them. Role suffixes like.servicekeep the file a ModuleScript, so it routes to the server and stays importable. Use.server/.clientonly for the handful of entry points that bootstrap the game.
Precedence (most specific wins): file affix → marker file → folder name → .tsx default → shared.
rotree --init generates a starting config. Everything is optional — the convention works out of the box.
{
"source": ["src"],
"jsx": "client",
"ts": { "output": "default.project.json", "build": "out" },
"luau": { "output": "default.project.json", "build": "src" },
"aliases": {
"repository": "ServerScriptService"
},
"template": {
"name": "my-game",
"tree": { "$className": "DataModel" }
}
}| Field | Description |
|---|---|
source |
Source directory, or an array of them to merge multiple places into one tree. Defaults to ["src"]. |
jsx |
Realm that unrouted .tsx/.jsx files default to. Defaults to "client". |
ts / luau / darklua |
Mode overrides — where compiled code lands (build) and the generated file name (output). Auto-detected from tsconfig.json / .darklua.json. |
aliases |
Register your own role suffixes → services (e.g. "repository": "ServerScriptService"). service, controller, and component are built in. |
template |
Base Rojo tree merged with Rotree's generated paths — services with properties, rbxts_include, package folders, etc. Can also be a path to a JSON file. |
keepRouteNames |
Keep structural realm keywords in node names instead of stripping them (main.server → main). Defaults to false. |
| Flag | Description |
|---|---|
-w, --watch |
Regenerate on file changes |
-i, --init |
Write a default .rotree.json |
-m, --mode <mode> |
luau | ts | darklua | custom (auto-detected if omitted) |
-s, --source <path> |
Override source dir (repeatable to merge) |
-o, --output <path> |
Override the generated project file |
-b, --build <path> |
Override where compiled code lands |
-t, --template <path> |
Path to a base Rojo tree JSON |
-j, --jsx <realm> |
Realm for .tsx/.jsx (default client) |
-k, --keepRouteNames |
Don't strip realm keywords from names |
-c, --config <path> |
Custom config file path |
Rotree is a fork of rogen by LDGerrits, used under the MIT license. Thanks to the original author for the feature-based routing foundation.
MIT © notmatical, with original portions © Loek Gerrits.