Getting started
RoFlux is two pieces: a server that watches your folder, and a Studio plugin that receives what it sends. The plugin lives inside the server binary, so you only ever install one thing.
Get the server
The quickest way is Rokit, which puts it on your PATH for you:
rokit add --global thekingofspace/RoFlux roflux
That makes roflux work from any folder. To pin a version for one project instead, run it inside the project without --global. That writes it into the project's rokit.toml, and anyone who clones the project gets the same version with rokit install.
You can also download the zip for your system from the releases page, unzip it, and put roflux somewhere on your PATH.
Building it yourself
RoFlux is a Rust program. You need a Rust toolchain, and a C++ compiler because the Luau runtime used by the script system is built from source.
To build and install it globally in one step:
cargo install --path Server
That puts roflux in ~/.cargo/bin, which is already on your PATH if you have Rust, so the command works from any folder. Remove it later with cargo uninstall roflux.
cargo install builds in a fresh folder by default, which means compiling the Luau runtime again from scratch. Add --target-dir Server/target to reuse what you have already built.
To build without installing, the binary lands in Server/target/release:
cargo build --release --manifest-path Server/Cargo.toml
Install the Studio plugin
roflux plugin
This writes RoFlux.rbxm into your local Studio plugins folder, replacing any previous copy:
| Platform | Location |
|---|---|
| Windows | %LOCALAPPDATA%\Roblox\Plugins |
| macOS | ~/Documents/Roblox/Plugins |
Restart Studio afterwards so it picks the plugin up. Because the plugin source is compiled into the binary, the installed plugin always matches the server version you are running. Rebuild the server, run roflux plugin again, and they stay in step.
The plugin talks to 127.0.0.1. If requests are blocked, turn on Allow HTTP Requests in Game Settings → Security.
Make a project
roflux init MyGame
That creates a default.project.json, an empty scripts/ folder for compile time hooks, a types.d.luau, and a .vscode/settings.json wired up for luau-lsp.
Add some code:
MyGame/
├─ default.project.json
├─ scripts/
└─ Shared/
├─ init.meta.json -- { "$Parent": "ReplicatedStorage" }
└─ Greeting.luau
Then start the server:
roflux serve MyGame
It prints the tree size, begins watching, and listens on port 34872.
Connect from Studio
Open the place you want to sync into, click RoFlux on the plugins toolbar, check the host and port, and press Connect.
RoFlux does nothing during a playtest. Studio re-runs plugins for the play DataModel, so the plugin checks RunService:IsEdit() and stays out of the way. Anything that arrives while a run is in progress is held, and when you stop the run it asks the server for a fresh sync and catches up.
The first sync
On connecting, the plugin does a dry run before touching anything and shows you every instance the sync would create, edit or delete. Approve it and the place is brought in line. Cancel and the connection is dropped, rather than leaving the place half matched.
That review happens once per connection. Everything after it applies immediately, so ordinary saves never interrupt you. Reconnect and you get the review again.
You can turn the review off entirely with review changes when connecting in the plugin's settings panel, which lets RoFlux overwrite freely.
Every change is recorded in the plugin's Changes panel, grouped per sync and filterable by created, edited, deleted and failed. Nothing routine is printed to the Studio console. Only real failures show up there.
Editor setup
Every compile writes sourcemap.json and refreshes .vscode/settings.json so luau-lsp resolves your tree:
{
"luau-lsp.types.definitionFiles": { "RoFlux": "types.d.luau" },
"luau-lsp.platform.type": "roblox",
"luau-lsp.sourcemap.enabled": true,
"luau-lsp.sourcemap.autogenerate": false
}
autogenerate is off because RoFlux writes the sourcemap itself. Existing keys in that file are kept. Only the ones above are set.
The generated types.d.luau declares the roflux global used by hook scripts, so scripts/ gets full autocomplete.
script.Child resolves against the sourcemap and gives you a typed instance. script:FindFirstChild("Child") is always typed as Instance? no matter what the sourcemap says. That is how luau-lsp works, not something RoFlux controls.