I find examples of adding a header meta tag, such as using an event subscriber or using hook_page_attachments.

The problem I have is that both of these methods add the tag only in the case of an authenticated user request. The anonymous cache work is done way before either of these methods fire, and even if I could force my change to a higher priority, I don't know that I would want that to happen on every anonymous page request.

In detail, when I subscribe to the response event, and set the header tag, it appears on the rendered page for an authenticated user. However, that tag is apparently not added to cache, because any subsequent anonymous requests do not include that tag. I'm assuming, then, that I need to trigger some mechanism to have the tag added to cache and not simply injected in the header during rendering.

What I want to do is persist a header meta tag in cache so that is included in anonymous requests. The tag value is based on a node field that only needs to be cached when it changes, so the node save event would be the ideal time.

What is the proper way of getting a custom header meta tag into the cache for a specific node?

Here is the code of my subscription, which fires for authenticated traffic, but is not persisted in cache:

namespace Drupal\my_http_injector\EventSubscriber;

use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\Core\Url;class MyHttpInjector implements EventSubscriberInterface {

     * Trigger pulled
    public function onRespond(FilterResponseEvent $event) {
        $response = $event->getResponse();
        // Get the response attachments

        if (is_object($response)) {
            if (method_exists($response, 'getAttachments')) {
                $attachments = $response->getAttachments();
                if (is_array($attachments) &&
                    isset($attachments['drupalSettings']) &&
                    isset($attachments['drupalSettings']['path']) &&
                    isset($attachments['drupalSettings']['path']['currentPath'])) {
                    // Get the requested path
                    $path = '/' . $attachments['drupalSettings']['path']['currentPath'];
                    $params = Url::fromUri("internal:" . $path)->getRouteParameters();
                    $entity_type = key($params);
                    if ($entity_type == 'node') {
                        $node = \Drupal::entityTypeManager()->getStorage($entity_type)->load($params[$entity_type]);
                        if (is_object($node)) {
                            $date = gmdate(DATE_RFC1123, $node->getChangedTime());
                            $response->headers->set('Last-Modified', $date);

     * {@inheritdoc}
    public static function getSubscribedEvents() {
        $events[KernelEvents::RESPONSE][] = ['onRespond'];
        return $events;
  • This is how caching works, the cache delivers pages before any code to build the page (not only the custom code for the header tags) is run and the cached page include the header tags you've set before. So this is the proper way. There seems to be an issue with your specific implementation, please provide more details. – 4k4 Jun 4 '18 at 19:00
  • I've added more detail. – JAyenGreen Jun 4 '18 at 19:06
  • The page cache stores the whole responses and this includes anything you've added in custom code. But I can't tell what is wrong in your case, can be a missing cache tag (although if you display a node the cache tag should already be there) or the response subscriber is not triggered for anonymous traffic, even after a cache clear. – 4k4 Jun 4 '18 at 19:15
  • I've added the code from my subscriber. – JAyenGreen Jun 4 '18 at 19:20
  • You can get the node directly from \Drupal::routeMatch()->getParameter('node'). Anonymous responses have drupalSettings only if you attach the corresponding js libraries. – 4k4 Jun 4 '18 at 19:39

Add the header in an event subscriber

  public function onKernelResponse(FilterResponseEvent $event) {
    $response = $event->getResponse();
    $node = $this->routeMatch->getParameter('node');
    // if you don't have injected $this->routeMatch use \Drupal::routeMatch()
    if ($node && $node instanceof NodeInterface) {
      $date = gmdate(DATE_RFC1123, $node->getChangedTime());
      $response->headers->set('Last-Modified', $date);

and it will get cached for authenticated and anonymous requests.

If you use additional dependencies add them to the response as well:

  if ($response instanceof CacheableResponseInterface) {

This is not necessary in this case because the rendering of the node has already added the cache data.

| improve this answer | |
  • Removed preceding comment. It does work with the onResponse and your use of routeMatch! Thank you! – JAyenGreen Jun 5 '18 at 14:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.