Skip to content
🌊 You are viewing documentation for the Maelstrom pre-release channel. Pin a stable release for production.

Getting Started

Welcome to Riptide! This guide walks you through every step β€” from installation to your first working Service and Controller β€” with no prior framework experience required.


Pesde is the recommended Luau package manager. Install it by following the Pesde docs, then run:

Terminal window
pesde add riptide/core

Pesde places Riptide inside luau_packages/. You reference it from your Rojo project as a Packages folder in ReplicatedStorage.

[dependencies]
Riptide = "riptide/core@0.9.0-maelstrom.2"

Download Riptide.rbxm from the latest release and drop it into ReplicatedStorage/Packages/.


Rojo syncs your local code files into Roblox Studio. Create a default.project.json at the root of your project:

{
"name": "my-riptide-game",
"tree": {
"$className": "DataModel",
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"Packages": {
"$path": "luau_packages"
},
"SharedModules": {
"$path": "src/shared"
}
},
"ServerScriptService": {
"$className": "ServerScriptService",
"main": {
"$path": "src/server/main.server.lua"
},
"Services": {
"$path": "src/server/Services"
}
},
"StarterPlayer": {
"$className": "StarterPlayer",
"StarterPlayerScripts": {
"$className": "StarterPlayerScripts",
"main": {
"$path": "src/client/main.client.lua"
},
"Controllers": {
"$path": "src/client/Controllers"
}
}
}
}
}

This produces the following hierarchy inside Roblox Studio:

ReplicatedStorage/
β”œβ”€β”€ Packages/ ← Riptide and other dependencies
└── SharedModules/ ← code used by both server and client
ServerScriptService/
β”œβ”€β”€ main ← server entry point (Script)
└── Services/ ← server-only modules (Folder)
StarterPlayer/StarterPlayerScripts/
β”œβ”€β”€ main ← client entry point (LocalScript)
└── Controllers/ ← client-only modules (Folder)

Create the matching local folders:

src/
β”œβ”€β”€ shared/ ← SharedModules
β”œβ”€β”€ server/
β”‚ β”œβ”€β”€ main.server.lua
β”‚ └── Services/
└── client/
β”œβ”€β”€ main.client.lua
└── Controllers/

In Riptide, server-side game logic lives in Services β€” plain Lua tables with special lifecycle methods.

Create src/server/Services/HelloService.lua:

-- src/server/Services/HelloService.lua
--!strict
local HelloService = {}
--[[
Init(Riptide) β€” runs SYNCHRONOUSLY before any module starts.
Use this phase to:
β€’ store references to other services
β€’ register network event handlers
β€’ set up initial state
Do NOT yield here (no task.wait, no async calls).
]]
function HelloService:Init(Riptide)
print("HelloService:Init β€” framework is setting up!")
end
--[[
Start(Riptide) β€” runs ASYNCHRONOUSLY (via task.spawn) after ALL
modules have finished Init. Use this phase to:
β€’ start game loops
β€’ connect to events
β€’ yield freely
]]
function HelloService:Start(Riptide)
print("HelloService:Start β€” everything is ready!")
end
--[[
OnPlayerAdded(Riptide, player) β€” called when a player joins.
Also replays for players already in the server after module
initialization and plugin readiness.
]]
function HelloService:OnPlayerAdded(Riptide, player)
print("Welcome, " .. player.Name .. "!")
end
--[[
OnPlayerRemoving(Riptide, player) β€” called when a player leaves.
Use this to clean up player-specific data.
]]
function HelloService:OnPlayerRemoving(Riptide, player)
print("Goodbye, " .. player.Name .. "!")
end
return HelloService

Client-side logic lives in Controllers β€” same idea, different folder.

Create src/client/Controllers/HelloController.lua:

-- src/client/Controllers/HelloController.lua
--!strict
local HelloController = {}
function HelloController:Init(Riptide)
-- Listen for a network event fired by the server
Riptide.Network.Register("ServerGreeting", function(message)
print("Server says:", message)
end)
end
function HelloController:Start(Riptide)
-- Tell the server we are ready
Riptide.Network.FireServer("ClientReady")
print("HelloController started on the client!")
end
return HelloController

Riptide does not start automatically. You must call Launch from both your server and client entry scripts.

Create src/server/main.server.lua:

-- src/server/main.server.lua
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
-- Require Riptide from the Packages folder
local Riptide = require(ReplicatedStorage.Packages.Riptide).Server
Riptide.Launch({
-- Required: the Folder containing your server Services
ModulesFolder = ServerScriptService.Services,
-- Optional: shared modules loaded BEFORE Services
SharedModulesFolder = ReplicatedStorage.SharedModules,
})

Create src/client/main.client.lua:

-- src/client/main.client.lua
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Riptide = require(ReplicatedStorage.Packages.Riptide).Client
Riptide.Launch({
-- Required: the Folder containing your client Controllers
ModulesFolder = Players.LocalPlayer.PlayerScripts.Controllers,
-- Optional: shared modules loaded BEFORE Controllers
SharedModulesFolder = ReplicatedStorage.SharedModules,
})

  1. Run rojo serve in your terminal.
  2. Connect via the Rojo Studio plugin.
  3. Press Play in Roblox Studio.

You should see this in the Output window:

🌊 [Riptide] Server Initialization Started...
HelloService:Init β€” framework is setting up!
HelloService:Start β€” everything is ready!
🌊 [Riptide] βœ… Server Start Phase Dispatched.
Welcome, Player1!

And on the client:

🌊 [Riptide] Client Initialization Started...
HelloController started on the client!
🌊 [Riptide] βœ… Client Start Phase Dispatched.

Congratulations β€” you have a working Riptide project! πŸŽ‰


  • Project Structure β€” recommended folder layout for larger games.
  • Module Lifecycle β€” understand the Load / Init / Start phases in depth.
  • Network β€” fire events between server and client.
  • State Replication β€” server-authoritative state synced to clients.
  • Plugins β€” extend the framework with modular, sandboxed plugins.