The new LookML runtime has been available since Looker 22.6. A "runtime" is the part of Looker that interprets LookML code. The new runtime is faster and checks for more LookML errors than the legacy runtime.
Looker strongly encourages all customers to migrate to the new runtime. The new LookML runtime is able to catch previously overlooked errors, so enabling the new runtime may cause new LookML errors to appear. These errors are not caused by the new runtime; rather, they are pre-existing errors that are now being found.
Additionally, customers that want to change their instance to Looker (Google Cloud core) must migrate to the new runtime beforehand.
How to switch to the new runtime
1. Turn off the "Use Legacy LookML Runtime" legacy feature if available
Some Looker instances are enabled with the Use Legacy LookML Runtime legacy feature. Disable the Use Legacy LookML Runtime legacy feature to transition the Looker instance to the new runtime.
If the Use Legacy LookML Runtime legacy feature is not available in the Legacy Features admin page of your Looker instance, your instance is already using the new runtime.
2. Ensure that your LookML projects are not configured with new_lookml_runtime:no
It is possible to override a Looker instance's global Use Legacy LookML Runtime setting by adding the new_lookml_runtime:no statement in the manifest file of a LookML project.
Ensure that your LookML project manifest files don't have the new_lookml_runtime parameter, or that new_lookml_runtime is set to yes on all LookML projects.
LookML issues the new runtime may find
After transitioning to the new runtime you may notice new errors in your LookML. The new errors are not caused by the new runtime; rather, they are pre-existing issues that are now being found.
Depending upon your LookML developer settings, you may be required to fix these errors before continuing to submit LookML changes. The following sections describe some of the issues that the new LookML runtime may find in your project and how to remedy them:
- Some Persistent Derived Tables may rebuild
- HTML literals inside Liquid expressions may be converted to Unicode
- Invalid references in
sql_distinct_keyresult in "unknown view" - "Distinct" type measures with no primary key produce different SQL
- Accessing
_filters[]in Liquid with a bare field reference will add the referenced field as a selected column - Liquid conditionals without an
{% else %}block insql_whereorsql_always_wheremay generate dangling SQL operators
Some Persistent Derived Tables may rebuild
Persistent Derived Table (PDT) keys are based on SQL generated by the LookML runtime. In some cases the new runtime may generate different (but equivalent) SQL for a PDT, resulting in a different PDT key. A change of a PDT key will cause the PDT to rebuild.
HTML literals inside Liquid expressions may be converted to Unicode
HTML tags in Liquid expressions may get converted to their Unicode equivalent by the new runtime. For example, a <strong> tag may get converted to <strong>. In the legacy runtime HTML tags could be compared directly, as in this example:
html:
{{ value |replace("<strong>"), "[" |replace("</strong>"), "]" }} ;;
In the new runtime comparisons need to be made against the Unicode instead:
html:
{{ value |replace("<strong>"), "[" |replace("</strong>"), "]" }} ;;
Invalid references in sql_distinct_key result in "unknown view"
With the new runtime a sql_distinct_key that references an unknown field or view will throw an exception. For example:
measure: total_shipping {
type: sum_distinct
sql: ${order_shipping} ;;
sql_distinct_key: ${some_incorrect_field_name} ;;
}
"Distinct" type measures with no primary key produce different SQL
A distinct type measure (average_distinct, median_distinct, percentile_distinct, sum_distinct) with no primary-key or sql_distinct_key parameter may produce different SQL in the new runtime.
Be sure to specify a primary-key or sql_distinct_key when building distinct type measures.
Accessing _filters[] in Liquid with a bare field reference will add the referenced field as a selected column
In Looker a "bare field reference" is one that is not enclosed in curly braces, such as users.created_date instead of ${users.created_date}.
The legacy runtime ignored bare field references when used with the _filters Liquid variable. The new runtime adds the field to the SELECT clause of the SQL query.
For example, in this dimension users.created_date is a bare reference:
dimension: name {
html:
{% if _filters[users.created_date] != NULL %}
{{rendered_value}} (created: {{_filters[users.created_date]}})
{% else %}
{{rendered_value}}
{% endif %}
;;
}
In the legacy runtime, _filters[users.created_date] would have always been ignored and only the second condition of the {% if %} would ever have been met. In the new runtime, users.created_date is added to the SELECT clause of the SQL query so that the condition can be evaluated.
The automatic addition of unexpected fields to Looker queries can be confusing to users, so best practice is not to use bare field references and instead use single quotes around the field name when using _filters[] in Liquid. For example, 'users.created_date'.
Liquid conditionals without an {% else %} block in sql_where or sql_always_where may generate dangling SQL operators
In the legacy runtime, when a Liquid {% if %} condition inside a SQL parameter, such as sql_where or sql_always_where, evaluated to an empty string, the LookML runtime automatically removed dangling logical operators, such as AND or OR, from the generated SQL query.
The new LookML runtime is stricter and evaluates SQL exactly as written. It no longer removes dangling logical operators when a Liquid condition evaluates to an empty string. As a result, if a Liquid {% if %} condition without an {% else %} block is not met, the generated SQL query might contain dangling operators that can cause database syntax errors.
For example, consider the following sql_always_where parameter with a Liquid {% if %} conditional:
explore: orders {
sql_always_where:
status != 'CANCELLED' AND
{% if orders.created_date._is_filtered %}
${created_date} >= '2026-01-01'
{% endif %} ;;
}
In the new runtime, the trailing AND operator is preserved exactly as written, which generates invalid SQL:
WHERE status != 'CANCELLED' AND
To prevent dangling SQL operators in the new runtime, use one of the following approaches:
Add an
{% else %}block with a neutral condition: add a default case, such as1=1, to ensure that the parameter always generates valid SQL syntax:explore: orders { sql_always_where: status != 'CANCELLED' AND {% if orders.created_date._is_filtered %} ${created_date} >= '2026-01-01' {% else %} 1=1 {% endif %} ;; }Use the
{% condition %}Liquid tag: refactor your LookML to use the{% condition %}tag, which lets Looker handle SQL logical operators automatically:explore: orders { sql_always_where: status != 'CANCELLED' AND {% condition orders.created_date %} ${created_date} {% endcondition %} ;; }