Services

Available in: Server Only

This module runs exclusively on the server side. Use Signals or Client Methods to communicate with the client.

A Service is a ModuleScript created with Owl.CreateService() and loaded from ServerScriptService via Owl.AddServices() (see Installation). It's a singleton, there's exactly one instance of DataService for the lifetime of the server reachable from any other Service or from the client's Controllers.

Anatomy of a Service

ServerScriptService/Services/DataService.lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Owl = require(ReplicatedStorage.OwlKnit.Owl)
 
local DataService = Owl.CreateService({
    Name = "DataService",
    Dependencies = {}, -- > // Names of the Services this one depends on
    Middleware   = { -- > // Middleware specific to this Service (optional)
        Inbound  = {},
        Outbound = {},
    },
    Client = {
        -- > // Everything here is reachable from Controllers
        PointsChanged = Owl.CreateSignal(),
        State = Owl.CreateProperty("Menu"),
    },
})
 
DataService._playerData = {}
 
function DataService:OwlInit()
    -- > // Internal setup, fetching references to other Services
    -- > // self.InventoryService = Owl.GetService("InventoryService")
end
 
function DataService:OwlStart()
    -- > // Event connections, main logic
end
 
-- > // Automatic hooks
function DataService:OwlOnPlayerAdded(plr: Player)
    self._playerData[plr.UserId] = { Points = 0 }
end
 
function DataService:OwlOnPlayerRemoving(plr: Player)
    self._playerData[plr.UserId] = nil
end
 
-- > // Server method (callable from other Services via Owl.GetService)
function DataService:AddPoints(plr: Player, amount: number)
    local data = self._playerData[plr.UserId]
    if not data then return end
 
    data.Points += amount
    self.Client.PointsChanged:Fire(plr, data.Points)
    self.Client.State:SetFor(plr, "InGame")
end
 
-- > // Client method (RemoteFunction, callable from a Controller)
-- > // The first argument is ALWAYS the calling player, injected by Owl.
function DataService.Client:GetMyPoints(plr: Player): number
    -- > // Token check to confirm the player is properly registered
    local token = Owl.GetPlrToken(plr)
    if not token then return 0 end
 
    local data = DataService._playerData[plr.UserId]
    return data and data.Points or 0
end
 
return DataService

Owl.CreateService options

Key Type Required Description
Name string Unique identifier, used by Owl.GetService(Name) and in log output
Dependencies {string} - Other Service names this one needs initialized first - see Dependencies
Middleware {Inbound, Outbound} - Filters applied only to this Service's remotes - see Middleware
Client table - Signals, Properties and Client methods exposed to Controllers

Server methods vs Client methods

Everything defined as function DataService:MethodName(...) is a server method callable from any other Service (typically after fetching it with Owl.GetService("DataService") inside OwlInit), never reachable from the client.

Everything defined inside the Client table or as function DataService.Client:MethodName(plr, ...) is exposed to Controllers as a RemoteFunction. Owl always injects the calling Player as the first argument automatically, you never pass it yourself when calling from the Controller side, see Client Methods.

Because any client can technically fire a RemoteFunction with a fabricated first argument in some setups, Owl also exposes Owl.GetPlrToken(plr), a lightweight check confirming plr really is the player who made this specific call. Reach for it in any Client method that trusts plr to look up sensitive data, like GetMyPoints above.

Reading a Service's state from outside

Other Services call Owl.GetService("Name") server-only, resolved once during OwlInit and cached on self, never re-fetched on every call:

function InventoryService:OwlInit()
    self.DataService = Owl:GetService("DataService")
end

Controllers never call Owl.GetService for anything other than reading Client members (Signals, Properties, Client methods), a Controller has no visibility into a Service's private state (self._playerData above) by design.

Where to go next

  • Controllers - the client-side counterpart.
  • Lifecycle - what OwlInit and OwlStart actually guarantee and in what order.
  • Signals and Properties - the two ways a Service talks to its Client table.
  • Automatic Hooks - the full list of hooks like OwlOnPlayerAdded, beyond the two shown here.