Skip to Content
AdvancedCustom field types

Custom field types

Two ways to add a custom input to the Edit panel:

  1. Inline type: "custom" — a one-off input on a single block.
  2. Plugin field type — reusable across blocks (and possibly across projects).

Inline custom fields

For one-shot inputs, use a custom field:

{ brandColor: { type: "custom", label: "Brand color", render: ({ value, onChange }) => ( <input type="color" value={(value as string) ?? "#000000"} onChange={(e) => onChange(e.target.value)} /> ), }, }

The render function receives:

interface CustomFieldRenderProps<Value> { value: Value; onChange: (value: Value) => void; id: string; name: string; readOnly?: boolean; }

Keep the component controlled — always drive the DOM from value and call onChange on edit. Blok handles history / undo around your input automatically.

Plugin field types

For a field type used in many blocks (or shipped as a package), register it via a plugin:

import { definePlugin, type PluginFieldRenderProps } from "@useblok/core"; function ColorPickerField({ value, onChange, field }: PluginFieldRenderProps<string>) { return ( <input type="color" value={value ?? "#000000"} onChange={(e) => onChange(e.target.value)} /> ); } export const colorFieldPlugin = definePlugin({ name: "@acme/color-field", fields: { "acme-color": { component: ColorPickerField }, }, });

Then in block configs:

{ brandColor: { type: "acme-color", label: "Brand color" }, }

The type string is matched at runtime. Namespaced names avoid collisions across plugins.

Plugin field render props

interface PluginFieldRenderProps<Value> { id: string; name: string; field: Record<string, unknown> & { type: string }; value: Value; onChange: (value: Value) => void; }

field is the raw field config — cast to your own type shape if you need extra options:

interface AcmeColorField { type: "acme-color"; label?: string; palette?: string[]; } function ColorPickerField({ field, value, onChange }: PluginFieldRenderProps<string>) { const { palette } = field as AcmeColorField; // ... }

Good patterns

  • Label + hint — Blok wraps your component in a <FieldRow> with label, description, and required marker. You don’t need to add those.
  • Keyboard access — Make sure your input is reachable with Tab.
  • Blur-commits — For expensive edits (like a large autocomplete), debounce onChange — but still fire on blur.
  • Undo friendliness — Each onChange creates a history entry. Batch rapid changes (like dragging a slider) with a debounce.

TypeScript note

Custom and plugin field types are validated at runtime — the FieldType TypeScript union only knows about built-in types. If you want type safety on your block configs, augment the module:

// your-types.d.ts import "@useblok/core"; declare module "@useblok/core" { interface FieldRegistry { "acme-color": { palette?: string[] }; } }

(This is a forward-looking API — the registry augmentation shape may change before 1.0.)

Last updated on