Overview

So I created (by hand-crafting) a content template for nodes in the teaser view mode. It works just fine, but with one major gap -- how can I link to the entity being displayed? There's no way to do this in a component tree. There's no adapter I can find for this, and entities don't have any field or property for this which I could map to a component prop.

Help?

Proposed resolution

Create a new prop source, called host-entity-url, which returns...the canonical URL of the host entity being used to render the content.

CommentFileSizeAuthor
#30 Screenshot 2025-10-28 at 2.37.56 PM.png129.91 KBwim leers

Issue fork canvas-3545859

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

phenaproxima created an issue. See original summary.

phenaproxima’s picture

My stop-gap measure has been to create a computed url field for nodes which returns the canonical URL of the node: https://git.drupalcode.org/project/drupal_cms/-/blob/2.x/drupal_cms_help... and https://git.drupalcode.org/project/drupal_cms/-/blob/2.x/drupal_cms_help....

phenaproxima’s picture

Assigned: Unassigned » phenaproxima

Per @effulgentsia's suggestion, the computed field should be ported into Canvas.

wim leers’s picture

Component: … to be triaged » Page builder
Related issues: +#3543408: Allow content templates to contain adapted prop sources

This could've been avoided if we had #3543408. See #3543408-3: Allow content templates to contain adapted prop sources.

(Please help prevent issue sprawl of hard-to-connect issues! 🙏)

wim leers’s picture

Per #3 😊 Looking forward to your MR!

phenaproxima’s picture

Title: In a content template, how can I link to an entity? » Add a computed `url` field on nodes which returns the canonical URL of the node
Issue summary: View changes
phenaproxima’s picture

Assigned: phenaproxima » Unassigned
phenaproxima’s picture

Status: Active » Needs review
Issue tags: -Needs title update, -Needs issue summary update
wim leers’s picture

Status: Needs review » Needs work

tedbow made their first commit to this issue’s fork.

tedbow’s picture

I tried to manually tests this. I merged this with 1.x but I couldn't find a component that I could link a prop to this field. Is there an existing one? I enabled `sdc_test_all_props` but it didn't have a prop I could find either. Since this mainly for content templates it seems reasonable we should be able to manually test this before merging

Also is there a reason not to create an adapter instead of base field. Won't a base field have the side effect of it being able to used in other module UI's but an adapter won't?

phenaproxima’s picture

I had originally implemented this as an adapter in Drupal CMS's shim module, but was emphatically told that we cannot use adapters because if they are present in any component's input in a content template, it will crash the UI.

I can't confirm or deny that, but it was a scary enough warning that (at @effulgentsia's suggestion), I changed the approach to use a computed field instead. That has worked for us, and is implemented in the shim module.

Having said that, the true solution is probably an adapter, but if Canvas doesn't support them right now, that leaves us in a tough spot.

wim leers’s picture

Choices to evaluate

I was asked to check whether:

  1. we want to pursue THIS MR's approach (new computed field)
  2. or we want to pursue bringing a subset of AdaptedPropSources into Canvas after all (see #3543408: Allow content templates to contain adapted prop sources)
  3. … or some other alternative

Evaluation

Of those:

  • #1 is simplest — aka this MR is the simplest because it's using all existing infrastructure
  • #3 one thing might be simpler still — rather than adding a new computed field, we could add a new computed canonical_url property to the "ID" entity key's field, which would return the /node/1 URL (without path processing), and canonical_url_alias (with path processing). That reduces chances of disruption for contrib, because the set of fields on every Node object remains exactly the same. (Contrib/custom modules are more likely to be iterating over all fields of an entity type+bundle than over the field properties on a particular field instance.)
  • #2 would be nicest, because both #1 and #3 just exacerbate future update path pains: it causes even more data needing updating when we'll finally have adapters 🫣 (which is why I opened #3536115: Allow use of same-shape-adapters ahead of general adapter support in #3464003, but it never became a high enough priority for me to work on it 😭)

What if there's a 4th option?

What if … we added a new type of prop source, called HostEntityUrlPropSource? Because DynamicPropSource is about fetching fields/field properties (Typed Data, basically), whereas this is about calling $entity→toUrl('canonical')→setAbsolute(). There's prior art for generating special URLs dynamically, too: DefaultRelativeUrlPropSource

See https://git.drupalcode.org/project/canvas/-/merge_requests/199. WDYT? 😊

phenaproxima’s picture

I'll get to the IS update later.

phenaproxima’s picture

Title: Add a computed `url` field on nodes which returns the canonical URL of the node » Add a `host-entity-url` prop source for linking to the host entity (i.e., a permalink)
Issue summary: View changes
Issue tags: -Needs issue summary update

phenaproxima’s picture

I can confirm that this does exactly what is intended; I tested it out in the context of Drupal CMS 2.x's card content template for blog posts, which is where we're currently using the computed url field as a shim.

I did at least fix the linting issues. I'm not sure what else would be needed for this to be mergeable.

effulgentsia’s picture

Component: Page builder » Shape matching
Status: Needs work » Reviewed & tested by the community

Code looks great. Tests pass. #19 confirms it addresses what Drupal CMS needs.

phenaproxima’s picture

Status: Reviewed & tested by the community » Fixed

Thank you! This is a nice solution.

Now that this issue is closed, please review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, please credit people who helped resolve this issue.

wim leers’s picture

Status: Fixed » Needs work
Issue tags: +Needs tests, +Needs documentation

Thanks for that test coverage! 😊


But … why was this merged?!?! It was clearly not ready. I spent a few mins hacking something together sprinkled with many @todos, @phenaproxima added minimal test coverage, and … that's it.

  1. Even
        // @todo if $content_entity_type_id has a `canonical` URL template, also offer `host-entity-url:absolute:canonical` as a choice. Something like:
        // $suggestions[] = (new HostEntityUrlPropSource())->asChoice();
    

    was merged as-is. 😅 Meaning no human can use it!

  2. And there's many more @todos beyond that.
  3. Doc updates are missing throughout.
  4. \Drupal\Tests\canvas\Kernel\Config\ContentTemplateValidationTest::setUp() should've been updated to use this, to prove it works end-to-end
  5. \Drupal\Tests\canvas\Kernel\Config\PatternValidationTest::testInvalidComponentTree() should've gotten a test case that tries to use it, to prove you can't use it in Pattern
  6. Same for PageRegion

This was rushed in. 👎

wim leers’s picture

Ah, I found now in private chat that #24.1 was skipped because:

@tedbow: but the issue summary doesn’t actually say whether the goal is to expose this in the UI. it just mentions handcrafting templates
@effulgentsia: No UI for this issue.

Which surprises me, but I guess it does mean Drupal CMS is unblocked 🚀

That does mean this issue should definitely be open again though, for A) tests, B) docs, C) updating our shape matching logic.

I think A + B could largely be handled by @phenaproxima. I am probably the only one who can do C.

phenaproxima’s picture

Assigned: Unassigned » wim leers
Status: Needs work » Needs review

Okay, responding to the points in #24:

  1. I tried uncommenting that, and it broke tests in very strange ways that, to me, are indecipherable. I assume this is what you were referring to when you said "I am probably the only one who can [update our shape matching logic]". So I left that as-is.
  2. I opened #3551455: HostEntityUrlPropSource should be able to support absolute or relative URLs, URL options to robust-ify and round out the capabilities of HostEntityUrlPropSource, and updated the @todos to refer to it. You are correct that what is in 1.x HEAD right now is enough to unblock Drupal CMS and remove any need for us to ship a computed url field as a shim -- for which we are very grateful!
  3. Made the doc updates you mentioned. I'm not sure if I missed anything, or if there's any docs that I'm unaware of which also need to be updated. Happy to do that if you point out where I should look.
  4. Updated as you suggested.
  5. Ditto.
  6. Ditto.

I'm assigning to you to handle the shape matching piece of it, which will presumably also need tests and documentation, so leaving those issue tags in place.

lauriii’s picture

Wouldn't the benefit of a computed URL field be that the URL would be available in JSON:API response too? It's currently pretty difficult to get the URL for a given entity from JSON:API. IMO it would make sense to provide it as an API there too.

wim leers’s picture

IMO it would make sense to provide it as an API there too.

That'd be a core issue. It's not Canvas' responsibility to do this on behalf of all entities in all of Drupal.


#27:

  1. 👍
  2. 👍, and thanks for creating that follow-up issue!
  3. Thanks!
  4. 🙏 Thanks!
  5. 🙏 Thanks!
  6. 🙏 Thanks!

I'll get on reviewing this 👍

wim leers’s picture

Priority: Normal » Major
StatusFileSize
new129.91 KB
  1. This is for sure the hard part, because FieldForComponentSuggester (as the name implies) is only designed for suggesting field data (field properties in fields on the entity, or from a referenced entity). But due to the MR that was merged here, it needs to be reorganized significantly, to allow for not only DynamicPropSources but also HostEntityUrlPropSources.

    It doesn't help that all of #3551455: HostEntityUrlPropSource should be able to support absolute or relative URLs, URL options was deferred to a follow-up: that just makes it harder to get everything right, because currently HostEntityUrlPropSource is de facto a one-trick pony, whereas it clearly will need to accept configurability in the future (even if only to be able to generate a relative OR an absolute URL, whereas currently it ALWAYS generates an absolute URL).

  2. 👍 — but … ⚠️ since I wrote #24, @f.mazeikis pointed out an important flaw in (Static|Dynamic)PropSource, and the same flaw also applies to this new prop source: it's failing to bubble cacheability (an absolute URL varies by the url.site cache context) → #3554184: Bubble cacheability of resolved props values and access results + `PropSourceBase::evaluate()` does not return cacheability at all should tackle it for all prop sources at once
  3. Lots of docs still needed updating. Fixed in https://git.drupalcode.org/project/canvas/-/merge_requests/222/diffs?com... + https://git.drupalcode.org/project/canvas/-/merge_requests/222/diffs?com...
  4. End-to-end tests were still missing, added those in https://git.drupalcode.org/project/canvas/-/merge_requests/222/diffs?com...

I've got #1 WIP locally, but am currently constantly distracted by a very sick baby to take care of 😅 Still, progress is being made:

phenaproxima’s picture

I think we should rename host-entity-url to permalink.

That's a much clearer, more standard name for what it does...and it's also completely accurate, regardless of which link template(s) it ultimately supports. For example, this is so clear:

# Implies 'canonical'
sourceType: 'permalink'

or:

# Still pretty clear!
sourceType: 'permalink:edit-form'

The word "permalink" also implies that an absolute URL will be the default, which is true (and IMHO, the correct behavior). If we later wish to support relative URLs, we could make that explicit: permalink:canonical:relative.

phenaproxima’s picture

Didn't find anything terribly wrong here but I do have some thoughts. And I personally think the time to rename is now, before this goes into RC and stable releases of Canvas.

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs tests, -Needs documentation

Went over this with Wim over Zoom. We are broadly on the same page about making Canvas's internals more grokable, but that's not in the scope of this issue. So some of the doc comment nits and minor feedback can wait until later, when the whole PropSource system is more internally consistent.

Has tests. Has docs. Shippety-ship it.

wim leers’s picture

Assigned: wim leers » Unassigned

Merging, given @phenaproxima's +1.

Three follow-ups:

  1. MR !222 causes the new choice to appear in the ContentTemplate UI, but clicking it has no effect, due to a bug in the UI — the child issues of #3541000: [META] Content templates UI for 1.0: only nodes, no exposed slots, no replacement for the view mode/display UI that built the "select a suggestion from the server-side provided list of suggestions" specifically was designed to have the client side treat the suggested sources as opaque JSON objects (see #/components/schemas/StructuredDataForPropShapeHierarchicalSuggestionPathWithChoice in /openapi.yml). Apparently the /ui code does not do that. Follow-up bug report filed: #3555068: Linking a `HostEntityUrlPropSource` to populate a `type: string, format: uri|uri-reference` in a `ContentTemplate` has no effect — until that is fixed, the choice will appear in the UI but clicking it won't do a thing:

  2. Make this new prop source actually have flags/options/settings to configure what kind of URL to generate: #3551455: HostEntityUrlPropSource should be able to support absolute or relative URLs, URL options.
  3. (long predates this issue, but this issue made that more obviously necessary) #3523446: Rename `FieldForComponentSuggester` to `PropSourceSuggester`
wim leers’s picture

Title: Add a `host-entity-url` prop source for linking to the host entity (i.e., a permalink) » Add a `host-entity-url` prop source for linking to the host entity

Fixing the title, because what the merged MR !199 landed is specifically not a permalink (because path processing is enabled by default, and path aliases are literally the opposite of permalinks — unless you install the contrib Redirect module) — see #3551455-3: HostEntityUrlPropSource should be able to support absolute or relative URLs, URL options.

wim leers’s picture

Status: Reviewed & tested by the community » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

wim leers’s picture

Looks like @lauriii thinks this MR didn't go far enough: #3555413: Allow linking to referenced entities: add `url` property to `EntityReferenceItem::propertyDefinitions()`. Which in turn may call into question the entire premise of the new prop source. As I wrote in #15 here: aka this MR is the simplest because it's using all existing infrastructure — which given @lauriii's latest remark over there actually is literally what he expects.

Which is totally reasonable, but it would mean that a new prop source makes less sense than adding a computed base field to every entity type — aka what this issue originally did 😅

IOW: why go through the pains of simulating the presence of entity URLs in the right place in the Typed Data tree (which is the path we're currently on) when we could much more simply just expose it as a computed field with multiple properties to offer multiple kinds of URLs?

wim leers’s picture

phenaproxima’s picture

To recap how we got here...

  1. Drupal CMS needed this functionality, 100%. We have listing pages that display cards (teasers) of nodes, and link the titles to the full node: an extremely common pattern that will be necessary for Canvas to be useful to site builders.
  2. Originally, Drupal CMS used its shim module to provide an adapter plugin to show the entity URL. This was strongly pushed back on, since I was warned that the use of adapters would break the Canvas UI.
  3. So we switched to a computed field (I think this might have been @effulgentsia's suggestion). That worked fine, but it was felt that the feature really had to be in Canvas, not Drupal CMS's helper module, or we'd have to maintain it forever.
  4. Then you suggested that a computed field was going to be painful because it alters the data model (which made sense at the time), and invented the HostUrlEntity prop source, which was rushed in so that Drupal CMS could take advantage of it by Canvas' RC.

Now, if we actually do need to traverse a data tree to get to URLs at any point in the tree, a new computed base field does make sense. I assume we'd implement it on nodes to start with, then expand that to other entity types as necessary.

I can see us having use for something like this: $node->url->canonical->setAbsolute()->setOption('query', ['foo' => 'bar'])->toString()

I could also see (hopefully) core adopting that at some point in the future.

So...count me in, I guess.

wim leers’s picture

$node->url->canonical->setAbsolute()->setOption('query', ['foo' => 'bar'])->toString()

Exactly!

  1. Well, almost — we wouldn't be able to return a \Drupal\Core\Url object as a property directly. So the ->setAbsolute()->setOption('query', ['foo' => 'bar'])->toString() part is not realistic I think, because how would we know to call those methods? ⇒ it should be available as a field property directly, with no choices available. So:
    $node->url->canonical_absolute
    

    +

    $node->url->canonical_relative
    

    (with url the new base field on all entity types, added by Canvas, and canonical_absolute and canonical_relative the 2 field properties.

  2. … and thanks to the static \Drupal\Core\Field\FieldItemInterface::propertyDefinitions(FieldStorageDefinitionInterface $field_definition) being able to determine the target entity type ID using the given field storage definition, it's possible to determine which link relations/templates exist for a given entity type, and hence expose also edit-form_absolute+<code>edit-form_relative, and so on. AKA what #3551455: HostEntityUrlPropSource should be able to support absolute or relative URLs, URL options was about.
    (Plus perhaps a special case for the canonical URL with + without path processing aka path aliases?)
wim leers’s picture

Status: Fixed » Patch (to be ported)
wim leers’s picture

Title: Add a `host-entity-url` prop source for linking to the host entity » [Likely Needs revert] Add a `host-entity-url` prop source for linking to the host entity
Status: Patch (to be ported) » Needs work

Similar sentiment @phenaproxima here in #43 and from @penyaskito at #3555413-12: Allow linking to referenced entities: add `url` property to `EntityReferenceItem::propertyDefinitions()`.

⇒marking unfixed, and flagging the likely need for a revert


⚠️ Note that this will make any future update path towards adapters much more painful. Because anything that uses a Canvas-altered-in field property that SHOULD use an adapter would then need to be updated later.

A simpler, clearer example of that general problem is an SDC's need (e.g. #3547303: The hero-blog component's `date` prop needs to be a timestamp) for a type: string, format: date prop to be populated by a Node's created</em> field. That field contains a UNIX timestamp, which is a <code>type: integer. That was intended to be solved by \Drupal\canvas\Plugin\Adapter\UnixTimestampToDateAdapter, but adapters are out of scope for 1.0.

But @lauriii just told me in a meeting that he expects this to work in 1.0, despite that not being part of #3541000: [META] Content templates UI for 1.0: only nodes, no exposed slots, no replacement for the view mode/display UI. 😅😬

So we might just have to accept that such a painful update path will be necessary. Thanks to the ComponentAudit service, we'll know how to find affected component instances (per revision, even). But it'll mean we won't be able to drop whichever computed field properties (or fields) we add in Canvas 1.x until at least 2.x, and realistically until much later.

Is that something we're willing to commit to? For content templates, the update path wouldn't be too painful (at most dozens of entities to update), but for content entities it'd be very complex.

wim leers’s picture

To give a sense of the complexity & scale of such an update path: essentially, we'd have to update potentially millions of rows (every component instance DB row that uses a computed field/field property added by Canvas) from something like:

{
"heading_text": "Sensational title!",
"date": 1770856866,
…
}

(👆 stored for a single component instance in its inputs column of the field table for a content entity)
+

expression: ℹ︎timestamp␟as_string_format_date

(👆 stored in the Component config entity)

to:

expression: ℹ︎timestamp␟value

+ a change to the per-component instance inputs column for every revision that uses it, to convey that this uses a static prop source + adapter, so something like:

{
"heading_text": "Sensational title!",
"date": {
  "sourceType": "adapter:day_count",
  "adapterInputs": {
    "unix": {
      "sourceType": "static",
      "expression": "ℹ︎timestamp␟value",
      "value": 1770856866
    }
  }
}
wim leers’s picture

Title: [Likely Needs revert] Add a `host-entity-url` prop source for linking to the host entity » Add a `host-entity-url` prop source for linking to the host entity
Status: Needs work » Fixed

Discussed with @effulgentsia in detail.

I discussed with him the full range of possible implementation choices (they're listed in #3555413: Allow linking to referenced entities: add `url` property to `EntityReferenceItem::propertyDefinitions()`).

Alex independently proposed what I previously proposed at #3555413-5: Allow linking to referenced entities: add `url` property to `EntityReferenceItem::propertyDefinitions()`:

[…] #3545859: Add a `host-entity-url` prop source for linking to the host entity did what it did because we didn't want to add a computed field to all of the entities. But for an entity reference to provide a canonical URL to the referenced empty … that seems kinda reasonable? Probably only the canonical and relative (so: type: string, format: uri-reference) one, and that'd be sufficient for 99% of cases?

(That would also be compatible with #3551455: HostEntityUrlPropSource should be able to support absolute or relative URLs, URL options, because you'd be unlikely to ever want to link to "the edit form of the referenced user/term" — that'd be more likely to only occur for the host entity itself.)

But he phrased it better than I did — this is our joint conclusion, which uses his phrasing:

  1. Let's keep HostEntityUrlPropSource this issue added, only for the host entity. There is indeed a likely future need for e.g. linking to the edit-form, or with/without path processing (path aliases), etc. → we have #3551455: HostEntityUrlPropSource should be able to support absolute or relative URLs, URL options for that.
  2. Its position in the suggestions available to the Site Builder when constructing a ContentTemplate is fine as-is (at the very top of the root level — only if the target prop shape is type: string, format: uri|uri-reference, of course).
  3. Let's add a url field property to \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::propertyDefinitions(), and make it only ever return the canonical URL. This would then simply reuse the existing Typed Data-based DynamicPropSource matching+suggesting logic — no changes needed.
  4. Rationale: a clash of field names (which we'd need to add if reverted HostEntityUrlPropSource and added a computed field instead) is far more likely than adding a computed field property. Any module could be altering base fields, but only one module can alter a field type's properties, which also exceedingly few modules do. This would mean it is feasible to bring this part into core. (Whereas making core auto-add a new (computed) base field to all (content) entity types would be fairly unlikely.)

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

wim leers’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.