Enums vs Literals
When to use enums vs literal types — a practical comparison
Cerial offers two ways to define union-like field types: enums and literals. Both create a set of allowed values for a field, but they serve different purposes and have different capabilities. This page helps you choose the right one.
Quick Comparison
| Feature | Enums | Literals |
|---|---|---|
| Variant types | String-only | String, int, float, bool, broad types, objects, tuples, literal refs, enum refs |
| Const object | Yes (StatusEnum) | No |
| Union type | Yes (StatusEnumType) | Yes (Status) |
| Runtime access | StatusEnum.ACTIVE | String literals only |
| Composition | Via extends inheritance | Inline refs (literal/enum names in body) |
| Object/tuple variants | No | Yes |
| Sub-field select | No | No (boolean select only) |
| OrderBy | Yes (alphabetical string ordering) | Excluded (mixed types make ordering ambiguous) |
| Where operators | eq, neq, in, notIn, contains, startsWith, endsWith | eq, neq, in, notIn (+ more based on variant types) |
When to Use Enums
Enums are the right choice when you have a fixed set of string labels and want runtime access through a generated const object. They're simpler, more ergonomic, and give you autocompletion:
enum Status { ACTIVE, INACTIVE, PENDING }
enum Role { ADMIN, EDITOR, VIEWER }
model User {
id Record @id
status Status
role Role
}import { StatusEnum, RoleEnum } from './generated/client';
// Const object provides autocompletion and safe refactoring
if (user.role === RoleEnum.ADMIN) {
// ...
}Choose enums when:
- All values are strings
- You want a const object for runtime access (
StatusEnum.ACTIVE) - You need
orderBysupport (alphabetical sorting) - You want a clean, Prisma-like declaration syntax
When to Use Literals
Literals are the right choice when you need mixed types, structured variants, or inline composition from other literals and enums:
# Mixed types — not possible with enums
literal Priority { 'low', 'medium', 'high', 1, 2, 3 }
# Broad types — accept any value of a type
literal Flexible { String, Int }
# Object variants — structured data in the union
object Point { x Float, y Float }
literal Shape { 'none', Point }
# Inline composition — combine multiple sources
enum Role { ADMIN, EDITOR, VIEWER }
literal Access { Role, 'custom', 'guest' }Choose literals when:
- You need numbers, booleans, or mixed types
- You want object or tuple variants in the union
- You want to compose a union from other literals or enums inline
- You don't need a runtime const object
Composition Styles
Both enums and literals support building on existing definitions, but through different mechanisms:
| Mechanism | Enums | Literals |
|---|---|---|
| Inline reference | Not supported | literal Extended { Base, 'extra' } — values expanded inline |
extends keyword | enum Child extends Parent { NEW_VALUE } | literal Child extends Parent { 'extra' } |
Enums use extends for inheritance — the child inherits all parent values and can add new ones. Literals can use either inline references (placing a literal or enum name directly in the variant list) or extends.
# Enum composition via extends
enum BaseRole { VIEWER, EDITOR }
enum AdminRole extends BaseRole { ADMIN, SUPER_ADMIN }
# Literal composition via inline reference
literal Status { 'active', 'inactive' }
literal ExtendedStatus { Status, 'archived' }Cross-Referencing
Literals can reference enums by name. When a literal includes an enum, the enum's string values are inlined into the literal's union:
enum Status { ACTIVE, INACTIVE, PENDING }
literal ExtendedStatus { Status, 'ARCHIVED', 'DELETED' }
# Resolves to: 'ACTIVE' | 'INACTIVE' | 'PENDING' | 'ARCHIVED' | 'DELETED'This lets you start with an enum for its const object benefits, then extend it into a literal when you need additional values or mixed types in a different context.
Decision Guide
- "I have a fixed set of string labels" → Use an enum
- "I need numbers, booleans, or mixed types" → Use a literal
- "I want runtime access via a const object" → Use an enum
- "I need object or tuple variants in the union" → Use a literal
- "I want to compose unions from other definitions" → Use a literal (inline refs) or an enum (
extends) - "I need
orderByon this field" → Use an enum (literals are excluded fromorderBy)
You can use both in the same project — enums for simple string constants that benefit from a const object, and literals for more complex union types. They work well together, especially since literals can reference enums.