Kommo + Looker Studio: Sales Dashboard Without Manual CRM Data Exports

Looker Studio (formerly Google Data Studio) is a free data visualization tool from Google. Connecting Kommo data to Looker Studio directly is not possible: there is no native connector. The correct architecture is via an intermediate database: Kommo API -> ETL script -> PostgreSQL -> Looker Studio connector. The result: live dashboards for pipeline, conversion, and team KPIs, updating every hour without manual exports.

This is a relevant task for custom analytics integrations for Kommo: Kommo’s built-in analytics is limited, and Looker Studio is a powerful and free tool that marketers and CMOs already know.

Why Kommo Cannot Connect to Looker Studio Directly

Looker Studio supports connections via “Community Connectors” - third-party plugins. There is no official Kommo connector in the connector gallery. Unofficial connectors that occasionally appear in the gallery use outdated API or work unreliably.

Why CSV export does not solve the problem. Some teams export deals from Kommo to CSV and load them into Google Sheets, from which Looker Studio reads data. This works, but:

  • Data is only current as of the moment of export (manual or scheduled)
  • Historical data across different periods cannot be loaded automatically
  • CSV does not contain all fields (for example, stage change history)
  • When the export structure changes, formulas in Google Sheets break

The correct approach - an ETL pipeline: Kommo API fetches data once per hour, saves it to PostgreSQL (or BigQuery), Looker Studio reads via a standard SQL connector.

Integration Architecture

Components:

  • Python ETL script (runs via cron every hour)
  • Kommo REST API v4: /api/v4/leads, /api/v4/contacts, /api/v4/pipelines
  • PostgreSQL (or BigQuery) as the data store
  • Looker Studio connected to PostgreSQL via a Community Connector or directly via BigQuery

Database schema (main tables):

CREATE TABLE kommo_leads (
    id BIGINT PRIMARY KEY,
    name TEXT,
    status_id INT,
    pipeline_id INT,
    price NUMERIC(12,2),
    responsible_user_id INT,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    closed_at TIMESTAMPTZ,
    loss_reason_id INT,
    -- custom fields (example)
    custom_source TEXT,
    custom_country TEXT,
    synced_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE kommo_lead_status_history (
    id BIGSERIAL PRIMARY KEY,
    lead_id BIGINT REFERENCES kommo_leads(id),
    from_status_id INT,
    to_status_id INT,
    changed_at TIMESTAMPTZ,
    responsible_user_id INT
);

CREATE TABLE kommo_pipelines (
    id INT PRIMARY KEY,
    name TEXT
);

CREATE TABLE kommo_statuses (
    id INT PRIMARY KEY,
    pipeline_id INT,
    name TEXT,
    sort INT,
    is_won BOOLEAN,
    is_lost BOOLEAN
);

Python ETL script:

import requests
import psycopg2
from datetime import datetime, timezone
import time

KOMMO_BASE = "https://YOUR_ACCOUNT.kommo.com"

def fetch_leads_page(token: str, page: int, updated_since: int = None) -> list:
    headers = {"Authorization": f"Bearer {token}"}
    params = {"page": page, "limit": 250, "with": "contacts,loss_reason"}
    if updated_since:
        params["filter[updated_at][from]"] = updated_since
    
    resp = requests.get(
        f"{KOMMO_BASE}/api/v4/leads",
        headers=headers,
        params=params
    )
    if resp.status_code == 204:
        return []  # No data
    resp.raise_for_status()
    return resp.json().get("_embedded", {}).get("leads", [])

def upsert_lead(conn, lead: dict):
    custom_fields = {cf["field_id"]: cf.get("values", [{}])[0].get("value") 
                     for cf in lead.get("custom_fields_values") or []}
    
    with conn.cursor() as cur:
        cur.execute("""
            INSERT INTO kommo_leads (
                id, name, status_id, pipeline_id, price, 
                responsible_user_id, created_at, updated_at, closed_at,
                loss_reason_id, custom_source, custom_country, synced_at
            ) VALUES (%s,%s,%s,%s,%s,%s,
                to_timestamp(%s), to_timestamp(%s), to_timestamp(%s),
                %s,%s,%s, NOW())
            ON CONFLICT (id) DO UPDATE SET
                name=EXCLUDED.name, status_id=EXCLUDED.status_id,
                pipeline_id=EXCLUDED.pipeline_id, price=EXCLUDED.price,
                updated_at=EXCLUDED.updated_at, closed_at=EXCLUDED.closed_at,
                loss_reason_id=EXCLUDED.loss_reason_id,
                custom_source=EXCLUDED.custom_source,
                custom_country=EXCLUDED.custom_country,
                synced_at=EXCLUDED.synced_at
        """, (
            lead["id"], lead["name"], lead["status_id"], lead["pipeline_id"],
            lead["price"], lead["responsible_user_id"],
            lead["created_at"], lead["updated_at"],
            lead.get("closed_at"), lead.get("loss_reason_id"),
            custom_fields.get(SOURCE_FIELD_ID),
            custom_fields.get(COUNTRY_FIELD_ID),
        ))
    conn.commit()

def run_etl(token: str, db_url: str, updated_since: int = None):
    conn = psycopg2.connect(db_url)
    page = 1
    total = 0
    while True:
        leads = fetch_leads_page(token, page, updated_since)
        if not leads:
            break
        for lead in leads:
            upsert_lead(conn, lead)
        total += len(leads)
        page += 1
        time.sleep(0.1)  # Rate limiting
    conn.close()
    print(f"ETL complete: {total} leads synced")

Connecting PostgreSQL to Looker Studio

Looker Studio does not have a built-in PostgreSQL connector, but there are several options:

Option A - Google BigQuery (recommended). Use BigQuery instead of PostgreSQL as the data store. Looker Studio has a native free connector to BigQuery. The ETL script writes to BigQuery via the google-cloud-bigquery client.

Option B - Community Connector for PostgreSQL. The Looker Studio gallery has third-party connectors (for example, Supermetrics PostgreSQL Connector). They are paid (~$50/month) but work reliably.

Option C - Google Sheets as an intermediate layer. The ETL script exports aggregated data to Google Sheets via the Sheets API. Looker Studio reads from Sheets. Works, but is limited to 10 million cells.

Example Dashboards in Looker Studio

“Pipeline by stage” dashboard: number of deals at each stage, average deal value, time in stage. SQL:

SELECT s.name AS stage, COUNT(l.id) AS deals,
       AVG(l.price) AS avg_deal_value
FROM kommo_leads l
JOIN kommo_statuses s ON l.status_id = s.id
WHERE l.pipeline_id = 1 AND NOT s.is_lost
GROUP BY s.name, s.sort ORDER BY s.sort;

“Manager KPIs” dashboard: number of closed deals, amount, conversion rate - broken down by manager for a selected period.

“Conversion by source” dashboard: where leads come from (field custom_source) and what percentage moves to Won.

Real Case: IT Consulting, 18 People

An IT consulting company, sales in EU and US. The CMO wanted a weekly pipeline report in Looker Studio - to share with board members who do not have access to Kommo.

Before integration: the CFO manually exported CSV from Kommo every Friday, loaded it into Google Sheets, updated formulas. 2-3 hours of work, data always slightly stale.

After integration: the ETL pipeline pulls data from Kommo into BigQuery every 2 hours. The Looker Studio dashboard is always current. Board members view it via a link without Kommo access. Filter by period, by manager, by source - all in one click.

Result: 2-3 hours/week -> 0 manual work. The dashboard is shared via link and updates automatically.

Who This Is For

Kommo + Looker Studio integration is relevant for:

  • Companies where the CMO or CFO wants to see sales data without CRM access
  • Teams that already use Google Workspace and BigQuery
  • Organizations with a requirement for regular reporting to a board or investors
  • Those who want to combine Kommo data with ad analytics in a single dashboard

For more advanced sales analytics, we also recommend looking at Prooflytics - a platform that unifies CRM and ad cabinet data without needing to build an ETL pipeline manually.

Frequently Asked Questions

How often is the data in the dashboard updated?

The update frequency depends on the ETL cron schedule. Typically run once per hour. For near-real-time reports (for example, for a call center) it can be set to every 5 minutes, but the Kommo API rate limits (60 requests/minute) need to be monitored. Looker Studio caches data for 12 hours by default - this value can be reduced in the data source settings.

Is a dedicated server needed for ETL?

No. The ETL script can be run as a Google Cloud Function on a schedule (Cloud Scheduler). Cost - a few dollars per month with hourly runs. Alternatively - a simple VPS or even a local machine with cron. BigQuery storage is free up to 10 GB.

How do you connect data from multiple Kommo pipelines?

The Kommo API returns pipeline_id with each deal. In PostgreSQL the kommo_pipelines table stores the name of each pipeline. In Looker Studio you create a filter by pipeline - and can switch between pipelines in one dashboard or create separate pages.

Can data from other sources be added to the same dashboard?

Yes. BigQuery allows joining data from different sources via JOIN. If you have data from Google Ads, Meta Ads, or other sources - they can be loaded into the same BigQuery and combined dashboards can be built: “cost per lead from each channel with CRM conversion.”

Looker Studio vs Metabase: which to choose?

Looker Studio is free and integrates well with the Google ecosystem. Metabase is better for teams that need complex SQL and self-hosted deployment. For sharing dashboards with external stakeholders (board, investors) Looker Studio is more convenient - the link works without authentication. A breakdown of the custom Kommo + Metabase integration is available on Exceltic.dev.

Next Step

If you need a Kommo dashboard in Looker Studio - describe the task to the Exceltic.dev team. We will review the required metrics, data structure, and select the appropriate storage type (BigQuery vs PostgreSQL). A standard project takes 2-3 weeks including dashboard setup.

More articles

All →