Connect Bubble to Looker using the API Connector with a two-call chain: POST to Looker's /api/4.0/login endpoint to get a Bearer access token, then query Looker data with POST /api/4.0/queries/run/json. This data-query path returns flat JSON that binds directly to Bubble Repeating Groups. For iframe embedding, be aware that Looker's SSO embed URL requires HMAC-SHA1 signing — something Bubble cannot do natively.
| Fact | Value |
|---|---|
| Tool | Looker |
| Category | Analytics |
| Method | Bubble API Connector |
| Difficulty | Advanced |
| Time required | 3–5 hours |
| Last updated | July 2026 |
Data-Query Path vs Embed Path: Choosing the Right Looker–Bubble Approach
Looker has two integration modes, and choosing between them shapes your entire Bubble setup.
The data-query path uses Looker API 4.0 to fetch raw query results as flat JSON. You define a Looker query (specifying a model, view, fields, and optional filters), POST it to /api/4.0/queries/run/json, and get back an array of row objects. These map naturally to Bubble Repeating Groups — each row becomes a cell, each field a text or number element. This path works fully within Bubble's API Connector with no external dependencies.
The embed path uses Looker's SSO embed to render a full Looker dashboard iframe inside a Bubble HTML element. The catch: Looker's SSO embed URL must be signed with a HMAC-SHA1 signature generated using your Looker embed secret. Bubble's API Connector cannot compute HMAC-SHA1 signatures natively. To use the embed path, you need a minimal external endpoint (for example, a Cloudflare Worker) that accepts the unsigned embed URL and returns a signed version — the embed secret never enters Bubble.
For most Bubble apps, the data-query path is the right starting point: simpler setup, data that integrates naturally with Bubble's native UI components, and no dependency on external services. If your use case requires the full Looker dashboard interactivity (drill-downs, filter controls, export to PDF), the embed path with an external signing function is the way to go.
Important context: Looker is an enterprise product. Access to the API requires the correct Looker platform tier with API access enabled by your Looker instance admin. Verify with your admin before beginning any Bubble configuration.
Integration method
Bubble's API Connector calls Looker API 4.0 server-side: first POST /api/4.0/login to get an access token, then POST /api/4.0/queries/run/json to fetch query results — all with credentials kept Private.
Prerequisites
- A Looker instance URL (e.g., https://yourcompany.looker.com) — Looker is enterprise software, so you must have an active Looker account with API access enabled by your instance admin
- A Looker API key pair: client_id and client_secret (generated in Looker Admin → API Keys for a service user — NOT your personal login credentials)
- Knowledge of the Looker explore, model, and view names you want to query — ask your Looker admin or data team if you are unsure
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble → Install)
- A paid Bubble plan if you want automatic token refresh via Backend Workflow scheduler (tokens expire after 1 hour)
Step-by-step guide
Generate a Looker API Key Pair and Confirm API Access
In Looker, go to Admin (gear icon at the top right of your Looker instance) → Users → find or create a service user specifically for Bubble API access. Using a dedicated service user (rather than your personal admin account) limits the scope of access and makes it easy to revoke if needed. On the user's profile page, scroll down to 'API Keys' and click 'Edit Keys' → 'New API Key'. Looker generates a client_id and client_secret pair. Copy both values immediately — the client_secret cannot be retrieved again after you close this modal. Grant the service user a Looker model permission set that allows Read access to the explores and models you want to query from Bubble. Work with your Looker admin to define the minimum permission set needed. Also note your Looker instance base URL (e.g., https://yourcompany.looker.com) — this is the base for all API calls. Do not confuse this with the Looker API base URL, which is https://yourcompany.looker.com/api/4.0. One critical point to understand: Looker has two completely separate secret credentials with different purposes. - client_secret: used in the /api/4.0/login call. Grants API access to query data, list explores, run queries. - embed secret: used to sign SSO embed URLs. Found in Looker Admin → Platform → Embed. Only needed if you are building the iframe embed path. Using the embed secret in the /api/4.0/login call (or vice versa) will always produce an authentication error. Make sure you are copying the API Key client_secret, not the embed secret, for the setup in the next step.
1{2 "looker_credentials_to_copy": {3 "client_id": "from Looker Admin → Users → [service user] → API Keys",4 "client_secret": "from the same API Keys section [shown only once]",5 "instance_base_url": "https://yourcompany.looker.com",6 "api_base_url": "https://yourcompany.looker.com/api/4.0"7 },8 "important": "Do NOT confuse client_secret (API access) with embed secret (SSO embed URL signing) — these are separate credentials in different parts of Looker Admin"9}Pro tip: Ask your Looker admin to verify that the API is enabled for your Looker instance tier. Not all Looker plans include API access, and the admin must enable it per user. If the /api/4.0/login call returns 404, API access may be disabled at the instance level.
Expected result: A Looker API client_id and client_secret pair copied securely, with your instance base URL noted, ready to configure in the Bubble API Connector.
Configure API Connector Group 1: Looker Login (Token Exchange)
In your Bubble app, click the Plugins tab → API Connector → 'Add another API'. Name this group 'Looker Auth'. Set the base URL to your Looker instance URL (e.g., https://yourcompany.looker.com). Click 'Add another call'. Name it 'Get Access Token'. Set the method to POST. Set the endpoint path to /api/4.0/login. In the Body section, set the body type to 'Form data' (application/x-www-form-urlencoded — the Looker login endpoint requires this, not JSON). Add two body parameters: - client_id: your Looker API client_id. Private checkbox: UNCHECKED (client_id is a public identifier in Looker's API model). - client_secret: your Looker API client_secret. Private checkbox: CHECKED. This is the sensitive credential — marking it Private ensures Bubble never logs it in the API Connector response panel or the Logs tab. Set 'Use as' to 'Action'. Click 'Initialize call'. Bubble will send the POST request to Looker. A successful response looks like: {"access_token": "abc123...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": null} If you see 'There was an issue setting up your call', check: - The endpoint URL path is exactly /api/4.0/login (not /api/3.1 or /api/4.1) - The body type is Form data, not JSON - The client_id and client_secret are from the Looker Admin → API Keys section, not from SSO embed settings - Your Looker admin has enabled API access for the service user Note that Looker tokens expire after 1 hour and there is no refresh_token mechanism — you must call /api/4.0/login again when the token expires. This is a Looker API design choice; plan your Bubble token-refresh strategy around it.
1{2 "method": "POST",3 "url": "https://yourcompany.looker.com/api/4.0/login",4 "body_type": "form_data",5 "body_params": {6 "client_id": "{{client_id}}",7 "client_secret": "<private>"8 },9 "use_as": "Action",10 "expected_response": {11 "access_token": "eyJ...",12 "token_type": "Bearer",13 "expires_in": 360014 }15}Pro tip: Looker access tokens expire after exactly 3600 seconds (1 hour). Store the token expiry time in the Bubble database as 'Current date/time + 3540 seconds' (one minute buffer). On page load, compare this timestamp to Current date/time to decide whether to use the cached token or fetch a new one.
Expected result: A successful Initialize call response with an access_token. The 'Get Access Token' call now appears as an Action in your Bubble Workflow editor.
Configure API Connector Group 2: Looker Data Queries
Click 'Add another API' in the API Connector to create a second group. Name it 'Looker Data API'. Set the base URL to https://yourcompany.looker.com/api/4.0. Add a shared header to this group: Key = Authorization, Value = Bearer [leave empty — you will populate this dynamically from a Workflow], Private = CHECKED. Add two calls inside this group: Call 1 — 'List Explores': GET /lookml_models. Use as Data. Initialize with a real Bearer token in the Authorization header (use the token you got from the login call above for initialization — it is valid for 1 hour). This returns your available LookML models and their explores — useful for discovering what data you can query. Call 2 — 'Run Query': POST /queries/run/json. Use as Action. Body type: JSON. The body specifies what data to query: { "model": "{{model_name}}", "view": "{{view_name}}", "fields": ["{{field1}}", "{{field2}}"], "filters": {}, "limit": "100" } The 'fields' array specifies which LookML dimensions and measures to include in the result (format: 'view_name.field_name', e.g., 'orders.total_revenue'). Ask your Looker admin or data team for the correct model, view, and field names for your use case. Initialize the Run Query call with real values for model, view, and at least one field, using a live Bearer token. The response is a JSON array of row objects where each key is a field name (e.g., {"orders.total_revenue": 12345, "orders.count": 87}). If you see 'There was an issue setting up your call' when initializing, the most common causes are: the access_token has expired (they last only 1 hour — get a fresh one from the login call), the model or view name is misspelled, or the service user does not have permission to access the specified explore.
1{2 "api_group": "Looker Data API",3 "base_url": "https://yourcompany.looker.com/api/4.0",4 "shared_headers": [5 {6 "key": "Authorization",7 "value": "Bearer {{dynamic_from_workflow}}",8 "private": true9 }10 ],11 "run_query_call": {12 "method": "POST",13 "path": "/queries/run/json",14 "body": {15 "model": "{{model_name}}",16 "view": "{{view_name}}",17 "fields": ["{{field1}}", "{{field2}}", "{{field3}}"],18 "filters": {19 "{{filter_field}}": "{{filter_value}}"20 },21 "sorts": ["{{sort_field}} desc"],22 "limit": "100"23 },24 "use_as": "Action"25 }26}Pro tip: Use Looker's API Explorer (available in Looker Admin → API Explorer or at https://yourcompany.looker.com/api-docs) to explore the available API endpoints and test query bodies interactively before setting them up in Bubble. It also shows the expected response shape so you know which fields to map in your Bubble Repeating Group.
Expected result: Two initialized API calls in the Looker Data API group: 'List Explores' returns your LookML models, and 'Run Query' returns a flat JSON array of rows from the specified Looker explore.
Build a Bubble Workflow to Chain Login, Query, and Display Data
Create a Repeating Group on your Bubble page. Set its Type of content to a new data type called 'Looker Row' with fields matching the Looker query response (for example: revenue number, order_count number, region text). Add text elements inside the Repeating Group cell for each field. Alternatively, use a Custom State of type 'text' (or a custom type) to hold the query result in memory without writing to the Bubble database — this avoids WU costs from database operations for one-time dashboard displays. Create a Workflow event: 'When page is loaded' → add actions: Action 1: Check if the stored token is still valid. Add a condition: 'Only when App Data Looker Token's expires_at < Current date/time'. If true, proceed. If false, skip to Action 3. Action 2 (token refresh, conditional): 'Plugins → Looker Auth - Get Access Token'. Run this when the stored token is expired. Then: 'Make changes to App Data Looker Token': access_token = Result of Step 2's access_token, expires_at = Current date/time + 3540 seconds. Action 3: 'Plugins → Looker Data API - Run Query'. In the Authorization dynamic field, enter 'Bearer ' + 'App Data Looker Token's access_token'. Set model, view, and fields dynamically (from Option Sets or hardcoded values). Set limit to 100. Action 4: 'Set Custom State' or 'Make changes to a list of Looker Row Things' — populate with the results from Step 3's response array. Bind the Repeating Group to the Custom State or the database search. In each Repeating Group cell, bind each text element to the corresponding Looker Row field. For large Looker explores returning many rows: use the 'limit' and 'offset' parameters with Bubble Pagination or a 'Load More' button. Querying Looker explores with complex joins and many rows can exceed Bubble's 30-second workflow timeout — keep queries focused and use Looker's filters to scope results before they reach Bubble. Add Bubble privacy rules to the Looker Row data type: Data tab → Privacy → Looker Row → restrict read access to logged-in users with the appropriate role. This prevents raw business data from being exposed via Bubble's public Data API.
1// Workflow: When page is loaded2//3// Condition check: Does Looker Token record's expires_at < Current date/time?4//5// Action 1 (if token expired):6// Plugins → Looker Auth - Get Access Token7// Result → access_token8//9// Action 2 (if token expired):10// Make changes to Looker Token11// access_token = Action 1's access_token12// expires_at = Current date/time + 3540 seconds13//14// Action 3:15// Plugins → Looker Data API - Run Query16// Authorization = "Bearer " + Looker Token's access_token17// model = your_model_name18// view = your_view_name19// fields = ["your_view.dimension1", "your_view.measure1"]20// limit = 10021//22// Action 4:23// Make changes to Looker Row list (or set Custom State)24// Populate from Action 3's result array25//26// Repeating Group data source: Search for Looker Rows27// Cell bindings:28// 'Revenue': Current cell's revenue formatted as currency29// 'Region': Current cell's regionPro tip: RapidDev's team has built Bubble internal tools pulling data from enterprise BI platforms including Looker — if you need help mapping complex Looker query responses to Bubble data types or building pagination, book a free scoping call at rapidevelopers.com/contact.
Expected result: A Bubble Repeating Group populated with live Looker query data, updating on page load with a token expiry check that avoids unnecessary login calls.
Handle Token Expiry with a Backend Workflow Scheduler (Paid Plan)
Looker access tokens expire after exactly 1 hour and there is no refresh token — you must call /api/4.0/login again. On a Bubble free plan, the page-load token check (from step 4) handles this adequately for pages loaded within session boundaries, but users who keep the same page open for over an hour will see stale or missing data until they reload. For a more robust production setup on a paid Bubble plan: 1. Go to Settings tab → API → check 'This app exposes a Workflow API' to enable Backend Workflows. 2. In the left sidebar, click 'Backend workflows' → 'Add a new API workflow'. Name it 'Refresh Looker Token'. 3. Inside this workflow, add two actions: (a) Plugins → Looker Auth - Get Access Token, (b) Make changes to App Data Looker Token: access_token = Step 1's access_token, expires_at = Current date/time + 3540 seconds. 4. Click the clock icon to create a recurring schedule: run every 55 minutes. With this scheduler running, the Looker token in the Bubble database is always fresh. Page-load Workflows can use the stored token directly without a login call most of the time, reducing WU consumption. For the WU-conscious: each /api/4.0/login call and each /queries/run/json call consumes WUs. Cache Looker query results in the Bubble database with a fetched_at timestamp and only re-query Looker when the cache is older than your acceptable freshness window (for most business data, 15–60 minutes is fine).
1// Backend Workflow: 'Refresh Looker Token'2// Settings → API → expose Workflow API → enabled3// Backend Workflows → New API Workflow → 'Refresh Looker Token'4//5// Action 1: Plugins → Looker Auth - Get Access Token6// Returns: access_token (valid 3600 seconds)7//8// Action 2: Make changes to App Data Looker Token9// access_token = Action 1's access_token10// expires_at = Current date/time + 3540 seconds11//12// Scheduled: every 55 minutes (3300 seconds)13//14// Note: Backend Workflows require a paid Bubble plan.Pro tip: Looker query results for business metrics typically change slowly (daily batch jobs). Set your cache freshness window accordingly — a 30-minute cache means at most one Looker API call per 30 minutes per metric, versus one call per page load, which can be a significant WU saving on high-traffic Bubble apps.
Expected result: A scheduled Backend Workflow that keeps the Looker access token fresh in the Bubble database, preventing session expiry issues for users who stay on a Bubble page for longer than one hour.
Embed a Looker Dashboard via SSO (Advanced — Requires External Signing)
If you need to embed a full Looker dashboard iframe rather than displaying queried data in Bubble's native UI components, you must use Looker's SSO embed URL feature. This requires generating a signed URL using your Looker embed secret and a HMAC-SHA1 algorithm. Bubble's API Connector cannot compute HMAC-SHA1 signatures natively — there is no native Bubble action for cryptographic signing. This is the key architectural limitation for the Looker embed path in Bubble. The practical solution for teams that need the full embed: deploy a minimal external function (a Cloudflare Worker is a good fit — free tier, global edge, ~10 lines of JavaScript) that accepts an unsigned Looker embed URL and returns a signed version. Call this external endpoint from Bubble's API Connector. The embed secret stays in the Cloudflare Worker's environment variables and never enters Bubble. In Bubble, the workflow for embed is: 1. Build the unsigned SSO embed URL (path + params including session length, user attributes, permissions) — this can be done with Bubble's dynamic text operators. 2. POST to your Cloudflare Worker endpoint with the unsigned URL as a body parameter. 3. The Worker returns the HMAC-SHA1 signed URL. 4. Inject the signed URL into an HTML element iframe src attribute in Bubble. The embed secret NEVER enters Bubble. The Cloudflare Worker holds it in its own environment variable (secure, not logged). This is the honest recommendation for this specific integration — attempting to sign the URL inside Bubble using workarounds (e.g., Toolbox plugin JavaScript) would leak the embed secret to the browser. If you do not have a Cloudflare Worker or similar endpoint, use the data-query path (steps 1–5) instead — it is fully supported natively in Bubble with no external dependencies.
1// Cloudflare Worker (external, NOT in Bubble) — minimal HMAC-SHA1 signing2// Store LOOKER_EMBED_SECRET in Cloudflare Worker environment variables34// workers/looker-sign.js5export default {6 async fetch(request, env) {7 if (request.method !== 'POST') return new Response('Method not allowed', { status: 405 });8 const { embed_url } = await request.json();9 const secret = env.LOOKER_EMBED_SECRET;10 // Build the string to sign per Looker SSO embed specification11 // See: https://cloud.google.com/looker/docs/single-sign-on-embedding12 const encoder = new TextEncoder();13 const keyData = encoder.encode(secret);14 const msgData = encoder.encode(embed_url);15 const key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']);16 const sig = await crypto.subtle.sign('HMAC', key, msgData);17 const hexSig = Array.from(new Uint8Array(sig)).map(b => b.toString(16).padStart(2, '0')).join('');18 const signed_url = embed_url + '&signature=' + hexSig;19 return new Response(JSON.stringify({ signed_url }), { headers: { 'Content-Type': 'application/json' } });20 }21};2223// Bubble HTML element (after calling the Worker from an API Connector call):24// <iframe src="[Dynamic: Result of Worker Call's signed_url]" width="100%" height="600" frameborder="0"></iframe>Pro tip: Review Looker's full SSO embed URL specification at cloud.google.com/looker/docs/single-sign-on-embedding before building this — the string-to-sign format includes specific fields in a specific order, and any deviation produces an invalid signature even if the HMAC calculation is correct.
Expected result: A Bubble HTML element rendering a full Looker dashboard iframe, with the HMAC-SHA1 signed SSO embed URL generated by an external Cloudflare Worker — the embed secret remains outside Bubble entirely.
Common use cases
Data Table Inside a Bubble Internal Tool
Fetch Looker query results into a Bubble Repeating Group to display structured business data — sales by region, active subscriptions by plan, support tickets by priority — inside an internal operations tool built in Bubble, without requiring users to have Looker access.
Show the last 30 days of subscription revenue by plan tier in a Bubble Repeating Group, queried live from Looker's sales explore.
Copy this prompt to try it in Bubble
Executive Summary Numbers in a Bubble Page Header
Pull one or two key business metrics from a Looker query (e.g., MRR, active users) and display them as large numbers in a Bubble page header — refreshed on page load so executives always see current figures without opening Looker.
Display current MRR and total active users at the top of the Bubble executive dashboard, queried from Looker's subscription explore.
Copy this prompt to try it in Bubble
Embedded Looker Dashboard in a Bubble Portal (Advanced)
Render a full interactive Looker dashboard iframe inside a Bubble HTML element using SSO embed — with a lightweight external signing function generating the HMAC-SHA1 signed URL — so users in the Bubble portal experience the full Looker dashboard without a separate Looker login.
Embed the Looker Marketing Dashboard in the Bubble client portal, with the signed SSO URL generated by an external Cloudflare Worker using the embed secret.
Copy this prompt to try it in Bubble
Troubleshooting
Looker login call returns 401 Unauthorized with message 'Invalid credentials'
Cause: Either the client_id or client_secret is wrong, or you accidentally used the embed secret (from Looker Admin → Platform → Embed) instead of the API key secret (from Looker Admin → Users → API Keys). These are separate credentials.
Solution: Go to Looker Admin → Users → find your service user → API Keys. Regenerate the key pair and copy both values fresh. Confirm you are using the client_secret from the API Keys section, not from the Embed section. Also verify the API Connector body type is 'Form data' (not JSON) — the Looker login endpoint requires form-encoded body, not JSON.
Run Query call succeeds but returns an empty array even though Looker shows data for the same query
Cause: The service user's Looker model permission set does not include access to the explore or model being queried, or the filter values produce zero matching rows.
Solution: Log in to Looker as an admin and run the exact same query body in Looker's API Explorer (Admin → API Explorer → POST /queries/run/json). If it returns data with admin credentials but not with the service user credentials, ask your Looker admin to update the service user's permission set to include the relevant model and explore. If both return empty, check your filter values — remove all filters first to confirm the explore has data, then add filters back one at a time.
Bubble Workflow times out when running a Looker query with complex joins
Cause: Looker queries over large explores with many joins can take more than 30 seconds, which is Bubble's workflow step timeout.
Solution: Add a 'limit' parameter to the query body (e.g., '100') and use Looker's 'filters' parameter to scope the query to a smaller dataset. Also check if the same Looker query runs in under 30 seconds in the Looker UI — if it is slow in Looker itself, optimize the LookML query first (add aggregate tables, cache policies) before addressing it in Bubble.
1{2 "model": "your_model",3 "view": "your_view",4 "fields": ["your_view.dimension1", "your_view.measure1"],5 "filters": { "your_view.date_dimension": "30 days" },6 "limit": "100"7}Looker data appears in the Bubble database but is accessible to all users via Bubble's public Data API
Cause: No Bubble privacy rules have been added to the data type storing Looker results. Bubble's Data API is public by default — all database records are readable by anyone who knows the data type name unless privacy rules restrict access.
Solution: Go to Data tab → Privacy → click on your Looker data type (e.g., Looker Row) → click the + button to add a rule → set 'When' condition to 'Current User is logged in' and/or 'Current User's role is Admin'. Enable 'Find this in searches' only for authorized roles. Save the privacy rules.
Looker access token expires mid-session and the page stops displaying data with no error message
Cause: Looker tokens expire after exactly 1 hour and there is no refresh_token mechanism. If the token in the Bubble database has expired and the page does not re-run the login call, the Authorization header on subsequent queries is invalid.
Solution: On page load, add a Workflow condition: 'Only when App Data Looker Token's expires_at < Current date/time' → run the login + update workflow. Also set a client-side recurring Workflow every 55 minutes (using Bubble's 'Schedule a workflow in X seconds' chained pattern) to proactively refresh the token before it expires. Or use the Backend Workflow scheduler (step 5) on a paid Bubble plan.
Best practices
- Always mark the Looker API client_secret as Private in the API Connector body parameters — it is the credential that authenticates all Looker API calls and must never appear in Bubble's Logs tab.
- Use a dedicated Looker service user for the API key pair rather than a personal admin account — this limits the scope of access and makes it easy to rotate or revoke credentials without affecting your personal Looker account.
- Cache Looker query results in the Bubble database with a fetched_at timestamp. Business data from Looker typically has daily or hourly granularity — refreshing on every page load wastes WUs without providing fresher data.
- Add Bubble privacy rules to every data type storing Looker results — Bubble's Data API exposes all records publicly by default, and business intelligence data should never be publicly readable.
- Store the Looker access token expiry time in the Bubble database (Current date/time + 3540 seconds) and check it on every page load before deciding whether to call /api/4.0/login again — this avoids an unnecessary login call when the token is still valid.
- Use the data-query path (POST /api/4.0/queries/run/json) as your default Bubble integration approach — it works natively within the API Connector, requires no external dependencies, and binds naturally to Bubble Repeating Groups.
- If you need the full Looker embed iframe experience, use an external HMAC-SHA1 signing function (Cloudflare Worker) — do NOT attempt to compute the signature inside Bubble's client-side JavaScript because this would expose the embed secret to the browser.
- Confirm API access with your Looker admin before starting Bubble configuration — Looker is enterprise software and API access must be explicitly enabled at the platform tier and per user. Starting setup without this confirmation leads to confusing 404 or 401 errors.
Alternatives
Power BI uses Azure AD OAuth + GenerateToken for embed — a flow that works natively through Bubble's API Connector without any external signing requirement. Looker's embed path requires HMAC-SHA1 signing that Bubble cannot do natively. If your primary need is embedding a BI dashboard in Bubble (rather than querying data), Power BI is significantly simpler to set up in Bubble than Looker's SSO embed path.
Google Looker Studio (formerly Data Studio) is a free Google BI tool that creates shareable report URLs — these can be embedded as iframes in Bubble HTML elements without any authentication token or HMAC signing. If your use case is displaying a shareable BI report in Bubble and your data is already in Google's ecosystem (GA4, Sheets, BigQuery), Looker Studio is a simpler alternative to Looker's enterprise setup.
GA4 Data API provides web traffic metrics (sessions, users, pageviews) via a simpler OAuth2 refresh_token chain. Looker provides business data from your data warehouse (revenue, orders, any custom metric). Use GA4 for web analytics reporting in Bubble; use Looker for business intelligence data from your company's data warehouse. Many teams use both — GA4 as a data source that feeds Looker.
Frequently asked questions
Do I need a specific Looker plan to access the API from Bubble?
Yes. Looker API 4.0 access depends on your Looker platform tier and instance configuration. Your Looker instance admin must enable API access and grant the appropriate permissions to the service user. If your company has Looker, ask your admin to confirm that API access is enabled before starting Bubble setup — getting a 404 on the /api/4.0/login endpoint usually means the API is not enabled at the instance level, not a Bubble configuration error.
Why can't Bubble sign the Looker SSO embed URL directly?
Looker's SSO embed URL signing requires HMAC-SHA1 cryptographic signing — a standard algorithm used to generate a signature that Looker can verify. Bubble's API Connector and Workflow editor do not include native HMAC signing actions. While Bubble's Toolbox plugin allows running arbitrary JavaScript in a browser context, computing HMAC-SHA1 there would expose the embed secret to every user's browser. The correct solution is an external signing endpoint (e.g., a Cloudflare Worker) that holds the embed secret server-side and returns only the signed URL to Bubble.
Can I display Looker data in a Bubble chart rather than a Repeating Group?
Yes. Bubble's built-in Chart element accepts a data source. Store the Looker query results in a Bubble data type (Looker Row), then set the Chart's data source to 'Search for Looker Rows'. Map the x-axis field to a dimension (e.g., date or region) and the y-axis to a metric value (e.g., revenue). Alternatively, use a third-party Bubble chart plugin if you need more complex visualization types than Bubble's native chart supports.
Can I use Looker filters to show each logged-in Bubble user only their own data?
Yes. In the Run Query call, make the 'filters' body parameter dynamic. In your Bubble Workflow, set the filter value to the Current User's relevant attribute (e.g., company_id, region) before calling the Looker query. The Looker query will return only rows matching that filter, providing per-user data scoping without needing Looker's row-level security or user attribute configuration — though Looker's native user attributes are a more robust solution for complex multi-tenant requirements.
What happens if the Looker query returns more than 100 rows?
By default, the API returns all rows up to the 'limit' value you set in the query body. If you need more than 100 rows, increase the limit (Looker supports up to thousands of rows per query). For very large result sets, use pagination: run the query with a 'limit' and 'offset' parameter, and add a 'Load More' button in Bubble that increments the offset. Very large result sets can cause Bubble Workflow timeouts (30-second limit) — optimize the Looker LookML or add filters to scope the result set.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation