Skip to content

Comments

custom variable type widget: single resource collection (1/3)#49

Open
interim17 wants to merge 10 commits intorun-formatfrom
feature/resource
Open

custom variable type widget: single resource collection (1/3)#49
interim17 wants to merge 10 commits intorun-formatfrom
feature/resource

Conversation

@interim17
Copy link
Contributor

@interim17 interim17 commented Feb 19, 2026

Problem

Advances #40

Decap CMS does not natively support a variable type object widget.
A fairly limited variable type list widget exists, but doesn't quite meet our needs, and it's use leads to significantly cluttered UI with deeply nested fields.

In this case we want a single collection for resources (datasets, cell lines, software tools) to associate with an idea, rather than individual collections for each of those resource types.

https://decapcms.org/docs/variable-type-widgets/

Solution

Built a custom widget to handle a variable type, in this case, a resource object that can be one of several varieties and will render unique fields in the UI based on the type selected.

This PR does not address fully migrating all existing resources, or even the desired final set of fields for any resource types, as its a fairly big lift to just get the custom widget built, styled and registered.

In cms/widgets we define a VariableTypeWidgetControl

Even though we only have one implementation, I figured this was likely a pattern that would be repeated or built upon in any of our projects using this CMS so this abstract implementation doesn't need to know anything about the specifics of the config.

VariableResourceWidget
Hold the config in constants where the fields are actually defined and the React component is VariableResourceUnionControl.

In cms.js
We register the component here, in future work we can also register a custom Preview component here.
we also register a hook in the preSave step of the Decap lifecycle, which allows us to use the nested "name" field for a required and hidden top-level name field, which helps to reduce UI noise.

In config.yml

The collection Resources uses the widget, and in the ideas collection relational widgets are used to make a select both for the general resources, and one filtered only to datasets.

Screenshot 2026-02-23 at 1 51 16 PM

@netlify
Copy link

netlify bot commented Feb 19, 2026

Deploy Preview for project-idea-board ready!

Name Link
🔨 Latest commit 6653ea5
🔍 Latest deploy log https://app.netlify.com/projects/project-idea-board/deploys/699ccbad16f4df000832296b
😎 Deploy Preview https://deploy-preview-49--project-idea-board.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@interim17 interim17 requested a review from Copilot February 19, 2026 23:26
@interim17 interim17 changed the title Feature/resource add custom widget for variable types to cms config: single resource collection (1/3) Feb 19, 2026
Comment on lines 106 to 130
- {
label: "Datasets",
name: "datasets",
widget: "relation",
collection: "resources",
search_fields: ["name"],
value_field: "{{slug}}",
display_fields: ["name"],
multiple: true,
required: false,
hint: "Select datasets from the Resources collection, if missing, please add to the Resources collection and then finish editing this entry.",
filters: [{ field: "resource.type", values: ["dataset"] }],
}
- {
label: "Resources",
name: "resources",
widget: "relation",
collection: "resources",
search_fields: ["resource.name", "resource.type"],
value_field: "{{slug}}",
display_fields: ["resource.type", "resource.name"],
multiple: true,
required: false,
hint: "Select other resources from the Resources collection, if missing, please add to the Resources collection and then finish editing this entry.",
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One select for any resource at all, and one filtered to just datasets. We can provide as many as desired/useful.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new CMS-managed “Resources” collection backed by a custom union widget, and wires it into the Decap CMS admin so “Ideas” can reference datasets/resources from a single collection.

Changes:

  • Extend static/admin/config.yml to add a new resources collection and new relation fields on Ideas.
  • Add a configurable union-style Decap CMS control (resource_union) to author different resource types.
  • Treat templateKey: resource markdown as “data-only” content in Gatsby page creation.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
static/admin/config.yml Adds “Datasets/Resources” relation fields to Ideas and introduces a new “Resources” collection using the custom union widget.
src/cms/widgets/VariableTypeWidget/types.ts Introduces shared config typings for the union widget.
src/cms/widgets/VariableTypeWidget/VariableTypeWidgetControl.tsx Implements the generic config-driven union widget control (including file field support).
src/cms/widgets/VariableResourceWidget/constants.ts Defines the resource-type configuration used by the union widget.
src/cms/widgets/VariableResourceWidget/VariableResourceUnionControl.tsx Binds the generic union widget to the “resource” use-case with concrete type config.
src/cms/widgets/VariableResourceWidget/copyResourceNameHandler.ts Adds a preSave hook to copy nested resource name into a required top-level field.
src/cms/cms.js Registers the custom widget and the preSave event handler with Decap CMS.
gatsby-node.js Adds resource to the list of data-only templateKeys to prevent page creation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 119 to 130
- {
label: "Resources",
name: "resources",
widget: "relation",
collection: "resources",
search_fields: ["resource.name", "resource.type"],
value_field: "{{slug}}",
display_fields: ["resource.type", "resource.name"],
multiple: true,
required: false,
hint: "Select other resources from the Resources collection, if missing, please add to the Resources collection and then finish editing this entry.",
}

This comment was marked as resolved.

Comment on lines 71 to 75
{ label: "Name", name: "name", type: "input" },
{
label: "Description",
name: "description",
type: "textarea",

This comment was marked as resolved.

Comment on lines +9 to +12
Certain fields are required to be top-level by the CMS, which clutters the UI
when we have a redundant field in widget. This preSave hook copies the resource.name
field to the top-level from the nested widget.
*/

This comment was marked as resolved.

};

const handleTypeChange = (newType: string) => {
onChange({ type: newType });
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleTypeChange calls onChange({ type: newType }), which drops all existing fields in the object whenever the user changes the type (e.g., name/description/link will be lost). Preserve the current normalized value when switching types (and optionally clear only the previous type-specific fields) so changing the selector doesn't wipe user input.

Suggested change
onChange({ type: newType });
const current = normalizeValue();
onChange({ ...current, type: newType });

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since fields won't match between types necessarily, I think we should drop them.

Comment on lines 52 to 56
{ label: "Name", name: "name", type: "input" },
{
label: "Description",
name: "description",
type: "textarea",

This comment was marked as resolved.

Comment on lines 1 to 7
import CMS from "decap-cms-core"

// FRor some reason I could not import CmsEventListener as as a type
// and it was resolving to the global CMS object, so this is the workaround.
type CmsEventListenerHandler = Parameters<typeof CMS.registerEventListener>[0]['handler'];
type CmsEventListenerHandlerArg = Parameters<CmsEventListenerHandler>[0];

This comment was marked as resolved.

Comment on lines 90 to 94
{ label: "Name", name: "name", type: "input" },
{
label: "Description",
name: "description",
type: "textarea",
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cellLine repeats name/description even though the widget's default base fields already include them, causing duplicate inputs bound to the same keys. Remove the duplicates or override baseFields for this type.

Copilot uses AI. Check for mistakes.
/**
* Implementation of VariableTypeWidgetControl for a union of different
* resource types (software tools, datasets, etc.).
* Config defined in RESOURCE_TYPES.

This comment was marked as resolved.

@interim17 interim17 changed the base branch from main to run-format February 23, 2026 22:09
@interim17 interim17 marked this pull request as ready for review February 23, 2026 23:40
@interim17 interim17 changed the title add custom widget for variable types to cms config: single resource collection (1/3) custom variable type widget: single resource collection (1/3) Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant