According to this Drupal 7 answer, I'm looking for a Drupal 8 solution to simply print the page content within a custom html.twig.html.

namespace Drupal\custom_module\Controller;

use Drupal\Core\Controller\ControllerBase;

class CustomModuleController extends ControllerBase {

  public function content() {

    return [
        '#theme' => 'custommodule_page',
        '#type' => 'page',

This approach seems to be working, but I am not sure how I can remove the core and theme CSS components.

2 Answers 2


You can completely take over the printed page if your return value is an extension of Symfony's Response class.

Here your return value is a render array so Drupal assumes it is to be rendered in a normal HTML page.

This complex looking diagram shows the flow of request to response: D8 Render Pipeline You can read more about the diagram here: https://www.drupal.org/developing/api/8/render/pipeline

The first takeaway on that docs page is

Routes whose controllers return a Response object bypass the pipeline below. They rely directly on the Symfony render pipeline.

In other words, most of the complexity of that diagram covers the case where the return value of a controller is NOT a Response object. Returning a Response object cuts out what Drupal normally does to wrap the render array in the html.html.twig file.

There are a lot of examples in core of direct returning of a response object. Here is one:

public function testFeed($use_last_modified, $use_etag, Request $request) {
  $response = new Response();

  $last_modified = strtotime('Sun, 19 Nov 1978 05:00:00 GMT');
  $etag = Crypt::hashBase64($last_modified);

  $if_modified_since = strtotime($request->server->get('HTTP_IF_MODIFIED_SINCE'));
  $if_none_match = stripslashes($request->server->get('HTTP_IF_NONE_MATCH'));

  // Send appropriate response. We respond with a 304 not modified on either
  // etag or on last modified.
  if ($use_last_modified) {
    $response->headers->set('Last-Modified', gmdate(DateTimePlus::RFC7231, $last_modified));
  if ($use_etag) {
    $response->headers->set('ETag', $etag);
  // Return 304 not modified if either last modified or etag match.
  if ($last_modified == $if_modified_since || $etag == $if_none_match) {
    return $response;

  // The following headers force validation of cache.
  $response->headers->set('Expires', 'Sun, 19 Nov 1978 05:00:00 GMT');
  $response->headers->set('Cache-Control', 'must-revalidate');
  $response->headers->set('Content-Type', 'application/rss+xml; charset=utf-8');

  // Read actual feed from file.
  $file_name = drupal_get_path('module', 'aggregator_test') . '/aggregator_test_rss091.xml';
  $handle = fopen($file_name, 'r');
  $feed = fread($handle, filesize($file_name));

  return $response;
  • Thanks! The "AggregatorTestRssController.php" helped me a lot. Commented Dec 27, 2015 at 19:10

Here is my simple controller for a custom page response:

  namespace Drupal\custom_module\Controller;

  use Drupal\Core\Controller\ControllerBase;
  use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
  use Drupal\Core\Template\TwigEnvironment;
  use Symfony\Component\HttpFoundation\Response;
  use Symfony\Component\HttpFoundation\Request;
  use Symfony\Component\DependencyInjection\ContainerInterface;

  class CustomModuleController extends ControllerBase implements ContainerInjectionInterface {

  protected $twig;

  public function __construct(TwigEnvironment $twig) {
    $this->twig = $twig;

  public static function create(ContainerInterface $container) {
    return new static($container->get('twig'));

  public function content(Request $request) {

    $response = new Response();

    $twigFilePath = drupal_get_path('module', 'custom_module') . '/templates/custom_module_page.html.twig';
    $template = $this->twig->loadTemplate($twigFilePath);

    $markup = $this->twig->loadTemplate($twigFilePath)->render();
    $response->headers->set('Content-Type', 'text/html; charset=utf-8');

    return $response;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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