How to manage, validate, test, and deploy LookML projects in a team environment.
Looker projects are backed by Git. Follow this branching strategy:
main (production)
├── develop (staging / QA)
│ ├── feature/add-marketing-attribution
│ ├── feature/new-customer-segments
│ └── fix/subscription-churn-calc
feature/{ticket-id}-short-descriptionfix/{ticket-id}-what-was-brokenrefactor/{area}-{what-changed}1. Create a branch from main in Looker IDE (Develop → New Branch)
2. Make changes in Development Mode
3. Validate LookML (Develop → Validate LookML) — fix all errors
4. Run Content Validator — checks for broken references in dashboards/looks
5. Test in Dev Mode — run explores, verify data accuracy
6. Open Pull Request — request review from analytics team
7. Review & Merge — reviewer checks logic, naming, performance
8. Deploy to Production — merge to main triggers production update
Before opening a PR, verify:
sql_on references resolve correctlyEXPLAIN on complex derived table SQLSELECT * in derived tables — only needed columnsAdd data tests to verify business logic:
test: revenue_is_never_negative {
explore_source: orders {
column: total_revenue { field: orders.total_revenue }
filters: [orders.order_date: "30 days"]
}
assert: revenue_positive {
expression: ${orders.total_revenue} >= 0 ;;
}
}
test: customer_count_reasonable {
explore_source: customers {
column: count { field: customers.total_customers }
}
assert: count_above_minimum {
expression: ${customers.total_customers} > 100 ;;
}
}# In model file — use environment variable or connection name convention
connection: "production_warehouse" # production
# connection: "staging_warehouse" # uncomment for staging| Environment | Connection | Data | Who Uses It |
|---|---|---|---|
| Development | Dev warehouse or sandbox | Sample/recent data | Individual developers |
| Staging | Production warehouse (read replica) | Full production data | QA, analytics leads |
| Production | Production warehouse | Full production data | All business users |
# Restrict model to specific user groups
access_grant: finance_data {
user_attribute: department
allowed_values: ["finance", "executive"]
}dimension: salary {
type: number
sql: ${TABLE}.salary ;;
required_access_grants: [hr_only]
}access_filter: {
field: orders.region
user_attribute: allowed_regions
}| Data Size | Refresh Frequency | PDT Type |
|---|---|---|
| <1M rows | Hourly | Standard PDT |
| 1-100M rows | Daily | Persistent Derived Table |
| >100M rows | Daily | Incremental PDT |
| Real-time needed | N/A | Native Derived Table (NDT) |
derived_table: {
datagroup_trigger: revenue_daily_refresh
increment_key: "order_date"
increment_offset: 3 # rebuild last 3 days to catch late-arriving data
sql:
SELECT
DATE(order_timestamp) AS order_date,
SUM(order_total) AS daily_revenue
FROM analytics.orders
WHERE DATE(order_timestamp) >= {% date_start order_date %}
GROUP BY 1
;;
}i__looker → HistoryWhen removing or renaming fields:
1. Add deprecation notice to the description:
dimension: old_field_name {
description: "DEPRECATED: Use new_field_name instead. Will be removed 2025-03-01."
hidden: yes # Hide from field picker
}2. Run Content Validator to find all references
3. Update dashboards/looks that use the old field
4. Wait one release cycle (give users time to update saved content)
5. Remove the field in the next release
Get the full Looker LookML Templates and unlock everything.
Get the complete guide with every chapter unlocked, including code samples, diagrams, and best practices.
Access all interactive tools with complete data, all workload profiles, and the full scenario library.
Downloadable source code, configuration files, and working examples from every chapter.
Free updates for life. Every new chapter, tool, and improvement included.