[META] Code Components: add an "content entity reference" prop type — enables "view modes" that combine multiple entities plus static inputs!
> [!note] Migrated issue > > <!--Drupal.org comment--> > > <!--Migrated from issue #3573831.--> > > Reported by: [wim leers](https://www.drupal.org/user/99777) > > Related to !624 !622 Jump to: * :white_check_mark: Proposed resolution * [:levitate:Product Speak](https://www.drupal.org#proposed-resolution-product-speak) * [:man_technologist: Code Component Developer Speak](https://www.drupal.org#proposed-resolution-code-component-developer-speak) * [:man_scientist: Canvas Engineering Speak](https://www.drupal.org#proposed-resolution-canvas-engineering-speak) * PoC/feasibility study * [:white_check_mark: "version 0": prove feasibility](https://www.drupal.org#poc-v0) * [:white_check_mark: "version 0.5": distill ADR + story map](https://www.drupal.org#poc-v0.5) * [:white_check_mark: ADR](https://www.drupal.org#adr) * [:octagonal_sign: Designs](https://www.drupal.org#designs) * [:white_check_mark: Plan/Story map](https://www.drupal.org#plan) * [:white_check_mark: Blocking technical debt](https://www.drupal.org#plan-blocking-technical-debt) * [Blocking missing features](https://www.drupal.org#plan-blocking-missing-features) * [Story map](https://www.drupal.org#plan-story-map) ### Overview Currently, when creating content that needs to reference existing entities (such as products, articles, or other content), users face two suboptimal alternatives: 1. they can use **automated lists** (powered by JSON:API) that pull entity data but don't allow manual content curation within Canvas (see [<span dir="">#3526297: Add way to view fetch results inside code editor</span>](https://www.drupal.org/project/experience_builder/issues/3526297 "Status: Closed (fixed)"), <span dir=""> https://git.drupalcode.org/project/canvas/-/work_items/3519737</span>, or perhaps best: the docs at https://project.pages.drupalcode.org/canvas/code-components/data-fetching/) 2. or they must manually recreate/copy entity information into Canvas components to maintain creative control. These types of pages are often created to create navigational pages. Lists solve the data duplication problem but eliminate the flexibility that makes Canvas powerful - the ability to customize content presentation. Manual recreation preserves Canvas flexibility but creates friction, data inconsistency, and maintenance overhead. ### Proposed resolution * [:levitate:Product Speak](https://www.drupal.org#proposed-resolution-product-speak) * [:man_technologist: Code Component Developer Speak](https://www.drupal.org#proposed-resolution-code-component-developer-speak) * [:man_scientist: Canvas Engineering Speak](https://www.drupal.org#proposed-resolution-canvas-engineering-speak) ### Proposed resolution::levitate:Product Speak [#](https://www.drupal.org#proposed-resolution-product-speak) Expected outcome from a Product POV, by @lauriii: > Upon completion, users will have the best of both worlds: they can manually select and curate specific entities within Canvas while still benefiting from live data connections to those entities. This preserves Canvas's core strength of creative control and manual content arrangement while eliminating data duplication and maintenance overhead. Users will be able to handpick exactly which products, articles, or other entities to feature, arrange them as desired within their Canvas layout, and ensure that the underlying data (prices, titles, images, etc.) stays automatically synchronized. The result will be faster content creation, maintained creative flexibility, improved data accuracy, and reduced long-term maintenance burden. Use cases by Lauri: <dl> <dt>Content Creator/Site Builder</dt> <dd> :grimacing: Now: A marketing team needs to create a promotional page featuring 5 specific products. Currently they must either manually recreate product information (title, price, image) or copy-paste from product pages, leading to outdated information when prices change. </dd> <dd> :partying_face: Then: Reference existing product entities directly in the canvas component (or in the case for a content template for product entities: allow the host entity to be linked) so that price updates, new images, and product details automatically reflect in the liftup without manual updates. </dd> <dt>Code Component Developer</dt> <dd> :grimacing: Now: A developer is setting up an Article content template which need to display related articles. Currently they must fetch generate the relevant JSON:API request. </dd> <dd> :partying_face: Then: Display ‘Related Articles’ entity reference field using ‘Article Card’ component which has an entity reference prop targeting Articles. </dd> </dl> Summarized by Wim: > **⇒ code components must be able to receive multiple kinds of structured data and combine such structured data to present complex information consistently :rocket:** > > _:bulb: In a way, it's Content Templates on steroids, because it's basically templating not a single content entity type+bundle, but many._ ### Proposed resolution: :man_technologist: Code Component Developer Speak [#](https://www.drupal.org#proposed-resolution-code-component-developer-speak) 1. Introduce a new prop type for Canvas' code components: "Reference". This allows picking a target (content) entity type + bundle, as well as which data to fetch for it. 2. Every "Reference" prop results in an object-shaped prop with key-value pairs determined by the data that is selected by the code component developer. For example: they might choose to reference article nodes, and then pick the "title" and "image" fields (perhaps using a different UI, but using the same nested menu structure as how component instances in a content template can have their component props linked to structured data). 3. The code component developer MUST account for the fact that they may not receive an object, but `null` (and for multiple cardinality later: an empty array) — either when no entity is referenced yet, OR the referenced entity has been deleted, OR on a content template the entity reference field may be optional, OR on a content template the entity reference field may be required but nodes were created before it was required. ### Proposed resolution : :man_scientist: Canvas Engineering Speak [#](https://www.drupal.org#proposed-resolution-canvas-engineering-speak) 1. Introduce a new prop type for Canvas' code components: `type: object, $ref: json-schema-definitions://canvas.module/content-entity-reference`. 2. Each such code component prop is called an "entity reference" prop. 3. There can be 0, 1 or many "entity reference" props in every code component 4. For each such "entity reference" prop (named `NAME`): 1. The code component developer must only be able to select data from the same entity type + bundle using a UI. 2. The selected data (which fields and field properties) is represented by a list of entity field prop expressions (`EntityFieldBasedPropExpressionInterface`). These must be stored under the `JavaScriptComponent` config entities' pre-existing `dataDependencies` (which in HEAD can contain `urls` and `drupalSettings`): it introduces `dataDependencies.entityFields`, which must contain a non-empty list of entity field prop expressions under `dataDependencies.entityFields.NAME`. 3. The selected data (N entity field prop expressions) represent the values the code component developer will receive in the code component automatically get keys using this basic algorithm: * by default: the name of the field (e.g. `uid`, `field_image`) * unless that field is an entity key, then use _that_: for node it'd not be `uid` but `owner` * if it is a reference expression, append `__` and then apply the same as the above for the field on the referenced entity (To be refined further, but this is the basic principle provided by @lauriii.) 4. All stored entity field prop expressions must be applied to (evaluated for) a given content entity object (no matter whether provided on a one-off basis aka `StaticPropSource` or provided through the structured data that a content template received aka `EntityFieldPropSource`), to generate the object shape chosen by the code component developer. 5. Based on `dataDependencies.entityFields`, the appropriate config dependencies can be calculated, which will ensure that a code component with entity reference prop(s) can only be imported on sites where they will actually work. 6. It must all be covered by precise config schema validation + tests — both for data integrity reasons, but also to provide an LLM (the `canvas_ai` submodule) with precise feedback when something is invalid. 7. New internal HTTP APIs to power the "field data picker" — but note that it's very different from the existing `PropSourceSuggester`, because _that_ starts from a given shape (JSON Schema), but _this_ needs to start from all field data, and it all needs to be available for picking: the shape (JSON schema) is the end (result), not the start (requirement). 8. When building a content template for some content entity type+bundle (e.g. "article" nodes) and a particular component instance has an "entity reference" prop that expects an entity of the same entity type+bundle, the _host entity itself_ must be linkable. ### User interface changes 1. Add new "reference" prop type — similar to how <span dir=""> https://git.drupalcode.org/project/canvas/-/work_items/3566541</span> added a <q>Date and time</q> prop type. Restrictions: * Such a prop MUST NOT be marked as required. (Because code components with such props must be built in a resilient manner: they must gracefully degrade when the referenced entity/entities are deleted.\ Which makes it consistent with Drupal core, which doesn't prevent deletion of an entity if it's referenced from somewhere.) * It MUST NOT have an example value. (This keeps it decoupled from the concrete content on a particular site.) * It MUST have a target content entity type specified (node, user, taxonomy_term …). If it's a content entity type with bundles, exactly one bundle MUST be specified. (Per @lauriii — this keeps things simple for now. Later, we can loosen this, and can allow multiple bundles or even omitting bundles.) * :new: (missed requirement, discovered in https://git.drupalcode.org/project/canvas/-/merge_requests/1112#note_915436): while the prior bullet restricts the _starting point_ to a single bundle, that is not feasible for following entity references. → It MUST support multi-target bundle reference fields, including picking data for it. * It must have a UI to select which entity field data the code component wishes to receive. It'd be similar to https://www.drupal.org/project/jsonapi_explorer or Prismic [content relationship](https://prismic.io/slice-machine>Slice%20Machine</a>'s%20<a%20href=) (which is literally what entity references are — Drupal has had this for decades :smile:). **Designs**, but essentially something like this:\ ![](https://www.drupal.org/files/issues/2026-02-16/Prismic%20Slice%20Machine%20%E2%80%94%20Content%20Relationship.png) * **DESIGNS:** https://www.figma.com/design/ZSlXxBDIGLV2riMAxCv9QE/Canvas-Code-Editor?node-id=204-24144&t=1tzTgeEC5CmL4EtT-1 2. Instances of such code components show Drupal core's `\Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget`, restricted to the target entity type (+ bundle) that was configured. At that point, they should look and feel like any other code component prop:\ ![](https://www.drupal.org/files/issues/2026-02-19/FR-4415%20dynamic%20prop%20sources.gif) 3. When building a content template and instantiating a code component with an "entity reference" prop whose entity type+bundle happen to match that for the content template, the host entity can be linked:\ ![](https://www.drupal.org/files/issues/2026-02-17/3573831%20HostEntityPropSource%20PoC.gif) ### PoC/feasibility study <dl> <dt>"version 0": prove feasibility</dt> <dd> * :man_scientist: Raw, unaltered PoC branch’s git commit history: https://git.drupalcode.org/issue/canvas-3573831/-/commits/FR-4415-PoC * This is a _working_ PoC. GIF showing instantiating a code component with an "entity reference" prop that accepts article nodes, first being populated by a `StaticPropSource`, then by a `DynamicPropSource`, and in both cases the code component rendering the selected data (title + author's name) from the referenced article node:\ ![](https://www.drupal.org/files/issues/2026-02-19/FR-4415%20dynamic%20prop%20sources%20e2e.gif) * This stores the data like any entity reference field would store its data: [DB contents](https://www.drupal.org/files/issues/2026-02-19/FR-4415%20PoC%20data%20storage%20illustrated.png) * Test code component used: [`FR-4415 PoC v2 canvas.js_component.b2.yml`](https://www.drupal.org/files/issues/2026-02-19/FR-4415%20PoC%20v2%20canvas.js_component.b2.yml) * :movie_camera: [Narrated demo video (3.5 mins)](https://www.drupal.org/files/issues/2026-02-19/FR-4415%20PoC%20demo%20480p.mov) * Updated for @lauriii's additional feature request in #3: if the content template is for the same entity type+bundle as a code component's "entity reference" prop, then it should be possible to link _that_, too:\ ![](https://www.drupal.org/files/issues/2026-02-17/3573831%20HostEntityPropSource%20PoC.gif) </dd> <dt>"version 0.5": distill story map</dt> <dd> Done, see [#12](https://www.drupal.org/project/canvas/issues/3573831#comment-16479465) and the end result:\ ![](https://www.drupal.org/files/issues/2026-02-20/3573831%20entity%20reference%20props%20v0.5%20demo.gif) </dd> </dl> ### ADR _Distilled from v0 while crafting v0.5._\ → [MR](https://git.drupalcode.org/project/canvas/-/merge_requests/624) → [see rendered result](https://git.drupalcode.org/issue/canvas-3573831/-/blob/3573831-ADR/docs/adr/0009-entity-reference-props-in-code-components.md) ### Designs _None yet._ Needed designs: 1. **at minimum** for the UX that allows a code component developer to select the data they want to be available in their code component (in a modal dialog, presumably?) 2. **probably** also for the exact UX for the prop type: selecting "Reference" as the prop type would not require designs, but we need to know (otherwise we'll guess) how that the modal dialog in point 1 is triggered 3. **possibly** a different (nicer :sweat_smile:) UX than Drupal core's "entity reference autocomplete widget"? ### Plan/Story map * [Blocking technical debt](https://www.drupal.org#plan-blocking-technical-debt) * [Blocking missing features](https://www.drupal.org#plan-blocking-missing-features) * [Story map](https://www.drupal.org#plan-story-map) [v0.5 PoC](https://git.drupalcode.org/project/canvas/-/merge_requests/622/commits) has 19 commits that have been carefully crafted to tell an incremental story, with its commit messages prefixed with: * `[blocking technical debt]` = minimal work-arounds for technical that needs fixing → see [Blocking technical debt](https://www.drupal.org#plan-blocking-technical-debt) * `[config]` = `JavaScriptComponent` config entity (aka "code components" in the UI) changes needed for this functionality, should mostly be mergeable as-is * `[component source]` = `JsComponent` component source changes needed for this functionality, should mostly be mergeable as-is * `[infra]` = infrastructure additions needed for this functionality, should mostly be mergeable as-is — ranging from validation constraints to shape matching to … ### Plan: Blocking technical debt 1. :white_check_mark: Soft-blocking: [<span dir="">#3574687: Convert \`@group foo\` to \`#\[Group(…)\]\` + other annotations — to restore the ability to run a subset of Canvas' test suite, enforce it using PHPCS</span>](https://www.drupal.org/project/canvas/issues/3574687 "Status: Closed (fixed)") 2. :white_check_mark: Soft-blocking: [<span dir="">#3574831: Remove GeneratedFieldExplicitInputUxComponentSourceBase's fallback field widget handling thanks to #3560005</span>](https://www.drupal.org/project/canvas/issues/3574831 "Status: Closed (fixed)") 3. :white_check_mark: Soft-blocking: <span dir=""> https://git.drupalcode.org/project/canvas/-/work_items/3558448</span> 4. :white_check_mark: Hard-blocking: <span dir=""> https://git.drupalcode.org/project/canvas/-/work_items/3555300</span> ### Plan: Blocking missing features 1. Hard-blocking: [<span dir="">#3574857: Add client-side transform for entity_reference_autocomplete to ReduxIntegratedFieldWidgetsHooks::fieldWidgetInfoAlter</span>](https://www.drupal.org/project/canvas/issues/3574857 "Status: Needs review") — unless the design says to not use the default Drupal entity reference autocomplete widget, this will ### Plan: Story map BE or FE, story sizes ranging from 1 (\<1 day), 2 (1–2 days), 3 (3/sprint), 5 (2/sprint), 8 (1/sprint), 13 (maybe in 1 sprint) — low confidence indicated by question mark: 1. \[BE, 2\] Implement missing validation constraints for `dataDependencies.entityFields` (→ make the failing unit tests pass) 2. \[BE, 3\] Missing test coverage for `PropSourceSuggester::matchEntityReferenceProp()` 3. \[BE, 5\] Add test code component that is then has all its facets tested by `JsComponentTest` 4. \[BE, 3\] Move `JsComponent::labelForDeveloper()` to `Labeler`, expand `PropExpressionKernelTest::testLabel()` 5. \[BE, 3\] Add test coverage for new `HostEntityPropSource` — see `PropSourceTest` 6. \[FE, 3?\] support for new "reference" prop type in code component editor UI, similar to <span dir=""> https://git.drupalcode.org/project/canvas/-/work_items/3566541</span> — step one: hardcoded to node+article, and hardcode the data to be fetched: title + author name 7. \[BE, 8?\] internal HTTP API for browsing available content entity types + bundles + the data contained by them — :octagonal_sign: SOFT blocked on designs: may need to change 8. \[FE, 13?\] support for new "reference" prop type in code component editor UI, similar to <span dir=""> https://git.drupalcode.org/project/canvas/-/work_items/3566541</span> — step two: actually allow selecting data — :octagonal_sign: HARD blocked on designs 9. \[FE, 5\] e2e test for the entity data selection modal, because this is very likely to run into edge cases 10. :interrobang: \[FE, 8?\] alternative entity reference widget, if that's what we decide — :octagonal_sign: HARD blocked on designs 11. :interrobang: \[FE, 5?\] update CLI tool to [support representing this in its `component.yml` representation](https://project.pages.drupalcode.org/canvas/code-components/cli-tool/prop-schemas/), must support bidirectional conversion (related prior art: <span dir=""> https://git.drupalcode.org/project/canvas/-/work_items/3537654</span>) > Related issue: [Issue #3585135](https://www.drupal.org/node/3585135)
issue