ZodulaZodula
Core

DocTypes

DocTypes

DocTypes

DocTypes are the core building blocks of Zodula applications. They define your data models with full TypeScript support and automatic API generation. We inspired by Frappe and Django for the DocType syntax. But the field type is not exactly the same as Frappe and Django please read the Field Types documentation for the complete list of field types.

What are DocTypes?

DocTypes are schema definitions that describe your data models. They provide:

  • Type-safe data models with automatic TypeScript generation
  • Automatic REST API endpoints for CRUD operations
  • Database migrations with schema diffing
  • Admin interface generation
  • Validation using Zod schemas

Creating a DocType

DocTypes are defined in TypeScript files in your app's doctype/ directory. Each DocType is exported as a default export using the $doctype function.

Basic DocType Definition

export default $doctype<"zodula__User">({
    name: {
        type: "Text",
        in_list_view: 1
    },
    email: {
        type: "Email",
        required: 1,
        unique: 1,
        in_list_view: 1
    },
    password: {
        type: "Password",
        required: 1,
        no_copy: 1
    },
    is_active: {
        type: "Check",
        default: "1",
        in_list_view: 1
    }
}, {
    label: "User",
    search_fields: "email\nname\nid"
})

Simple DocType

export default $doctype({
    name: {
        type: "Text",
        label: "Name",
        required: 1,
        unique: 1
    },
    description: {
        type: "Text",
        label: "Description"
    },
    version: {
        type: "Text",
        label: "Version",
        required: 1
    }
}, {
    label: "App",
    is_system_generated: 1,
    naming_series: "{{name}}"
});

Field Types

Zodula supports various field types for different data needs. See the Field Types documentation for a complete reference of all available field types and their configuration options.

DocType Configuration

DocTypes support various configuration options in the second parameter of the $doctype function:

export default $doctype({
    // Field definitions...
}, {
    // Configuration options...
})

Basic Configuration Options

OptionTypeDescriptionExample
labelstringHuman-readable display name for the DocType"User", "Customer Information"
display_fieldstringField name to display as the primary identifier"name", "email"
search_fieldsstringNewline-separated list of field names to include in search indexing"name\nemail\nid"

Example: Basic Configuration

export default $doctype({
    name: {
        type: "Text",
        label: "Name",
        required: 1
    },
    email: {
        type: "Email",
        label: "Email",
        required: 1
    }
}, {
    label: "User",
    display_field: "name",
    search_fields: "name\nemail\nid"
})

Advanced Configuration Options

OptionTypeDescriptionDefault
is_single0 or 1Whether this DocType allows only a single record (like settings)0
is_submittable0 or 1Whether the DocType supports submission workflows for approval0
is_system_generated0 or 1Whether this DocType is system-generated (not user-created)0
track_changes0 or 1Whether to track all changes to documents0
comments_enabled0 or 1Whether comments are enabled for documents0
require_user_permission0 or 1Whether user permissions are required to access this DocType0
naming_seriesstringTemplate for automatic document naming (e.g., "{{name}}" or "CUST-{{####}}")-

Example: Advanced Configuration

export default $doctype({
    app: {
        type: "Text",
        label: "App",
        required: 1
    },
    name: {
        type: "Text",
        label: "Name",
        required: 1
    },
    label: {
        type: "Text",
        label: "Label"
    }
}, {
    label: "Doctype",
    is_system_generated: 1,
    display_field: "label",
    search_fields: "app\nname",
    track_changes: 1,
    comments_enabled: 1
})

Configuration Options Reference

label

Human-readable display name for the DocType, shown in the UI and documentation.

{
    label: "Customer Information"
}

display_field

The field name that will be used as the primary identifier when displaying the document. This field is typically used in reference links and dropdowns.

{
    display_field: "name"  // or "email", "title", etc.
}

search_fields

A newline-separated list of field names that should be indexed for full-text search. Include fields that users will commonly search for.

{
    search_fields: "name\nemail\nphone\nid"
}

is_single

When set to 1, the DocType will only allow a single record to exist (like system settings). All queries will automatically return or update the single record.

{
    is_single: 1  // Only one record allowed
}

is_submittable

When set to 1, the DocType supports submission workflows. Documents can be submitted for approval and go through workflow states (Draft → Submitted → Approved).

{
    is_submittable: 1  // Enables submission workflow
}

is_system_generated

Marks the DocType as system-generated, preventing users from modifying or deleting it through the UI.

{
    is_system_generated: 1  // System-protected DocType
}

track_changes

When enabled, all changes to documents are tracked in a history table, allowing you to see who changed what and when.

{
    track_changes: 1  // Enable change tracking
}

comments_enabled

Enables the comments feature for documents, allowing users to add comments and discussions on records.

{
    comments_enabled: 1  // Enable comments
}

require_user_permission

When enabled, users must have explicit permissions to access this DocType. Useful for sensitive data.

{
    require_user_permission: 1  // Require permissions
}

naming_series

Defines a template for automatic document naming. Use {{name}} to include field values, or patterns like {{####}} for auto-incrementing numbers.

{
    naming_series: "CUST-{{####}}"  // Generates: CUST-0001, CUST-0002, etc.
}
// or
{
    naming_series: "{{name}}"  // Uses the 'name' field value
}

DocType Hooks

DocTypes support lifecycle hooks for custom logic. You can use the .on() method to register event handlers that execute at specific points in a document's lifecycle.

export default $doctype<"zodula__User">({
    name: {
        type: "Text",
        in_list_view: 1
    },
    email: {
        type: "Email",
        required: 1,
        unique: 1,
        in_list_view: 1
    },
    password: {
        type: "Password",
        required: 1,
        no_copy: 1
    },
    is_active: {
        type: "Check",
        default: "1",
        in_list_view: 1
    }
}, {
    label: "User",
    search_fields: "email\nname\nid"
})
.on("before_change", async ({ doc, old }) => {
    doc.password = await Bun.password.hash(doc.password as string);
})
.on("after_cancel", () => { 
    // Custom logic after cancel
})

For detailed information about all available events, event context, and advanced patterns, see the DocType Events documentation.

Automatic API Generation

Once you define a DocType, Zodula automatically generates REST endpoints:

List Records

GET /api/resources/zodula__User?limit=10&page=1&sort=name&order=asc

Response:

{
  "count": 100,
  "page": 1,
  "limit": 10,
  "docs": [
    {
      "name": "USER-001",
      "email": "john@example.com",
      "name": "John Doe",
      "is_active": 1
    }
  ]
}

Get Single Record

GET /api/resources/zodula__User/USER-001

Create Record

POST /api/resources/zodula__User
Content-Type: application/json

{
  "email": "jane@example.com",
  "name": "Jane Smith",
  "password": "secret123"
}

Update Record

PUT /api/resources/zodula__User/USER-001
Content-Type: application/json

{
  "name": "Jane Updated"
}

Delete Record

DELETE /api/resources/zodula__User/USER-001

Programmatic SDK

Use the server-side SDK for type-safe operations:

// Select records
const result = await $zodula
  .doctype("zodula__User")
  .select()
  .where("email", "=", "me@example.com")
  .limit(10)
  .page(1);

// Insert record
const user = await $zodula
  .doctype("zodula__User")
  .insert({ 
    email: "me@example.com", 
    password: "secret",
    name: "John Doe"
  });

// Update record
await $zodula
  .doctype("zodula__User")
  .update("USER-001", { name: "John Updated" });

// Delete record
await $zodula
  .doctype("zodula__User")
  .delete("USER-001");

// Get single record
const user = await $zodula
  .doctype("zodula__User")
  .get("USER-001");

Migrations

When you modify DocTypes, generate and apply migrations:

# Generate migrations (diff DocTypes vs DB)
nailgun generate

# Apply migrations
nailgun migrate

Field Configuration

For detailed information about configuring individual fields (field types, properties, validation, etc.), see the Field Types documentation.

Next Steps