If you've been building pages with Canvas in Drupal, you've likely run into a frustrating wall: there's no built-in way to reference taxonomy terms from your component props. 

You define a type: string prop, hoping to wire it to a vocabulary, but Canvas has no idea you want an entity reference. You end up with a plain text field instead of an autocomplete, and no way to tie your component to Drupal's taxonomy system.

You're not alone. Over the past few months, several developers in the Drupal community have reached out to me with the same problem. They were building SDC (Single Directory Component) based components for Canvas - cards, teasers, hero banners - and needed a way to let content editors pick taxonomy terms like categories, tags, or topics. Some tried creating custom field types. Others attempted to hack the prop shape pipeline. None of it was straightforward, and none of the solutions were reusable.

That's why I built Canvas Entity Reference, a contributed Drupal module that bridges Canvas's JSON Schema-driven prop system with Drupal's entity reference field system. It's now live on drupal.org and ready to use.

The problem

Canvas uses https://json-schema.org/ to define component props. When you declare a prop as type: string, Canvas renders a text input. When you use type: boolean, you get a checkbox. This works great for simple values, but there's no native schema type for "a reference to a taxonomy term entity." This means if you want a "Category" field on your card component that lets editors search and select from existing taxonomy terms - with autocomplete, auto-creation, and live preview - you had to figure out the entire prop shape pipeline, write custom transforms, handle the autocomplete widget's "Label (ID)" format, and wire up field storage settings. All of this required deep knowledge of Canvas internals that isn't documented anywhere.

The challenges people were facing:

  • No autocomplete: A type: string prop renders a plain text input, not an entity reference autocomplete widget.
  • Transform chain complexity: Canvas uses a Redux-based form state with a transform chain to convert widget values into component prop values. Getting entity references to work with this pipeline requires custom JavaScript transforms that understand Drupal's autocomplete format.
  • Display value corruption: Canvas's parseNewValue() function applies transforms to the displayed value, turning "Technology (5)" into just 5 in the input field - making it impossible for editors to see what they selected.
  • No multi-value support: Even if you got a single term working, comma-separated multi-value tags required a completely different widget configuration and transform chain.
  • Auto-creation: Content editors expect to type a new term and have it created automatically, but wiring up the auto-create behavior inside Canvas's SPA Architecture is non-trivial.

The solution

Canvas Entity Reference solves all of this with a single $ref in your component's JSON Schema. No PHP code to write. No field configuration to manage. No transforms to build manually.

Here's what the module does under the hood:

  1. Intercepts the prop shape pipeline via hook_canvas_storable_prop_shape_alter() and reconfigures matching prop as entity_reference fields targeting taxonomy_term entities.
  2. Registers custom Canvas transforms that correctly normalize the "Label (ID)" autocomplete format into numeric entity IDs for live preview and save operations.
  3. Adds a sentinel hidden field to prevent Canvas from corrupting the displayed autocomplete value - editors see "Technology (5)" instead of just 5.
  4. Supports both single and multi-value references - use type: string for one term or type: array for comma-separated tags.
  5. Optionally auto-creates new terms on blur via a lightweight API endpoint, so editors can type a new term name, and it gets created in the configured vocabulary automatically.

How to install Canvas Entity Reference 

composer require drupal/canvas_entity_reference

drush en canvas_entity_reference

The module requires Drupal 10.3+ or 11, the https://www.drupal.org/project/canvas module, and Drupal core's Taxonomy module.

Configuring the module

After enabling the module, visit Administration > Configuration > Content > Canvas Entity Reference (/admin/config/content/canvas-entity-reference).

Here you'll find two settings:

  • Target vocabularies: Select which taxonomy vocabularies should be available for entity reference props. Leave all unchecked to allow all vocabularies on the site.
  • Allow auto-creation of new terms: When enabled (the default), typing a term name that doesn't exist will automatically create it in the configured vocabulary when the field loses focus. Disable this if you want editors to only select from existing terms.

Using it in your components

The entire integration is driven by a well-known $ref URI that you add to your component's .component.yml file. The module provides a JSON Schema definition at:

json-schema-definitions://canvas_entity_reference.module/taxonomy-term-referece

When Canvas encounters a prop with this $ref, the module automatically configures the prop as a taxonomy term entity reference with the appropriate widget, transforms, and field settings.

Single-value reference (One term)

For a prop that accepts a single taxonomy term - like a primary category:

props:
  type: object
  properties:
    category:
      type: string
      title: Category
      $ref: json-schema-definitions://canvas_entity_reference.module/taxonomy-
  term-reference

This renders as a standard Drupal entity reference autocomplete field in the Canvas editor. Start typing, and matching terms appear. Select one, and the component preview updates in real time.

In your Twig template, use the variable directly - Canvas resolves the entity reference to the term's name:

<span class="badge">{{ category }}</span>

Multi-value reference (Tags)

For props that accept multiple terms - like tags or categories:

props:
  type: object
  properties:
    tags:
      type: array
      title: Tags
      items:
        type: string
        $ref: json-schema-definitions://canvas_entity_reference.module/taxonomy-term-reference

This renders as a comma-separated autocomplete tags field. Editors can type

multiple terms separated by commas: "Technology (5), Design (12), UX (3)"

You can optionally limit cardinality with maxItems:

    tags:
      type: array
      title: Tags
      maxItems: 5
      items:
        type: string
        $ref: json-schema-definitions://canvas_entity_reference.module/taxonomy-term-reference

In your Twig template, loop over the array:

{% for term in tags %}
  <span class="tag">{{ term }}</span>
{% endfor %}

The example component

The module ships with an Example Taxonomy Card component that demonstrates the full integration. 

You'll find it under components/example-taxonomy-card/ in the module directory.

Here's the complete component schema (example-taxonomy-card.component.yml):

'$schema': 'https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json'
name: Example Taxonomy Card
status: experimental
group: Examples
description: >
  Example component demonstrating Canvas entity reference integration.
  Uses $ref-based schema matching to render a taxonomy term autocomplete field in the Canvas page builder.
props:
  type: object
  required:
    - title
  properties:
    title:
      type: string
      title: Title
      description: The card heading text.
      examples:
        - 'Example Title'
    description:
      type: string
      title: Description
      description: A short summary displayed below the title.
      examples:
        - 'Example destination.'
    category:
      type: array
      title: Category
      description: >
        One or more taxonomy term references. In Canvas, this renders as a comma-separated autocomplete field that searches taxonomy terms.
      items:
        type: string
        $ref: json-schema-definitions://canvas_entity_reference.module/taxonomy-term-reference
    show_category:
      type: boolean
      title: Show category badge
      description: Whether to display the category badge on the card.
      default: true
    link_url:
      type: string
      title: Link URL
      description: The card destination URL.
      format: uri
      examples:
        - 'https://example.com/article'

The key part is the category prop — it's defined as type: array with items containing the $ref. That's all you need. Canvas Entity Reference handles the rest.

The Twig template (example-taxonomy-card.twig) renders each term as a badge:

{% set tag = link_url ? 'a' : 'div' %}
{% set show_badge = show_category|default(true) and category is not empty %}
<{{ tag }}
  class="example-taxonomy-card"
  {% if link_url %}href="{{ link_url }}"{% endif %}
>
  <div class="example-taxonomy-card__content">
    {% if show_badge %}
      {% for term in category %}
        <span class="example-taxonomy-card__badge">
          {{ term }}
        </span>
      {% endfor %}
    {% endif %}
    {% if title %}
      <h3 class="example-taxonomy-card__title">{{ title }}</h3>
    {% endif %}
    {% if description %}
      <p class="example-taxonomy-card__description">{{ description }}</p>
    {% endif %}
  </div>
</{{ tag }}>

To try it out:

  1. Enable the module and make sure at least one taxonomy vocabulary exists (e.g., "Tags" from the standard install profile).
  2. Open Canvas and add the Example Taxonomy Card component to a page.
  3. Fill in the title and description.
  4. In the Category field, start typing — you'll see an autocomplete dropdown with matching taxonomy terms.
  5. Select existing terms or type new ones (they'll be auto-created on blur if the setting is enabled).
  6. The component preview updates in real time as you select terms.
  7. Check “Show Category Badge option”.

How it works under the hood

For those curious about the technical implementation, here's a brief overview of the architecture.  

  • Schema matching: The module implements hook_canvas_storable_prop_shape_alter(). When a prop's JSON Schema contains the well-known $ref URI or the x-entity-type: taxonomy_term marker, the module reconfigures the prop's field type, storage settings, instance settings, widget, and cardinality — all automatically.
  • Transform integration: Canvas uses a chain of JavaScript transforms to convert form widget values into component prop values. The module registers custom transforms via hook_field_widget_info_alter():
    • Single-value widget: A three-step chain (entityReferenceAutocomplete -> firstRecord -> mainProperty) extracts one numeric term ID from the autocomplete format.
    • Multi-value tags widget: A single entityReferenceAutocomplete transform with {multi: true} returns a flat array of IDs like [1, 2, 3].
  • The sentinel field trick: One of the trickiest problems was that Canvas's parseNewValue() applies transforms to the displayed input value, turning "Technology (5)" into just 5. The module solves this by adding a hidden field alongside the autocomplete widget. This causes Canvas to treat the prop as having "multiple inputs for a single value," which makes parseNewValue() skip transforms on the display value. Transforms still run during save and preview via formStateToStore().
  • Auto-creation: A JavaScript behavior attached to the autocomplete widget detects unresolved terms (those without an (ID) suffix) on blur. It calls a lightweight API endpoint that creates the term in the configured vocabulary — or returns the existing term if a match is found — then updates the input with the proper "Name (ID)" format.

Final thoughts

Canvas Entity Reference currently supports taxonomy terms, which covers the most common use case. Support for other entity types (nodes, media, users) could follow the same pattern and may be added in future releases or as companion modules.

If you've been struggling to get entity references working in Canvas components, give this module a shot! 

Install it, add one line of $ref to your component schema, and you're done!

Feedback, bug reports, and contributions are welcome in this issue queue.

Contact us

LET'S DISCUSS YOUR IDEAS. 
WE'D LOVE TO HEAR FROM YOU.

CONTACT US SUBMIT RFP