Architecture
How Canvas Override integrates with the Canvas module and Drupal.
Overview
Canvas Override integrates with the Canvas module through service decoration, hook implementations, and constraint validation. This document explains the architectural decisions and how the module's components fit together.
Core Concept
The Canvas module provides visual page building through ContentTemplates defined at the content type level. Canvas Override adds a per-content layer by:
- Adding a
field_canvas_layout(component_tree) field to content types. - Overriding Canvas's
ComponentTreeLoaderto support nodes as canvas entities. - Splitting the view builder to handle per-content vs. template rendering.
- Managing required field validation during Canvas API requests.
Module Components
Service Provider
CanvasOverrideServiceProvider swaps Canvas's ComponentTreeLoader with
CanvasOverrideComponentTreeLoader. This is the primary integration point that
allows nodes to be treated as valid canvas entities.
Component Tree Loader
CanvasOverrideComponentTreeLoader extends Canvas's ComponentTreeLoader:
getCanvasFieldName(entity): For Canvas Override-enabled nodes, returns the component_tree field name. Bypasses the upstream restriction that only allows ContentTemplate entities.load(entity): When a node's canvas field is empty (first editor open), copies the ContentTemplate default components into the node's own field. This avoids returning a danglingComponentTreeItemListthat causes 500 errors withcontent_moderation.
View Builder
CanvasOverrideNodeViewBuilder extends Canvas's
ContentTemplateAwareViewBuilder:
- Splits entities into two groups in
buildComponents(): - Override entities: Nodes with non-empty
field_canvas_layout— rendered via the decorated view builder (field renders directly). - Template entities: Nodes without per-content layout — rendered via parent class logic (uses ContentTemplate).
- Adds appropriate cache contexts for both paths.
Constraint Validator Decorator
CanvasOverrideConstraintValidator decorates Canvas's
ComponentTreeMeetsRequirementsConstraintValidator:
- For Canvas Override-enabled nodes: removes
EntityFieldandHostEntityUrlfrom the "absence" constraint, allowing field linking on per-content layouts. - For other entities: delegates to the original validator.
The service ID matches the original class name (required for Drupal's
ClassResolver).
Access Check
CanvasTabAccessCheck implements the _canvas_override_access route
requirement:
- Checks user permissions (admin, global, or per-bundle).
- Checks that the content type has Canvas Override enabled.
- Caches per permissions and node type entity.
Data Flow
Enabling Canvas Override
Admin enables checkbox on content type form
→ Form submit handler saves third-party setting
→ ensureCanvasField() creates field, storage, and display config
→ Dynamic permission generated for the bundle
Editing a Node in Canvas
Editor clicks Canvas tab
→ CanvasRedirectController redirects to Canvas editor
→ Canvas editor requests component tree via API
→ CanvasOverrideComponentTreeLoader::load()
→ If field empty: seeds from ContentTemplate
→ Returns component tree for editing
→ Non-layout fields hidden from editor form
→ Editor saves
→ Required field values restored from database
→ NotNull constraints bypassed for non-layout fields
→ RestoreRequiredFieldsConstraintValidator (final restoration)
→ Non-layout fields restored before save
→ Node saved with title + field_canvas_layout
Rendering a Node
Node view requested
→ CanvasOverrideNodeViewBuilder::buildComponents()
→ Splits into override_entities and template_entities
→ Override: renders field_canvas_layout directly
→ Template: uses ContentTemplate via parent class
→ If node has per-content layout: output replaced with field_canvas_layout
→ Theme wrapper removed (Canvas output is self-contained)
Required Field Handling
Canvas Override edits only the layout and title. Other required fields are not present in the Canvas editor form. The module includes multiple safeguards at different stages of the form and validation lifecycle to prevent required field validation failures and ensure field values are preserved during Canvas API requests.
Key design decision: the module uses unset() on form elements instead of
#access = FALSE. Canvas core's extractFormValues() still processes fields
with #access = FALSE, which clears their values. Using unset() prevents
form processing entirely.
Service Definitions
# Access check for Canvas tab routes
canvas_override.access_check:
class: Drupal\canvas_override\Access\CanvasTabAccessCheck
tags:
- { name: access_check, applies_to: _canvas_override_access }
# Constraint validator decoration
# Service ID matches original class (required for ClassResolver)
Drupal\canvas\Plugin\...\ComponentTreeMeetsRequirementsConstraintValidator:
class: Drupal\canvas_override\Plugin\...\CanvasOverrideConstraintValidator
arguments:
- '@canvas_override.inner_constraint_validator'
Constants
| Constant | Value | Location |
|---|---|---|
CANVAS_OVERRIDE_FIELD_NAME |
field_canvas_layout |
canvas_override.module |
Next Steps
- Services and Extension Points — Detailed service documentation
- API Reference — Key classes and methods