Editorial cover graphic with a concentric ring motif in gold on cream.

Custom style definitions were fighting the Canvas cache

What YamlDiscovery changes about defining Canvas styles, and why the old approach kept going stale.

The friction

An editor asked for a new spacing option in a Canvas toolbar’s Style panel. Nothing exotic, just an extra choice between the existing “tight” and “generous” presets. I added it to the plugin class, cleared caches, reloaded the page. The option showed up in the dropdown. I picked it. Nothing happened. The section kept the old spacing until I did a full cache rebuild, at which point it worked, then broke again the next time someone else edited a style definition without knowing they had to do the same thing.

That’s not a bug you can point a debugger at. Everything about the code was correct. The problem was where the definitions lived and how Drupal decided when it was allowed to stop trusting its own cache of them.

How style definitions actually resolved

Canvas Builder’s Style panel doesn’t invent CSS at edit time. Each option (a spacing preset, a colour, a border treatment) is a style definition: a small plugin that maps a human label in the toolbar to a set of CSS custom property values applied to the section. Those plugins are discovered the way most Drupal plugins are discovered, through a plugin manager that extends DefaultPluginManager and, critically, implements CachedDiscoveryInterface.

The definitions themselves were hardcoded: a PHP array, one entry per style, returned from a method on the plugin’s discovery class. That’s a completely normal way to define a small, fixed set of plugins. The trouble was that “small and fixed” stopped being true the moment editors wanted to add their own presets without a deploy, and the discovery class had no way to know that anything on disk had changed, because nothing was reading from disk. The plugin cache wasn’t stale because Drupal’s caching was wrong. It was stale because the thing it was caching, a hardcoded array, genuinely never changed from its own point of view, even when I’d just changed it.

What YamlDiscovery changes

Drupal\Core\Discovery\YamlDiscovery is core’s mechanism for defining plugins in YAML files instead of PHP arrays, the same pattern behind *.libraries.yml and a good chunk of Drupal’s own plugin systems. Swapping the style definitions to it did two things at once, and only one of them was the one I was after.

The first was the obvious win: a style preset became a YAML file a theme could ship, rather than a PHP class a module had to own. Themers could add spacing and colour options without touching PHP at all.

canvas_builder.style_definitions.yml
spacing_relaxed:
label: 'Relaxed'
properties:
'--section-gap': '4rem'
'--section-padding-block': '3rem'

The second, less obvious win was cache correctness. YamlDiscovery reads the filesystem and hands its results to a CacheBackendDecorator. This is the part that actually fixed the bug: Drupal’s plugin cache invalidation is keyed to know when YAML-sourced definitions need re-scanning, in a way a hand-rolled hardcoded array never participated in. The style panel wasn’t failing to invalidate its cache. It had no mechanism to tell the cache system anything had changed, because the cache system only knows to ask discovery classes it recognises as sources of truth. Moving to YamlDiscovery didn’t fix a caching bug so much as it made the plugin manager cacheable correctly for the first time. The previous version had been working by accident, on the assumption that its data never changed, which held right up until an editor asked for one more spacing option.

What it took in practice

The migration itself was small: write the YAML schema, swap the plugin manager’s discovery method, keep the plugin ID format identical so no section on an existing page lost its assigned style. The part that took longer was making sure every existing hardcoded definition round-tripped through YAML with the same computed CSS custom properties, because a silent mismatch there would have been the same class of bug all over again, just moved one layer down.

What is still not fully settled

The open question is where per-editor overrides should live once presets come from themes. If a theme ships spacing_relaxed and an editor nudges it slightly for one section, is that a new definition, an inline override on the block, or a themeable variant scoped to that page? Each answer has a different caching story, and I’d rather get it right once than migrate a second time. If you’ve built per-instance overrides on top of YAML-sourced plugin definitions and found a shape that didn’t reintroduce staleness, I’d like to see it.