Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Collection Definition Schema

Full reference for every property accepted by crap.collections.define(slug, config).

Top-Level Properties

PropertyTypeDefaultDescription
labelstable{}Display names for the admin UI
labels.singularstringslugSingular name (e.g., “Post”)
labels.pluralstringslugPlural name (e.g., “Posts”)
timestampsbooleantrueAuto-manage created_at and updated_at
fieldsFieldDefinition[]{}Field definitions (see Fields)
admintable{}Admin UI options
hookstable{}Lifecycle hook references
authboolean or tablenilAuthentication config (see Auth Collections)
uploadboolean or tablenilUpload config (see Uploads)
accesstable{}Access control function refs
versionsboolean or tablenilVersioning and drafts config (see Versions & Drafts)
soft_deletebooleanfalseEnable soft deletes (see Soft Deletes)
soft_delete_retentionstringnilAuto-purge retention period (e.g., "30d"). Requires soft_delete = true.
liveboolean or stringnilLive update broadcasting (see Live Updates)
mcptable{}MCP tool config. { description = "..." } for MCP tool descriptions.
indexesIndexDefinition[]{}Compound indexes (see Indexes below)

admin

PropertyTypeDefaultDescription
use_as_titlestringnilField name to display as the row label in admin lists
default_sortstringnilDefault sort field. Prefix with - for descending (e.g., "-created_at")
hiddenbooleanfalseHide this collection from the admin sidebar
list_searchable_fieldsstring[]{}Fields to search when using the admin list search bar

hooks

All hook values are arrays of string references in module.function format.

PropertyTypeDescription
before_validatestring[]Runs before field validation. Has CRUD access.
before_changestring[]Runs after validation, before write. Has CRUD access.
after_changestring[]Runs after create/update (inside transaction). Has CRUD access. Errors roll back.
before_readstring[]Runs before returning read results. No CRUD access.
after_readstring[]Runs after read, before response. No CRUD access.
before_deletestring[]Runs before delete. Has CRUD access.
after_deletestring[]Runs after delete (inside transaction). Has CRUD access. Errors roll back.
before_broadcaststring[]Runs after commit, before broadcast. No CRUD access. See Live Updates.

See Hooks for full details.

auth

Set to true for defaults, or provide a config table:

-- Simple
auth = true

-- With options
auth = {
    token_expiry = 3600,
    disable_local = false,
    strategies = {
        { name = "api-key", authenticate = "hooks.auth.api_key_check" },
    },
}

See Auth Collections for the full schema.

upload

Set to true for defaults, or provide a config table:

-- Simple
upload = true

-- With options
upload = {
    mime_types = { "image/*" },
    max_file_size = 10485760,
    image_sizes = {
        { name = "thumbnail", width = 300, height = 300, fit = "cover" },
    },
    format_options = {
        webp = { quality = 80 },
    },
}

See Uploads for the full schema.

access

PropertyTypeDescription
readstringLua function ref for read access.
createstringLua function ref for create access.
updatestringLua function ref for update access.
deletestringLua function ref for delete access.

If a property is omitted, that operation is allowed for everyone.

See Access Control for full details.

versions

Set to true for defaults (drafts enabled, unlimited versions), or provide a config table:

-- Simple: versions with drafts
versions = true

-- With options
versions = {
    drafts = true,
    max_versions = 20,
}

-- Versions without drafts (pure audit trail)
versions = {
    drafts = false,
    max_versions = 50,
}
PropertyTypeDefaultDescription
draftsbooleantrueEnable draft/publish workflow with _status field
max_versionsinteger0Max versions per document. 0 = unlimited.

See Versions & Drafts for the full workflow.

Indexes

Field-Level Indexes

Set index = true on a field to create a B-tree index on its column. This speeds up queries that filter or sort on that field. Unique fields are already indexed by SQLite, so index = true is skipped when unique = true.

crap.fields.text({ name = "status", index = true }),
crap.fields.date({ name = "published_at", index = true }),

For localized fields, one index is created per locale column (e.g., idx_posts_title__en, idx_posts_title__de).

Compound Indexes

Use the top-level indexes array for multi-column indexes:

indexes = {
    { fields = { "status", "created_at" } },
    { fields = { "category", "slug" }, unique = true },
}
PropertyTypeDefaultDescription
fieldsstring[]requiredColumn names to include in the index.
uniquebooleanfalseCreate a UNIQUE index.

Indexes are synced idempotently on startup: missing indexes are created with CREATE INDEX IF NOT EXISTS, and stale indexes (from removed fields or changed definitions) are dropped. Only indexes with the idx_{collection}_ naming prefix are managed — external indexes are left untouched.

Complete Example

crap.collections.define("posts", {
    labels = {
        singular = "Post",
        plural = "Posts",
    },
    timestamps = true,
    admin = {
        use_as_title = "title",
        default_sort = "-created_at",
        hidden = false,
        list_searchable_fields = { "title", "slug", "content" },
    },
    fields = {
        crap.fields.text({
            name = "title",
            required = true,
            hooks = {
                before_validate = { "hooks.posts.trim_title" },
            },
        }),
        crap.fields.text({
            name = "slug",
            required = true,
            unique = true,
        }),
        crap.fields.select({
            name = "status",
            required = true,
            default_value = "draft",
            options = {
                { label = "Draft", value = "draft" },
                { label = "Published", value = "published" },
                { label = "Archived", value = "archived" },
            },
        }),
        crap.fields.richtext({ name = "content" }),
        crap.fields.relationship({
            name = "tags",
            relationship = { collection = "tags", has_many = true },
        }),
    },
    hooks = {
        before_change = { "hooks.posts.auto_slug" },
    },
    access = {
        read   = "hooks.access.public_read",
        create = "hooks.access.authenticated",
        update = "hooks.access.authenticated",
        delete = "hooks.access.admin_only",
    },
})