
Looker Studio
- 76 installs
- 93 repo stars
- Updated May 14, 2026
- thatrebeccarae/claude-marketing
Helps with ai & agent building tasks.
About
looker-studio is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- looker-studio
- AI & Agent Building
- AI-coding skill
Looker Studio by the numbers
- 76 all-time installs (skills.sh)
- +9 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #5,442 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thatrebeccarae/claude-marketing --skill looker-studioAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 76 |
|---|---|
| repo stars | ★ 93 |
| Last updated | May 14, 2026 |
| Repository | thatrebeccarae/claude-marketing ↗ |
What it does
Helps with ai & agent building tasks.
Files
Looker Studio (Google Data Studio)
Expert-level guidance for Looker Studio — building dashboards, connecting data sources, designing visualizations, and creating automated marketing reports.
Install
git clone https://github.com/thatrebeccarae/claude-marketing.git && cp -r claude-marketing/skills/looker-studio ~/.claude/skills/Core Capabilities
Dashboard Design
- Layout and visual hierarchy best practices
- Executive summary vs detailed operational dashboards
- Mobile-responsive report design
- Interactive controls: date range selectors, filters, drill-downs
- Consistent styling with themes and color palettes
Data Sources & Connectors
- Native (free): Google Analytics 4, Google Ads, Google Sheets, BigQuery, Search Console, YouTube Analytics
- Partner connectors: Facebook Ads, Microsoft Ads, LinkedIn Ads, HubSpot, Salesforce, Shopify, Klaviyo, Semrush
- Community connectors: Hundreds of third-party sources
- Data blending: Join multiple sources on shared dimensions
- Custom queries: BigQuery SQL, Google Sheets formulas as data sources
Calculated Fields & Metrics
- Regex-based field creation (REGEXP_MATCH, REGEXP_REPLACE, REGEXP_EXTRACT)
- CASE statements for custom groupings and bucketing
- Date functions for period-over-period comparisons
- Aggregation (SUM, AVG, COUNT_DISTINCT, MEDIAN)
- Blended field calculations across sources
Formula Syntax Rules
- No comments allowed — Looker Studio formulas do not support
--,//, or/* */style comments. Never include comments in formulas. Add context in the field description instead. - No inline flags in simple cases — prefer
CONTAINS_TEXT(),LOWER(), or exact match over regex when possible - RE2 regex engine — supports
(?i)for case-insensitive, but does NOT support lookaheads/lookbehinds - String escaping — use
\\for literal backslash in regex patterns within string literals (e.g.,"\\s"for whitespace,"\\|"for literal pipe)
Report Automation
- Scheduled email delivery (PDF snapshots)
- Embedded reports in websites and portals
- Template reports for client scaling
- Data freshness monitoring
Dashboard Templates by Use Case
Marketing Performance Dashboard
Page 1: Executive Summary
|- KPI scorecards (Revenue, ROAS, CPA, Spend, Conversions)
|- Period-over-period trend lines
|- Channel performance table (sortable)
|- Budget pacing gauge chart
Page 2: Paid Media Deep-Dive
|- Google Ads performance (campaign breakdown)
|- Meta Ads performance (campaign breakdown)
|- Microsoft Ads performance
|- Cross-channel spend allocation (pie/donut)
|- CPA trend by channel (combo chart)
Page 3: SEO & Organic
|- Google Search Console: impressions, clicks, CTR, position
|- Top queries table
|- Page-level performance
|- Organic landing page engagement (from GA4)
Page 4: Email & CRM
|- Email campaign metrics (opens, clicks, revenue)
|- List growth trend
|- Flow/automation revenue
|- Subscriber engagement tiers
Page 5: Conversion Funnel
|- Funnel visualization (awareness -> consideration -> conversion)
|- Landing page performance table
|- Device breakdown
|- Geographic heatmapE-commerce Dashboard
Page 1: Revenue Overview
|- Revenue, Orders, AOV, Conversion Rate scorecards
|- Revenue trend (daily/weekly)
|- Revenue by channel
|- Top products table
Page 2: Customer Acquisition
|- New vs returning customer revenue
|- CAC by channel
|- LTV:CAC ratio
|- First-order source attributionSEO Dashboard
Page 1: Organic Performance
|- Total clicks, impressions, CTR, avg position
|- Trend lines (90-day)
|- Top 20 queries (with position change)
|- Top landing pages
|- Device + country breakdownKey Visualization Guidelines
| Data Type | Best Chart | Avoid |
|---|---|---|
| KPI with comparison | Scorecard with delta | Pie chart |
| Trend over time | Line chart or area chart | Bar chart (if >7 periods) |
| Category comparison | Horizontal bar chart | 3D charts |
| Part of whole | Stacked bar or donut | Pie with >6 slices |
| Distribution | Histogram or heatmap | Scatter (if not correlation) |
| Geographic | Geo map or heatmap | Tables for location data |
| Funnel | Custom funnel (shapes) | Bar chart |
| Table data | Table with heatmap bars | Unsorted tables |
Workflow: Build a Dashboard
When asked to create a Looker Studio dashboard:
1. Define Purpose — Who views it? How often? What decisions does it inform? 2. Identify Data Sources — Which platforms/connectors needed? Any blending? 3. Design KPI Framework — Primary metrics, secondary metrics, diagnostic metrics 4. Plan Layout — Page structure, visual hierarchy, interactivity 5. Create Calculated Fields — Custom metrics, CASE groupings, regex transformations 6. Build Visualizations — Chart types matched to data types, consistent formatting 7. Add Controls — Date range, filters, drill-down parameters 8. Style & Polish — Theme, colors, fonts, logos, white space 9. Test & Validate — Cross-reference numbers with source platforms 10. Set Up Delivery — Scheduled emails, sharing permissions, embedding
Calculated Field Recipes
Period-over-Period Comparison
CASE
WHEN date_field >= DATE_DIFF(TODAY(), INTERVAL 30 DAY) THEN "Current Period"
WHEN date_field >= DATE_DIFF(TODAY(), INTERVAL 60 DAY) THEN "Previous Period"
ELSE "Older"
ENDChannel Grouping (Custom)
CASE
WHEN REGEXP_MATCH(source_medium, "google.*cpc|google.*paid") THEN "Google Ads"
WHEN REGEXP_MATCH(source_medium, "facebook|fb|meta|instagram") THEN "Meta Ads"
WHEN REGEXP_MATCH(source_medium, "bing.*cpc|microsoft") THEN "Microsoft Ads"
WHEN REGEXP_MATCH(source_medium, "email|klaviyo|braze") THEN "Email"
WHEN source_medium = "organic" THEN "Organic Search"
WHEN REGEXP_MATCH(source_medium, "social") THEN "Organic Social"
ELSE "Other"
ENDROAS Calculation
SUM(revenue) / SUM(cost)How to Use This Skill
Ask me questions like:
- "Build a marketing performance dashboard in Looker Studio"
- "How do I connect Facebook Ads data to Looker Studio?"
- "Create a calculated field for custom channel grouping"
- "Design an executive summary page with KPI scorecards"
- "How do I blend Google Ads and GA4 data?"
- "Build an SEO dashboard with Search Console data"
- "What's the best way to show period-over-period comparisons?"
- "Help me set up automated email reports for my client"
For detailed Looker Studio function reference, connector setup guides, and advanced techniques, see REFERENCE.md.
---
DTC Dashboard Recipes
Dashboard templates designed for DTC e-commerce teams running Klaviyo + Shopify + GA4.
1. CRM Performance Dashboard
Track email and SMS marketing effectiveness with Klaviyo data.
Page 1: Email & SMS Overview
|- Scorecards: Total Revenue, Flow Revenue %, Campaign Revenue %, List Size
|- Revenue trend: flows vs campaigns over time (area chart)
|- Channel split: email vs SMS revenue (stacked bar)
|- Engagement tiers donut: Active / Warm / At-Risk / Lapsed
Page 2: Flow Performance
|- Flow revenue table (sortable by revenue, click rate)
|- Welcome Series funnel (sent -> opened -> clicked -> converted)
|- Abandoned Cart recovery rate trend
|- Flow-over-flow comparison (combo chart)
Page 3: Campaign Performance
|- Campaign table: send date, subject, open rate, click rate, revenue
|- A/B test results (winner highlighting)
|- Send time heatmap (day of week x hour)
|- Unsubscribe rate trendData source: Google Sheets (fed by data_pipeline.py --action sync-klaviyo)
2. Lifecycle Marketing Dashboard
Map flow performance across the customer journey.
Page 1: Journey Overview
|- Stage funnel: Prospect -> New Customer -> Active -> VIP -> At-Risk -> Lapsed
|- Revenue by stage (horizontal bar)
|- Stage transition rates
Page 2: Stage Deep-Dive
|- Filter control: select lifecycle stage
|- Flow performance for selected stage
|- Customer count trend per stage
|- Revenue per customer by stage (combo chart)3. Revenue Attribution Dashboard
Reconcile Klaviyo-attributed revenue with Shopify actuals.
Page 1: Attribution Overview
|- Scorecards: Shopify Revenue, Klaviyo Attributed, Attribution %, Gap
|- Daily revenue: Shopify total vs Klaviyo attributed (dual axis)
|- Channel breakdown: Email, SMS, Flows, Campaigns (stacked bar)
|- Attribution gap trend line
Page 2: Channel Detail
|- Channel performance table (Klaviyo revenue per channel)
|- Shopify source breakdown (UTM-based)
|- Overlap analysis notesData source: Blended — Shopify Orders sheet + Klaviyo Revenue sheet
4. Campaign ROI Tracker
Campaign-level ROI with A/B test insights.
Page 1: Campaign Scorecard
|- Filter: date range, campaign type, channel
|- Campaign table with conditional formatting (green/red on benchmarks)
|- Revenue per recipient trend
|- Best-performing subject lines (top 10)
Page 2: Send Optimization
|- Send time analysis (heatmap: day x hour)
|- Audience size vs performance scatter
|- Frequency analysis: sends per subscriber per monthDTC Calculated Field Library
Copy-paste formulas for common DTC metrics in Looker Studio.
Customer Lifetime Value (LTV)
SUM(total_revenue) / COUNT_DISTINCT(customer_email)Customer Acquisition Cost (CAC)
SUM(ad_spend) / COUNT_DISTINCT(CASE WHEN order_number = 1 THEN customer_email ELSE NULL END)LTV:CAC Ratio
(SUM(total_revenue) / COUNT_DISTINCT(customer_email)) / (SUM(ad_spend) / COUNT_DISTINCT(new_customer_email))Repeat Purchase Rate
COUNT_DISTINCT(CASE WHEN order_count > 1 THEN customer_email ELSE NULL END) / COUNT_DISTINCT(customer_email) * 100Flow Revenue Percentage
SUM(CASE WHEN source_type = "flow" THEN revenue ELSE 0 END) / SUM(revenue) * 100Engagement Tier
CASE
WHEN days_since_last_open <= 30 THEN "Active (0-30d)"
WHEN days_since_last_open <= 90 THEN "Warm (31-90d)"
WHEN days_since_last_open <= 180 THEN "At-Risk (91-180d)"
ELSE "Lapsed (180d+)"
ENDRevenue Per Recipient
SUM(revenue) / SUM(recipients)Discount Impact
SUM(discount_amount) / SUM(gross_revenue) * 100Data Source Setup
Connecting Klaviyo Data (Free Method)
Paid connectors (Supermetrics, $30-100/mo) work but aren't necessary. The free pattern:
1. Run the data pipeline script to push Klaviyo data to Google Sheets:
python scripts/data_pipeline.py --action sync-klaviyo --sheet-id YOUR_SHEET_ID2. In Looker Studio, add data source > Google Sheets > select the spreadsheet 3. Set date field type to Date in the data source config 4. Schedule sync — run the pipeline daily via cron or n8n
Connecting Shopify Data (Free Method)
1. Push order data to Sheets:
python scripts/data_pipeline.py --action sync-shopify --sheet-id YOUR_SHEET_ID --days 302. Connect the Sheet in Looker Studio 3. Blend with Klaviyo sheet on Date dimension for attribution view
Connecting GA4 Data (Native — Free)
1. In Looker Studio, add data source > Google Analytics > select your GA4 property 2. No intermediary needed — native connector is comprehensive and free
Creating Pre-Formatted Sheets
# Create a sheet with correct headers for a CRM dashboard
python scripts/data_pipeline.py --action create-sheet --template crm-dashboard
# See all available templates
python scripts/data_pipeline.py --action list-templatesAnalysis Examples
For complete dashboard build walkthroughs and use cases, see EXAMPLES.md.
Scripts
The skill includes a data pipeline script for pushing data to Google Sheets:
Sync Klaviyo Data
python scripts/data_pipeline.py --action sync-klaviyo --sheet-id SPREADSHEET_IDSync Shopify Orders
python scripts/data_pipeline.py --action sync-shopify --sheet-id SPREADSHEET_ID --days 30Create Dashboard Sheet
python scripts/data_pipeline.py --action create-sheet --template crm-dashboardTroubleshooting
Data not refreshing: Google Sheets data in Looker Studio caches for ~15 minutes. Force refresh with the "Refresh data" button in Looker Studio, or set the data source cache to 1 minute in report settings.
Calculated field errors: Common causes:
- Comments in formulas — Looker Studio does NOT support
--,//, or/* */comments. Never include comments in calculated field formulas. Use the field description for documentation instead. - Type mismatches — Ensure date fields are typed as Date (not Text) in the data source config. Use CAST() to convert between types.
- Regex escaping — Patterns are inside string literals, so backslashes need double-escaping (e.g.,
"\\s"for whitespace,"\\|"for literal pipe).
Blending shows nulls: Left outer join means unmatched rows from the right source show null. Ensure join keys match exactly (case-sensitive, same date format).
Sheets row limit: Google Sheets has a 10M cell limit. For large datasets, aggregate before writing (daily summaries instead of per-event data), or use BigQuery instead.
Service account permission errors: The service account email must be shared on the target Google Sheet with Editor access. Check IAM permissions if Drive API calls fail.
Security & Privacy
- Never hardcode API credentials in scripts — use
.envfiles - Store service account JSON outside version control
- Add
.envand credential files to.gitignore - The data pipeline writes to Google Sheets you control — data stays in your Google Workspace
- Pipeline scripts are read-only against Klaviyo and Shopify APIs
- Use least-privilege API scopes (read-only keys)
# Google Sheets / Looker Studio Data Pipeline Credentials
# --------------------------------------------------
# This pipeline pushes Klaviyo/Shopify data to Google Sheets,
# which Looker Studio reads as a free data source.
#
# Setup:
# 1. Go to Google Cloud Console (https://console.cloud.google.com)
# 2. Create a project (or select existing)
# 3. Enable the Google Sheets API and Google Drive API
# 4. Go to IAM & Admin > Service Accounts > Create Service Account
# 5. Download the JSON key file and store it securely outside your repo
# 6. Share your target Google Sheet with the service account email
# (the email looks like: name@project.iam.gserviceaccount.com)
GOOGLE_SHEETS_CREDENTIALS_PATH=/path/to/service-account-key.json
# Optional: Pre-existing spreadsheet ID (found in the Sheet URL)
# If not set, the pipeline will create a new spreadsheet
# GOOGLE_SHEETS_SPREADSHEET_ID=your_spreadsheet_id_here
# Optional: Klaviyo API key (for sync-klaviyo action)
# KLAVIYO_API_KEY=pk_your_key_here
# Optional: Shopify credentials (for sync-shopify action)
# SHOPIFY_STORE_URL=https://your-store.myshopify.com
# SHOPIFY_ACCESS_TOKEN=shpat_your_access_token_here
# Optional: GA4 credentials (for sync-ga4 action)
# GOOGLE_ANALYTICS_PROPERTY_ID=your_property_id
# GOOGLE_APPLICATION_CREDENTIALS=/path/to/ga4-service-account.json
Looker Studio DTC Dashboard Examples
Practical examples of building dashboards for DTC e-commerce teams using Klaviyo, Shopify, and GA4 data.
Example 1: CRM Performance Dashboard
User Request: "Build a Looker Studio dashboard to track our Klaviyo email and SMS performance"
Setup Steps: 1. Sync Klaviyo data to Google Sheets 2. Connect the Sheet as a data source in Looker Studio 3. Build dashboard pages with KPIs, trends, and engagement tiers
Data Pipeline Command:
# Create the pre-formatted sheet
python scripts/data_pipeline.py --action create-sheet --template crm-dashboard
# Sync Klaviyo data
python scripts/data_pipeline.py --action sync-klaviyo --sheet-id YOUR_SHEET_IDDashboard Layout:
Page 1: Email & SMS Overview
|- Scorecards: Total Email Revenue, Total SMS Revenue, List Size, Avg Open Rate
|- Revenue trend: Email vs SMS over time (area chart, last 90 days)
|- Campaign performance table (sortable by revenue, click rate)
|- Engagement tier donut: Active / Warm / At-Risk / Lapsed
Page 2: Flow Performance
|- Flow revenue table with conditional formatting
|- Top 5 flows by revenue (horizontal bar)
|- Click rate by flow (bar chart with benchmark line)
|- Monthly flow revenue trendKey Calculated Fields:
# Open Rate (from Sheets data)
SUM(Opens) / SUM(Recipients) * 100
# Click-to-Open Rate
SUM(Clicks) / SUM(Opens) * 100
# Revenue Per Recipient
SUM(Revenue) / SUM(Recipients)
# Engagement Tier (if days_since_last_open is in your data)
CASE
WHEN days_since_last_open <= 30 THEN "Active (0-30d)"
WHEN days_since_last_open <= 90 THEN "Warm (31-90d)"
WHEN days_since_last_open <= 180 THEN "At-Risk (91-180d)"
ELSE "Lapsed (180d+)"
ENDExpected Insights:
- Flow vs campaign revenue split (healthy: flows = 40-60% of email revenue)
- Engagement tier distribution (warning if Active < 30%)
- Revenue per recipient trends (declining = list fatigue)
Example 2: Lifecycle Marketing Dashboard
User Request: "I want to see how our Klaviyo flows perform across the customer lifecycle"
Setup Steps: 1. Sync Klaviyo flow data with lifecycle stage tagging 2. Map flows to customer journey stages 3. Build stage-by-stage performance view
Data Pipeline Command:
python scripts/data_pipeline.py --action create-sheet --template lifecycle
python scripts/data_pipeline.py --action sync-klaviyo --sheet-id YOUR_SHEET_IDDashboard Layout:
Page 1: Lifecycle Overview
|- Stage funnel (shapes): Prospect -> New -> Active -> VIP -> At-Risk -> Lapsed
|- Revenue by stage (horizontal bar, color-coded by stage)
|- Customer count by stage (donut)
|- Stage transition rates table
Page 2: Flow Detail (with filter control)
|- Dropdown: Select lifecycle stage
|- Filtered flow table: all flows in selected stage
|- Revenue trend for selected stage (line chart)
|- Key metrics: messages sent, delivered, opened, clicked, convertedKey Calculated Fields:
# Lifecycle Stage from Flow Name
CASE
WHEN REGEXP_MATCH(Flow_Name, "(?i)welcome|sign.?up|lead") THEN "Prospect -> New"
WHEN REGEXP_MATCH(Flow_Name, "(?i)post.?purchase|thank|review") THEN "New -> Active"
WHEN REGEXP_MATCH(Flow_Name, "(?i)abandon|browse|cart") THEN "Active Engagement"
WHEN REGEXP_MATCH(Flow_Name, "(?i)vip|loyal|reward") THEN "VIP"
WHEN REGEXP_MATCH(Flow_Name, "(?i)win.?back|re.?engage|sunset") THEN "At-Risk -> Lapsed"
WHEN REGEXP_MATCH(Flow_Name, "(?i)replenish|reorder") THEN "Active Retention"
ELSE "Other"
END
# Conversion Rate
SUM(Conversions) / SUM(Messages_Sent) * 100
# Revenue per Message
SUM(Revenue) / SUM(Messages_Sent)Expected Insights:
- Which lifecycle stages generate the most revenue
- Gap identification: stages without flows need coverage
- Win-back effectiveness: is the sunset/re-engagement flow actually recovering customers?
Example 3: Revenue Attribution Dashboard
User Request: "Help me reconcile Klaviyo-attributed revenue with Shopify actuals"
Setup Steps: 1. Sync both Klaviyo revenue data and Shopify order data to Sheets 2. Blend on date dimension in Looker Studio 3. Build comparison view with gap analysis
Data Pipeline Commands:
python scripts/data_pipeline.py --action create-sheet --template revenue-attribution
python scripts/data_pipeline.py --action sync-klaviyo --sheet-id YOUR_SHEET_ID
python scripts/data_pipeline.py --action sync-shopify --sheet-id YOUR_SHEET_ID --days 30Dashboard Layout:
Page 1: Attribution Overview
|- Scorecards: Shopify Revenue, Klaviyo Attributed, Attribution %, Gap Amount
|- Daily comparison: Shopify total vs Klaviyo attributed (dual-axis combo chart)
|- Attribution percentage trend (line chart with 30-day moving average)
|- Channel breakdown table: email, SMS, flows, campaigns (Klaviyo side)
Page 2: Deep Dive
|- Shopify revenue by source (UTM-based breakdown)
|- Klaviyo revenue by channel and flow/campaign
|- Date-level reconciliation table (sortable, with gap column)
|- Notes field for explaining discrepanciesKey Calculated Fields:
# Attribution Percentage (blended data)
SUM(Klaviyo_Revenue) / SUM(Shopify_Revenue) * 100
# Revenue Gap
SUM(Shopify_Revenue) - SUM(Klaviyo_Revenue)
# Over-Attribution Flag
CASE
WHEN SUM(Klaviyo_Revenue) > SUM(Shopify_Revenue) * 1.1 THEN "Over-Attributed"
WHEN SUM(Klaviyo_Revenue) < SUM(Shopify_Revenue) * 0.15 THEN "Under-Attributed"
ELSE "Normal Range"
ENDExpected Insights:
- Typical Klaviyo attribution: 25-45% of Shopify revenue
- Over-attribution (>50%) signals overlap with other channels or double-counting
- Under-attribution (<15%) suggests tracking gaps in flows/campaigns
- Discrepancies often spike during sales events (multiple touchpoints)
Example 4: Campaign ROI Tracker
User Request: "I want to compare campaign performance week-over-week and find what works"
Setup Steps: 1. Sync campaign data from Klaviyo 2. Build filterable campaign comparison view 3. Add A/B test result tracking
Data Pipeline Command:
python scripts/data_pipeline.py --action create-sheet --template campaign-performance
python scripts/data_pipeline.py --action sync-klaviyo --sheet-id YOUR_SHEET_IDDashboard Layout:
Page 1: Campaign Scorecard
|- Date range filter + channel filter (email/SMS)
|- Campaign table: date, name, subject line, recipients, opens,
open rate, clicks, click rate, revenue, RPR
|- Conditional formatting: green if above benchmark, red if below
|- Revenue per recipient trend (line chart, weekly aggregation)
|- Top 10 subject lines by open rate (horizontal bar)
Page 2: Optimization Insights
|- Send time heatmap (day of week x hour of day -> color = open rate)
|- Audience size vs conversion rate scatter plot
|- Campaign frequency: sends per subscriber per week (trend)
|- A/B test results table (variant A vs B, winner highlighted)Key Calculated Fields:
# Revenue Per Recipient
SUM(Revenue) / SUM(Recipients)
# Click-to-Open Rate
SUM(Clicks) / SUM(Opens) * 100
# Performance vs Benchmark
CASE
WHEN Open_Rate >= 0.25 THEN "Above Benchmark"
WHEN Open_Rate >= 0.18 THEN "At Benchmark"
ELSE "Below Benchmark"
END
# Send Day
FORMAT_DATETIME("%A", Send_Date)
# Send Hour
FORMAT_DATETIME("%H", Send_Date)Expected Insights:
- Revenue per recipient trend reveals list fatigue or improvement
- Send time heatmap shows optimal scheduling (typically Tue-Thu, 10am-2pm)
- A/B test patterns across multiple tests reveal audience preferences
Example 5: Customer Cohort Dashboard
User Request: "Build a dashboard showing customer cohort LTV and retention"
Setup Steps: 1. Sync Shopify order data with customer first-order date 2. Calculate cohort metrics in Sheets or Looker Studio 3. Build cohort visualization
Data Pipeline Command:
python scripts/data_pipeline.py --action sync-shopify --sheet-id YOUR_SHEET_ID --days 180Dashboard Layout:
Page 1: Cohort Overview
|- Monthly acquisition cohort table (rows = cohort month, columns = month 1-6 LTV)
|- Conditional formatting: darker green = higher LTV
|- Cumulative LTV curve by cohort (line chart, one line per monthly cohort)
|- Repeat purchase rate by cohort (bar chart)
|- New vs returning customer revenue split (stacked area)
Page 2: Retention Analysis
|- Retention waterfall: % of cohort that purchased in month 2, 3, 4...
|- Average time between first and second purchase (histogram)
|- Cohort size trend (bar chart of new customers per month)
|- LTV:CAC ratio by acquisition channel (if ad spend data available)Key Calculated Fields:
# Cohort Month (from first order date)
FORMAT_DATETIME("%Y-%m", First_Order_Date)
# Months Since First Purchase
DATE_DIFF(Order_Date, First_Order_Date) / 30
# Cumulative LTV
SUM(Order_Total)
# Is Repeat Customer
CASE
WHEN Order_Count > 1 THEN "Repeat"
ELSE "One-Time"
END
# Repeat Purchase Rate
COUNT_DISTINCT(CASE WHEN Order_Count > 1 THEN Customer_Email ELSE NULL END)
/ COUNT_DISTINCT(Customer_Email) * 100Expected Insights:
- Healthy LTV curves show steady increase over 6+ months
- Flat curves after month 1 = retention problem (need post-purchase flows)
- Compare cohorts: are newer cohorts retaining better? (validates improvements)
- Time-to-second-purchase sweet spot informs win-back flow timing
Example 6: Data Pipeline Setup Walkthrough
User Request: "Walk me through setting up the data pipeline from scratch"
Full Setup Steps:
Step 1: Create Google Service Account
1. Go to https://console.cloud.google.com
2. Create project (or select existing)
3. Enable Google Sheets API and Google Drive API
4. IAM & Admin > Service Accounts > Create Service Account
5. Download JSON key file
6. Store securely (NOT in your repo)Step 2: Configure Environment
# Create .env file in the looker-studio skill directory
cp .env.example .env
# Edit .env with your credentials path
GOOGLE_SHEETS_CREDENTIALS_PATH=/path/to/your-service-account.json
KLAVIYO_API_KEY=pk_your_key_here
SHOPIFY_STORE_URL=https://your-store.myshopify.com
SHOPIFY_ACCESS_TOKEN=shpat_your_token_hereStep 3: Install Dependencies
pip install -r requirements.txtStep 4: Create a Dashboard Sheet
python scripts/data_pipeline.py --action list-templatesOutput:
{
"templates": {
"crm-dashboard": "CRM Performance Dashboard -- email/SMS KPIs, list growth, engagement tiers",
"lifecycle": "Lifecycle Marketing Dashboard -- flow performance across customer journey",
"campaign-performance": "Campaign ROI Tracker -- campaign comparison, A/B test results",
"revenue-attribution": "Revenue Attribution Dashboard -- Klaviyo vs Shopify reconciliation"
}
}python scripts/data_pipeline.py --action create-sheet --template crm-dashboardOutput:
{
"status": "success",
"spreadsheet_id": "1BxiMVs0XRA5nFMdKv...",
"spreadsheet_url": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKv...",
"template": "crm-dashboard",
"sheets_created": ["Campaign Metrics", "Flow Metrics", "List Growth"],
"next_steps": [
"Share the sheet with your team",
"Run sync commands to populate",
"Connect this Sheet as a data source in Looker Studio"
]
}Step 5: Sync Data
# Sync Klaviyo campaign and flow data
python scripts/data_pipeline.py --action sync-klaviyo --sheet-id 1BxiMVs0XRA5nFMdKv...
# Sync Shopify orders (last 30 days)
python scripts/data_pipeline.py --action sync-shopify --sheet-id 1BxiMVs0XRA5nFMdKv... --days 30Step 6: Connect in Looker Studio
1. Go to https://lookerstudio.google.com
2. Create > Report
3. Add data source > Google Sheets
4. Select your spreadsheet and sheet tab
5. Configure field types (set Date columns to Date type)
6. Start building chartsStep 7: Automate (Optional)
Set up a daily cron job or n8n workflow to keep data fresh:
# crontab -e
0 6 * * * cd /path/to/looker-studio/scripts && python data_pipeline.py --action sync-klaviyo --sheet-id YOUR_ID
0 6 * * * cd /path/to/looker-studio/scripts && python data_pipeline.py --action sync-shopify --sheet-id YOUR_ID --days 7Pro Tips
Pick the Right Template
- Starting from scratch? Use
crm-dashboardfor the broadest view - Revenue questions? Use
revenue-attributionto reconcile Klaviyo vs Shopify - Optimizing flows? Use
lifecycleto see journey-stage performance - Campaign testing? Use
campaign-performancefor A/B and timing insights
Blend for Cross-Platform Views
In Looker Studio, blend Klaviyo and Shopify sheets on the Date dimension to get unified views like:
- Email revenue as % of total Shopify revenue
- Campaign sends correlated with order spikes
- Discount usage in Shopify overlaid with promo campaigns in Klaviyo
Keep Data Fresh
Google Sheets data in Looker Studio caches for ~15 minutes. For real-time needs:
- Use Looker Studio's "Data freshness" setting (1 minute minimum)
- Click the refresh button in the report for immediate update
- Schedule pipeline syncs every 6 hours for daily dashboards
MIT License
Copyright (c) 2026 Rebecca Rae Barton
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Looker Studio Reference
Data Source Connectors
Native (Free) Google Connectors
| Connector | Data Available |
|---|---|
| Google Analytics 4 | Sessions, users, events, conversions, ecommerce, demographics |
| Google Ads | Campaigns, keywords, ads, conversions, quality score, auction insights |
| Google Sheets | Any tabular data (manual or API-fed) |
| BigQuery | SQL-queryable data warehouse |
| Search Console | Queries, pages, impressions, clicks, CTR, position |
| YouTube Analytics | Views, watch time, subscribers, demographics, revenue |
| Google Cloud Storage | CSV, JSON files |
| Campaign Manager 360 | Display & video campaign data |
| Display & Video 360 | Programmatic campaign data |
Popular Partner Connectors
| Connector | Provider | Cost |
|---|---|---|
| Facebook Ads | Supermetrics, Funnel, Windsor.ai | $30-100/mo |
| Microsoft Ads | Supermetrics, Windsor.ai | $30-100/mo |
| LinkedIn Ads | Supermetrics | $30-100/mo |
| HubSpot | Supermetrics, Porter Metrics | $30-100/mo |
| Shopify | Supermetrics, Coupler.io | $30-100/mo |
| Klaviyo | Supermetrics | $30-100/mo |
| Salesforce | Supermetrics | $50-150/mo |
| TikTok Ads | Supermetrics | $30-100/mo |
| Semrush | Supermetrics | $30-100/mo |
Free Workarounds
- Use Google Sheets as intermediary (pull data via API/Zapier/n8n -> Sheet -> Looker Studio)
- BigQuery: Load data from any source, connect natively
- CSV upload: Manual periodic updates
Calculated Fields Reference
Data Types
| Type | Description | Example |
|---|---|---|
| Number | Numeric values | SUM(revenue) |
| Text | String values | CONCAT(first_name, " ", last_name) |
| Date | Date/datetime | TODATE(date_string, 'BASIC', '%Y%m%d') |
| Boolean | True/false | clicks > 100 |
| Geo | Geographic data | Country, city |
Arithmetic
# Basic math
revenue - cost # Profit
(revenue - cost) / revenue * 100 # Profit Margin %
SUM(revenue) / SUM(cost) # ROAS
SUM(conversions) / SUM(clicks) * 100 # Conversion Rate %
SUM(cost) / SUM(conversions) # CPA
SUM(clicks) / SUM(impressions) * 100 # CTR %Text Functions
# Concatenation
CONCAT(source, " / ", medium)
# Substring
SUBSTR(campaign_name, 1, 10)
# Replace
REPLACE(url, "https://", "")
# Lowercase/Uppercase
LOWER(source)
UPPER(medium)
# Length
LENGTH(page_title)
# Contains check (returns boolean)
CONTAINS_TEXT(campaign_name, "brand")CASE Statements
# Custom channel grouping
CASE
WHEN REGEXP_MATCH(source_medium, "(?i)google.*cpc") THEN "Google Ads"
WHEN REGEXP_MATCH(source_medium, "(?i)(facebook|fb|meta|ig).*paid") THEN "Meta Ads"
WHEN REGEXP_MATCH(source_medium, "(?i)bing.*cpc") THEN "Microsoft Ads"
WHEN REGEXP_MATCH(source_medium, "(?i)linkedin.*cpc") THEN "LinkedIn Ads"
WHEN REGEXP_MATCH(medium, "(?i)email") THEN "Email"
WHEN REGEXP_MATCH(medium, "(?i)organic") THEN "Organic Search"
WHEN REGEXP_MATCH(medium, "(?i)(social|facebook|instagram|twitter|linkedin)") THEN "Organic Social"
WHEN REGEXP_MATCH(medium, "(?i)referral") THEN "Referral"
WHEN source = "(direct)" THEN "Direct"
ELSE "Other"
END
# Performance tier
CASE
WHEN conversion_rate >= 5 THEN "High Performer"
WHEN conversion_rate >= 2 THEN "Average"
ELSE "Needs Attention"
END
# Revenue buckets
CASE
WHEN revenue >= 1000 THEN "$1,000+"
WHEN revenue >= 500 THEN "$500-999"
WHEN revenue >= 100 THEN "$100-499"
ELSE "Under $100"
ENDRegex Functions
# Match (returns boolean)
REGEXP_MATCH(page_path, "/blog/.*")
# Extract
REGEXP_EXTRACT(page_path, "/blog/(.*)")
# Replace
REGEXP_REPLACE(utm_campaign, "_", " ")
# Common patterns
REGEXP_MATCH(source, "(?i)(google|bing|yahoo|duckduckgo)") # Search engines
REGEXP_MATCH(page_path, "^/(product|shop|store)/") # Product pages
REGEXP_EXTRACT(url, "utm_campaign=([^&]+)") # Extract UTM paramDate Functions
# Current date/time
CURRENT_DATE()
CURRENT_DATETIME()
# Date parts
YEAR(date_field)
MONTH(date_field)
WEEK(date_field)
DAY(date_field)
QUARTER(date_field)
# Date math
DATE_DIFF(date1, date2) # Days between dates
DATETIME_ADD(date_field, INTERVAL 7 DAY)
DATETIME_SUB(date_field, INTERVAL 30 DAY)
# Date formatting
FORMAT_DATETIME("%Y-%m-%d", date_field)
FORMAT_DATETIME("%B %Y", date_field) # "January 2024"
# Date parsing
TODATE(date_string, 'BASIC', '%Y%m%d')
PARSE_DATETIME('%Y-%m-%dT%H:%M:%S', datetime_string)Aggregation Functions
SUM(metric)
AVG(metric)
COUNT(dimension)
COUNT_DISTINCT(dimension)
MIN(metric)
MAX(metric)
MEDIAN(metric)
PERCENTILE(metric, 90) # 90th percentileConditional Aggregation
# Count with condition
SUM(CASE WHEN device = "mobile" THEN sessions ELSE 0 END)
# Weighted average
SUM(ctr * impressions) / SUM(impressions) # Weighted CTRChart Types & Best Practices
Scorecard
- Best for: KPIs with period comparison
- Settings: Show comparison period, compact numbers, conditional formatting
- Use custom date ranges for accurate comparisons
Time Series (Line Chart)
- Best for: Trends over time
- Settings: Smooth lines for trends, dual axis for different scales
- Limit to 3-4 series for readability
Bar Chart
- Best for: Category comparisons
- Horizontal for long labels, vertical for time-based
- Sort by value (not alphabetical) for impact
Table
- Best for: Detailed data exploration
- Settings: Heatmap bars, pagination, sortable columns
- Add conditional formatting for at-a-glance insights
Pie/Donut Chart
- Best for: Simple part-of-whole (max 5-6 slices)
- Avoid for: Many categories, similar-sized slices
- Donut with KPI in center is effective
Geo Map
- Best for: Geographic distribution
- Settings: Color scale (sequential for values, diverging for comparison)
- Bubble map for city-level data
Combo Chart
- Best for: Two related metrics with different scales (e.g., spend + CPA)
- Line + bar combination
- Use dual Y-axes carefully
Treemap
- Best for: Hierarchical data with size + color dimensions
- Settings: Size = volume metric, color = efficiency metric
Controls (Interactive Filters)
Date Range Control
- Pre-set options: Last 7/14/28/30/90 days, this/last month/quarter/year
- Custom range picker
- Comparison period: previous period, same period last year
- Applies to all charts on page (or linked chart group)
Filter Control
- Dropdown: Single or multi-select from dimension values
- Search bar: Type-to-filter for large lists
- Slider: Numeric range selection
- Checkbox: Boolean toggle
Data Control
- Lets viewer switch data sources (e.g., different GA4 properties)
- Useful for multi-client reports with same structure
Parameters
- User-defined variables (text, number, date)
- Reference in calculated fields: @parameter_name
- Example: CPA goal parameter -> conditional formatting in charts
Data Blending
How It Works
- Joins multiple data sources on shared dimensions (join keys)
- Left outer join by default
- Max 5 data sources per blend
- Blended data can be used in charts and calculated fields
Common Blends
Blend 1: Google Ads + GA4
|- Join key: Date + Campaign (UTM)
|- From Google Ads: Cost, Clicks, Impressions
|- From GA4: Sessions, Conversions, Revenue
Result: Full-funnel view with ROAS
Blend 2: Multiple Ad Platforms
|- Join key: Date
|- From Google Ads: Cost, Conversions
|- From Facebook Ads: Cost, Conversions
|- From Microsoft Ads: Cost, Conversions
Result: Cross-platform daily spend/conversion comparison
Blend 3: Google Sheets + GA4
|- Join key: Page URL
|- From Sheets: Content category, author, publish date
|- From GA4: Pageviews, engaged sessions, conversions
Result: Content performance with editorial metadataLimitations
- Cannot filter a blend on a field from only one source
- Aggregation happens before joining (pre-aggregated)
- Performance can be slower with large datasets
- Null handling: unmatched rows show null for the missing source
Report Design Best Practices
Layout Principles
1. Z-pattern: Important KPIs top-left, details flow right and down 2. Visual hierarchy: Largest/boldest = most important metric 3. White space: Don't crowd charts -- breathing room improves readability 4. Consistent grid: Align charts to an invisible grid (12-column recommended) 5. Page structure: Summary -> Detail -> Deep-dive (progressive disclosure)
Color Guidelines
- Max 5-7 colors in a single chart
- Use brand colors consistently
- Red = negative/down, green = positive/up (universal convention)
- Sequential palette for ranges (light -> dark)
- High contrast for accessibility
Report Sizing
- Desktop optimized: 1200 x 900px per page (16:9 landscape)
- PDF export: Standard page sizes work best
- Embedded: Match container width, fluid height
- Mobile: Use responsive layout mode, vertical scroll
Performance Tips
- Limit data sources per page (3-4 max)
- Use date range controls to limit data pulled
- Avoid blending large datasets
- Use extract data sources for slow connections
- Reduce calculated field complexity where possible
Sharing & Distribution
Sharing Options
| Method | Who Can View | Real-time? |
|---|---|---|
| Link sharing (viewers) | Anyone with link | Yes |
| Link sharing (editors) | Collaborators | Yes |
| Embed (iframe) | Website visitors | Yes |
| Scheduled email (PDF) | Email recipients | Snapshot |
| Download (PDF/CSV) | Manual | Snapshot |
Embedding
<iframe
width="800"
height="600"
src="https://lookerstudio.google.com/embed/reporting/REPORT_ID/page/PAGE_ID"
frameborder="0"
style="border:0"
allowfullscreen
sandbox="allow-storage-access-by-user-activation allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
></iframe>Template Reports
- Create a report with placeholder data source
- Share as template link
- Viewers make a copy with their own data source
- Useful for: agency-client scaling, community templates
# Looker Studio Data Pipeline Dependencies
# Install with: pip install -r requirements.txt
google-api-python-client>=2.0.0,<3.0.0
google-auth>=2.0.0,<3.0.0
python-dotenv>=1.0.0,<2.0.0
gspread>=5.0.0,<7.0.0
#!/usr/bin/env python3
"""
Looker Studio Data Pipeline
Pushes Klaviyo, Shopify, and GA4 data to Google Sheets for
Looker Studio ingestion. Solves the common DTC problem where
free Looker Studio connectors for Klaviyo are limited.
Pattern: Klaviyo/Shopify API -> Google Sheets -> Looker Studio
Usage:
python data_pipeline.py --action sync-klaviyo --sheet-id SPREADSHEET_ID
python data_pipeline.py --action sync-shopify --sheet-id SPREADSHEET_ID --days 30
python data_pipeline.py --action create-sheet --template crm-dashboard
python data_pipeline.py --action list-templates
Environment Variables:
GOOGLE_SHEETS_CREDENTIALS_PATH: Path to Google service account JSON (required)
GOOGLE_SHEETS_SPREADSHEET_ID: Default spreadsheet ID (optional)
KLAVIYO_API_KEY: Klaviyo private API key (for sync-klaviyo)
SHOPIFY_STORE_URL: Shopify store URL (for sync-shopify)
SHOPIFY_ACCESS_TOKEN: Shopify Admin API token (for sync-shopify)
"""
import os
import sys
import json
import argparse
from datetime import datetime, timedelta
from typing import Dict, List, Optional
try:
import gspread
from google.oauth2.service_account import Credentials
from dotenv import load_dotenv
except ImportError:
print("Error: Required packages not installed.", file=sys.stderr)
print(
"Install with: pip install gspread google-auth python-dotenv",
file=sys.stderr,
)
sys.exit(1)
SCOPES = [
"https://spreadsheets.google.com/feeds",
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive",
]
TEMPLATES = {
"crm-dashboard": {
"description": "CRM Performance Dashboard — email/SMS KPIs, list growth, engagement tiers",
"sheets": [
{
"name": "Campaign Metrics",
"headers": [
"Date", "Campaign Name", "Channel", "Recipients",
"Opens", "Open Rate", "Clicks", "Click Rate",
"Conversions", "Revenue", "Unsubscribes",
],
},
{
"name": "Flow Metrics",
"headers": [
"Date", "Flow Name", "Flow Type", "Messages Sent",
"Opens", "Open Rate", "Clicks", "Click Rate",
"Conversions", "Revenue",
],
},
{
"name": "List Growth",
"headers": [
"Date", "List Name", "New Subscribers", "Unsubscribes",
"Net Growth", "Total Size",
],
},
],
},
"lifecycle": {
"description": "Lifecycle Marketing Dashboard — flow performance across customer journey",
"sheets": [
{
"name": "Lifecycle Flows",
"headers": [
"Date", "Flow Name", "Stage", "Messages Sent",
"Delivered", "Opens", "Clicks", "Conversions",
"Revenue", "Revenue Per Recipient",
],
},
{
"name": "Customer Stages",
"headers": [
"Date", "Stage", "Customer Count", "Revenue",
"Avg Order Value", "Repeat Rate",
],
},
],
},
"campaign-performance": {
"description": "Campaign ROI Tracker — campaign comparison, A/B test results, send-time analysis",
"sheets": [
{
"name": "Campaigns",
"headers": [
"Date", "Campaign Name", "Subject Line", "Channel",
"Audience", "Recipients", "Opens", "Open Rate",
"Clicks", "Click Rate", "CTR", "Revenue",
"Revenue Per Recipient", "Unsubscribes", "Bounces",
],
},
{
"name": "A/B Tests",
"headers": [
"Date", "Campaign", "Variant", "Subject Line",
"Recipients", "Open Rate", "Click Rate", "Winner",
],
},
],
},
"revenue-attribution": {
"description": "Revenue Attribution Dashboard — Klaviyo vs Shopify revenue reconciliation",
"sheets": [
{
"name": "Klaviyo Revenue",
"headers": [
"Date", "Source", "Campaign/Flow Name", "Channel",
"Attributed Revenue", "Orders", "AOV",
],
},
{
"name": "Shopify Revenue",
"headers": [
"Date", "Source", "Channel", "Orders",
"Gross Revenue", "Discounts", "Net Revenue", "AOV",
],
},
{
"name": "Reconciliation",
"headers": [
"Date", "Klaviyo Attributed", "Shopify Total",
"Attribution %", "Gap", "Notes",
],
},
],
},
}
class LookerDataPipeline:
"""Push marketing data to Google Sheets for Looker Studio."""
def __init__(self):
"""Initialize with Google Sheets credentials."""
load_dotenv()
creds_path = os.environ.get("GOOGLE_SHEETS_CREDENTIALS_PATH")
if not creds_path:
raise ValueError(
"GOOGLE_SHEETS_CREDENTIALS_PATH environment variable not set. "
"Set it to the path of your Google service account JSON file."
)
if not os.path.exists(creds_path):
raise FileNotFoundError(f"Credentials file not found: {creds_path}")
credentials = Credentials.from_service_account_file(creds_path, scopes=SCOPES)
self.gc = gspread.authorize(credentials)
self.default_spreadsheet_id = os.environ.get("GOOGLE_SHEETS_SPREADSHEET_ID")
def sync_klaviyo_metrics(
self, sheet_name: str = "Klaviyo Metrics", spreadsheet_id: Optional[str] = None
) -> Dict:
"""
Pull Klaviyo flow + campaign metrics and push to Google Sheets.
Requires KLAVIYO_API_KEY environment variable.
"""
api_key = os.environ.get("KLAVIYO_API_KEY")
if not api_key:
raise ValueError("KLAVIYO_API_KEY not set — required for Klaviyo sync")
import requests
headers = {
"Authorization": f"Klaviyo-API-Key {api_key}",
"accept": "application/json",
"revision": "2024-10-15",
}
# Fetch campaigns
campaigns_url = "https://a.klaviyo.com/api/campaigns/?filter=equals(messages.channel,'email')"
resp = requests.get(campaigns_url, headers=headers, timeout=30)
resp.raise_for_status()
campaigns = resp.json().get("data", [])
campaign_rows = []
for c in campaigns[:50]: # Limit for Sheets
attrs = c.get("attributes", {})
campaign_rows.append([
attrs.get("created_at", "")[:10],
attrs.get("name", ""),
"email",
attrs.get("audiences", {}).get("included", [{}])[0].get("id", "")
if attrs.get("audiences", {}).get("included") else "",
attrs.get("status", ""),
])
# Fetch flows
flows_url = "https://a.klaviyo.com/api/flows/"
resp = requests.get(flows_url, headers=headers, timeout=30)
resp.raise_for_status()
flows = resp.json().get("data", [])
flow_rows = []
for f in flows[:50]:
attrs = f.get("attributes", {})
flow_rows.append([
attrs.get("created", "")[:10],
attrs.get("name", ""),
attrs.get("status", ""),
attrs.get("trigger_type", ""),
])
# Write to Sheets
sid = spreadsheet_id or self.default_spreadsheet_id
if not sid:
raise ValueError("No spreadsheet ID provided. Use --sheet-id or set GOOGLE_SHEETS_SPREADSHEET_ID")
spreadsheet = self.gc.open_by_key(sid)
# Campaigns sheet
self._write_to_sheet(
spreadsheet, "Campaigns",
[["Date", "Campaign Name", "Channel", "Audience ID", "Status"]] + campaign_rows,
)
# Flows sheet
self._write_to_sheet(
spreadsheet, "Flows",
[["Date", "Flow Name", "Status", "Trigger Type"]] + flow_rows,
)
return {
"status": "success",
"campaigns_synced": len(campaign_rows),
"flows_synced": len(flow_rows),
"spreadsheet_id": sid,
}
def sync_shopify_orders(
self,
sheet_name: str = "Shopify Orders",
spreadsheet_id: Optional[str] = None,
days: int = 30,
) -> Dict:
"""
Pull Shopify order data and push to Google Sheets.
Requires SHOPIFY_STORE_URL and SHOPIFY_ACCESS_TOKEN.
"""
store_url = os.environ.get("SHOPIFY_STORE_URL", "").rstrip("/")
access_token = os.environ.get("SHOPIFY_ACCESS_TOKEN")
if not store_url or not access_token:
raise ValueError(
"SHOPIFY_STORE_URL and SHOPIFY_ACCESS_TOKEN required for Shopify sync"
)
import requests
api_version = os.environ.get("SHOPIFY_API_VERSION", "2024-10")
base_url = f"{store_url}/admin/api/{api_version}"
headers = {
"X-Shopify-Access-Token": access_token,
"Content-Type": "application/json",
}
created_at_min = (datetime.utcnow() - timedelta(days=days)).isoformat() + "Z"
url = f"{base_url}/orders.json"
params = {
"status": "any",
"created_at_min": created_at_min,
"limit": 250,
}
resp = requests.get(url, headers=headers, params=params, timeout=30)
resp.raise_for_status()
orders = resp.json().get("orders", [])
order_rows = []
for o in orders:
order_rows.append([
o.get("created_at", "")[:10],
o.get("name", ""),
o.get("email", ""),
o.get("financial_status", ""),
o.get("fulfillment_status", "") or "unfulfilled",
float(o.get("total_price", 0)),
float(o.get("total_discounts", 0)),
float(o.get("subtotal_price", 0)),
len(o.get("line_items", [])),
o.get("source_name", ""),
"Yes" if o.get("cancelled_at") else "No",
])
sid = spreadsheet_id or self.default_spreadsheet_id
if not sid:
raise ValueError("No spreadsheet ID. Use --sheet-id or set GOOGLE_SHEETS_SPREADSHEET_ID")
spreadsheet = self.gc.open_by_key(sid)
self._write_to_sheet(
spreadsheet, sheet_name,
[[
"Date", "Order", "Email", "Financial Status",
"Fulfillment", "Total Price", "Discounts",
"Subtotal", "Items", "Source", "Cancelled",
]] + order_rows,
)
return {
"status": "success",
"orders_synced": len(order_rows),
"spreadsheet_id": sid,
"period": f"Last {days} days",
}
def sync_ga4_summary(
self,
sheet_name: str = "GA4 Summary",
spreadsheet_id: Optional[str] = None,
days: int = 30,
) -> Dict:
"""
Pull GA4 report data and push to Google Sheets.
Requires GOOGLE_ANALYTICS_PROPERTY_ID and GOOGLE_APPLICATION_CREDENTIALS.
"""
property_id = os.environ.get("GOOGLE_ANALYTICS_PROPERTY_ID")
ga_creds = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
if not property_id or not ga_creds:
raise ValueError(
"GOOGLE_ANALYTICS_PROPERTY_ID and GOOGLE_APPLICATION_CREDENTIALS "
"required for GA4 sync"
)
try:
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import (
DateRange, Dimension, Metric, RunReportRequest,
)
except ImportError:
raise ImportError(
"google-analytics-data package required for GA4 sync. "
"Install with: pip install google-analytics-data"
)
ga_client = BetaAnalyticsDataClient()
request = RunReportRequest(
property=f"properties/{property_id}",
date_ranges=[DateRange(start_date=f"{days}daysAgo", end_date="yesterday")],
metrics=[
Metric(name="sessions"),
Metric(name="activeUsers"),
Metric(name="bounceRate"),
Metric(name="conversions"),
],
dimensions=[Dimension(name="date")],
limit=days,
)
response = ga_client.run_report(request)
rows = [["Date", "Sessions", "Active Users", "Bounce Rate", "Conversions"]]
for row in response.rows:
date_val = row.dimension_values[0].value
rows.append([
f"{date_val[:4]}-{date_val[4:6]}-{date_val[6:8]}",
row.metric_values[0].value,
row.metric_values[1].value,
row.metric_values[2].value,
row.metric_values[3].value,
])
sid = spreadsheet_id or self.default_spreadsheet_id
if not sid:
raise ValueError("No spreadsheet ID. Use --sheet-id or set GOOGLE_SHEETS_SPREADSHEET_ID")
spreadsheet = self.gc.open_by_key(sid)
self._write_to_sheet(spreadsheet, sheet_name, rows)
return {
"status": "success",
"rows_synced": len(rows) - 1,
"spreadsheet_id": sid,
"period": f"Last {days} days",
}
def create_dashboard_sheet(self, template: str) -> Dict:
"""
Create a pre-formatted Google Sheet for a dashboard template.
Args:
template: Template name (crm-dashboard, lifecycle, campaign-performance, revenue-attribution)
"""
if template not in TEMPLATES:
raise ValueError(
f"Unknown template: {template}. "
f"Available: {', '.join(TEMPLATES.keys())}"
)
tmpl = TEMPLATES[template]
title = f"Klaviyo Pack — {template.replace('-', ' ').title()} ({datetime.now():%Y-%m-%d})"
spreadsheet = self.gc.create(title)
# Create sheets from template
for i, sheet_def in enumerate(tmpl["sheets"]):
if i == 0:
# Rename default sheet
worksheet = spreadsheet.sheet1
worksheet.update_title(sheet_def["name"])
else:
worksheet = spreadsheet.add_worksheet(
title=sheet_def["name"],
rows=1000,
cols=len(sheet_def["headers"]),
)
worksheet.update([sheet_def["headers"]], value_input_option="RAW")
# Bold headers
worksheet.format("1:1", {
"textFormat": {"bold": True},
"backgroundColor": {"red": 0.9, "green": 0.9, "blue": 0.95},
})
return {
"status": "success",
"spreadsheet_id": spreadsheet.id,
"spreadsheet_url": spreadsheet.url,
"template": template,
"description": tmpl["description"],
"sheets_created": [s["name"] for s in tmpl["sheets"]],
"next_steps": [
"Share the sheet with your team",
f"Run sync commands to populate: python data_pipeline.py --action sync-klaviyo --sheet-id {spreadsheet.id}",
"Connect this Sheet as a data source in Looker Studio",
],
}
def list_templates(self) -> Dict:
"""List available dashboard templates."""
return {
"templates": {
name: tmpl["description"]
for name, tmpl in TEMPLATES.items()
},
"usage": "python data_pipeline.py --action create-sheet --template TEMPLATE_NAME",
}
def _write_to_sheet(
self, spreadsheet, sheet_name: str, data: List[List]
) -> None:
"""Write data rows to a sheet, creating it if needed."""
try:
worksheet = spreadsheet.worksheet(sheet_name)
worksheet.clear()
except gspread.WorksheetNotFound:
worksheet = spreadsheet.add_worksheet(
title=sheet_name,
rows=max(len(data), 100),
cols=len(data[0]) if data else 10,
)
if data:
worksheet.update(data, value_input_option="RAW")
# Bold header row
worksheet.format("1:1", {
"textFormat": {"bold": True},
"backgroundColor": {"red": 0.9, "green": 0.9, "blue": 0.95},
})
def main():
parser = argparse.ArgumentParser(
description="Push marketing data to Google Sheets for Looker Studio",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# List available templates
python data_pipeline.py --action list-templates
# Create a new dashboard sheet
python data_pipeline.py --action create-sheet --template crm-dashboard
# Sync Klaviyo data to existing sheet
python data_pipeline.py --action sync-klaviyo --sheet-id YOUR_SPREADSHEET_ID
# Sync Shopify orders (last 30 days)
python data_pipeline.py --action sync-shopify --sheet-id YOUR_SPREADSHEET_ID --days 30
# Sync GA4 summary
python data_pipeline.py --action sync-ga4 --sheet-id YOUR_SPREADSHEET_ID --days 30
""",
)
parser.add_argument(
"--action",
required=True,
choices=[
"sync-klaviyo", "sync-shopify", "sync-ga4",
"create-sheet", "list-templates",
],
help="Action to perform",
)
parser.add_argument(
"--sheet-id", help="Google Spreadsheet ID (from the Sheet URL)",
)
parser.add_argument(
"--sheet-name", help="Target sheet/tab name within the spreadsheet",
)
parser.add_argument(
"--template",
choices=list(TEMPLATES.keys()),
help="Dashboard template for create-sheet action",
)
parser.add_argument(
"--days", type=int, default=30,
help="Number of days of data to sync (default: 30)",
)
args = parser.parse_args()
try:
if args.action == "list-templates":
# list-templates doesn't need credentials
result = {
"templates": {
name: tmpl["description"]
for name, tmpl in TEMPLATES.items()
},
"usage": "python data_pipeline.py --action create-sheet --template TEMPLATE_NAME",
}
else:
pipeline = LookerDataPipeline()
if args.action == "sync-klaviyo":
result = pipeline.sync_klaviyo_metrics(
sheet_name=args.sheet_name or "Klaviyo Metrics",
spreadsheet_id=args.sheet_id,
)
elif args.action == "sync-shopify":
result = pipeline.sync_shopify_orders(
sheet_name=args.sheet_name or "Shopify Orders",
spreadsheet_id=args.sheet_id,
days=args.days,
)
elif args.action == "sync-ga4":
result = pipeline.sync_ga4_summary(
sheet_name=args.sheet_name or "GA4 Summary",
spreadsheet_id=args.sheet_id,
days=args.days,
)
elif args.action == "create-sheet":
if not args.template:
print(
"Error: --template required for create-sheet action",
file=sys.stderr,
)
sys.exit(1)
result = pipeline.create_dashboard_sheet(template=args.template)
else:
print(f"Error: Unknown action: {args.action}", file=sys.stderr)
sys.exit(1)
print(json.dumps(result, indent=2))
except ValueError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
except Exception:
print("Error: Pipeline operation failed. Check credentials and spreadsheet ID.", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()