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
| Option | Type | Description | Example |
|---|---|---|---|
label | string | Human-readable display name for the DocType | "User", "Customer Information" |
display_field | string | Field name to display as the primary identifier | "name", "email" |
search_fields | string | Newline-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
| Option | Type | Description | Default |
|---|---|---|---|
is_single | 0 or 1 | Whether this DocType allows only a single record (like settings) | 0 |
is_submittable | 0 or 1 | Whether the DocType supports submission workflows for approval | 0 |
is_system_generated | 0 or 1 | Whether this DocType is system-generated (not user-created) | 0 |
track_changes | 0 or 1 | Whether to track all changes to documents | 0 |
comments_enabled | 0 or 1 | Whether comments are enabled for documents | 0 |
require_user_permission | 0 or 1 | Whether user permissions are required to access this DocType | 0 |
naming_series | string | Template 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=ascResponse:
{
"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-001Create 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-001Programmatic 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 migrateField Configuration
For detailed information about configuring individual fields (field types, properties, validation, etc.), see the Field Types documentation.
Next Steps
- Learn about Field Types in detail
- See how to Create DocTypes using the scaffold command
Zodula