Getting Started with Drupal Template Customization for Beginners

Introduction

Having designed custom themes for over 15 websites using Drupal, I have seen how template customization can elevate user experience and reduce friction for visitors. Customizing Drupal templates is not just about aesthetics; it's about creating a seamless user journey that can improve engagement and conversions.

Drupal 10 (released December 2022) offers an improved theming system and modern tools for building accessible, responsive sites. This guide walks you through practical template customization tasks: creating a sub-theme, overriding Twig templates, and building a small custom block module so you can apply changes to a real project.

By the end of this tutorial you'll be able to customize Drupal templates effectively, integrate CSS frameworks, and follow security and performance best practices for production-ready themes.

Introduction to Drupal and Its Template System

Overview of Drupal

Drupal is a flexible content management system (CMS) that powers many high-traffic websites. Its modular architecture enables you to extend functionality via modules and control presentation via themes. Separating content from presentation through the theme layer simplifies maintenance and updates.

  • Content Management
  • Modular Architecture
  • Custom Themes
  • Extensible Functionality

To inspect your Drupal environment status, use Drush (commonly used with Drupal 9/10):

drush status

Drush is typically installed via Composer (Composer 2 required for modern Drupal projects). Example versions commonly used in Drupal 10 projects: Composer 2.x, Drush 11.x.

Understanding the Basics of Drupal Themes

What Are Themes?

Themes control layout, markup, and asset inclusion (CSS/JS). Drupal themes provide Twig templates (.html.twig), libraries (.libraries.yml), and an info file (.info.yml) describing the theme to Drupal.

  • Theme Hierarchy and inheritance
  • Template files (.twig)
  • Asset management via .libraries.yml
  • Sub-themes to preserve parent updates

List installed themes with Drush:

drush theme:list

Setting Up Your Drupal Environment for Customization

Preparing Your Local Environment

Recommended local tooling for Drupal 10 development:

  • Composer 2.x to manage project dependencies
  • Drush 11.x for CLI tasks
  • Git for version control (GitHub or Bitbucket for remote hosting)
  • Local PHP stack: DDEV, Lando, or platform-agnostic stacks like XAMPP/MAMP if preferred

Example: Create a Drupal project with Composer:

composer create-project drupal/recommended-project mysite
cd mysite
composer require drush/drush:^11

Install Drupal using the web installer or Drush site-install command:

drush site:install standard --account-name=admin --account-pass="StrongPass123"

Enable Twig debug mode (recommended during development)

Enabling Twig debug early in development helps you discover theme suggestions and confirm which template file to override. The recommended, reversible approach is:

  1. Copy the example services file to a development file (from project root):
    cp sites/example.services.yml sites/development.services.yml
  2. Edit sites/development.services.yml and set the Twig parameters:
    parameters:
      twig.config:
        debug: true
        auto_reload: true
        cache: false
  3. Enable the file in your settings.php (add or uncomment):
    $settings['container_yamls'][] = DRUPAL_ROOT . '/sites/development.services.yml';

With Twig debug enabled you'll see HTML comments in rendered pages listing available theme suggestions and template file locations. Remember to disable debug and re-enable Twig caching before deploying to production.

Integrating CSS Frameworks

This section shows practical, production-minded ways to integrate a CSS framework such as Bootstrap into your Drupal sub-theme. Two common approaches are using a package manager (recommended for reproducible builds) or using a CDN (simple and fast for prototypes).

Recommended: Install via npm (reproducible builds)

Install a specific Bootstrap release into your front-end build assets. Example commands and versions used in many projects: Node.js 18.x, npm 9.x, Bootstrap 5.3.2.

# from your theme root
npm init -y
npm install bootstrap@5.3.2 --save-prod
# or use yarn: yarn add bootstrap@5.3.2

Then reference the packaged CSS from your theme's .libraries.yml. Use type: file for local paths that will be served from the theme (do not use type: external for node_modules paths you copy or bundle into your theme).

Example my_sub_theme.libraries.yml snippet (note type: file for local package files):

global-styling:
  css:
    theme:
      css/style.css: {}
      ../node_modules/bootstrap/dist/css/bootstrap.min.css: { type: file }
  js:
    ../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js: { type: file }

Notes:

  • Copying or bundling vendor assets into your theme's build output is often preferable for production deployments (use a build step such as webpack, gulp, or a simple copy task) so your webserver serves them from the theme directory.
  • Pin exact package versions (e.g., bootstrap@5.3.2) in package.json to ensure reproducible builds.

Simple: Use a CDN (quick prototypes)

For quick prototypes you can attach a remote CSS file. To avoid hardcoding a long CDN URL in documentation, visit the framework's site (for Bootstrap, see https://getbootstrap.com/) to copy the exact CDN link for the release you want. Then add it as an external CSS entry in your .libraries.yml.

Attaching the library

Attach globally via your theme's .info.yml or attach per-template with Twig:

{{ attach_library('my_sub_theme/global-styling') }}

Security considerations:

  • Prefer serving assets from your own CDN or build output for integrity controls in production.
  • If using a CDN in production, use Subresource Integrity (SRI) and pin the exact release.

Exploring Theme Layer and Template Files

Understanding the Theme Layer

Navigate to /themes or /themes/custom to explore theme directories. Twig templates control rendering; typical files include:

  • page.html.twig β€” page wrapper (header, content, footer)
  • node.html.twig β€” node (content) output
  • block.html.twig β€” block output

Reference the official project resources at the Drupal home page: https://www.drupal.org/

Note on Twig versions: Drupal 10 uses a modern Twig integration (Twig 2.x/3.x family depending on your environment). Twig in Drupal auto-escapes variables; avoid using |raw unless you fully trust and sanitize the content. For reference and fuller coverage of Twig filters and functions, see the Twig project homepage: https://twig.symfony.com/ (see Documentation β†’ Filters/Functions).

Making Your First Customization: Step-by-Step Guide

Create a Sub-Theme (Practical)

Create a sub-theme to preserve parent theme updates. Using a starter theme (e.g., Classy or Starterkit) is recommended.

Drupal 10 introduced the Theme Starterkit generator (Drush or manual scaffold). Example file basics for a sub-theme named my_sub_theme:

  • my_sub_theme.info.yml
  • my_sub_theme.libraries.yml
  • templates/page.html.twig (override)
  • css/style.css referenced in libraries

Example minimal my_sub_theme.info.yml:

name: 'My Sub Theme'
type: theme
base theme: classy
description: 'A simple sub-theme of Classy for demos.'
core_version_requirement: ^10
libraries:
  - my_sub_theme/global-styling
regions:
  header: 'Header'
  content: 'Content'
  footer: 'Footer'

Example my_sub_theme.libraries.yml (local CSS file and potential bundled vendor files):

global-styling:
  css:
    theme:
      css/style.css: {}
  js: {}

Provide a very simple CSS rule so beginners see an immediate visual change. Create themes/custom/my_sub_theme/css/style.css with:

/* themes/custom/my_sub_theme/css/style.css */
body {
  background-color: #f0f0f0; /* simple visible change for beginners */
}

Attach the library in your page.html.twig or via .info.yml as shown above. After adding files, rebuild caches (you can use Drush or the admin UI).

Cache guidance: Rebuild caches when you add/modify templates or hook implementations β€” for example, run a cache rebuild (drush cr) or use the admin UI (Configuration β†’ Development β†’ Performance β†’ Clear all caches).

Concrete Project: Build a Custom Dynamic Block

This small project creates a module that provides a dynamic block rendered through a custom Twig template. It demonstrates PHP plugin code, hook_theme(), and a Twig override.

1) Module scaffold

Create folder: /modules/custom/custom_block_demo

custom_block_demo.info.yml

name: 'Custom Block Demo'
type: module
description: 'Provides a demo block and Twig template.'
core_version_requirement: ^10
package: Custom

2) Block plugin

File: /modules/custom/custom_block_demo/src/Plugin/Block/CustomDynamicBlock.php

 \Drupal::service('datetime.time')->getCurrentTime(),
      'message' => $this->t('Hello from CustomDynamicBlock'),
    ];

    return [
      '#theme' => 'custom_block_demo_template',
      '#items' => $items,
      // Demo: minimal cache shown below. See production example after this snippet.
      '#cache' => [
        'max-age' => 0,
      ],
    ];
  }
}

Production-friendly caching example

Instead of disabling caching (max-age 0), set appropriate cache contexts/tags and a sensible max-age for production. Example render array snippet to use in production:

return [
  '#theme' => 'custom_block_demo_template',
  '#items' => $items,
  '#cache' => [
    'contexts' => ['url.path'],       // vary by URL path if content differs per path
    'tags' => ['node_list'],         // invalidate when node lists change
    'max-age' => 3600,               // cache for 1 hour (adjust to your needs)
  ],
];

Choose cache contexts and tags relevant to your block's data sources (for example, user.roles if output varies by role, or route.name if it varies by route).

3) Register the Twig template with hook_theme()

File: /modules/custom/custom_block_demo/custom_block_demo.module

 [
      'variables' => ['items' => NULL],
      'template' => 'custom-block-demo',
      'path' => $path . '/templates',
    ],
  ];
}

4) Twig template

Create: /modules/custom/custom_block_demo/templates/custom-block-demo.html.twig

{# templates/custom-block-demo.html.twig #}

{{ items.message }}

Server timestamp: {{ items.time }}


5) Enable the module and place the block

Enable with Drush or the UI. Example:

drush en custom_block_demo -y

Place the block at /admin/structure/block or via the Block Layout UI.

Security and performance notes

  • Twig auto-escapes variables by default. Only use |raw for trusted, sanitized content.
  • Set appropriate cache metadata for blocks in production (cache contexts, tags, max-age) instead of disabling cache. Example: 'contexts' => ['url.path'] or 'tags' => ['node_list'] where appropriate.
  • Avoid calling heavy services on every render; use cache tags, lazy builders, or background jobs for expensive operations.

Troubleshooting Common Issues

Beginners often face predictable issues when customizing themes. This section lists symptoms, likely causes, and fixes.

1) Template changes not visible

  • Cause: Drupal caches Twig templates and render arrays.
  • Fix: Rebuild caches β€” you can use the admin UI (Configuration β†’ Development β†’ Performance β†’ Clear caches) or from CLI: drush cr. When editing templates frequently during development, enable Twig debug mode using a development services file (see earlier). Disable template caching locally while developing.

2) Theme not applying after enabling

  • Cause: Missing .info.yml or incorrect directory structure.
  • Fix: Ensure your_theme.info.yml exists, has core_version_requirement, and the directory resides in /themes/custom/your_theme. Clear caches and check Admin β†’ Appearance.

3) CSS/JS not loading

  • Cause: Libraries not declared or not attached.
  • Fix: Declare assets in your_theme.libraries.yml and attach either via #attached in render arrays or from Twig with {{ attach_library('your_theme/global-styling') }}. If you reference files from node_modules, ensure your build step copies them into the theme or use type: file and serve them from a path Drupal can access.

4) Twig template override not picked up for a field/block

  • Cause: Incorrect template name or missing theme suggestion.
  • Fix: Use Twig debug mode to see theme suggestions (enable Twig debugging as described earlier). Check the HTML comments that indicate available suggestion names and create the corresponding file in your theme's templates directory.

5) Common CLI pitfalls

  • If Drush commands fail, ensure you're running them from the project root where vendor/bin/drush is available (or use the globally installed Drush if configured).
  • Composer-managed projects require running composer install after pulling changes that update composer.json.

Best Practices and Resources for Ongoing Learning

Development Best Practices

  • Version control all theme and module code (Git). Use branches for features and PRs for reviews.
  • Keep logic out of Twig; use preprocess functions (.theme files) to prepare variables for templates.
  • Declare assets in .libraries.yml and attach them explicitly to limit asset bloat.
  • Set proper cache metadata (contexts, tags, max-age) for dynamic content.

Authoritative starting points (root domains):

  • Drupal project home: https://www.drupal.org/ β€” use the site's search to find the Theming Drupal guide and other theming references.
  • Twig project home: https://twig.symfony.com/ β€” see Documentation β†’ Filters/Functions for filter and function references.
  • Bootstrap (framework home): https://getbootstrap.com/ β€” copy the CDN or installation instructions from the site for the exact release you require.

When collaborating, use code reviews and automated checks (PHPStan, PHPUnit, and a linter) to maintain quality. For front-end asset pipelines, consider linters like stylelint and build-time bundlers such as webpack or Vite.

Key Takeaways

  • Drupal's theme layer relies on Twig templates and a clear file structure (.info.yml, .libraries.yml, templates/).
  • Use sub-themes to preserve parent theme updates and keep changes maintainable.
  • Prepare variables in preprocess functions, keep Twig presentation-only, and respect Twig auto-escaping for security.
  • Manage assets via libraries and set proper cache metadata for better performance in production.

Frequently Asked Questions

What is the best way to start customizing Drupal templates?
Start by enabling Twig debugging and creating a simple sub-theme. Inspect theme suggestions in the HTML comments generated by Twig debug to find the correct template to override. Practice by overriding one template (e.g., node.html.twig) and using preprocess functions in your theme's .theme file to modify variables.
How do I manage CSS and JavaScript files in my custom theme?
Declare CSS and JS in your_theme.libraries.yml and attach them with {{ attach_library('your_theme/library-name') }} in Twig or with a render array's #attached property. The legacy drupal_add_library() is deprecated β€” prefer libraries.yml and attach methods. For reproducible builds, install front-end dependencies via npm and reference the dist files or bundle them into your theme build output.

Conclusion

Customizing Drupal templates is a powerful way to shape user experience. This guide provided practical steps: set up your environment, scaffold a sub-theme, override Twig templates, and implement a small custom block module. Follow security and performance best practicesβ€”avoid disabling caching in production and sanitize any dynamic output.

Continue learning by checking the official Drupal project site and practicing with small modules and theme changes in a local environment. Hands-on work is the fastest path to confidence.

Isabella White

Isabella White is Data Science Student with 6 years of experience specializing in ML core concepts, Python for data science, pandas, numpy. Isabella White is a Data Science Student with 6 years of hands-on experience in computer programming, graphics, and web development. Despite being a student, she has built an impressive portfolio covering multiple domains including programming fundamentals, visual computing, and modern web technologies. Her work demonstrates a strong foundation in computer science principles combined with practical implementation skills across various programming languages and frameworks.


Published: Jul 20, 2025 | Updated: Dec 26, 2025