Free Guide
LookML Best Practices Guide + AI Skill
The patterns we've refined across 200+ Looker implementations and 16,000+ trained engineers. Plus a drop-in AI skill that enforces them automatically.
What's Inside
This guide covers the 10 patterns that separate production-quality LookML from the mess most teams end up with. Every rule comes from real audits — not theory.
1. Project Structure
Organize with clear separation of concerns. Auto-generated views, refinements, derived tables, and explores each get their own folder.
project/
├── views/
│ ├── _autogen/ # Auto-generated — NEVER modify
│ ├── _refined/ # Refinements with business logic
│ └── derived_tables/ # NDTs, SQL DTs, PDTs
├── explores/ # Explore definitions (separate from views)
├── models/
├── tests/ # Data tests
└── dashboards/One view per file. File name matches view name. Auto-generated views are read-only. All business logic lives in refinement files.
2. Auto-Generated Views
Only use ${TABLE}.column_name syntax. No labels, descriptions, measures, or business logic.
view: orders {
sql_table_name: `project.dataset.orders` ;;
dimension: order_id {
type: number
sql: ${TABLE}.order_id ;;
}
}When the schema changes upstream, you can regenerate this file without losing any work.
3. Refinements Over Extends
Use the + prefix to add business logic without touching originals:
include: "/views/_autogen/orders.view.lkml"
view: +orders {
dimension: order_id {
primary_key: yes
description: "Unique identifier for each order."
}
measure: total_revenue {
type: sum
sql: ${amount} ;;
description: "Sum of all order amounts in USD."
value_format_name: usd
}
}Refinements let you layer business logic on top of auto-generated views. The original file stays clean and regenerable.
4. The Substitution Operator
This is the most common source of bugs in LookML. The rules are simple but almost always violated:
| Context | Syntax | Example |
|---|---|---|
| Database column (autogen only) | ${TABLE}.column | sql: ${TABLE}.order_id ;; |
| Same-view field reference | ${field_name} | sql: ${amount} * ${quantity} ;; |
| Cross-view field reference | ${view.field} | sql_on: ${orders.user_id} = ${users.id} ;; |
Never use ${TABLE}.column outside auto-generated views. Always use ${field_name} within the same view.
5. Every Field Needs a Description
Every description should answer three questions:
- What is this? — Business meaning
- What are the possible values? — Enums, nulls, edge cases
- How should I use this? — Guidance for the user
dimension: is_first_purchase {
type: yesno
description: "Yes if this is the customer's earliest order by created date.
Useful for new customer acquisition analysis.
Returns No for customers whose only order was cancelled."
}Descriptions also feed Looker's Gemini AI for natural language querying — better descriptions means better AI answers.
6. Derived Tables
- Prefer Native Derived Tables (NDTs) over SQL-based
- Always define a primary key in derived table views
- Use
datagroup_trigger(notpersist_for) for PDTs - For complex transformations — build in dbt instead
7. Naming Conventions
| Object | Convention | Example |
|---|---|---|
| View files | snake_case, match view name | orders.view.lkml |
| Refined view files | append _r | orders_r.view.lkml |
| Dimensions | snake_case, business-friendly | customer_name |
| Measures | verb + noun | total_revenue |
| Yesno dimensions | prefix is_ or has_ | is_first_purchase |
| Dimension groups | no "date" or "time" suffix | created, shipped |
| Primary keys | entity + _id | order_id |
8. Primary Keys & Joins
- Every view must have a primary key. No exceptions.
- Always specify
relationshipon every join. - Hide composite/technical primary keys.
- Wrong relationships = wrong measures (silent data errors).
9. Caching With Datagroups
Always use datagroups over persist_for. Align cache invalidation with your ETL schedule.
datagroup: daily_etl {
max_cache_age: "24 hours"
sql_trigger: SELECT MAX(updated_at) FROM orders ;;
}
explore: orders {
persist_with: daily_etl
}10. Data Tests
Run tests before every deploy to production:
test: pk_not_null {
explore_source: orders {
column: order_id { field: orders.order_id }
}
assert: {
expression: ${order_id} != null ;;
}
}The AI Skill
The SKILL.md file encodes all 10 rules above into a format that AI coding assistants understand. Drop it into your project and your AI will enforce these patterns every time it writes LookML.
Works with:
- Claude Code — place in your project root or
.claude/skills/ - Cursor — place in project root
- GitHub Copilot — reference in your prompt context
Go Deeper
This guide covers the foundation. For deep dives on specific topics, read:
Need a professional LookML audit?
We've audited hundreds of Looker instances. Book a free 30-minute call and we'll review your project structure, explores, and permissions.
Book a Free LookML Audit