How to Have Your Drupal 7 Theme Ready for Drupal 9
If you develop for Drupal 9, you probably noticed that theming has improved a lot. And if you're upgrading your theme to D9, given Drupal 7's upcoming end of life in November 2022, you'll find the job considerably easier if you follow some basic but crucial steps.
Let's look at the main theming features on Drupal 9 and how to have your Drupal 7 theme ready for this latest version of Drupal.
👩💻 Want to dig deeper into themes? Enroll in our Advanced Drupal Theming training!
Drupal 9's Main Theming Features
Drupal 9 has many features that make life easier for anyone who wants to upgrade their theme. Here are some of them:
- Semantic HTML5 markup by default
- Improved out-of-the-box accessibility features
- Use of Twig as the theme engine instead of PHPTemplate
- Previous theme_* functions and *.tpl.php files replaced by *.html.twig files
- Default performance-enhancing features (like CSS and JS aggregation)
- Responsive features OOB
- Attachment of JS and CSS assets by using libraries (instead of drupal_add_css and drupal_add_js)
- CSS file structure based on SMACSS & BEM
- CSS3 pseudo selectors
- Classy as base theme
Of all those changes, the most important is probably the theme engine moving to Twig. Twig is a PHP-based compiled templating language, which means that when your web page is rendered, the Twig engine takes the template and converts it into a compiled PHP template stored in a protected directory in sites/default/files/php/twig. Once the compilation is done, the template files are cached for reuse and are recompiled when the Twig cache is cleared.
Your Drupal 9 Theme Migration Checklist
As for the migration of Drupal 7 themes to Drupal 9, there's no tool to do it automatically or to flag what needs changing. So when we do it, we're facing a whole theme rebuild—which can be overwhelming since we'll create a theme from scratch and replicate items manually from our D7 theme. However, having a migration checklist can help smooth the process, so these are the steps we follow for rebuilding our themes:
- Create the folder structure
- Create the necessary files
- Define regions
- Create and load your libraries
- Move theme settings
- Create breakpoints if needed
- Convert templates to Twig
Let's go through each one of them!
Step 1: Create the folder structure
You should locate themes in Drupal 9 in the themes folder of the root Drupal installation. It's recommended to classify them whether they are contri or custom themes, so your structure should look something like:
drupal9 /core /modules /themes /contrib /custom /theme_name
Step 2: Create the necessary files
The only file Drupal needs to recognize your theme is the .info.yml file: the old .info file is not needed anymore as it was replaced by .info.yml. This file follows yaml syntax, so remember to set the indentation to spaces instead of tabs, and respect that indentation. Here's an example, assuming we're creating a theme called simple:
name: Simpletype: themedescription: Drupal 9 version of the D7 Simple theme.core_version_requirement: ^8 || ^9base theme: false
The base theme key introduction is essential here. By using it, you can define the name of another theme you want to inherit from, but if you don't want your theme to be a subtheme, you still need to add the key but with a "false" value.
Step 3: Define your regions
The regions for Drupal 9 themes are defined in the theme_name.info.yml file under the key "regions":
regions: header: 'Header' navigation: 'Navigation bar' highlighted: 'Highlighted' help: 'Help' content: 'Content' sidebar_first: 'First sidebar' sidebar_second: 'Second sidebar' footer: 'Footer'
Step 4: Create and load your libraries
Instead of using the old drupal_add_js to add Javascript assets or drupal_add_css to add CSS files, you should create and load libraries. To create them, you'll need to add a new theme_name.libraries.yml file and define the libraries in it:
global_styles: version: 3.0.1 css: theme: dist/css/styles.min.css: { minified: true } js: dist/js/jquery.visible.min.js: { minified: true } dist/js/theme.min.js: { minified: true } dependencies: - core/jquery - core/drupal
To load the libraries, you have many options:
- Load libraries everywhere: add the library name in the theme_name.info.yml file under the libraries key:
libraries: - theme_name/global_styles
- Load libraries conditionally in the template file: you can use the function attach_library in the template file that you want to load the library in:
{{ attach_library('theme_name/font-awesome') }}
- Load libraries conditionally in a pre-process function: you will use the '#attached' key in the variables received as argument:
$variables['#attached']['library'][] = 'theme_name/lib';
You can find more information about how to override libraries and the differences between D7 and D9 here.
Step 5: Move your theme settings
Theme settings management didn't change, so you can move your theme-settings.php and it should work. Just be careful of dependencies and services you might need.
Step 6: Create breakpoints if needed
If you were using breakpoints in your theme's Drupal 7 version, you had to install the contributed module breakpoints. In Drupal 9, however, you won't need it since this functionality is now part of core. All you need is to create a new theme_name.breakpoints.yml file and add your breakpoints there. For example:
bearskin.small-width: label: small mediaQuery: '(min-width: 0px)' weight: 2 multipliers: - 1xbearskin.medium-width: label: medium mediaQuery: 'all and (min-width: 400px) and (max-width: 800px)' weight: 1 multipliers: - 1xbearskin.large-width: label: large mediaQuery: 'all and (min-width: 1000px)' weight: 0 multipliers: - 1xview raw
Step 7: Convert your templates
With the theme engine moving from PHPTemplate to Twig, there are some significant changes in the templates. Some online tools can help you ease this process, such as https://php2twig.com/, but in general, in the following table, you have a list of old vs. new way of writing templates:
PHPTemplate
Twig
Comments
<?php </p></p><p>/** </p><p> * @file</p><p> * File description</p><p> */</p><p>?></p></td><td><p>{# </p><p>/** </p><p> * @file</p><p> * File description</p><p> */</p><p>#}</p></td></tr><tr><td><p><strong>File names</strong></p></td><td><p>html.tpl.php</p><p>page.tpl.php</p><p>node--[content-type].tpl.php</p></td><td><p>html.html.twig</p><p>page.html.twig</p><p>node--[content-type].html.twig</p></td></tr><tr><td><p><strong>Function names</strong></p></td><td><p>THEME_node_links()</p></td><td><p>node-links.html.twig</p></td></tr><tr><td><p><strong>Templates and pre-process</strong></p></td><td><p>node--event-teaser.tpl.php</p><p>hook_preprocess_node__event_teaser()</p></td><td><p>node--event-teaser.html.twig</p><p>hook_preprocess_node()</p></td></tr><tr><td><p><strong>Assigning variables</strong></p></td><td><p><?php $tags = $content->tags; ?></p><p> </p><p><?php $args = array('@author' => $author, '@date' => $created); ?></p></td><td><p>{% set tags = content.tags %}</p><p> </p><p>{% set args = {'@author': author, '@date': created} %}</p></td></tr><tr><td><p><strong>Printing variables</strong></p></td><td><p><?php print $tags; ?>
<?php print $content; ?>
<?php print $item['#item']['alt']; ?>
{{ tags }}
{{ content }}
{{ item['#item'].alt }}
Conditionals
<?php if ($content->tags): ?> </p><p><?php endif; ?>
<?php if ($count > 0): ?></p><p><?php endif; ?>
{% if content.tags %}
{% endif %}
{% if count > 0 %}
{% endif %}
Check if variables are empty or defined
<?php if (!empty($content->tags)): ?></p><p><?php endif; ?>
<?php if (isset($content->tags)): ?></p><p><?php endif; ?>
{% if content.tags is not empty %}
{% endif %}
{% if content.tags is defined %}
{% endif %}
Loops
<?php foreach ($users as $user) {} ?>
{% for user in users %}
{% endfor %}
Filters
<?php print check_plain($title); ?>
<?php print $title; ?>
{{ title|striptags }}
{{ title|raw }}
Translations
<?php print t('Home'); ?>
<?php print t('Welcome, @username', array('@username' => $user->name)); ?></p></td><td><p>{{ 'Home'|t }}</p><p> </p><p>{{ 'Welcome, @username'|t({ '@username': user.name }) }}</p><p> </p><p>{% set username = user.name %}</p><p>{% trans %}</p><p> Welcome, {{ username }}</p><p>{% endtrans %}</p></td></tr><tr><td><p><strong>Imploding lists</strong></p></td><td><p><?php echo implode(', ', $usernames); ?>
{{ usernames | join(', ') }}
{{ usernames | safe_join(', ') }}
A Note About Templates and Pre-Processes
In Drupal 9, only the base hook of a pre-processing function exists. For example, in D7, you can have a "node--event-teaser.tpl.php" and subsequently a "hook_preprocess_node__event__teaser" pre-processing function available for you in your template.php file. In D8, you can still have the template file for your event teaser, but you only get the base hook, "hook_preprocess_node.", Instead, you'd need to do logic within the base to operate on your event teaser specifically.
You can find more information about the differences between PHPTemplate and Twig here.
For now, you're ready to go and start rebuilding your themes! If you need any more help, here are some extra resources:
- Port your Drupal 7 theme to Drupal 8: https://www.zivtech.com/blog/port-your-drupal-7-theme-drupal-8
- Comparison PHPTemplate to Twig: https://www.drupal.org/node/1918824
- Twigify: https://www.drupal.org/project/twigify
- Port theme: https://www.zivtech.com/blog/port-your-drupal-7-theme-drupal-8
- Conversion instructions: https://www.drupal.org/docs/theming-drupal/drupal-twig-conversion-instructions-tplphp-to-htmltwig
- PHP to Twig: https://phptotwig.com/
>> Need help with your migration?
Our team is ready to help! Get in touch
+ more awesome articles by Evolving Web