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

# Types

> The schema constructor namespace: every factory Flux.Types exposes.

```luau theme={null}
local Flux = require(game:GetService("ReplicatedStorage").Flux)
local T = Flux.Types
```

Every function in this namespace returns a `Type`, which is a `{ Write, Read }` pair. A
schema is nothing more than an array of these.

## The Type interface

```luau theme={null}
export type Type<T = any> = {
	Write: (w: Buffers.Writer, v: T) -> (),
	Read: (r: Buffers.Reader) -> T,
}
```

<ResponseField name="Write" type="(w: Buffers.Writer, v: T) -> ()">
  Appends `v` to the writer at its current cursor. Writes no type tag, because the layout is
  implied by position.
</ResponseField>

<ResponseField name="Read" type="(r: Buffers.Reader) -> T">
  Decodes one value from the reader and advances the cursor.
</ResponseField>

<Info>
  Because a `Type` is just a table of two functions, you can write your own. Anything
  matching this shape works as a schema entry. See [Custom types](#custom-types).
</Info>

## The namespace at a glance

<CardGroup cols={2}>
  <Card title="Numbers" icon="hashtag" href="/api/types/numbers">
    `UInt8` · `UInt16` · `UInt32` · `Int8` · `Int16` · `Int32` · `Float32` · `Float64`
  </Card>

  <Card title="Primitives" icon="font" href="/api/types/primitives">
    `Bool` · `String` · `Any`
  </Card>

  <Card title="Roblox datatypes" icon="cube" href="/api/types/roblox">
    `Vector2` · `Vector3` · `CFrame` · `Color3` · `Instance` · `Enum`
  </Card>

  <Card title="Composites" icon="layer-group" href="/api/types/composites">
    `Optional` · `Array` · `Struct`
  </Card>
</CardGroup>

## Complete listing

| Constructor           | Luau type  | Wire size            |
| --------------------- | ---------- | -------------------- |
| `T.UInt8()`           | `number`   | 1                    |
| `T.UInt16()`          | `number`   | 2                    |
| `T.UInt32()`          | `number`   | 4                    |
| `T.Int8()`            | `number`   | 1                    |
| `T.Int16()`           | `number`   | 2                    |
| `T.Int32()`           | `number`   | 4                    |
| `T.Float32()`         | `number`   | 4                    |
| `T.Float64()`         | `number`   | 8                    |
| `T.Bool()`            | `boolean`  | 1                    |
| `T.String(maxLen?)`   | `string`   | 1 to 3 plus len      |
| `T.Vector2()`         | `Vector2`  | 8                    |
| `T.Vector3()`         | `Vector3`  | 12                   |
| `T.CFrame()`          | `CFrame`   | 28                   |
| `T.Color3()`          | `Color3`   | 3                    |
| `T.Instance()`        | `Instance` | 1 to 3               |
| `T.Enum(enum)`        | `EnumItem` | 1                    |
| `T.Optional(t)`       | `T?`       | 1 plus inner         |
| `T.Array(t, maxLen?)` | `{ T }`    | 1 to 3 plus elements |
| `T.Struct(fields)`    | table      | sum of fields        |
| `T.Any()`             | `any`      | 1 plus dynamic       |

<Note>
  These sizes are the payload only, because a schema writes no tag byte. Compare with the
  [dynamic type tag table](/concepts/wire-format/type-tags#tag-table), where every row costs
  one byte more.
</Note>

## Datatypes with no schema constructor

The dynamic encoder handles more datatypes than `Types` does. These have no dedicated
constructor:

| Datatype     | Dynamically encoded                                                                                                                   | Schema constructor                                                                                                                                                         |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `UDim`       | <Tooltip tip="Encoded automatically by the dynamic encoder, with no schema required."><Icon icon="check" color="#16a34a" /></Tooltip> | <Tooltip tip="No dedicated constructor in Flux.Types, so use T.Any() for this entry or write a custom type."><Icon icon="xmark" color="#dc2626" /></Tooltip> use `T.Any()` |
| `UDim2`      | <Tooltip tip="Encoded automatically by the dynamic encoder, with no schema required."><Icon icon="check" color="#16a34a" /></Tooltip> | <Tooltip tip="No dedicated constructor in Flux.Types, so use T.Any() for this entry or write a custom type."><Icon icon="xmark" color="#dc2626" /></Tooltip> use `T.Any()` |
| `Region3`    | <Tooltip tip="Encoded automatically by the dynamic encoder, with no schema required."><Icon icon="check" color="#16a34a" /></Tooltip> | <Tooltip tip="No dedicated constructor in Flux.Types, so use T.Any() for this entry or write a custom type."><Icon icon="xmark" color="#dc2626" /></Tooltip> use `T.Any()` |
| `BrickColor` | <Tooltip tip="Encoded automatically by the dynamic encoder, with no schema required."><Icon icon="check" color="#16a34a" /></Tooltip> | <Tooltip tip="No dedicated constructor in Flux.Types, so use T.Any() for this entry or write a custom type."><Icon icon="xmark" color="#dc2626" /></Tooltip> use `T.Any()` |
| `TweenInfo`  | <Tooltip tip="Encoded automatically by the dynamic encoder, with no schema required."><Icon icon="check" color="#16a34a" /></Tooltip> | <Tooltip tip="No dedicated constructor in Flux.Types, so use T.Any() for this entry or write a custom type."><Icon icon="xmark" color="#dc2626" /></Tooltip> use `T.Any()` |
| `buffer`     | <Tooltip tip="Encoded automatically by the dynamic encoder, with no schema required."><Icon icon="check" color="#16a34a" /></Tooltip> | <Tooltip tip="No dedicated constructor in Flux.Types, so use T.Any() for this entry or write a custom type."><Icon icon="xmark" color="#dc2626" /></Tooltip> use `T.Any()` |

<Tip>
  For the numeric ones you can usually do better than `T.Any()` by decomposing. A `UDim2` is
  four numbers, so `T.Struct({{ "XS", T.Float32() }, { "XO", T.Int16() }, { "YS",
      T.Float32() }, { "YO", T.Int16() }})` beats both `T.Any()` and the built-in 16-byte dynamic
  encoding. Or just write a [custom type](#custom-types).
</Tip>

## Usage

```luau theme={null}
local T = Flux.Types

local Spawn = Flux.Server("Spawn", {
	T.Instance(),
	T.Vector3(),
	T.UInt16(),
	T.Optional(T.Enum(Enum.TeamColor)),
})

Spawn:FireAll(character, position, 100, Enum.TeamColor.Bright_blue)
```

<Note>
  A schema is a positional list. `schema[1]` encodes the first argument, `schema[2]`
  the second, and so on. The number of arguments you pass to `Fire` has to match the
  number of entries in the schema. Passing more arguments than the schema describes
  will error when Flux reaches a `nil` entry, and passing fewer writes a shorter
  packet than the receiver expects to read.
</Note>

<Warning>
  Schemas apply to events only. A schema encodes the payloads of `Fire`,
  `FireUnreliable`, `FireAll` and `FireAllUnreliable`. `Invoke` requests and their
  responses always fall back to the dynamic `WriteAny`/`ReadAny` encoder, whatever
  schema you passed to `Flux.Server` or `Flux.Client`.
</Warning>

## Constructors are called, not referenced

Each entry is a function call. Passing the function itself gives you a value with no `Write`
or `Read` field, and the failure surfaces later as a confusing error inside the encoder.

```luau theme={null}
-- CORRECT
local schema = { T.UInt8(), T.String() }

-- WRONG: these are functions, not Types
local schema = { T.UInt8, T.String }
```

<Info>
  Every call allocates a fresh table, so build a schema once and reuse it rather than
  constructing one inside a hot function. Because both sides have to agree anyway, a shared
  module is the natural home. See [Shared Modules](/guides/shared-modules).
</Info>

## Custom types

Any table with a matching `Write` and `Read` pair is a valid schema entry. This is the escape
hatch for datatypes `Types` does not cover, or for domain-specific packing.

```luau theme={null}
local Buffers = require(ReplicatedStorage.Flux.Utils.Buffers)

-- A normalised direction packed into two bytes instead of twelve.
local function Direction(): Flux.Types.Type<Vector3>
	return {
		Write = function(w, v)
			Buffers.WriteI8(w, math.floor(v.X * 127))
			Buffers.WriteI8(w, math.floor(v.Y * 127))
			Buffers.WriteI8(w, math.floor(v.Z * 127))
		end,
		Read = function(r)
			return Vector3.new(
				Buffers.ReadI8(r) / 127,
				Buffers.ReadI8(r) / 127,
				Buffers.ReadI8(r) / 127
			)
		end,
	}
end

local Aim = Flux.Server("Aim", { T.Instance(), Direction() })
```

<Warning>
  A custom type has to write and read exactly the same byte sequence in the same order, and
  both sides have to use the identical implementation. A mismatch does not raise a clear
  error; it desynchronises the cursor and corrupts every value after it in the packet.
</Warning>

## Next steps

<CardGroup cols={2}>
  <Card title="Number types" icon="hashtag" href="/api/types/numbers">
    Widths, ranges and overflow behaviour.
  </Card>

  <Card title="Composites" icon="layer-group" href="/api/types/composites">
    `Optional`, `Array` and `Struct` in detail.
  </Card>
</CardGroup>
