
ClickHouse Logs Queries
OfficialFreeEfficiently manage Supabase logs with ClickHouse SQL.
Free · Opens the source repo
What ClickHouse Logs Queries does
The ClickHouse Logs Queries skill provides developers and data analysts with the necessary tools to write, review, and migrate SQL queries for Supabase logs stored in a ClickHouse-backed table. This skill is particularly useful for those working with the logs.all.otel analytics endpoint, where log entries from various services are consolidated into a single table. Unlike the previous BigQuery model, which required complex joins and multiple tables, this skill simplifies log querying by allowing users to directly access log attributes through a unified schema.
Users can leverage this skill in two primary scenarios: writing or reviewing logs queries in the Logs Explorer or integrating logs queries into the Supabase Studio codebase. The skill includes guidance on how to translate existing BigQuery queries into the ClickHouse format, ensuring a smooth transition for teams migrating from one system to another. The references provided in the bundled files offer essential insights into codebase integration and migration best practices.
The logs table includes critical fields such as id, timestamp, event_message, severity_text, and a log_attributes map that contains structured data specific to each log source. This allows for rich querying capabilities, enabling users to filter logs by service source and access detailed attributes without the need for unnesting operations. The skill emphasizes best practices for writing efficient queries, including the importance of filtering by source and including limits to manage performance effectively.
Overall, the ClickHouse Logs Queries skill is designed for engineers and data professionals who need to work with log data in Supabase, providing them with the necessary tools and practices to efficiently query and analyze logs.
When to use it
Use this skill when you need to write or review SQL queries for Supabase logs or when integrating logs queries into the Supabase codebase.
When not to use it
This skill is not suitable for users who are not working with Supabase logs or those who do not require SQL querying capabilities.
What you can build with it
Writing a New Logs Query
Utilize this skill to craft a new SQL query for analyzing recent requests in the Logs Explorer.
Migrating from BigQuery
Refer to the migration guide to convert existing BigQuery logs queries into the ClickHouse format seamlessly.
Integrating Logs Queries into Codebase
Use this skill to wire analytics log SQL into the Supabase Studio codebase effectively.
How to install ClickHouse Logs Queries
View source1. Install with the skills CLI
npx skills add supabase/supabase/clickhouse-logs-queries --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by supabaseQuerying Supabase logs (ClickHouse)
Supabase logs live in a single ClickHouse logs table, served by the
logs.all.otel analytics endpoint. Every log line from every part of the stack
is one row in this table, tagged by a source column. This replaces the older
BigQuery model, where each service had its own table and fields were reached
through cross join unnest(metadata).
Two kinds of work use this skill, and they share the same SQL model:
- Writing or reviewing a logs query (in the Logs Explorer or anywhere a raw ClickHouse logs query is needed). Start here in this file.
- Wiring a logs query in the Studio codebase (branded analytics SQL, the endpoint picker, the OTEL query builders). Read references/codebase-integration.md.
If you are converting an existing BigQuery logs query, read references/bigquery-migration.md for the full translation table.
The logs table
Each row has a small set of real columns. Everything specific to a service lives
in log_attributes.
| Column | Type | Notes |
|---|---|---|
id | String | Unique log identifier. |
timestamp | DateTime64 (UTC) | When the log was produced. Order/compare it directly. |
event_message | String | The raw log line. |
severity_text | String | Log level, when the source sets one. |
source | String | The service the log came from. Always filter on this. |
log_attributes | Map(String, String) | Structured per-source fields, keyed by a dotted path. |
timestamp is formatted like 2026-06-22T09:34:06.215000 (ISO 8601, microsecond
precision, no trailing Z). In the Logs Explorer the selected time range is
applied for you, so you rarely need to write a timestamp filter by hand.
A minimal, well-formed query. Lead with a comment naming the query, filter by
source, and always limit:
-- recent edge requests
select timestamp, event_message
from logs
where source = 'edge_logs'
order by timestamp desc
limit 100;
Sources
source selects the service. The common ones:
edge_logs— API gateway requests and responsespostgres_logs— database statements and errors (also where pg_cron logs live)auth_logs— authentication and authorization activityfunction_edge_logs— edge function requests and responsesfunction_logs—consoleoutput from inside edge functionsstorage_logs— object upload and retrieval activityrealtime_logs— Realtime client connectionspostgrest_logs,supavisor_logs,pgbouncer_logs— mostlyid,timestamp,event_message
The Logs Explorer Field Reference drawer lists every source and the fields it actually sets. When in doubt about a key, discover it from real data rather than guessing (see below).
Reading fields from log_attributes
log_attributes maps a string key to a string value. Read a field with bracket
access. There are no unnesting joins:
select
log_attributes['request.method'] as method,
log_attributes['request.path'] as path,
log_attributes['response.status_code'] as status
from logs
where source = 'edge_logs'
The key keeps the dotted path that BigQuery expressed through nested structs, with
the metadata root dropped: BigQuery metadata.request.method becomes
log_attributes['request.method']. Keep the full prefix — request.cf.country
is log_attributes['request.cf.country'], not log_attributes['cf.country'].
Common keys by source:
edge_logs:request.method,request.path,request.search,response.status_code,identifierpostgres_logs:parsed.error_severity,parsed.detail,parsed.hint,parsed.query,identifierauth_logs:level,status,path,msg,errorfunction_edge_logs:response.status_code,request.method,request.pathname,function_id,execution_id,execution_time_msfunction_logs:event_type,function_id,execution_id,level
Numeric fields are strings
Map values are always strings. To compare or aggregate a numeric field, wrap it in
toInt32OrZero, which returns 0 for missing or non-numeric values so it never
errors on partial data:
select count() as server_errors
from logs
where source = 'edge_logs'
and toInt32OrZero(log_attributes['response.status_code']) between 500 and 599
Discover the keys a source sets
Read mapKeys from recent rows rather than guessing key names:
select arrayJoin(mapKeys(log_attributes)) as key, count() as n
from logs
where source = 'postgres_logs'
group by key
order by n desc
limit 100;
arrayJoin(mapKeys(...)) flattens the map keys into one row per key so you can
rank them by frequency. (The Studio codebase does exactly this for the Field
Reference drawer and to feed real keys to the AI rewrite.)
ClickHouse vs BigQuery functions
These are the substitutions that trip people up most:
| Need | BigQuery | ClickHouse |
|---|---|---|
| Count rows | count(*) | count() |
| Regex match | regexp_contains(x, 'p') | match(x, 'p') |
| Substring match | x like '%p%' | x ilike '%p%' (case-insensitive) or like |
| Numeric coercion | cast(x as int64) | toInt32OrZero(x) |
| Read the timestamp | cast(timestamp as datetime) | timestamp (use the column directly) |
| Map keys | n/a (used unnest) | mapKeys(log_attributes) |
The logs.all.otel analytics endpoint (and the Logs Explorer on top of it)
rejects count(*) and select * — use count() and list the columns you need.
(Raw ClickHouse supports both; this is a constraint of the logs query surface.)
Best practices
These keep queries correct and cheap. Log tables are large; an unbounded scan reads far more data than you need.
- Start every query with an identifying comment (e.g.
-- errors since last deploy). It labels the query in logs and review, and makes each of several queries in a file easy to tell apart. - Always include a
LIMIT. Even for aggregates while you iterate. - Always query
from logs where source = '...'. There is no per-service table (noedge_logs,postgres_logs, etc. table) — there is onelogstable, andsourcescopes it to a service. Filtering bysourceis required, not just an optimization. - Keep the time range tight. A smaller window returns results faster.
- Filter on the real columns (
source,timestamp) before reaching intolog_attributes. - Order by
timestamp descto see the most recent logs first. - Use
count(), notcount(*)orselect *.
Worked examples
Requests by status code:
select
toInt32OrZero(log_attributes['response.status_code']) as status,
count() as count
from logs
where source = 'edge_logs'
group by status
order by count desc
limit 50
Auth errors:
select timestamp, event_message, log_attributes['msg'] as message
from logs
where source = 'auth_logs'
and log_attributes['level'] in ('error', 'fatal')
order by timestamp desc
limit 100
Search the raw message:
select timestamp, event_message
from logs
where source = 'postgres_logs'
and event_message ilike '%deadlock%'
order by timestamp desc
limit 100
Postgres errors grouped by severity (the canonical unnest-to-map conversion):
select log_attributes['parsed.error_severity'] as severity, count() as count
from logs
where source = 'postgres_logs'
and log_attributes['parsed.error_severity'] in ('ERROR', 'FATAL', 'PANIC')
group by severity
order by count desc
limit 100
When the user pastes a BigQuery query
Convert it rather than running it as-is. The mechanical steps (drop the
per-service table for from logs where source = ..., remove every
cross join unnest(...), rewrite unnest-alias columns as log_attributes['...']
lookups, swap the functions above) are spelled out with a full before/after in
references/bigquery-migration.md. The Logs
Explorer also has a built-in Rewrite to ClickHouse action that does this with
AI; point users to it for one-off conversions in the dashboard.
Frequently asked questions about ClickHouse Logs Queries
Similar skills
EF Core D2 Database Diagram Generator
Visualize your EF Core models as D2 diagrams effortlessly.
Safe SQL Execution
Ensure secure SQL execution in Supabase applications.
Oracle to PostgreSQL Migration
Identify migration risks between Oracle and PostgreSQL.
SSMA Console
Streamline Oracle to SQL Server migrations with ease.
SQL Performance Optimization
Enhance SQL query efficiency across all databases.
Snowflake Semantic Views
Manage and validate Snowflake semantic views effortlessly.
