> ## 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.

# Migrating

> Moving to Flux from raw remotes or a Warp-style API.

## From raw remotes

Flux replaces `RemoteEvent`, `UnreliableRemoteEvent` and `RemoteFunction`. The mapping is mostly
mechanical.

| Raw                                  | Flux                                |
| ------------------------------------ | ----------------------------------- |
| `Instance.new("RemoteEvent")`        | `Flux.Server("Name")`               |
| `remote:FireClient(player, ...)`     | `event:Fire(player, ...)`           |
| `remote:FireAllClients(...)`         | `event:FireAll(...)`                |
| `remote:FireServer(...)`             | `event:Fire(...)`                   |
| `remote.OnServerEvent:Connect(fn)`   | `event:Connect(fn)`                 |
| `remote.OnClientEvent:Connect(fn)`   | `event:Connect(fn)`                 |
| `remote.OnServerEvent:Once(fn)`      | `event:Once(fn)`                    |
| `remote.OnClientEvent:Wait()`        | `event:Wait()`                      |
| `unreliable:FireClient(player, ...)` | `event:FireUnreliable(player, ...)` |
| `unreliable:FireAllClients(...)`     | `event:FireAllUnreliable(...)`      |
| `func.OnServerInvoke = fn`           | `event.OnInvoke = fn`               |
| `func:InvokeServer(...)`             | `event:Invoke(...)`                 |
| `func:InvokeClient(player, ...)`     | no equivalent, see below            |

### Before and after

<CodeGroup>
  ```luau Raw remotes theme={null}
  -- Server
  local remotes = ReplicatedStorage.Remotes
  local damageEvent = remotes.Damage
  local shopFunction = remotes.Shop

  damageEvent:FireClient(player, humanoid, 25)
  damageEvent.OnServerEvent:Connect(function(player, target)
  	-- ...
  end)

  shopFunction.OnServerInvoke = function(player, itemId)
  	return purchase(player, itemId)
  end
  ```

  ```luau Flux theme={null}
  -- Server
  local Flux = require(ReplicatedStorage.Flux)

  local Damage = Flux.Server("Damage")
  local Shop = Flux.Server("Shop")

  Damage:Fire(player, humanoid, 25)
  Damage:Connect(function(player, target)
  	-- ...
  end)

  Shop.OnInvoke = function(player, itemId)
  	return purchase(player, itemId)
  end
  ```
</CodeGroup>

There is no instance tree to maintain, no separate `UnreliableRemoteEvent` per message, and no
`RemoteFunction`.

### What changes behaviourally

<AccordionGroup>
  <Accordion title="Sends are delayed by up to one frame" icon="clock">
    `FireClient` puts bytes on the wire during the call. `Fire` appends to a buffer that flushes on
    the next `Heartbeat`, up to about 16 ms later.

    This is almost always invisible, but a `Fire` immediately followed by destroying the referenced
    instance may flush after the instance is gone.
  </Accordion>

  <Accordion title="Connections are not RBXScriptConnections" icon="plug">
    `Connect` returns a plain table with only `Disconnect`. There is no `Connected` property and no
    `Destroy`, so helpers like `Trove` or `Janitor` will not accept it directly. Wrap it in a
    function.

    More importantly, nothing is cleaned up when your script is destroyed. Raw remote connections
    die with their script; Flux listeners do not. See [Connections](/guides/connections).
  </Accordion>

  <Accordion title="Listener order is not registration order" icon="shuffle">
    Flux walks its listener list backwards and dispatches each with `task.spawn`, so the most
    recently connected runs first and all run concurrently. Raw `RBXScriptSignal` ordering
    assumptions do not carry over.
  </Accordion>

  <Accordion title="Invoke errors lose their stack" icon="triangle-exclamation">
    `RemoteFunction` errors propagate with more context. Flux transmits the message text and
    re-raises it with `error(tostring(...))`, so there is no stack trace and no error object.
  </Accordion>

  <Accordion title="Invocations have a 60-second timeout" icon="hourglass">
    A `RemoteFunction` invoke can hang indefinitely. Flux raises `"Request timed out"` after 60
    seconds, and the timeout is not configurable.
  </Accordion>

  <Accordion title="One name means one channel" icon="link">
    Two `Flux.Server("Chat")` calls share listeners, handlers and schemas, because the object is a
    typed handle over state keyed by name. Two `Instance.new("RemoteEvent")` calls were genuinely
    separate. Namespace your names.
  </Accordion>
</AccordionGroup>

### Replacing InvokeClient

Flux has no server-to-client invocation, so split the round trip into two events:

<CodeGroup>
  ```luau Server theme={null}
  local Request = Flux.Server("Settings.Request")
  local Reply = Flux.Server("Settings.Reply")

  Reply:Connect(function(player, settings)
  	applySettings(player, settings)
  end)

  Request:Fire(player)
  ```

  ```luau Client theme={null}
  local Request = Flux.Client("Settings.Request")
  local Reply = Flux.Client("Settings.Reply")

  Request:Connect(function()
  	Reply:Fire(getLocalSettings())
  end)
  ```
</CodeGroup>

<Tip>
  `InvokeClient` was always risky, because a client that never responds parks the server thread
  indefinitely, which is an exploit primitive. Two events let you choose your own timeout and
  default.
</Tip>

***

## From a Warp-style API

If you have used Warp, Flux's shape will be familiar: named events, one `Server` and `Client`
pair, `Fire` and `Connect`, and reliable plus unreliable on the same object. Most call sites port
directly.

These are the differences to check for.

<AccordionGroup>
  <Accordion title="No unified constructor" icon="code-branch">
    There is no `ReferenceBridge` or equivalent auto-detecting factory. Flux's internal `Bridge`
    module is the batching layer, not an environment selector, and it is not re-exported.

    Branch on `RunService:IsServer()` in shared modules. See
    [Shared Modules](/guides/shared-modules).
  </Accordion>

  <Accordion title="Invocations are one-directional" icon="arrow-right">
    Client to server only. The server has no `Invoke` and the client has no `OnInvoke`.
  </Accordion>

  <Accordion title="Connections are not auto-cleaned" icon="broom">
    Do not carry over an assumption that destroying a script releases its listeners. Audit every
    `Connect` in code that can re-run: respawn scripts, reopened UI, hot-reloaded modules.
  </Accordion>

  <Accordion title="Circular tables are not supported" icon="rotate">
    Flux's encoder has no visited-set, so a self-referencing table overflows the stack rather than
    being handled.
  </Accordion>

  <Accordion title="Schemas are a first-class option" icon="ruler">
    `Flux.Server(name, schema)` takes an optional positional layout that removes per-value type
    tags and lets you declare exact widths. Worth adopting for high-frequency events. See
    [Schemas](/guides/schemas).
  </Accordion>
</AccordionGroup>

### Generics port directly

The generic form is the same idea: `<Args...>` for arguments, `<Out...>` for invoke returns.

```luau theme={null}
local myEvent = Flux.Server("MyEvent") :: Flux.Server<{ Arg: string }>
myEvent:Fire(player, { Arg = "Hello World" })

local myInvokeEvent = Flux.Client("MyInvokeEvent") :: Flux.Client<(), (boolean, string)>
local success, message = myInvokeEvent:Invoke()
```

***

## Migration checklist

<Steps>
  <Step title="Install and bootstrap">
    Place Flux in `ReplicatedStorage` and ensure one server script calls `Flux.Server(...)` during
    startup. Without it, clients hang on `WaitForChild`.
  </Step>

  <Step title="Port one system at a time">
    Flux coexists with raw remotes, because it only owns its own two instances. Migrate a single
    feature, verify it, then move on.
  </Step>

  <Step title="Namespace your event names">
    Names are global keys and travel in every packet. Use `"Combat.Hit"` over `"Hit"`, and keep hot
    names short.
  </Step>

  <Step title="Audit every Connect for cleanup">
    This is the most common migration bug. Any `Connect` in a script that can re-run needs an
    explicit `Disconnect`.
  </Step>

  <Step title="Re-check your validation">
    Generics and schemas are not runtime validation. Every server listener still needs type checks,
    range clamps and ownership verification.
  </Step>

  <Step title="Add schemas to hot paths last">
    Get correctness first. Then schematise the handful of events that fire every frame, where
    `T.Float32()` over an implicit `f64` and `T.Enum()` over enum strings are the real savings.
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Known limits" icon="triangle-exclamation" href="/reference/limits">
    Read this before shipping.
  </Card>

  <Card title="Schemas" icon="ruler" href="/guides/schemas">
    Optimising the events that matter.
  </Card>
</CardGroup>
