> ## Documentation Index
> Fetch the complete documentation index at: https://flux-docs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Shared Modules

> Using Flux from code that runs on both the server and the client.

`Flux.Server` and `Flux.Client` are context-guarded. Each one throws if called from the
wrong side:

```luau theme={null}
Flux.Server("Name") -- on a client: "Flux.Server can only be called on the server."
Flux.Client("Name") -- on the server: "Flux.Client can only be called on the client."
```

<Warning>
  Flux has no unified constructor. There is no `Flux.ReferenceBridge`, `Flux.Event` or
  auto-detecting factory in the public API.

  The `Bridge` module inside Flux is unrelated. It is the internal buffer batching and
  flush layer, not an environment selector, and it is not re-exported from the root
  module. See [Bridge](/api/internals/bridge) if you are curious what it actually does.
</Warning>

So a `ModuleScript` required from both sides cannot call either constructor
unconditionally. Branch on `RunService` instead.

## The pattern

```luau ReplicatedStorage/Net.luau icon="code-branch" theme={null}
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Flux = require(ReplicatedStorage.Flux)

local IS_SERVER = RunService:IsServer()

local function event(name: string, schema: { Flux.Types.Type }?)
	if IS_SERVER then
		return Flux.Server(name, schema)
	end
	return Flux.Client(name, schema)
end

return {
	Chat = event("Chat"),
	Damage = event("Damage", { Flux.Types.Instance(), Flux.Types.UInt16() }),
}
```

Now both sides require the same module and get the object appropriate to their context:

<CodeGroup>
  ```luau Server theme={null}
  local Net = require(ReplicatedStorage.Net)

  Net.Chat:Connect(function(player, message)
  	print(player.Name, message)  -- player parameter present
  end)

  Net.Chat:FireAll("Welcome!")
  ```

  ```luau Client theme={null}
  local Net = require(ReplicatedStorage.Net)

  Net.Chat:Connect(function(message)
  	print(message)               -- no player parameter
  end)

  Net.Chat:Fire("Hello from the client!")
  ```
</CodeGroup>

<Note>
  This also happens to be the right place to keep schemas, since both sides have to
  declare identical layouts. One definition, so the two cannot drift. Compare with the
  shared-module approach in [Schemas](/guides/schemas#how-a-schema-is-applied).
</Note>

## Keeping types accurate across the branch

The helper above returns a union of two different shapes, which the type checker cannot
narrow at the call site. Give each side its own typed view instead:

```luau ReplicatedStorage/Net.luau theme={null}
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Flux = require(ReplicatedStorage.Flux)

export type ChatArgs = (string)
export type DamageArgs = (Instance, number)

local IS_SERVER = RunService:IsServer()

local function raw(name: string, schema: { Flux.Types.Type }?): any
	if IS_SERVER then
		return Flux.Server(name, schema)
	end
	return Flux.Client(name, schema)
end

local Net = {
	Chat = raw("Chat"),
	Damage = raw("Damage", { Flux.Types.Instance(), Flux.Types.UInt16() }),
}

-- Two views over the same objects, one per context.
export type ServerNet = {
	Chat: Flux.Server<ChatArgs>,
	Damage: Flux.Server<DamageArgs>,
}

export type ClientNet = {
	Chat: Flux.Client<ChatArgs>,
	Damage: Flux.Client<DamageArgs>,
}

return Net
```

<CodeGroup>
  ```luau Server theme={null}
  local Net = require(ReplicatedStorage.Net) :: Net.ServerNet
  --> Net.Chat:Fire(player, "hi")  fully typed, player required
  ```

  ```luau Client theme={null}
  local Net = require(ReplicatedStorage.Net) :: Net.ClientNet
  --> Net.Chat:Fire("hi")          fully typed, no player
  ```
</CodeGroup>

<Tip>
  Because the argument packs, `ChatArgs` and `DamageArgs`, are declared once and reused by
  both views, the server and client signatures can never disagree about what an event
  carries. They differ only in whether a `Player` is prepended, which Flux's generic types
  handle for you.
</Tip>

## Asymmetric behaviour to remember

A shared module hides the context, so it is easy to forget these differences. They do not
disappear.

|                    | Server                         | Client          |
| ------------------ | ------------------------------ | --------------- |
| `Connect` callback | `(player, ...)`                | `(...)`         |
| `Wait` returns     | `(Player, ...)`                | `(...)`         |
| Broadcast          | `FireAll`, `FireAllUnreliable` | not available   |
| Invocation         | `OnInvoke` handler             | `Invoke` caller |

<Warning>
  Code that genuinely runs identically on both sides cannot call `Connect` with one
  callback signature. If a shared function needs to handle received messages, keep the
  listener registration in context-specific scripts and let the shared module own only
  construction.
</Warning>

## Next steps

<CardGroup cols={2}>
  <Card title="Generics" icon="shapes" href="/guides/generics">
    Declaring argument packs once and reusing them.
  </Card>

  <Card title="Migrating" icon="right-left" href="/reference/migrating">
    Coming from raw remotes or a Warp-style API.
  </Card>
</CardGroup>
