You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Proposal: a plugin extension point for custom field types
Context
While building the blocks page-builder field (an ordered, heterogeneous list of references into other collections, with a stacked-slide-over admin editing UX), I ended up modifying core framework files rather than shipping the feature as a plugin:
packages/core/src/schema/types.ts — added "blocks" to FIELD_TYPE_TO_COLUMN
packages/core/src/schema/zod-generator.ts — value-shape validation for the field
packages/core/src/emdash-runtime.ts — manifest validation-forwarding (this needed its own fix, since allowedCollections wasn't in the forwarding allowlist and was being silently dropped)
packages/admin/src/components/FieldEditor.tsx — added "blocks" to the hardcoded field-type picker
packages/admin/src/components/BlocksField.tsx, BlockEntryEditor.tsx, BlockDrillContext.tsx — the admin editing UI itself
Every one of these was necessary because EmDash's plugin system currently has no extension point for registering a whole new field type. It has one for registering a new widget for an existing type (packages/plugins/field-kit does exactly this for json fields — object-form, list, grid, tags), but nothing analogous for "here's a brand-new field.type value, here's its column mapping, here's how to validate its value shape, here's its own config schema, here's the component that edits it."
What's the actual gap
Looking at what genuinely required core access vs. what was just living there by convenience:
Would need a new extension point:
Registering the field.type string itself against a column-type mapping (currently a fixed dispatch table in schema/types.ts)
Contributing a Zod schema for the field's value shape (currently hardcoded per type in zod-generator.ts)
Contributing a schema for the field's own validation config (e.g. allowedCollections), forwarded generically rather than through a hardcoded allowlist — this is the exact bug I had to fix mid-build: allowedCollections wasn't in emdash-runtime.ts's manifest-forwarding condition, so the admin silently ignored the restriction
Somewhere for the admin's field-type picker (FieldEditor.tsx) and its generic field-dispatch component to look up "which component renders field.type === 'blocks'"
Wouldn't need anything new — already just consumes the public API:
resolveBlocks() and <Blocks /> (in packages/core/src/query.ts / packages/core/src/components/Blocks.astro) are built entirely out of already-exported primitives (getEmDashCollection, requestCached, etc.). A plugin package could ship equivalents today, unmodified, with zero new core changes — they live in core purely for discoverability, not because of any technical dependency.
The admin editing UI itself (BlocksField, BlockEntryEditor, the drill-down panel stack) is self-contained React. It only needs a mount point — somewhere to be told "render this component for this field type" — which is precisely the missing seam above, not a deeper architectural blocker.
The proposal
Extend the existing plugin admin.fieldWidgets pattern (widget-for-an-existing-type) with a sibling that registers whole new types, something like:
definePlugin({id: "page-builder",schema: {fieldTypes: [{name: "blocks",columnType: "json",// maps onto an existing storage kind, not a new onevalueSchema: (field)=>/* zod schema for the stored value */,validationSchema: (field)=>/* zod schema for the field's own config, e.g. allowedCollections */,},],},admin: {entry: "@emdash-cms/plugin-page-builder/admin",fieldTypes: [{name: "blocks",label: "Blocks",component: "BlocksField"}],},});
If this existed, the entire blocks feature — schema registration, resolveBlocks/<Blocks />, and the whole admin editing UI — could ship as a single standalone @emdash-cms/plugin-page-builder package, touching zero files in packages/core or packages/admin. It would also generalize the exact bug class I hit (a field type's validation config silently dropped because it wasn't in a hardcoded allowlist) — any future field type, first-party or plugin, would forward its own validation config by construction rather than by remembering to update an allowlist.
Explicitly out of scope here (separate, unrelated ask)
Two Block Kit elements (object, reference_picker) were added to packages/blocks in the same work, but they solve a different problem: they extend the Portable Text editor's element vocabulary so other plugins' custom rich-text blocks can group sub-fields or link to a content entry. That vocabulary (string, image, media_picker, and now object/reference_picker) is also not currently plugin-extensible, but it's an independent gap from the field-type one above, would need its own separate extension point, and isn't something the blocks page-builder field itself needs.
Why raise this now
Per the contribution policy, this is a design proposal, not a bug fix — it changes how plugins can extend the schema/admin system, which is exactly the kind of thing that should get maintainer buy-in via Discussion before real implementation work starts, rather than showing up as a surprise PR.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Proposal: a plugin extension point for custom field types
Context
While building the
blockspage-builder field (an ordered, heterogeneous list of references into other collections, with a stacked-slide-over admin editing UX), I ended up modifying core framework files rather than shipping the feature as a plugin:packages/core/src/schema/types.ts— added"blocks"toFIELD_TYPE_TO_COLUMNpackages/core/src/schema/zod-generator.ts— value-shape validation for the fieldpackages/core/src/api/schemas/schema.ts—allowedCollectionsvalidation configpackages/core/src/emdash-runtime.ts— manifest validation-forwarding (this needed its own fix, sinceallowedCollectionswasn't in the forwarding allowlist and was being silently dropped)packages/admin/src/components/FieldEditor.tsx— added"blocks"to the hardcoded field-type pickerpackages/admin/src/components/BlocksField.tsx,BlockEntryEditor.tsx,BlockDrillContext.tsx— the admin editing UI itselfEvery one of these was necessary because EmDash's plugin system currently has no extension point for registering a whole new field type. It has one for registering a new widget for an existing type (
packages/plugins/field-kitdoes exactly this forjsonfields —object-form,list,grid,tags), but nothing analogous for "here's a brand-newfield.typevalue, here's its column mapping, here's how to validate its value shape, here's its own config schema, here's the component that edits it."What's the actual gap
Looking at what genuinely required core access vs. what was just living there by convenience:
Would need a new extension point:
field.typestring itself against a column-type mapping (currently a fixed dispatch table inschema/types.ts)zod-generator.ts)allowedCollections), forwarded generically rather than through a hardcoded allowlist — this is the exact bug I had to fix mid-build:allowedCollectionswasn't inemdash-runtime.ts's manifest-forwarding condition, so the admin silently ignored the restrictionFieldEditor.tsx) and its generic field-dispatch component to look up "which component rendersfield.type === 'blocks'"Wouldn't need anything new — already just consumes the public API:
resolveBlocks()and<Blocks />(inpackages/core/src/query.ts/packages/core/src/components/Blocks.astro) are built entirely out of already-exported primitives (getEmDashCollection,requestCached, etc.). A plugin package could ship equivalents today, unmodified, with zero new core changes — they live in core purely for discoverability, not because of any technical dependency.BlocksField,BlockEntryEditor, the drill-down panel stack) is self-contained React. It only needs a mount point — somewhere to be told "render this component for this field type" — which is precisely the missing seam above, not a deeper architectural blocker.The proposal
Extend the existing plugin
admin.fieldWidgetspattern (widget-for-an-existing-type) with a sibling that registers whole new types, something like:If this existed, the entire
blocksfeature — schema registration,resolveBlocks/<Blocks />, and the whole admin editing UI — could ship as a single standalone@emdash-cms/plugin-page-builderpackage, touching zero files inpackages/coreorpackages/admin. It would also generalize the exact bug class I hit (a field type's validation config silently dropped because it wasn't in a hardcoded allowlist) — any future field type, first-party or plugin, would forward its own validation config by construction rather than by remembering to update an allowlist.Explicitly out of scope here (separate, unrelated ask)
Two Block Kit elements (
object,reference_picker) were added topackages/blocksin the same work, but they solve a different problem: they extend the Portable Text editor's element vocabulary so other plugins' custom rich-text blocks can group sub-fields or link to a content entry. That vocabulary (string,image,media_picker, and nowobject/reference_picker) is also not currently plugin-extensible, but it's an independent gap from the field-type one above, would need its own separate extension point, and isn't something theblockspage-builder field itself needs.Why raise this now
Per the contribution policy, this is a design proposal, not a bug fix — it changes how plugins can extend the schema/admin system, which is exactly the kind of thing that should get maintainer buy-in via Discussion before real implementation work starts, rather than showing up as a surprise PR.
All reactions