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

Field Hooks

Field-level hooks operate on individual field values rather than the full document context.

Signature

function hook(value, context)
    -- transform value
    return new_value
end
ParameterTypeDescription
valueanyCurrent field value
contexttableSee context fields below

Return value: The new field value. This replaces the existing value in the data.

Context Table

FieldTypeDescription
field_namestringName of the field being processed
collectionstringCollection slug
operationstring"create", "update", "find", "find_by_id"
datatableFull document data (read-only snapshot)
usertable/nilAuthenticated user document (nil if unauthenticated)
ui_localestring/nilAdmin UI locale code (e.g., "en", "de")

Typed Contexts

The type generator (crap-cms typegen) emits per-collection field hook contexts with typed data fields:

  • Collections: crap.field_hook.{PascalCase} — e.g., crap.field_hook.Posts has data: crap.data.Posts
  • Globals: crap.field_hook.global_{slug} — e.g., crap.field_hook.global_site_settings has data: crap.global_data.SiteSettings

Use the typed context when a hook is specific to one collection:

---@param value number|nil
---@param context crap.field_hook.Inquiries
---@return number|nil
return function(value, context)
    -- context.data is typed as crap.data.Inquiries
    -- IDE autocompletes context.data.name, context.data.email, etc.
    return value
end

For shared hooks that work across multiple collections, use the generic crap.FieldHookContext (where data is table<string, any>).

Events

EventCRUD AccessUse Case
before_validateYesNormalize values before validation (trim, lowercase, etc.)
before_changeYesTransform values after validation (compute derived fields)
after_changeYesSide effects after write with CRUD access (logging, cascades)
after_readNoTransform values before response (formatting, computed fields)

Definition

crap.fields.text({
    name = "title",
    hooks = {
        before_validate = { "hooks.fields.trim" },
        before_change = { "hooks.fields.sanitize_html" },
        after_read = { "hooks.fields.add_word_count" },
    },
})

Example

-- hooks/fields.lua
local M = {}

function M.trim(value, ctx)
    if type(value) == "string" then
        return value:match("^%s*(.-)%s*$")
    end
    return value
end

function M.slugify(value, ctx)
    -- Auto-generate slug from title if empty
    if (value == nil or value == "") and ctx.data.title then
        return crap.util.slugify(ctx.data.title)
    end
    return value
end

return M