# How to Integrate Lovable with Looker

- Tool: Lovable
- Difficulty: Advanced
- Time required: 60 minutes
- Last updated: March 2026

## TL;DR

To integrate Lovable with Looker, create a Supabase Edge Function that calls the Looker API using client ID and secret credentials stored in Cloud → Secrets. The Edge Function handles look execution, dashboard embedding via SSO URL generation, and query running. Use Lovable's chat to build embedded BI dashboards with secure SSO token generation and custom analytics interfaces.

## Embed Looker dashboards and run custom queries in Lovable with the Looker API

Looker is Google Cloud's enterprise BI platform centered on LookML — a data modeling language that defines metrics, dimensions, and relationships in a version-controlled codebase. The Looker API provides programmatic access to three categories of functionality: analytics execution (running Looks and ad-hoc queries), content management (dashboards, Looks, and scheduled deliveries), and embedded analytics (generating signed SSO URLs for secure external embedding).

The embed SSO pattern is the highest-value use case for Lovable integrations. Instead of giving end users direct Looker access (which requires Looker seats), you generate SSO URLs server-side that create temporary, scoped Looker sessions. Your users see a Looker dashboard embedded in your app without needing Looker accounts — the SSO token controls which dashboards they can access and what filters are pre-applied.

Looker is architecturally distinct from Microsoft Power BI. Power BI uses DAX and integrates tightly with the Microsoft ecosystem (Azure, Teams, Office). Looker uses LookML and is Google Cloud native with strong BigQuery integration. For Google Cloud infrastructure teams, Looker is the natural BI choice. For Microsoft-heavy organizations, Power BI's ecosystem integration makes more sense.

## Before you start

- A Lovable account with a project that has Lovable Cloud enabled
- A Looker instance (Looker Cloud or Looker core) with admin access
- Looker API3 credentials — a client_id and client_secret generated in the Looker admin panel under Users → {your user} → Edit → API3 Keys
- An embed secret configured in Looker Admin → Platform → Embed for SSO embedding
- The hostname of your Looker instance — typically {company}.looker.com or {company}.cloud.looker.com

## Step-by-step guide

### 1. Generate Looker API3 credentials and embed secret

Looker uses a two-credential system for API authentication. API3 credentials consist of a client_id and client_secret that are used to obtain a short-lived access token. These tokens are then used for all subsequent API calls.

To generate API3 credentials, log into your Looker instance as an admin. Navigate to Admin → Users and click on your user account (or a service account user you create specifically for API access). Click 'Edit' and scroll to the 'API3 Keys' section. Click 'New API3 Key'. Looker generates a client_id and client_secret — copy both immediately, as the client_secret is only shown once.

For the embed SSO functionality, you also need an embed secret. Navigate to Admin → Platform → Embed in your Looker instance. Enable Looker Embedding and note the embed secret (or generate a new one). This embed secret is used to sign SSO URLs server-side — it is different from the API3 credentials and is specifically for the iframe embedding feature.

Also note your Looker instance hostname. If your Looker is accessed at company.looker.com, that is the hostname. For Looker Cloud instances, it may be company.cloud.looker.com. This hostname is used as the base URL for all API calls and embed URLs.

Keep in mind that Looker API3 credentials are user-scoped — the API token inherits the permissions of the user whose credentials you use. For a dashboard embedding service account, create a dedicated Looker user with appropriate view permissions and generate API3 keys for that user.

**Expected result:** You have a Looker API3 client_id, client_secret, an embed secret, and your Looker instance hostname. These four values are ready to store in Cloud → Secrets.

### 2. Store Looker credentials in Cloud → Secrets

Open your Lovable project and navigate to Cloud → Secrets via the '+' icon next to Preview. Add the following secrets:

- Name: LOOKER_CLIENT_ID — Value: your API3 client_id
- Name: LOOKER_CLIENT_SECRET — Value: your API3 client_secret
- Name: LOOKER_EMBED_SECRET — Value: your embed secret from Admin → Platform → Embed
- Name: LOOKER_HOST — Value: your Looker instance hostname (e.g., company.looker.com)

All four secrets are required for the full integration. The client_id and client_secret authenticate API calls. The embed secret signs SSO URLs for secure dashboard embedding. The host value parameterizes all API endpoints and embed URL construction.

Looker credentials provide access to your organization's business data — dashboards, reports, and query results may contain sensitive revenue, customer, or operational data. Apply strict access controls to these secrets.

**Expected result:** LOOKER_CLIENT_ID, LOOKER_CLIENT_SECRET, LOOKER_EMBED_SECRET, and LOOKER_HOST appear in Cloud → Secrets with masked values.

### 3. Create the Looker API proxy Edge Function with SSO URL generation

Create the Edge Function that handles three functions: obtaining Looker API tokens via client credentials, making API calls to run looks and queries, and generating SSO embed URLs for dashboard embedding.

The API token flow: POST to https://{host}/api/4.0/login with client_id and client_secret to obtain a short-lived token. This token is used for all subsequent API calls in the same function invocation.

The SSO URL generation flow is more complex. The SSO URL is constructed from the dashboard path, the viewer's identity (email and external_user_id), permissions, and a nonce and expiry. The URL is then signed using HMAC-SHA256 with the embed secret. Looker's embedding documentation specifies the exact parameter order for signing.

The code below implements the complete Looker SSO URL signing process. It is the most technically involved part of this integration — pay careful attention to the parameter encoding and signing order.

```
// supabase/functions/looker-api/index.ts
async function getLookerToken(host: string, clientId: string, clientSecret: string): Promise<string> {
  const response = await fetch(`https://${host}/api/4.0/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({ client_id: clientId, client_secret: clientSecret }),
  });
  const data = await response.json();
  if (!data.access_token) throw new Error(`Looker login failed: ${JSON.stringify(data)}`);
  return data.access_token;
}

async function generateEmbedSSOUrl(
  host: string,
  embedSecret: string,
  dashboardId: string,
  userEmail: string,
  userId: string,
  permissions: string[],
  filters: Record<string, string> = {}
): Promise<string> {
  const nonce = crypto.randomUUID();
  const time = Math.floor(Date.now() / 1000);
  const sessionLength = 3600; // 1 hour

  const embedPath = `/embed/dashboards/${dashboardId}`;
  const filterParams = Object.keys(filters).length > 0
    ? '?' + new URLSearchParams(filters).toString()
    : '';

  const params = [
    `nonce=${encodeURIComponent(JSON.stringify(nonce))}`,
    `time=${time}`,
    `session_length=${sessionLength}`,
    `external_user_id=${encodeURIComponent(JSON.stringify(userId))}`,
    `permissions=${encodeURIComponent(JSON.stringify(permissions))}`,
    `models=${encodeURIComponent(JSON.stringify([]))}`,
    `group_ids=${encodeURIComponent(JSON.stringify([]))}`,
    `external_group_id=${encodeURIComponent(JSON.stringify(''))}`,
    `user_attributes=${encodeURIComponent(JSON.stringify({}))}`,
    `access_filters=${encodeURIComponent(JSON.stringify({}))}`,
    `first_name=${encodeURIComponent(JSON.stringify(userEmail.split('@')[0]))}`,
    `last_name=${encodeURIComponent(JSON.stringify(''))}`,
    `user_timezone=null`,
    `force_logout_login=true`,
    `url=${encodeURIComponent(embedPath + filterParams)}`,
  ];

  const stringToSign = `${host}\n${embedPath}\n${params.join('\n')}`;

  const key = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(embedSecret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  );
  const signature = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(stringToSign));
  const signatureHex = Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, '0')).join('');

  return `https://${host}/login/embed/${encodeURIComponent(embedPath)}?${params.join('&')}&signature=${signatureHex}`;
}

Deno.serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response(null, {
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization',
      },
    });
  }

  const clientId = Deno.env.get('LOOKER_CLIENT_ID')!;
  const clientSecret = Deno.env.get('LOOKER_CLIENT_SECRET')!;
  const embedSecret = Deno.env.get('LOOKER_EMBED_SECRET')!;
  const host = Deno.env.get('LOOKER_HOST')!;

  if (!clientId || !clientSecret || !host) {
    return new Response(JSON.stringify({ error: 'Looker credentials not configured' }), {
      status: 500,
      headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
    });
  }

  const { action, params } = await req.json();

  try {
    if (action === 'embed_url') {
      const ssoUrl = await generateEmbedSSOUrl(
        host, embedSecret,
        params.dashboard_id,
        params.user_email,
        params.user_id,
        params.permissions || ['access_data', 'see_user_dashboards'],
        params.filters || {}
      );
      return new Response(JSON.stringify({ url: ssoUrl }), {
        headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
      });
    }

    const token = await getLookerToken(host, clientId, clientSecret);
    const apiHeaders = { 'Authorization': `token ${token}`, 'Content-Type': 'application/json' };

    let url: string;
    if (action === 'dashboards') {
      url = `https://${host}/api/4.0/dashboards`;
    } else if (action === 'run_look') {
      url = `https://${host}/api/4.0/looks/${params.look_id}/run/json`;
    } else if (action === 'run_query') {
      url = `https://${host}/api/4.0/queries/${params.query_id}/run/json`;
    } else {
      return new Response(JSON.stringify({ error: 'Invalid action' }), {
        status: 400,
        headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
      });
    }

    const response = await fetch(url, { method: 'GET', headers: apiHeaders });
    const data = await response.json();

    return new Response(JSON.stringify(data), {
      status: response.ok ? 200 : response.status,
      headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
    });
  } catch (err) {
    return new Response(JSON.stringify({ error: err.message }), {
      status: 500,
      headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
    });
  }
});
```

**Expected result:** The looker-api Edge Function is deployed. Calling it with { action: 'dashboards' } returns the list of Looker dashboards. Calling it with { action: 'embed_url', params: { dashboard_id, user_email, user_id } } returns a signed SSO URL.

### 4. Embed Looker dashboards in the Lovable React app

With the Edge Function generating SSO URLs, use Lovable's chat to build the embedded dashboard component. The component calls the Edge Function to get a fresh SSO URL, then renders a Looker iframe using that URL.

Looker's embed SDK (available as @looker/embed-sdk on npm) provides better control over the embedded dashboard than a raw iframe — it supports events (dashboard:loaded, dashboard:run:complete), filter injection, and programmatic dashboard control. For basic embedding without the SDK, a raw iframe with the SSO URL is sufficient.

For multi-dashboard portals, build a navigation sidebar that lists available dashboards (fetched from the Edge Function using action='dashboards') and updates the iframe src when the user selects a different dashboard. Each navigation triggers a new SSO URL generation for the selected dashboard.

For user-context filtering — where each user should only see their own data — the filters parameter in the embed_url action pre-applies Looker filters to the embedded dashboard. The filter keys must match the field names in the Looker LookML model. For example, if the dashboard has a filter on the 'users.organization_id' dimension, pass filters: { 'users.organization_id': user.organization_id } to scope the dashboard to the current user's organization.

For complex enterprise embedded analytics with multi-tenant data isolation, custom LookML development, and white-label BI portals, RapidDev's team can help design the full architecture including the Looker model configuration and Supabase user-attribute mapping.

```
// src/components/LookerEmbed.tsx
import { useState, useEffect } from 'react';
import { supabase } from '@/lib/supabase';

interface LookerEmbedProps {
  dashboardId: string;
  filters?: Record<string, string>;
  height?: string;
}

export function LookerEmbed({ dashboardId, filters = {}, height = '80vh' }: LookerEmbedProps) {
  const [embedUrl, setEmbedUrl] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const generateUrl = async () => {
    setLoading(true);
    setError(null);
    try {
      const { data: { user } } = await supabase.auth.getUser();
      if (!user) throw new Error('Not authenticated');

      const { data, error: fnError } = await supabase.functions.invoke('looker-api', {
        body: {
          action: 'embed_url',
          params: {
            dashboard_id: dashboardId,
            user_email: user.email,
            user_id: user.id,
            permissions: ['access_data', 'see_user_dashboards'],
            filters,
          },
        },
      });

      if (fnError) throw fnError;
      setEmbedUrl(data.url);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => { generateUrl(); }, [dashboardId]);

  if (loading) return <div className="flex items-center justify-center" style={{ height }}>Loading dashboard...</div>;
  if (error) return <div className="text-red-500 p-4">Error: {error}</div>;

  return (
    <div className="w-full relative" style={{ height }}>
      <iframe
        src={embedUrl!}
        className="w-full h-full border-0 rounded-lg"
        allowFullScreen
        title="Looker Dashboard"
      />
    </div>
  );
}
```

**Expected result:** A Looker dashboard is embedded in the Lovable app inside an iframe. The dashboard loads with the correct user context and any pre-applied filters. The loading spinner disappears once the dashboard finishes rendering.

## Best practices

- Store all Looker credentials in Cloud → Secrets — they provide access to potentially sensitive business intelligence data that should never appear in browser code
- Create a dedicated Looker service account user for API access with the minimum permissions needed — do not use an admin user's credentials for application API access
- Generate fresh SSO URLs on each page load rather than caching them — SSO URLs have a defined expiry and may be single-use, making caching unreliable
- Use Looker's user_attributes parameter in SSO URL generation to pass user context (like organization_id or region) that triggers row-level security filters in the LookML model
- Request Looker API tokens freshly on each Edge Function invocation rather than storing them — tokens are short-lived and the login call is fast
- Implement CORS correctly on the Edge Function since the iframe will load the Looker URL directly, but the SSO URL generation call from React goes through the Edge Function
- Test SSO URL signing with Looker's embed test page at https://{your-host}/admin/embed/test before integrating with Lovable — this confirms the embed secret and signing parameters are correct

## Use cases

### Embedded customer analytics portal

Build a customer portal where each customer can see their own analytics dashboard embedded from Looker. The Edge Function generates a Looker SSO URL scoped to the customer's organization ID, and the iframe in your Lovable app displays their filtered dashboard without any direct Looker access.

Prompt example:

```
Create a customer analytics page that calls the looker-api Edge Function to generate a Looker SSO embed URL for the currently logged-in user's organization. The SSO URL should pre-filter the dashboard to show only that customer's data. Display the Looker dashboard in a full-screen iframe. Show a loading spinner until the dashboard iframe loads.
```

### Embedded executive dashboard with custom branding

Create an executive reporting page that embeds a pre-built Looker dashboard with your company's branding and custom navigation. The embed uses SSO to authenticate the viewer and apply their permission level, while the custom Lovable wrapper provides the navigation, header, and context that Looker's interface does not.

Prompt example:

```
Build an executive dashboard page with your company's branded header and navigation. Below the header, embed the Looker dashboard with ID 42 using a signed SSO URL from the looker-api Edge Function. Add a date range picker above the iframe that updates the Looker embed URL's filters when the date changes. The embed should fill the remaining viewport height.
```

### Custom query runner and data export tool

Build a data exploration tool that lets analysts run custom queries against Looker's LookML model and export results to CSV or display them in a table. The tool uses the Looker Query API to execute inline queries and stream results back through the Edge Function.

Prompt example:

```
Create a query runner page where data analysts can select a LookML model and explore from a dropdown, add dimensions and measures from a checklist fetched from the Looker API, apply filters, and run the query via the looker-api Edge Function. Display results in a sortable table with a 'Export CSV' button. Show row count and query execution time.
```

## Troubleshooting

### SSO embed URL returns 'Invalid embed signature' when loaded in the iframe

Cause: The SSO URL signing order or encoding is incorrect. Looker's SSO URL signature is extremely sensitive to parameter order, encoding, and newline characters in the string to sign.

Solution: Compare your signing implementation against Looker's official embed documentation at docs.looker.com/reference/embed-api. Verify the string to sign uses the exact parameter order Looker specifies. Check that newlines in the string to sign are Unix newlines (\n) not Windows newlines (\r\n). Log the string to sign before hashing and compare it to a working example from Looker's documentation.

### Looker API login returns 404 or 'Not Found' for the /api/4.0/login endpoint

Cause: The LOOKER_HOST value in Cloud → Secrets does not include the correct hostname, or your Looker instance uses a different API path.

Solution: Verify the LOOKER_HOST value is just the hostname without protocol or path — for example, company.looker.com not https://company.looker.com/api. Check your Looker instance's API explorer at https://{your-host}/api/4.0/ to confirm the API version and login endpoint path.

### Embedded dashboard shows 'Session expired' or redirects to Looker login page

Cause: The SSO URL has expired (default 1 hour) or was already used (Looker invalidates nonces after use in some configurations).

Solution: Generate a fresh SSO URL each time the dashboard page is loaded — do not cache SSO URLs in localStorage or Supabase. Add a refresh button that calls the Edge Function to get a new URL and updates the iframe src. If sessions are expiring mid-session, increase the session_length parameter in the SSO URL generation, up to the maximum your Looker configuration allows.

### Looker API returns 403 'Forbidden' or 'You do not have permission to perform this action'

Cause: The API3 credentials belong to a user without permission to access the requested resource, or the credentials have been revoked.

Solution: Log into Looker and verify the service account user's permissions include access to the dashboards and models you are querying. Navigate to Admin → Users → {service account user} → Roles to check assigned permissions. Also verify the API3 key has not been revoked — go to the user's profile and check the API3 Keys section shows active keys.

## Frequently asked questions

### Do end users of my Lovable app need Looker accounts to see embedded dashboards?

No — the Looker embed SSO pattern specifically enables external users to view Looker dashboards without Looker accounts. Your Lovable app generates a signed SSO URL server-side that creates a temporary, scoped Looker session for the viewer. The viewer sees the Looker dashboard inside an iframe but never interacts directly with Looker. This is the primary use case for embedded analytics: providing BI insights to customers or stakeholders who do not have and do not need Looker seats.

### What is LookML and do I need to know it to use Looker's API?

LookML is Looker's data modeling language — it defines how your database tables map to business concepts (dimensions, measures, and relationships). You do not need to know LookML to use the Looker API for embedding or running existing Looks and dashboards. LookML is the responsibility of the data team that built and maintains the Looker instance. To use the API effectively, you need to know the IDs of the dashboards and Looks you want to embed, and the field names of filters you want to apply — your data team can provide these.

### How is Looker different from Looker Studio (formerly Google Data Studio)?

Looker (formerly Looker Data Sciences, acquired by Google in 2019) is an enterprise BI platform with LookML data modeling, governance features, and programmatic API access. Looker Studio (formerly Google Data Studio) is a free, self-service visualization tool that connects to data sources like Google Sheets, BigQuery, and Analytics. They are different products that happen to share the Looker brand. The API described in this tutorial is for Looker (enterprise), not Looker Studio.

### What permissions does the Looker embed SSO URL need?

The permissions parameter in the SSO URL generation controls what the embedded user can do. For read-only dashboard viewing, use ['access_data', 'see_user_dashboards']. To allow users to explore data and create their own queries, add 'explore'. To allow downloading data, add 'download_without_limit'. The permissions available depend on how your Looker instance is configured — check with your Looker admin for the permission names that correspond to the capabilities you want to enable for embedded users.

---

Source: https://www.rapidevelopers.com/lovable-integration/looker
© RapidDev — https://www.rapidevelopers.com/lovable-integration/looker
