Adding links to a custom entity

Using the method ::toUrl() in custom entities is very handy when using pre-defined URLs, like canonical and add-form. But what if we want to create a new URL and use it in the same way?

Let’s assume we have a custom entity named CustomPost, and that we wish to obtain a “preview” URL using this method, how can we do it?

$post->toUrl('preview');

Let’s start by adding a link handler in the custom entity plugin definition:

links = {
  "collection" = "/admin/content/custom_post",
  ...
  "preview" = "/admin/content/custom_post/preview/{custom_post}",
}

Now we have a URL, but we don’t have a route. Let’s create it in the .routing.yml file. Links defined in custom entities follow this route pattern: entity.<entitytype>._

# Custom entity route for "preview" link
entity.custom_post.preview:
  path: '/admin/content/custom_post/preview/{entity}'
  defaults:
    _title: 'Preview'
    _controller: '\Drupal\mymodule\Controller\MyCustomController::preview'
  requirements:
    _permission: 'access any content'
  options:
    parameters:
      entity:
        type: entity:custom_post

In this route definition, we’re specifying that we want to receive the entity parameter as CustomPost entity. However, this translation will not happen automatically when we have an instance of CustomPost, so we have to tell the custom entity how to do it.

In the CustomPost class, override the method urlRouteParameters to catch the new “preview” link and inject the necessary parameters. Note that the array id must match the name of the route parameter name we want to replace.

  /**
   * {@inheritdoc}
   */
  public function urlRouteParameters($rel) {
    if ($rel === 'preview') {
      return ['entity' => $this->id()];
    }
    return parent::urlRouteParameters($rel);
  }

Finally, we can define the controller action that receives a parameter of type CustomPost

class MyCustomController extends ControllerBase {
  public function preview(CustomPost $post) {
    $build = [];
    // @todo build the preview
    return $build;
  }
}

Clear cache and the new link should be ready to be used.