Skip to main content
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

(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.
(r: Buffers.Reader) -> T
Decodes one value from the reader and advances the cursor.
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.

The namespace at a glance

Numbers

UInt8 · UInt16 · UInt32 · Int8 · Int16 · Int32 · Float32 · Float64

Primitives

Bool · String · Any

Roblox datatypes

Vector2 · Vector3 · CFrame · Color3 · Instance · Enum

Composites

Optional · Array · Struct

Complete listing

These sizes are the payload only, because a schema writes no tag byte. Compare with the dynamic type tag table, where every row costs one byte more.

Datatypes with no schema constructor

The dynamic encoder handles more datatypes than Types does. These have no dedicated constructor:
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.

Usage

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

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

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

Next steps

Number types

Widths, ranges and overflow behaviour.

Composites

Optional, Array and Struct in detail.