Field Reference
Complete guide to adding and configuring field types in Content Engine, including Postgres mappings, Zod validation, and AdminCP controls.
Content Engine provides field descriptors exported from @vitnode/core/content via the field helper. Each field generates a typed Drizzle column, Zod validation schemas, and an interactive AdminCP input automatically.
Supported Field Types
| Field Method | Postgres Column | Zod Schema | AdminCP Form Control |
|---|---|---|---|
field.text() | varchar(maxLength) | z.string() | Text Input |
field.textarea() | text() | z.string() | Textarea |
field.number({ integer: true }) | integer() | z.number().int() | Integer Input |
field.number({ integer: false }) | double precision | z.number() | Number Input |
field.boolean() | boolean() | z.boolean() | Switch Toggle |
field.enum({ values: [...] }) | varchar(length) | z.enum([...]) | Select Dropdown / Radio |
field.dateTime() | timestamp() | z.date() / ISO string | Date / Time Picker |
field.slug() | varchar(maxLength) | z.string() | Slug Generator |
field.user() | integer() (FK core_users) | z.number() | User Combobox |
field.user({ multiple: true }) | Junction table | z.array(z.number()) | Multi-user Combobox |
field.file() | integer() (FK core_files) | z.number() | Drag-and-drop File Upload |
field.file({ multiple: true }) | Junction table | z.array(z.number()) | Multi-file Gallery Uploader |
field.relation() | integer() (FK target table) | z.number() | Relation Combobox |
field.relation({ multiple: true }) | Junction table | z.array(z.number()) | Multi-relation Combobox |
field.group({ fields: ... }) | Prefixed columns | z.object(...) | Grouped Card |
field.repeatable({ fields: ... }) | Child table | z.array(...) | Repeatable List |
field.blocks() | jsonb | z.array(...) | None yet - see Widgets |
Field Configurations
Text and Strings
Configure string length, required state, and multi-language support:
import { defineContentType, field } from "@vitnode/core/content"
export const postContentType = defineContentType({
id: "blog.post",
tableName: "blog_posts",
fields: {
title: field.text({
required: true,
minLength: 3,
maxLength: 200,
localized: true,
}),
summary: field.textarea({
maxLength: 500,
localized: true,
}),
},
})localized: true marks a field for multi-language translation. When localization: { enabled: true } is enabled on the content type, localized field values are stored in the generated {tableName}_translations table rather than on the base table.
Numbers, Booleans, and Enums
Number fields require the integer boolean option. When integer: true, Content Engine generates an integer column in PostgreSQL; when integer: false, it generates a double precision column for floating-point values.
fields: {
views: field.number({
integer: true,
defaultValue: 0,
min: 0,
}),
rating: field.number({
integer: false,
min: 0,
max: 5,
}),
featured: field.boolean({
defaultValue: false,
}),
status: field.enum({
values: ["draft", "published", "archived"] as const,
defaultValue: "draft",
}),
}Slugs, Dates, and User Relations
Auto-generate slugs from another field, default timestamps to current time, or link users:
fields: {
slug: field.slug({
source: "title",
}),
publishedAt: field.dateTime({
defaultNow: true,
}),
author: field.user({
required: true,
}),
contributors: field.user({
multiple: true,
ordered: true,
}),
}source: "title"automatically derives the slug from thetitlefield upon creation. Whensourceis omitted, the slug must be explicitly supplied in the create payload.defaultNow: trueconfigures PostgreSQL and Drizzle to automatically default the column value tonow()upon insertion.
Single and Multi-File Uploads
VitNode connects file fields directly to core_files with storage adapters (Local disk, S3, Cloudflare R2):
fields: {
// Single cover image (max 5 MB)
coverImage: field.file({
maxBytes: 5 * 1024 * 1024,
allowedExtensions: [".jpg", ".jpeg", ".png", ".webp"],
allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
}),
// Gallery of screenshots
gallery: field.file({
multiple: true,
min: 1,
max: 10,
maxBytes: 10 * 1024 * 1024,
allowedExtensions: [".jpg", ".jpeg", ".png", ".webp"],
allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
}),
}Automatic Multipart Upload Pipeline
In the AdminCP, AutoFormFile uploads attachments via TanStack Query and standard multipart API routes (POST /api/.../uploads/{field}). Uploaded records are stored in core_files and assigned by ID.
Common Field Options
Field descriptors accept these standard attributes:
Prop
Type
Search, Ordering, and Filter Configuration
searchable and sortable are not field descriptor options. Searching and ordering are configured at the feature level to separate AdminCP management from public API delivery and global search indexing:
1. AdminCP Table (admin.list)
Configure how administrators search and sort the AdminCP data table:
admin.list.searchableFields: Array of sharedtext,textarea, orslugfields searchable via the table search input.admin.list.orderableFields: Array of shared scalar columns administrators can click to sort. System columns (id,createdAt,updatedAt) and publication columns (status,publishedAt) are always allowed.admin.list.defaultOrderBy: The column name string to sort by initially (e.g.'updatedAt').admin.list.defaultOrder: Sort direction ('asc' | 'desc').
Localized Fields Cannot Be SQL-Sorted on the Base Table
Because localized fields live on the secondary {tableName}_translations table, they cannot be ordered with SQL ORDER BY on base table queries. Therefore, admin.list.orderableFields accepts only shared columns. However, localized fields can be displayed as cells in admin.list.columns and used for admin.titleField (resolved in the editor's language).
2. Public API (publicApi)
Control which fields public consumers can query, search, and sort:
publicApi.searchableFields: Columns scanned by the public?search=parameter.publicApi.orderableFields: Columns accepted by the public?orderBy=parameter (in addition topublishedAt).publicApi.filterableFields: Columns accepted as exact equality filters in query parameters (e.g.?category=1).
3. Global Search Index (search)
Synchronize records with the VitNode search and discovery engine:
search.titleField: Non-nullable text field used as the search result heading.search.descriptionField: Optional text or textarea field prepended to the indexed body.search.contentFields: Array of fields and group/repeatable leaves concatenated into the full-text search document.
Learn More
Defining a Content Type
Declare a Content Engine content type once to get a Postgres table, typed CRUD routes, and interactive AdminCP screens.
Database & Migrations
How the Content Engine maps content models to PostgreSQL tables using createContentModel, handles system columns, and executes Drizzle Kit migrations.