Skip to main content
Enum is an enumeration type that can only select from a predefined list of values. It provides type safety and makes it easy to configure selection options in user interfaces.

What is an Enum?

Enum defines a limited set of values to provide:

Type Safety

Prevents undefined value inputAutomatically converts to Union type in TypeScript

Auto UI Generation

Automatic selection list configurationImmediately usable in Select, ButtonSet components

Label Management

Separation of keys and display namesEasy multi-language support

Maintainability

Centralized managementOnly one place to modify when values change

Defining Enums

Defining in entity.json

Define as key-label pairs in the Entity’s enums section.
user.entity.json
{
  "id": "User",
  "props": [
    {
      "name": "role",
      "type": "enum",
      "id": "UserRole",
      "desc": "User role"
    },
    {
      "name": "status",
      "type": "enum",
      "id": "UserStatus",
      "desc": "Account status"
    }
  ],
  "enums": {
    "UserRole": {
      "admin": "Administrator",
      "normal": "Normal User",
      "guest": "Guest"
    },
    "UserStatus": {
      "active": "Active",
      "inactive": "Inactive",
      "suspended": "Suspended",
      "deleted": "Deleted"
    }
  }
}
Structure:
  • Enum ID: PascalCase (e.g., UserRole, PostStatus)
  • Key: lowercase, numbers, underscores (e.g., admin, draft_saved)
  • Label: Display text (e.g., β€œAdministrator”, β€œDraft Saved”)

Defining in Sonamu UI

1

Navigate to Entity edit page

Select an Entity in Sonamu UI.
2

Scroll to Enums section

The Enums section is below Props, Indexes, Subsets.
3

Click Add Enum

Add a new Enum.Enter Enum ID:
  • Enter in PascalCase
  • Use {Entity} pattern to associate with Entity
    • Example: $ModelRole β†’ UserRole (auto-converted)
4

Add Enum values

Each Enum is displayed as a separate tab, add key-label pairs with the β€œAdd Row” button.
  • Key: lowercase letters, numbers, underscores only
  • Label: Display name
5

Save

When you save the Entity, the Enum is saved together.

Auto-Generated Code

When you define an Enum, Sonamu automatically generates the following:

1. Zod Schema

sonamu.generated.ts
import { z } from "zod";

// Enum schema
export const UserRole = z.enum(["admin", "normal", "guest"]);

// Enum labels
export const UserRoleLabels = {
  admin: "Administrator",
  normal: "Normal User",
  guest: "Guest",
} as const;

2. TypeScript Type

user.types.ts
import { UserRole } from "./sonamu.generated";

// Inferred as Union type
export type UserRole = z.infer<typeof UserRole>;
// Result: "admin" | "normal" | "guest"

3. Label Helper Function

sonamu.generated.ts
// Get label function
export function getUserRoleLabel(role: UserRole): string {
  return UserRoleLabels[role];
}

Using Enums

Using in Props

{
  "props": [
    {
      "name": "role",
      "type": "enum",
      "id": "UserRole",
      "desc": "User role",
      "dbDefault": "normal"
    }
  ]
}
Database:
  • Column type: text
  • Stored value: Key value (e.g., "admin")
TypeScript:
type User = {
  id: number;
  role: "admin" | "normal" | "guest"; // Union type
};

Using in API

import { UserRole } from "./user.types";

export class UserModel extends BaseModel {
  @api({ httpMethod: "POST" })
  async updateRole(userId: number, role: UserRole) {
    // role is type-safe
    await this.puri()
      .where("id", userId)
      .update({ role });
      
    return { success: true };
  }
}

Displaying Labels

import { getUserRoleLabel } from "./sonamu.generated";

const user = await UserModel.findById(1);
const roleLabel = getUserRoleLabel(user.role);
// "Administrator"

Common Enum Patterns

1. OrderBy Enum

Define sorting options.
{
  "enums": {
    "UserOrderBy": {
      "id-desc": "ID Newest First",
      "id-asc": "ID Oldest First",
      "created_at-desc": "Created At Newest First",
      "created_at-asc": "Created At Oldest First",
      "username-asc": "Name Order"
    }
  }
}
Usage:
async findAll(orderBy: UserOrderBy = "id-desc") {
  const [field, direction] = orderBy.split("-");
  return this.puri()
    .orderBy(field, direction as "asc" | "desc")
    .many();
}

2. SearchField Enum

Define searchable fields.
{
  "enums": {
    "UserSearchField": {
      "email": "Email",
      "username": "Name",
      "phone": "Phone"
    }
  }
}
Usage:
async search(field: UserSearchField, keyword: string) {
  return this.puri()
    .whereLike(field, `%${keyword}%`)
    .many();
}

3. Status Enum

Define statuses.
{
  "enums": {
    "PostStatus": {
      "draft": "Draft",
      "published": "Published",
      "archived": "Archived",
      "deleted": "Deleted"
    }
  }
}
Usage:
async publish(postId: number) {
  await this.puri()
    .where("id", postId)
    .update({ status: "published" });
}

4. Type/Category Enum

Define classifications.
{
  "enums": {
    "NotificationType": {
      "email": "Email",
      "sms": "SMS",
      "push": "Push Notification",
      "in_app": "In-App Notification"
    }
  }
}

Enum Array Type

You can store multiple Enum values as an array.
{
  "props": [
    {
      "name": "permissions",
      "type": "enum[]",
      "id": "Permission",
      "desc": "Permission list"
    }
  ],
  "enums": {
    "Permission": {
      "read": "Read",
      "write": "Write",
      "delete": "Delete",
      "admin": "Admin"
    }
  }
}
Database:
  • Column type: text[]
  • Stored value: ["read", "write"]
TypeScript:
type User = {
  id: number;
  permissions: ("read" | "write" | "delete" | "admin")[];
};
Usage:
// Check permission
function hasPermission(user: User, permission: Permission): boolean {
  return user.permissions.includes(permission);
}

// Grant permission
async grantPermission(userId: number, permission: Permission) {
  const user = await this.findById(userId);
  if (!user.permissions.includes(permission)) {
    await this.puri()
      .where("id", userId)
      .update({
        permissions: [...user.permissions, permission]
      });
  }
}

Using Enums in Frontend

Select Component

import { UserRoleSelect } from "@/components/UserRoleSelect";

function UserForm() {
  const [role, setRole] = useState<UserRole>("normal");
  
  return (
    <UserRoleSelect
      value={role}
      onChange={setRole}
    />
  );
}

ButtonSet Component

import { UserStatusButtonSet } from "@/components/UserStatusButtonSet";

function StatusFilter() {
  const [status, setStatus] = useState<UserStatus | null>(null);
  
  return (
    <UserStatusButtonSet
      value={status}
      onChange={setStatus}
      allowNull // Allow deselection
    />
  );
}

Enum Validation

Zod automatically validates Enum values.
import { UserRole } from "./sonamu.generated";

export const UpdateRoleParams = z.object({
  userId: z.number(),
  role: UserRole, // Auto-validated
});

// When invalid value is input
UpdateRoleParams.parse({
  userId: 1,
  role: "invalid_role" // Error!
});

Enum Naming Conventions

PurposePatternExample
Entity status{Entity}StatusPostStatus, OrderStatus
Entity type{Entity}TypeNotificationType, PaymentType
Entity role{Entity}RoleUserRole, MemberRole
Sort options{Entity}OrderByUserOrderBy, PostOrderBy
Search fields{Entity}SearchFieldUserSearchField
Category{Entity}CategoryProductCategory
Permission{Entity}PermissionUserPermission

$Model Pattern

Use $Model to dynamically include the Entity name:
{
  "enums": {
    "$ModelRole": {
      "admin": "Administrator",
      "normal": "Normal"
    },
    "$ModelStatus": {
      "active": "Active",
      "inactive": "Inactive"
    }
  }
}
Conversion result:
  • User Entity β†’ UserRole, UserStatus
  • Post Entity β†’ PostRole, PostStatus

Cautions

Caution When Changing Enum ValuesChanging an Enum key that’s already stored in the database will cause mismatch with existing data.How to change:
  1. Add new key
  2. Data migration (old key β†’ new key)
  3. Remove old key
Key Naming RestrictionsEnum Keys can only use:
  • Lowercase letters (a-z)
  • Numbers (0-9)
  • Underscores (_)
Hyphens (-) cannot be used.
Labels Can Be Changed FreelyLabels are for display purposes, so they can be changed anytime. Only Keys are stored in the database.

Enum vs Separate Table

CriteriaEnumSeparate Table
Number of valuesFew (< 20)Many (> 20)
Change frequencyRarelyFrequently
Additional infoNot neededNeeded (description, order, etc.)
PerformanceFast (no JOIN)Slower (JOIN needed)
Recommended useStatus, role, typeCategory, code tables
Enum use examples:
  • User roles (admin, user, guest)
  • Post status (draft, published, deleted)
  • Notification types (email, sms, push)
Separate table use examples:
  • Region list (Seoul, Busan, …)
  • Product categories (hierarchical structure)
  • Country codes (many additional info)

Next Steps