Which hook will allow me to change links in a page?

ee flag

Which hook would allow me to change each link (given in <a> tags) in content pages and menus and change their attributes?

I need to implement this in a theme that uses Olivero as parent theme and the obvious MYTHEME_preprocess_links() results in a blank white screen (WSOD).

The only hook I've been able to use to change the page content is this one.

function MYTHEME_preprocess_field(&$variables): void {
    $content = $variables['items'][0]['content'] ?? false;
    $type = $content['#type'] ?? '';

    if ($content && $type === 'processed_text') {
        $dom = new DomDocument();

        $errors = libxml_get_errors();

        if (count($errors) === 0) {
            $edited = 0;

            foreach ($dom->getElementsByTagName('a') as $link) {
leymannx avatar
ne flag
We need to see your code to have the possibility to understand what's wrong with it.
Matthew avatar
ee flag
Thank you for reading my question. :) In this case I'm not certain I have code to share. I've simply been trying a bunch of theme hooks to see which get triggered and which actually give me access to the <a> tags within page contents. I must have tried 50 variations of hooks. I won't be able to remember them all.
cn flag
Depending on what you are doing, it might be better to create an input filter. This will affect the content wherever it is displayed in that filter, but it might be a more robust solution than using a hook in your theme.
ru flag
There is no single "preprocesses all links everywhere" hook. Links in content are mostly field templates (use hook_preprocess_field) or editorial text (use a text filter). If not covered by those two, you have to look for module-specific hooks (like hook_preprocess_menu from menu module). Those are usually documented in MODULE_NAME.api.php of the module
ee flag

With thanks to the commenters I was able to figure out how to edit the DOM in content pages and menus.

Regarding version: Drupal 10.

Replace "MYTHEME" with the machine name of your theme. Open your file to see the name of your theme. It is case-sensitive.

Within your /themes/MYTHEME/ directory you may have a MYTHEME.theme file wherein you are able to declare hook functions. These hooks include those that allow you to preprocess data before it gets build into a template. It is here that you are able to adjust the HTML strings to your needs.

These hooks include a $variables parameter that will eat up all of your available memory if you try to print it via var_dump.

Editing content

Content includes those that you build in /admin/content and those you build in /admin/structure/block/block-content.

The hook used to adjust the HTML of content pages is MYTHEME_preprocess_field(&$variables).

Inspect $variables['entity_type'] to determine if you are editing content or block-content.

The HTML string that will be used to build your content is in $variables['items'][0]['content']['#text']. Adjust that string via string manipulation or DOM manipulation with DOMDocument then overwrite $variables['items'][0]['content']['#text'] with your edited string.

If using DOMDocument you may need to ignore all errors generated by loadHTML() because this feature does not understand HTML5 elements, but it can still work with them.

function MYTHEME_preprocess_field(&$variables): void {
    $content = $variables['items'][0]['content'] ?? false;
    $type = $content['#type'] ?? '';
    // Blocks are the pieces that are not content. These include the sidebar, search, header, and footer.
    $block = $variables['entity_type'] === 'block_content';

    if ($content && $type === 'processed_text') {
        $dom = new DomDocument();

        $dom->loadHTML('<div>' . $content['#text'] . '</div>', $options);
        // Use this variable to inspect the errors when troubleshooting.
        // $errors = libxml_get_errors();

        $parsedHTML = $dom->saveHTML();

        // Errors are fine as long as the HTML string was successfully parsed by DOMDocument.
        if ($parsedHTML) {
            // Your code here to manipulate DOM.

Editing menus

The hook used to adjust the HTML of menus is MYTHEME_preprocess_menu(&$variables).

There are various menus in a Drupal page. These include admin, main, and account.

The menu items to adjust are within $variables['items']. The structure of each element within that array is:

 - menu_link_content:{unique_id}(array)
 -  - is_expanded(boolean)
 -  - is_collapsed(boolean)
 -  - in_active_trail(boolean)
 -  - attributes(object)
 -  - title(string) Sample menu item
 -  - url(object)
 -  - below(array)
 -  -  - menu_link_content:{unique_id}(array)
 -  -  -  - is_expanded(boolean)
 -  -  -  - is_collapsed(boolean)
 -  -  -  - in_active_trail(boolean)
 -  -  -  - attributes(object)
 -  -  -  - title(string) Sample sub-menu item
 -  -  -  - url(object)
 -  -  -  - below(array)
 -  -  -  - original_link(object)

Each menu item may have sub-items. These are saved in the ['below'] key as an array.

If you want to adjust the url the ['url'] element holds a Drupal URL object. Refer to the documentation on how to adjust a Drupal URL object.

Here is an example to get you started:

function adjustMenuItem($item) {
    // Your code to adjust the elements of the $item array.

    // Handle recursive array elements.
    if (isset($item['below']) && is_array($item['below']) && count($item['below']) > 0) {
        foreach ($item['below'] as $key => $value) {
            $item['below'][$key] = adjustMenuItem($value);

    return $item;

function MYTHEME_preprocess_menu(&$variables): void {
    $name = $variables['menu_name'] ?? '';

    if ($name === 'main' && is_array($variables['items'])) {
        foreach ($variables['items'] as $key => $value) {
            $variables['items'][$key] = adjustMenuItem($value);
I sit in a Tesla and translated this thread with Ai:


Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.