Cerial

Literals

Union types with specific values, broad types, and structured variants

Literals define union types for fields — a single field that can hold one of several specific values or structured types. They're more flexible than enums: where enums are limited to named string constants, literals can mix strings, numbers, booleans, broad types, objects, tuples, and references to other literals or enums.

Defining a Literal

Declare a literal with the literal keyword followed by a name and a comma-separated list of variants:

literal Status { 'active', 'inactive', 'pending' }

literal Priority { 1, 2, 3 }

literal Mixed { 'low', 'high', 1, 2, true }

Variants can also be written on separate lines, one per line without commas. Each variant represents one allowed value or type that the field can hold.

Variant Kinds

Literals support nine kinds of variants:

KindExampleDescription
String'active'An exact string value
Integer1, 42An exact integer value
Float3.14An exact float value
Booleantrue, falseAn exact boolean value
Broad typeString, Int, Float, Bool, DateAny value of that type
Object refMyObjectA defined object type
Tuple refMyTupleA defined tuple type
Literal refOtherLiteralVariants from another literal (expanded inline)
Enum refMyEnumVariants from an enum (expanded as string values)

String values are quoted with single quotes. Numbers and booleans are bare values. Type names and references use PascalCase identifiers.

Broad Types

Broad type variants accept any value of that type, not just specific values:

literal Flexible { String, Int }

A field of type Flexible accepts any string or any integer:

await client.db.Model.create({
  data: { value: 'anything' },  // OK — any string
});

await client.db.Model.create({
  data: { value: 12345 },  // OK — any integer
});

The five broad types are String, Int, Float, Bool, and Date.

Composition

Literals can include variants from other literals or enums. The referenced type's values are expanded and deduplicated into the parent literal:

literal Status { 'active', 'inactive', 'pending' }
literal ExtendedStatus { Status, 'archived', 'deleted' }
# ExtendedStatus = 'active' | 'inactive' | 'pending' | 'archived' | 'deleted'
enum Role { ADMIN, EDITOR, VIEWER }
literal RoleOrCustom { Role, 'custom' }
# RoleOrCustom = 'ADMIN' | 'EDITOR' | 'VIEWER' | 'custom'

When a literal references an enum, the enum's string values are inlined into the literal's union. This lets you build on existing enums when you need a broader set of values.

Literals also support extends for inheritance (distinct from inline composition). The child literal inherits all variants from the parent and can add new ones. See the Extends page for details.

Object Variants

Literals can include object types as variants, creating a union of primitive values and structured data:

object Point {
  x Float
  y Float
}

literal Shape { 'none', Point }
// Create with string variant
await client.db.Model.create({
  data: { shape: 'none' },
});

// Create with object variant — all required fields must be present
await client.db.Model.create({
  data: { shape: { x: 1.5, y: 2.5 } },
});

Object fields inside literals support optional (?) and nullable (@nullable) modifiers:

object PointOpt {
  label String
  x Float?
  y Float? @nullable
}

literal ShapeOpt { 'none', PointOpt }

Decorator restrictions

Only ? and @nullable are enforced when an object is stored through a literal. Other decorators (@default, @createdAt, @updatedAt, @readonly) cannot be expressed in the inline type definition and will not apply through the literal path. A warning is shown during generation if decorated objects are used in literals.

The same object can be used both in a literal and directly on a model field — decorators apply normally when used directly.

Tuple Variants

Literals can include tuple types as variants:

tuple Coord {
  x Float,
  y Float
}

literal Position { 'origin', Coord }
// String variant
await client.db.Model.create({
  data: { pos: 'origin' },
});

// Tuple variant — array form
await client.db.Model.create({
  data: { pos: [1.5, 2.5] },
});

// Tuple variant — object form (named elements)
await client.db.Model.create({
  data: { pos: { x: 1.5, y: 2.5 } },
});

Nesting Restrictions

Objects and tuples used in literals must be flat — they can only contain:

  • Primitive fields (String, Int, Float, Bool, Date)
  • Literal-typed fields (if the inner literal contains only primitive variants)

Flat objects and tuples with only primitive fields are valid:

object Coordinate {
  x Float
  y Float
}

literal SimpleValue { 'text', 42 }

# Allowed — object with only primitives
literal Shape { 'circle', 'square', Coordinate }

object TaggedPoint {
  label String
  kind SimpleValue    # OK: SimpleValue only has primitive variants
}

literal Data { TaggedPoint, 'raw' }

Objects with nested objects, tuples, or complex literal fields are rejected at parse time:

object Inner {
  value String
}

object Outer {
  nested Inner        # has an object sub-field
}

# REJECTED — Outer contains a nested object field
literal Bad { Outer, 'fallback' }

literal Complex { 'a', Int, AnotherLiteral }

object DeepRef {
  tag Complex          # Complex contains literalRef to AnotherLiteral
}

# REJECTED — DeepRef's literal field references a non-simple literal
literal AlsoBad { DeepRef, 'none' }

Objects with nested object fields, tuple fields, or complex literal fields inside a literal are rejected at parse time. This restriction also applies to tuple elements — only primitives and simple literal-typed elements are allowed.

Literals referenced inside another literal's object/tuple variants must themselves contain only simple variants (no objectRef, tupleRef, or literalRef). Deep nesting is not supported.

Using Literals on Fields

Literal types are used on fields just like any other type. They support optionality, nullability, defaults, and arrays:

literal Status { 'active', 'inactive', 'pending' }
literal Priority { 1, 2, 3 }

model Task {
  id Record @id
  status Status                     # required
  priority Priority @default(1)     # required with default
  prevStatus Status?                # optional
  nullStatus Status? @nullable      # optional + nullable
  tags Status[]                     # array of literal values
}

Generated TypeScript

For a literal with only primitive variants, Cerial generates a single union type:

literal Status { 'active', 'inactive', 'pending' }
export type Status = 'active' | 'inactive' | 'pending';

For a literal with object or tuple variants, both an output type and an input type are generated:

object Point {
  x Float
  y Float
}

literal Shape { 'none', Point }
// Output type — returned from queries
export type Shape = 'none' | Point;

// Input type — used in create/update
export type ShapeInput = 'none' | PointInput;

Key points about generated types:

  • Output uses base types (Point), input uses input variants (PointInput)
  • For tuples: output is always array form, input accepts both array and object form
  • A separate Input type is only generated when the literal contains object or tuple variants

See Filtering, Select, and Update for query operations.

On this page