Expand URL variable in custom languages.yaml

I want to expand the {{ base_url }} variable below, while using custom translations. However, for some reason I only get the string literal of the PRIVACY_STATEMENT_QUESTION variable. Does anyone know how I can expand this properly?

This is the languages.yaml I’ve created for my custom theme.

        PRIVACY_STATEMENT_QUESTION: "Have you read our <a href=\"{{ base_url ~ '/privacy-verklaring' }}\">privacy statement</a> and do you agree?"
        PRIVACY_STATEMENT_QUESTION: "Heb je onze <a href=\"{{ base_url ~ '/privacy-verklaring' }}\">privacy verklaring</a> gelezen en ga je akkoord?"

I then modified my custom data.html.twig with this:

{% block field_label %}
{# Custom fields #}
{% if index == 'privacy-policy' %}
    <strong>{{ 'THEME_TZM.PRIVACY_STATEMENT_QUESTION'|t|markdown }}</strong>
{% else %}
    <strong>{{ field.label|t|e }}</strong>:
{% endif %}
{# Enf of custom fields #}
{% endblock %}

Which as a result does not expand the {{ base_url }} variable. It looks like this Have you read our <a href="{{ base_url ~ '/privacy-verklaring' }}">privacy statement</a> and do you agree?.

I’m probably missing something I could’ve found in the docs, I tried to Duck for it, but can’t find something.

1 Like

@AquaL1te, It seems you’re making a conceptual mistake by thinking that during Twig processing, Twig will recursively process any Twig code contained in a variable. That’s not going to happen.

If you need to parse a Twig template contained in a string, use:

{{ include(template_from_string("Hello {{ name }}")) }}

For translations, there is a more elegant solution. See the docs about Translations with Variables.

In your case use in languages.yaml:

PRIVACY_STATEMENT_QUESTION: Have you read our <a href="%s">privacy statement</a> and do you agree?

and in Twig:

{% set privacyUrl = base_url ~ '/privacy-verklaring' %}
<strong>{{ 'THEME_TZM.PRIVACY_STATEMENT_QUESTION'|t(privacyUrl)|raw }}</strong>

Note the |raw filter instead of |markdown. You’re not parsing Markdown. But want raw HTML as output.

1 Like

Go the duck :slight_smile:

There might be a better way. I got this working with:

{{ evaluate_twig('THEME_TZM.PRIVACY_STATEMENT_QUESTION'|t)|raw }}

You need to process it through Twig and escape the HTML in there.

1 Like

@hughbris, Now OP might be suffering choice-stress… :slight_smile:

1 Like

I thought of translation placeholders. It really depends on context and preference.

But yes, a selection of options.

1 Like

Interestingly enough I get a URL in the thankyou page, but in my email inbox I get plain text. No URL, but also no HTML/markdown. Just the text. This is kinda acceptable, but I would prefer to email the URL of the privacy policy as well. Is raw the only and best filter for this job? I can of course also try out @hughbris 's solution. But I kinda like this placeholder method.

@AquaL1te, When trying to solve an issue, it is important to be precise. As said in your previous post, remarks like “I get plain text. No URL, but also no HTML/markdown. Just the text.” don’t carry much information.

How does “Just the text” look like? A copy of the HTML element containing “just the text” in the email is more informative.

The email works fine on this end, but that doesn’t help you… I’m not using your environment and given the info you’ve given, I can’t replay yours.


  • Enable the Clockwork debugger
  • Dump intermediate values to the debugger using {{ dump(variable) }}.
    • {{ dump(base_url) }},
    • {{ dump(grav.uri.base) }}
    • dump value of translation
    • dump value after filter |raw has been applied.
    • In data.html.twig, create hardcoded <a> anchor with a fixed url as href and see how that comes across the email.
      It might be that the mailserver or any other intermediate alters the email.

Other then that, I cannot do much for you anymore…

1 Like

Yep agreeing with @pamtbaau, can you show us your email form action? You can generate both plain text and/or html (multipart) emails, which I usually do as it helps with spam detection. SO not sure if you are doing that, it’s a good start to state that.

1 Like

Good call on the content_type, I didn’t have that included. But it also didn’t change the outcome. See below for what I get in the thankyou page and in my inbox. On the thankyou page I get the URL, but not in the email. The font does have a different color, but it’s not clickable. I’ll do the debugging later. I have to check this Clockwork debugger.

Thank you page
Thank you page

Email in inbox
Email in inbox

Below is my contact form.

title: Contact
    name: contact
            label: 'Name'
            placeholder: 'Enter your (first) name'
            autocomplete: true
            autofocus: true
            type: text
                required: true
                message: 'Your (first) name'
            label: 'Email'
            placeholder: 'Enter your email address'
            type: email
                required: true
                message: 'Your email address'
            label: 'Region'
            placeholder: 'Enter your region (city, municipality or province)'
            type: text
                required: false
            label: 'Message'
            placeholder: 'Enter your message'
            type: textarea
                required: true
                message: 'Your message'
            label: 'Have you read our [privacy statement](/privacy-verklaring) and do you agree?'
            type: checkboxes
            markdown: true
                agreed: 'I''ve read it and I agree'
                required: true
                message: 'Privacy policy'
            label: Captcha
            type: captcha
            recaptcha_not_validated: 'Captcha not valid!'
            type: honeypot
            type: submit
            value: Send
            type: reset
            value: Reset
        dns-blacklist: true
        captcha: true
            subject: '[Contactformulier] {{ form.value.name|e }}'
            body: '{% include ''forms/data.html.twig'' %}'
            content_type: 'text/html'
            fileprefix: contact-
            dateformat: Ymd-His-u
            extension: txt
            body: '{% include ''forms/data.txt.twig'' %}'
        message: 'Thanks for your message! If you don''t see a message from us in your inbox within a few days, check then your spam folder. Maybe it ended up there unintentionally.'
        display: thankyou
    markdown: true
    twig: true

# Contact form
##### Please leave a question or suggestion, you can also do this via our [social-media](/#contact-nieuws). Also check our [internal search](/search) and [FAQ](/tzm/faq), maybe your question has already been answered.


A screenshot of the thankyou page and email doesn’t really show the HTML element that has been generated…

As said:

A copy of the HTML element containing “just the text” in the email is more informative.

NB. Any reason for including the toplevel process field? Markdown is being process by default and I don’t see any Twig inside the Markdown of the page.

@AquaL1te, Added an extra suggestion in my previous reply #7.

So in the source of the email the output is:

<strong>Have you read our <a href==3D"">privacy statement</a> and do you agree?</strong>

Which explains why the URL is not clickable. Manually including a hard coded URL in HTML does work fine (nu.nl is clickable and opens the website). Which outputs as this in the source of the email:

<strong>Have you read our <a href==3D"">privacy statement</a> and do you agree? <a href=3D"https://nu.nl">tes=

It seems like the base_url is an empty variable. Using base_url_absolute is empty in the output of data.html.twig, but outputs a full https://example.com/en path in a regular page using the Quark theme (which I based my custom theme on). When using the base_url_absolute I get the same output in my inbox. I’ve set absolute_urls: false in system.yaml.

This is what’s going on in the data.html.twig, which is used by the form.

{% set privacy_url = base_url_absolute ~ '/privacy-verklaring' %}
<strong>{{ 'THEME_TZM.PRIVACY_STATEMENT_QUESTION'|t(privacy_url)|raw }}</strong>

About the process stanza. I once copied an example from the docs, it included that part. I’ve removed it now and it indeed makes no difference. I’ll leave it out.

Could it be that the base_url_absolute is not set because for some reason it’s not processed as being on the Grav website, but as something external to it? And thus the base_url_absolute is empty? Because the variable works fine when using it in a regular Grav page.

I hope this is sufficient information. Basically:

  1. Emails can process HTML URLs just fine, since hardcoded manually inserted URLs work fine.
  2. Variables are not expanded in the data.html.twig, but are elsewhere in Grav, such as regular pages.

@AquaL1te, Thanks for the more concrete information. The generated HTML made me dig some further…

  • The fact that base_url is empty doesn’t sound an alarm. When Grav is installed in the root of the domain, base_url is expected to return an empty string.
  • However, “empty” can mean two things,
    • The variable contains an empty string
    • The variable does not exist in which case Twig happily ignores the output without throwing an error.
      Only a {{ dump(variable) }} will show a NULL value in Clockwork when the variable does not exist.
      Hint: Install Clockwork
  • The fact that base_url_absolute does not return anything IS worrying. It should always return a value.
    This means the variable does not exist.

Because if this, I did some research…

  • Grav code: All variables that are available in ‘normal’ page templates, are being passed to template data.html.twig, when creating the thankyou page and email

  • However, according to Twig:

    But as with PHP functions, macros don’t have access to the current template variables.

  • According to Twig again:

    Tip: You can pass the whole context as an argument by using the special _context variable.


  • Pass the context into the macro, by altering the top and bottom line in data.html.twig as follows:
    {% macro render_field(form, fields, scope, context) %}
    {{ macro.render_field(form, form.fields, '', _context) }}
  • Access the variables using context, like:
    {% set privacy_url = context.base_url_absolute ~ '/privacy-verklaring' %}

Final plee: Provide accurate and factual information instead of “it doesn’t work” :wink:


Awesome! These context lines and using context.base_url_absolute were the fix. I’ll for sure share more details in the future. Thanks for all the help and patience.

See also the issue I’ve created: Template 'data.html.twig' does not have access context variables · Issue #569 · getgrav/grav-plugin-form · GitHub

1 Like