Skip to content

Types

const name: string = "Alice"
const age: number = 30
const active: boolean = true
type User {
name: string,
email: string,
age: number,
}

Construct records with the type name:

const user = User(name: "Alice", email: "a@b.com", age: 30)

Update with spread:

const updated = User(..user, age: 31)

Include fields from other record types using spread syntax:

type BaseProps {
className: string,
disabled: boolean,
}
type ButtonProps {
...BaseProps,
onClick: fn() -> (),
label: string,
}
// ButtonProps has: className, disabled, onClick, label

Multiple spreads are allowed:

type A { x: number }
type B { y: string }
type C { ...A, ...B, z: boolean }

Rules:

  • Spread must reference a record type (not a union or alias)
  • Field name conflicts between spreads or with direct fields are compile errors
  • The resulting type is a flat record

Discriminated unions with variants:

type Color {
| Red
| Green
| Blue
| Custom { r: number, g: number, b: number }
}

When a variant name could be ambiguous (e.g., multiple unions have a variant called Active), use qualified syntax:

type Filter { | All | Active | Completed }
const f = Filter.All
const g = Filter.Active

This is especially useful when passing variants as arguments:

setFilter(Filter.Completed)

Unambiguous variants can still be used without qualification:

match filter {
All -> showAll(),
Active -> showActive(),
Completed -> showCompleted(),
}

For npm interop with TypeScript libraries that use string literal unions:

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"
type Status = "loading" | "error" | "success"

Match on them with exhaustiveness checking:

fn describe(method: HttpMethod) -> string {
match method {
"GET" -> "fetching",
"POST" -> "creating",
"PUT" -> "updating",
"DELETE" -> "removing",
}
}
// Compiler error if you miss a variant

String literal unions compile to the same TypeScript type:

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

For pure Floe code, prefer regular tagged unions (| Get | Post) since they work with constructors, for-blocks, and provide better type safety. String literal unions exist primarily for seamless npm interop.

For operations that can fail:

type Result<T, E> { | Ok { T } | Err { E } }
const result = Ok(42)
const error = Err("something went wrong")

For values that may be absent:

type Option<T> { | Some { T } | None }
const found = Some("hello")
const missing = None

Propagate errors concisely:

fn getUsername(id: string) -> Result<string, Error> {
const user = fetchUser(id)? // returns Err early if it fails
Ok(user.name)
}

Compile-time distinct types that erase at runtime:

type UserId = Brand<string, "UserId">
type PostId = Brand<string, "PostId">
// userId and postId are both strings at runtime,
// but can't be mixed up at compile time

Types where only the defining module can see the internal structure:

opaque type Email = string
// Only this module can construct/destructure Email values

Anonymous lightweight product types:

const point: (number, number) = (10, 20)
const entry: (string, number) = ("key", 42)

Destructure with pattern matching:

const (x, y) = point
fn divmod(a: number, b: number) -> (number, number) {
(a / b, a % b)
}
match divmod(10, 3) {
(_, 0) -> "divides evenly",
(q, r) -> `${q} remainder ${r}`,
}

Tuples compile to TypeScript readonly tuples: (number, string) becomes readonly [number, string], and (1, "a") becomes [1, "a"] as const.

type Name = string
type Callback = fn(Event) -> ()
BannedWhyUse Instead
anyDisables type checkingunknown + narrowing
nullNullable reference bugsOption<T>
undefinedRedundant with nullOption<T>
enumCompiles to runtime objectsUnion types
interfaceRedundant with typetype