Skip to content

Server Modules

Structure

return function(Modules, ClientModules, Services)
    local module = {}

    -- function definitions

    return module
end
argument description
Modules A table that contains all the other server modules
ClientModules A table with all the client module functions exposed to the server
Services A table to access Roblox services, handy when you want to test your modules

Calling Server Modules

The Modules table makes it really easy to communicate with any other server modules. All you need is to index the table with the module name and then you can access its functions. For example, if you have a module called ModuleA that defines a function named Print:

-- ServerModules/ModuleA.lua
return function(Modules, ClientModules, Services)
    local module = {}

    function module.Print(value)
        print('from ModuleA:', value)
    end

    return module
end

You can access the ModuleA from any other module server module by using Modules.ModuleA:

-- ServerModules/ModuleB.lua
return function(Modules, ClientModules, Services)
    local module = {}

    function module.Example()
        Modules.ModuleA.Print('foo')
    end

    return module
end

Warning

You can't access the Modules and ClientModules in the scope of the function that returns the module itself, since the table may not have been populated yet with the other modules. Always call other module functions inside one of your module's function.

return function(Modules, ClientModules, Services)
    local module = {}

    local value = nil

    function module.Init()
        value = Modules.OtherModule.ItsFunction()
    end

    return module
end
return function(Modules, ClientModules, Services)
    local module = {}

    local value = Modules.OtherModule.ItsFunction()

    return module
end

Calling Client Modules

The ClientModules table works exactly like the Modules table, except that you will only be able to call functions that are exposed as remote events or remote functions (ending with _event or _func).

Also, since you are calling from the server to a client, you need to specify which player are you calling to, by placing the player object as the first argument.

You can find more information about this on the client module page.

Exposing Functions to Client Modules

crosswalk allow server modules to define functions that can be called from any client modules. In order to explain how to do achieve this, follow the next example.

Example

In order to be able to call a function from any client modules, a suffix needs to be added to the function name. For the example used with this section, we will use two variant of the same function, with and without a return value:

function module.IncreaseCounter(amount)
    counter = counter + amount
end
function module.IncreaseCounter(amount)
    counter = counter + amount
    return counter
end

If you want to it to be called from any client modules, a few modifications need to be made:

  • add the appropriate suffix
  • change the signature
  • return the validation value

Suffixes

In server modules, different suffixes can be used, which depends on the use case you have. There are two main type of suffixes:

  • _event, for functions without a result
  • _func, for functions that return a result

Unlike with client modules, a security level can also be specified through a function name. This will determine how crosswalk will wrap up the functions to add a layer of key exchanging mecanism. Under the hood, what is happening is that instead of only sending the given arguments, crosswalk will also send a key to the server. The server will verify that key to make sure that the call is valid.

The default mode is the most secure, where each function call is sent with a different security key. The difference with the _risky level is that an initial security key will be set and then used for each call. The last level, _danger, will connect directly to the RemoteEvent or RemoteFunction, without any additional code.

suffix security
_event or _func Exchanging new security key each call
_risky_event or _risky_func Exchanging the same security key everytime
_danger_event or _danger_func No security keys exchanged

If you need performance, because you need to call the function really often from the client side, you should go with the _risky or _danger level.

If we update our example, we should have one of these three possibilities:

function module.IncreaseCounter_event(amount)
    counter = counter + amount
end

function module.IncreaseCounter_risky_event(amount)
    counter = counter + amount
end

function module.IncreaseCounter_danger_event(amount)
    counter = counter + amount
end
function module.IncreaseCounter_func(amount)
    counter = counter + amount
    return counter
end

function module.IncreaseCounter_risky_func(amount)
    counter = counter + amount
    return counter
end

function module.IncreaseCounter_danger_func(amount)
    counter = counter + amount
    return counter
end

Signature

Since the function can be called from any client modules, the first parameter passed to the function will be the player calling the function. This step is easy, simply add a parameter to the function signature:

function module.IncreaseCounter_event(player, amount)
    counter = counter + amount
end
function module.IncreaseCounter_func(player, amount)
    counter = counter + amount
    return counter
end

Validation

Since the exposed function is meant to be called from a client module, it needs to return a boolean that validate the execution. For example, you can check if the parameters have the correct type. If you are using the _func suffix, that means that your function needs to return its value after the validation.

The function is now exposed to any client modules.

function module.IncreaseCounter_event(player, amount)
    if type(amount) ~= 'number' then
        return false
    end

    counter = counter + amount

    return true
end
function module.IncreaseCounter_func(player, amount)
    if type(amount) ~= 'number' then
        return false
    end

    counter = counter + amount

    return true, counter
end

Important

This validation is required. Any function exposed to clients using _func or _event must return true or false to indicate that the function was called with normal values in the right context. This is crucial to be able to react to bad actors that are bypassing the game systems and trying to manually trigger remote functions or remote events.

This relates to the special function OnUnapprovedExecution that is automatically called when this validation process fails.

Calling Exposed Functions

Even if the function ends a suffix, exposed functions are called without their suffix. That means that if a function is declared as DoSomething_event, DoSomething_risky_event or with any other suffix, you can call it using only DoSomething.

From Client Modules

Since the call is made from a client to the server module, you don't have to provide the player object, it will be automatically set with the player calling the server module.

return function(Modules, ServerModules, Services)
    local module = {}

    function module.Greet(player)
        ServerModules.SomeServerModule.SayHello('Welcome!')
    end

    return module
end
return function(Modules, ClientModules, Services)
    local module = {}

    function module.SayHello_event(player, message)
        print('Hello', player.Name, ':', message)

        return true
    end

    return module
end

From Server Modules

Even if the function is exposed to client modules, you can also call it from other server modules. Here is an example with a module named ModuleA that calls ModuleB:

return function(Modules, ClientModules, Services)
    local module = {}

    function module.Greet(player)
        Modules.ModuleB.SayHello(player, 'Welcome!')
    end

    return module
end
return function(Modules, ClientModules, Services)
    local module = {}

    function module.SayHello_event(player, message)
        print('Hello', player.Name, ':', message)

        return true
    end

    return module
end