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

# Primitive Types

> Booleans, strings, and the dynamic escape hatch.

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

## Bool

<ResponseField name="T.Bool()" type="Type<boolean>">
  One byte: `1` for `true`, `0` for `false`.
</ResponseField>

```luau theme={null}
local Toggle = Flux.Server("Toggle", {
	T.String(32),
	T.Bool(),
})

Toggle:FireAll("night_mode", true)
```

<Note>
  A full byte per boolean, because there is no bit-packing. Eight flags cost eight bytes. If
  you send many booleans together, pack them into a `T.UInt8()` bitfield yourself:

  ```luau theme={null}
  local flags = 0
  if isSprinting then flags = bit32.bor(flags, 1) end
  if isCrouching then flags = bit32.bor(flags, 2) end
  if isAiming    then flags = bit32.bor(flags, 4) end

  State:FireAllUnreliable(flags)
  ```
</Note>

<Info>
  `Read` returns `value == 1`, so any byte other than `1` decodes as `false`.
</Info>

***

## String

<ResponseField name="T.String(maxLen: number?)" type="Type<string>">
  A varint length prefix followed by the raw bytes.
</ResponseField>

<ParamField path="maxLen" type="number">
  Optional maximum byte length, asserted on write.
</ParamField>

```luau theme={null}
local Chat = Flux.Server("Chat", { T.String() })
local Name = Flux.Server("Name", { T.String(20) })
```

### Size

| Length       | Total bytes |
| ------------ | ----------- |
| `""`         | 1           |
| `"gg"`       | 3           |
| 127 chars    | 128         |
| 128 chars    | 130         |
| 20,000 chars | 20,003      |

The varint prefix is one byte up to 127, two up to 16,383, and three up to 2,097,151.

<Info>
  Strings are written with `buffer.writestring`, so they are byte sequences rather than
  character sequences. Binary data is safe, and `maxLen` counts bytes, not characters. A UTF-8
  emoji costs four against the limit.
</Info>

### The maxLen bound

<Warning>
  `maxLen` is enforced on write only, and it raises a Luau error rather than dropping the
  message:

  ```luau theme={null}
  assert(#v <= maxLen, "string exceeds max length")
  ```

  Two consequences follow.

  First, it throws in your code at the `Fire` call site, not inside Flux. A server `Fire` with
  an over-long string errors inside your listener.

  Second, it is not inbound validation. `Read` ignores `maxLen` entirely, so a tampered client
  can send a string of any length and your server listener receives it in full.

  Always re-check length inside server listeners handling client data:

  ```luau theme={null}
  Chat:Connect(function(player, message)
      if typeof(message) ~= "string" or #message > 200 then
          return
      end
      -- safe to use
  end)
  ```
</Warning>

***

## Any

<ResponseField name="T.Any()" type="Type<any>">
  Falls back to the dynamic `WriteAny` and `ReadAny` encoder for this one entry.
</ResponseField>

Use it when a single argument's shape genuinely varies but the rest of the payload is fixed.

```luau theme={null}
local Analytics = Flux.Server("Analytics", {
	T.String(64),  -- event name, always a string
	T.Any(),       -- arbitrary payload
})

Analytics:Fire(player, "level_complete", {
	Level = 7,
	Time = 84.2,
	Deaths = 3,
})
```

<Note>
  `T.Any()` costs a type tag byte plus the value, which is exactly what an unschematised
  argument costs. It is the escape hatch rather than an optimisation: an event that is all
  `T.Any()` entries is byte-for-byte identical to one with no schema, just more verbose.
</Note>

It is also the only schema option for datatypes `Types` has no constructor for:

```luau theme={null}
local Layout = Flux.Server("Layout", {
	T.Any(),  -- UDim2, since no T.UDim2() exists
	T.Any(),  -- TweenInfo, since no T.TweenInfo() exists
})
```

<Tip>
  For those, a [custom type](/api/types#custom-types) usually beats `T.Any()`. A `UDim2`
  through `T.Any()` costs 17 bytes; decomposed into two `Float32` scales and two `Int16`
  offsets it costs 12, with no tag.
</Tip>

<Warning>
  `T.Any()` inherits every dynamic-encoder limitation. Circular tables overflow the stack,
  unsupported datatypes become `nil` with only a sender-side warning, and non-integer numbers
  cost a full 8 bytes. See [Serialization](/concepts/serialization).
</Warning>

## Next steps

<CardGroup cols={2}>
  <Card title="Roblox datatypes" icon="cube" href="/api/types/roblox">
    Vectors, CFrames, instances and enums.
  </Card>

  <Card title="Composites" icon="layer-group" href="/api/types/composites">
    Nesting types into arrays and structs.
  </Card>
</CardGroup>
