Updated August 2026. This is a full rewrite of the 2023 article, which contained invalid LookML and copy-paste artifacts. Everything below is current Looker behavior.
Looker's built-in visualizations cover most reporting needs, but three customization layers are worth knowing well: color collections and themes, Liquid-driven HTML formatting in LookML, and the custom visualization API for when you need a chart Looker does not ship.
Choosing and configuring built-in visualizations
Visualization type is chosen in the Explore or dashboard tile editor, not in LookML. What LookML can do is make fields behave well in any visualization: value_format_name for consistent number formatting, drill_fields for click-through, and group_label so related measures sit together in the field picker.
view: orders {
measure: total_revenue {
type: sum
sql: ${TABLE}.amount ;;
value_format_name: usd_0
drill_fields: [order_detail*]
}
set: order_detail {
fields: [order_id, created_date, users.name, total_revenue]
}
}
The set: named in drill_fields must exist; referencing detail* without defining a detail set is one of the most common validator errors we see in inherited projects. If you want a saved visualization to travel with the model, define it in a LookML dashboard file (.dashboard.lookml), where each element carries its type and visualization settings.
Color palettes: collections and themes
There is no color_palette parameter in a model file. Colors are managed in three places:
- Per visualization: the Series tab in the visualization editor lets you pick a palette from the instance's color collections or set individual series colors. This is stored with the Look or dashboard tile.
- Color collections (instance-wide): in Admin > Platform > Color Collections an admin can create custom collections with categorical, sequential and diverging palettes that match your brand. Mark one as the default and every new visualization picks it up.
- Themes (embedded dashboards): Admin > Platform > Themes defines fonts, background and tile colors for embedded dashboards, selected with a
themeparameter in the embed URL. Themes change chrome, not series colors; combine them with a color collection for a fully branded embed.
In a LookML dashboard file you can pin a palette per element so the model and the branding deploy together:
- name: revenue_by_region
type: looker_column
model: ecommerce
explore: orders
fields: [users.region, orders.total_revenue]
series_colors:
orders.total_revenue: "#1A73E8"
color_application:
collection_id: vistelio-brand
palette_id: vistelio-categorical
Liquid and HTML for conditional formatting
The html: parameter on a dimension or measure renders a Liquid template in place of the plain value, in table and single-value visualizations and in drill tooltips. The Liquid variables available are value (the raw value), rendered_value (with value_format applied), filterable_value, link and linked_value, plus other fields' values via ${view_name.field_name._value} style references. There is no _dimension_value variable.
Here is a measure that colors itself by threshold:
view: orders {
measure: total_revenue {
type: sum
sql: ${TABLE}.amount ;;
value_format_name: usd_0
html: {% if value < 1000 %}
<span style="color: #C5221F;">{{ rendered_value }}</span>
{% elsif value < 10000 %}
<span style="color: #E37400;">{{ rendered_value }}</span>
{% else %}
<span style="color: #188038;">{{ rendered_value }}</span>
{% endif %} ;;
}
}
Two rules keep this sane. First, put the html: on the measure itself; do not create a string dimension that wraps a measure, because a dimension's sql: cannot reference a measure (that was the bug in the 2023 example). Second, compare on value, not rendered_value; the rendered string has currency symbols and separators in it. For per-row formatting that depends on another field, reference it explicitly:
dimension: status_badge {
type: string
sql: ${status} ;;
html: {% if orders.is_late._value == "Yes" %}
<span style="background:#FCE8E6;padding:2px 6px;">{{ value }} (late)</span>
{% else %}
{{ value }}
{% endif %} ;;
}
Looker sanitizes the HTML to a safe subset (inline styles, spans, images, links), which is plenty for badges, progress bars made of <div>s, and logos looked up from a URL in another field. For table-wide rules that analysts can edit without LookML, the table visualization's conditional formatting settings do the same job without code.
Custom visualizations with the Visualization API
When you need a chart Looker does not have (a sankey, a network graph, a specific gauge), you write a JavaScript visualization against the Custom Visualization API and install it on the instance. The contract is unchanged from the original API: register an object with id, label, options, create and updateAsync.
looker.plugins.visualizations.add({
id: "revenue_gauge",
label: "Revenue Gauge",
options: {
target: { type: "number", label: "Target", default: 100000 },
color: { type: "string", display: "color", label: "Bar color", default: "#1A73E8" },
},
create: function (element, config) {
element.innerHTML = "<div class='gauge'><div class='bar'></div><div class='label'></div></div>";
this.bar = element.querySelector(".bar");
this.label = element.querySelector(".label");
},
updateAsync: function (data, element, config, queryResponse, details, done) {
this.clearErrors();
if (queryResponse.fields.measures.length < 1) {
this.addError({ title: "No measure", message: "Add one measure to the query." });
return;
}
const measure = queryResponse.fields.measures[0].name;
const value = data[0][measure].value;
const pct = Math.max(0, Math.min(100, (value / config.target) * 100));
this.bar.style.width = pct + "%";
this.bar.style.background = config.color;
this.label.textContent = LookerCharts.Utils.textForCell(data[0][measure]);
done();
},
});
The options object defines controls that appear in the visualization editor; config carries their current values. queryResponse.fields tells you which dimensions and measures the user selected, and LookerCharts.Utils.textForCell gives you the rendered value with value_format applied so your custom chart matches the rest of the dashboard.
Installing it. Custom visualizations are not added from a dropdown in the IDE. You either:
- install one of the ready-made visualizations from the Looker Marketplace (Admin > Platform > Marketplace), or
- host your built JavaScript file on HTTPS (a bucket, a CDN, or a Looker extension) and register it under Admin > Platform > Visualizations with an id, a label and the URL.
Once registered, the visualization appears in the chart picker for every user. Use the looker-open-source/custom_visualizations_v2 repository on GitHub as a starting point; it includes the development harness and build configuration.
Which layer to use
- Brand colors everywhere: a default color collection, plus a theme for embeds.
- Thresholds and badges in tables:
html:with Liquid on the measure, or table conditional formatting. - A chart type Looker lacks: a custom visualization, installed via Admin > Platform > Visualizations.
Need a bespoke visualization or a branded embedded dashboard? Our Looker developers build them; contact us.