Build with AI, import instantly.
The ForjeGames Studio plugin links your Roblox Studio session directly to the web editor. Describe what you want to build — the AI generates it, and it appears in Studio within one second. No copy-paste, no manual import.
~1s
Command latency
Free
Plugin cost
100%
Local — no uploads
Three steps. No Roblox account permissions required — the plugin runs entirely client-side and only reaches out to forjegames.com.
https://forjegames.com/api/studio/plugin. Alternatively, download ForjeGames.rbxmx and place it in %LOCALAPPDATA%\Roblox\Plugins\.● Connected.Skip the file download. Open View → Command Bar in Studio and paste:
loadstring(game:HttpGet("https://forjegames.com/api/studio/plugin"))()One-click import of AI-generated builds
Every AI response containing Luau or model data executes directly in your open Studio session. No copy-paste, no file export.
Real-time sync between web editor and Studio
The plugin polls the ForjeGames server every 1 second. Commands appear in Studio within ~1 second of being issued from the web editor.
Automatic material and lighting setup
AI-generated builds include material assignments, part properties, and lighting values. The plugin applies them all in a single atomic operation.
Undo/redo support via ChangeHistoryService
Every plugin action creates a named waypoint in Studio's undo stack. Press Ctrl+Z to undo any ForjeGames operation as if you built it manually.
API key stored locally, never uploaded
Your session token lives only in the plugin's runtime memory. It is never written to disk or sent to any server except forjegames.com.
Works with Team Create
Multiple collaborators can each connect their own Studio session simultaneously. Each session receives its own isolated session token.
The complete Luau source is served live from GET /api/studio/plugin. The code below is identical to what you download — inspect it, fork it, or load it with loadstring.
| 1 | -- ForjeGames Studio Plugin v1.0.0 |
| 2 | -- https://forjegames.com/docs/studio |
| 3 | -- |
| 4 | -- Install: Place this file in %LOCALAPPDATA%\Roblox\Plugins\ |
| 5 | -- Or paste the loadstring below in the Studio Command Bar: |
| 6 | -- loadstring(game:HttpGet("https://forjegames.com/api/studio/plugin"))() |
| 7 | |
| 8 | local BASE_URL = "https://forjegames.com" |
| 9 | local POLL_MS = 1000 -- sync interval (ms) |
| 10 | local HB_MS = 5000 -- heartbeat interval (ms) |
| 11 | local PLUGIN_VER = "1.0.0" |
| 12 | |
| 13 | -- ── Services ───────────────────────────────────────────────────────────────── |
| 14 | |
| 15 | local HttpService = game:GetService("HttpService") |
| 16 | local RunService = game:GetService("RunService") |
| 17 | local StudioService = game:GetService("StudioService") |
| 18 | local ChangeHistoryService = game:GetService("ChangeHistoryService") |
| 19 | |
| 20 | -- ── State ───────────────────────────────────────────────────────────────────── |
| 21 | |
| 22 | local sessionToken : string? = nil |
| 23 | local connectionCode : string? = nil |
| 24 | local connected = false |
| 25 | local lastSyncAt = 0 |
| 26 | local lastHbAt = 0 |
| 27 | |
| 28 | -- ── Plugin toolbar ──────────────────────────────────────────────────────────── |
| 29 | |
| 30 | local toolbar = plugin:CreateToolbar("ForjeGames") |
| 31 | local connectBtn = toolbar:CreateButton("Connect", "Connect to ForjeGames.com", "rbxassetid://0") |
| 32 | local statusLabel = toolbar:CreateButton("● Disconnected", "", "rbxassetid://0") |
| 33 | statusLabel.ClickableWhenViewportHidden = true |
| 34 | |
| 35 | -- ── Helpers ─────────────────────────────────────────────────────────────────── |
| 36 | |
| 37 | local function request(method, path, body) |
| 38 | local ok, result = pcall(function() |
| 39 | local opts = { |
| 40 | Url = BASE_URL .. path, |
| 41 | Method = method, |
| 42 | Headers = { ["Content-Type"] = "application/json" }, |
| 43 | } |
| 44 | if body then opts.Body = HttpService:JSONEncode(body) end |
| 45 | local res = HttpService:RequestAsync(opts) |
| 46 | if res.Success then |
| 47 | return HttpService:JSONDecode(res.Body) |
| 48 | end |
| 49 | end) |
| 50 | return ok, result |
| 51 | end |
| 52 | |
| 53 | local function setStatus(label, isConnected) |
| 54 | connected = isConnected |
| 55 | statusLabel.Text = isConnected and ("● " .. label) or ("○ " .. label) |
| 56 | end |
| 57 | |
| 58 | -- ── Connection dialog ───────────────────────────────────────────────────────── |
| 59 | |
| 60 | local function promptForCode() |
| 61 | local gui = Instance.new("ScreenGui") |
| 62 | gui.Name = "ForjeGamesConnect" |
| 63 | gui.ResetOnSpawn = false |
| 64 | gui.Parent = game:GetService("CoreGui") |
| 65 | |
| 66 | local frame = Instance.new("Frame") |
| 67 | frame.Size = UDim2.new(0, 320, 0, 200) |
| 68 | frame.Position = UDim2.new(0.5, -160, 0.5, -100) |
| 69 | frame.BackgroundColor3 = Color3.fromRGB(20, 20, 20) |
| 70 | frame.BorderSizePixel = 0 |
| 71 | frame.Parent = gui |
| 72 | |
| 73 | Instance.new("UICorner").CornerRadius = UDim.new(0, 12) |
| 74 | Instance.new("UICorner").Parent = frame |
| 75 | |
| 76 | -- [UI elements omitted for brevity — see full source at /api/studio/plugin] |
| 77 | |
| 78 | local connectButton = Instance.new("TextButton") |
| 79 | connectButton.Size = UDim2.new(1, -32, 0, 38) |
| 80 | connectButton.Position = UDim2.new(0, 16, 0, 148) |
| 81 | connectButton.BackgroundColor3 = Color3.fromRGB(212, 175, 55) |
| 82 | connectButton.Text = "Connect" |
| 83 | connectButton.Parent = frame |
| 84 | |
| 85 | connectButton.MouseButton1Click:Connect(function() |
| 86 | local code = string.upper(string.gsub(input.Text, "%s", "")) |
| 87 | local ok, data = request("POST", "/api/studio/auth", { |
| 88 | code = code, |
| 89 | placeId = tostring(game.PlaceId), |
| 90 | pluginVersion = PLUGIN_VER, |
| 91 | }) |
| 92 | if ok and data and (data.token or data.sessionToken) then |
| 93 | sessionToken = data.token or data.sessionToken |
| 94 | setStatus("Connected", true) |
| 95 | gui:Destroy() |
| 96 | end |
| 97 | end) |
| 98 | end |
| 99 | |
| 100 | -- ── Connect button handler ──────────────────────────────────────────────────── |
| 101 | |
| 102 | connectBtn.Click:Connect(function() |
| 103 | if connected then |
| 104 | sessionToken = nil |
| 105 | setStatus("Disconnected", false) |
| 106 | else |
| 107 | promptForCode() |
| 108 | end |
| 109 | end) |
| 110 | |
| 111 | -- ── Command executor ────────────────────────────────────────────────────────── |
| 112 | |
| 113 | local function executeCommand(cmd) |
| 114 | local cmdType = cmd.type or cmd.command |
| 115 | |
| 116 | if cmdType == "execute_luau" then |
| 117 | local fn, err = loadstring(cmd.data and cmd.data.code or cmd.code or "") |
| 118 | if fn then |
| 119 | local ok, runErr = pcall(fn) |
| 120 | if not ok then warn("[ForjeGames] Script error:", runErr) end |
| 121 | else |
| 122 | warn("[ForjeGames] Compile error:", err) |
| 123 | end |
| 124 | |
| 125 | elseif cmdType == "insert_model" then |
| 126 | local assetId = cmd.data and cmd.data.assetId |
| 127 | if assetId then |
| 128 | local ok, model = pcall(function() |
| 129 | return game:GetService("InsertService"):LoadAsset(tonumber(assetId)) |
| 130 | end) |
| 131 | if ok then |
| 132 | model.Parent = workspace |
| 133 | ChangeHistoryService:SetWaypoint("ForjeGames: Insert " .. tostring(assetId)) |
| 134 | end |
| 135 | end |
| 136 | |
| 137 | elseif cmdType == "update_property" then |
| 138 | local data = cmd.data or {} |
| 139 | local target = workspace:FindFirstChild(data.instancePath or "", true) |
| 140 | if target then |
| 141 | pcall(function() target[data.property] = data.value end) |
| 142 | end |
| 143 | |
| 144 | elseif cmdType == "delete_model" then |
| 145 | local target = workspace:FindFirstChild((cmd.data or {}).instancePath or "", true) |
| 146 | if target then |
| 147 | target:Destroy() |
| 148 | ChangeHistoryService:SetWaypoint("ForjeGames: Delete") |
| 149 | end |
| 150 | end |
| 151 | end |
| 152 | |
| 153 | -- ── Sync loop ───────────────────────────────────────────────────────────────── |
| 154 | |
| 155 | RunService.Heartbeat:Connect(function() |
| 156 | if not sessionToken then return end |
| 157 | local now = tick() * 1000 |
| 158 | |
| 159 | if now - lastHbAt >= HB_MS then |
| 160 | lastHbAt = now |
| 161 | task.spawn(function() |
| 162 | request("POST", "/api/studio/update", { |
| 163 | sessionToken = sessionToken, |
| 164 | event = "heartbeat", |
| 165 | placeId = tostring(game.PlaceId), |
| 166 | }) |
| 167 | end) |
| 168 | end |
| 169 | |
| 170 | if now - lastSyncAt >= POLL_MS then |
| 171 | lastSyncAt = now |
| 172 | task.spawn(function() |
| 173 | local ok, data = request("GET", |
| 174 | "/api/studio/sync?sessionToken=" .. HttpService:UrlEncode(sessionToken)) |
| 175 | if ok and data and data.commands then |
| 176 | for _, cmd in ipairs(data.commands) do |
| 177 | executeCommand(cmd) |
| 178 | end |
| 179 | end |
| 180 | end) |
| 181 | end |
| 182 | end) |
| 183 | |
| 184 | print("[ForjeGames] Plugin loaded v" .. PLUGIN_VER .. ". Click 'Connect' in the toolbar.") |