A practical guide to Looker development
An Explore in Looker is the starting point for analysis — it defines which view(s) users can query, and how they relate to each other.
Think of an Explore as a curated lens into your data.
It tells Looker:
A user opens an Explore to slice, dice, filter, and visualize — all without writing SQL.
from:
view (base view)join:
blocksModel → Explore → View
📌 TL;DR:
An Explore connects views together and acts as the interface for end users to explore data through Looker’s UI — without needing SQL.
An Explore block lives in a model file (.model.lkml
) and defines what view to explore, and what joins (if any) are allowed.
explore: orders {
from: orders
}
This creates an Explore named orders
that starts from the orders.view
.
from:
vs joins:
from:
defines the base view (starting table for queries)join:
lets you bring in additional views (tables) via join logicYou can only have one from:
per explore, but as many join:
blocks as needed.
from:
) anchors the explore📌 Example with a join:
explore: orders {
from: orders
join: users {
type: left_outer
sql_on: ${orders.user_id} = ${users.id} ;;
}
}
Joins in Looker allow you to pull in fields from other views (tables) into an Explore. All joins are defined inside the explore:
block in a model file.
Every join:
block needs at minimum:
name:
(implied by join: users
)type:
(join type)sql_on:
(join condition)Example:
join: users {
type: left_outer
sql_on: ${orders.user_id} = ${users.id} ;;
}
type:
Optionsleft_outer
— default and most commoninner
— only matching rowsfull_outer
— uncommon, supported in some dialectscross
— avoid unless intentionalrelationship:
(optional but important)Options:
one_to_one
many_to_one
(most common)one_to_many
many_to_many
(use cautiously)Example:
relationship: many_to_one
sql_on:
: ${orders.user_id} = ${users.id}
Explores can include built-in filters that control what data users see — either for UX reasons or for access control.
always_filter:
Applies a filter automatically every time someone queries the Explore — users can’t remove or override it.
Useful for:
Example:
always_filter: {
filters: [status: "active"]
}
access_filter:
Implements row-level security — filters data based on user attributes.
Example:
Only show rows where the region
matches the user’s assigned region.
access_filter: {
field: users.region
user_attribute: user_region
}
user_attribute
is managed in the Looker Admin panelalways_filter
= hardcodedaccess_filter
= dynamic, based on the logged-in userexplore:
blockYou can use fields:
and set:
to control what fields are visible or drillable inside an Explore.
fields: [...]
Limits which dimensions and measures are exposed to users when they open the Explore.
Example:
fields: [orders.order_id, orders.order_date, users.first_name]
Useful for:
set:
for Reusable Field GroupsDefine field groups once and reuse them across Explores or joins.
Example:
set: user_core_fields {
fields: [users.id, users.email, users.signup_date]
}
Then include in an Explore or join like:
fields: [user_core_fields]
fields:
is allowed inside explore
, join
, and set
Below is a complete explore:
block from a model file. It starts from the orders
view, joins users
and products
, and applies both an always_filter
and access_filter
.
explore: orders {
from: orders
always_filter: {
filters: [orders.is_test_order: "no"]
}
access_filter: {
field: users.region
user_attribute: user_region
}
join: users {
type: left_outer
sql_on: ${orders.user_id} = ${users.id} ;;
relationship: many_to_one
fields: [user_core_fields]
}
join: products {
type: left_outer
sql_on: ${orders.product_id} = ${products.id} ;;
relationship: many_to_one
}
}