This guide shows you how to build metric queries to fetch data from Lightdash explores. For detailed API documentation, see Metric Queries API.
A metric query specifies:
import { type MetricQuery } from "@lightdash/common";
const query: MetricQuery = {
exploreName: "orders",
dimensions: ["orders_customer_id", "orders_status"],
metrics: ["orders_count", "orders_total_amount"],
filters: { dimensions: { id: "root", and: [] } },
sorts: [{ fieldId: "orders_created_at", descending: false }],
limit: 1000,
tableCalculations: [],
};First, discover what fields are available in your explore:
import { getFields, getDimensions, getMetrics, type Explore } from "@lightdash/common";
const explore: Explore = /* ... get explore ... */;
// Get all fields
const allFields = getFields(explore);
console.log(`Total fields: ${allFields.length}`);
// Get dimensions (grouping columns)
const dimensions = getDimensions(explore);
console.log(`Dimensions: ${dimensions.length}`);
dimensions.forEach(d => console.log(` - ${d.name}: ${d.label}`));
// Get metrics (aggregations)
const metrics = getMetrics(explore);
console.log(`Metrics: ${metrics.length}`);
metrics.forEach(m => console.log(` - ${m.name}: ${m.label}`));Choose which dimensions and metrics to include:
import { type MetricQuery } from "@lightdash/common";
const query: MetricQuery = {
exploreName: "orders",
// Select first 2 dimensions
dimensions: dimensions.slice(0, 2).map(d => d.name),
// Select first 3 metrics
metrics: metrics.slice(0, 3).map(m => m.name),
filters: { dimensions: { id: "root", and: [] } },
sorts: [],
limit: 100,
tableCalculations: [],
};Filter the data using various operators:
import { FilterOperator, UnitOfTime, type FilterRule } from "@lightdash/common";
// Date filter: last 30 days
const dateFilter: FilterRule = {
id: "date_filter",
target: { fieldId: "orders_created_at" },
operator: FilterOperator.IN_THE_PAST,
values: [30],
settings: { unitOfTime: UnitOfTime.days },
};
// String filter: specific status
const statusFilter: FilterRule = {
id: "status_filter",
target: { fieldId: "orders_status" },
operator: FilterOperator.EQUALS,
values: ["completed"],
};
// Add to query
query.filters = {
dimensions: {
id: "root",
and: [dateFilter, statusFilter],
},
};Sort results by one or more fields:
import { type SortField } from "@lightdash/common";
query.sorts = [
// Primary sort: by metric descending
{ fieldId: "orders_total_amount", descending: true },
// Secondary sort: by dimension ascending
{ fieldId: "orders_customer_id", descending: false },
];Control how many rows to return:
query.limit = 500; // Return up to 500 rowsGet data from the last N days:
import { FilterOperator, UnitOfTime } from "@lightdash/common";
const query: MetricQuery = {
exploreName: "orders",
dimensions: ["orders_order_date"],
metrics: ["orders_count"],
filters: {
dimensions: {
id: "root",
and: [{
id: "recent",
target: { fieldId: "orders_created_at" },
operator: FilterOperator.IN_THE_PAST,
values: [7],
settings: { unitOfTime: UnitOfTime.days },
}],
},
},
sorts: [{ fieldId: "orders_order_date", descending: false }],
limit: 1000,
tableCalculations: [],
};Get top 10 customers by revenue:
const query: MetricQuery = {
exploreName: "customers",
dimensions: ["customers_customer_id", "customers_name"],
metrics: ["customers_total_revenue"],
filters: { dimensions: { id: "root", and: [] } },
sorts: [{ fieldId: "customers_total_revenue", descending: true }],
limit: 10,
tableCalculations: [],
};Orders that are either "pending" OR "processing":
import { FilterOperator } from "@lightdash/common";
const query: MetricQuery = {
exploreName: "orders",
dimensions: ["orders_status"],
metrics: ["orders_count"],
filters: {
dimensions: {
id: "root",
or: [
{
id: "pending",
target: { fieldId: "orders_status" },
operator: FilterOperator.EQUALS,
values: ["pending"],
},
{
id: "processing",
target: { fieldId: "orders_status" },
operator: FilterOperator.EQUALS,
values: ["processing"],
},
],
},
},
sorts: [],
limit: 100,
tableCalculations: [],
};Orders between $100 and $500:
import { FilterOperator } from "@lightdash/common";
const rangeFilter = {
id: "amount_range",
target: { fieldId: "orders_total_amount" },
operator: FilterOperator.IN_BETWEEN,
values: [100, 500],
};
query.filters = {
dimensions: {
id: "root",
and: [rangeFilter],
},
};Create metrics at query time without modifying the explore:
import { MetricType, CustomFormatType, type AdditionalMetric } from "@lightdash/common";
const avgOrderValue: AdditionalMetric = {
label: "Average Order Value",
type: MetricType.AVERAGE,
table: "orders",
baseDimensionName: "total_amount",
formatOptions: {
type: CustomFormatType.CURRENCY,
currency: "USD",
round: 2,
},
};
query.additionalMetrics = [avgOrderValue];Add post-query calculations:
import { type TableCalculation, CustomFormatType } from "@lightdash/common";
const percentOfTotal: TableCalculation = {
name: "percent_of_total",
displayName: "% of Total",
sql: "${orders_count} / SUM(${orders_count}) OVER ()",
format: {
type: CustomFormatType.PERCENT,
round: 1,
},
};
query.tableCalculations = [percentOfTotal];Create binned dimensions at query time:
import { CustomDimensionType, BinType, type CustomBinDimension } from "@lightdash/common";
const amountBins: CustomBinDimension = {
id: "amount_bins",
name: "amount_bins",
type: CustomDimensionType.BIN,
table: "orders",
dimensionId: "orders_total_amount",
binType: BinType.CUSTOM_RANGE,
customRange: [
{ from: undefined, to: 50 }, // Less than 50
{ from: 50, to: 200 }, // 50-200
{ from: 200, to: undefined }, // 200+
],
};
query.customDimensions = [amountBins];
query.dimensions = ["amount_bins"]; // Use the binned dimensionEQUALS - Exact matchNOT_EQUALS - Not equalSTARTS_WITH - Begins withENDS_WITH - Ends withINCLUDE - Contains substringNOT_INCLUDE - Does not containEQUALS - Equal toNOT_EQUALS - Not equal toGREATER_THAN - Greater thanGREATER_THAN_OR_EQUAL - Greater than or equalLESS_THAN - Less thanLESS_THAN_OR_EQUAL - Less than or equalIN_BETWEEN - Between two valuesEQUALS - Exact dateIN_THE_PAST - Last N days/weeks/months/yearsIN_THE_NEXT - Next N days/weeks/months/yearsIN_THE_CURRENT - Current day/week/month/yearIN_BETWEEN - Between two datesNULL - Is nullNOT_NULL - Is not nullCreate field maps once and reuse them:
import { getFieldMap } from "@lightdash/common";
// Create map once
const fieldMap = getFieldMap(explore);
// O(1) lookups
const field = fieldMap["orders_customer_id"];Check fields exist before adding to query:
const requestedDimensions = ["orders_customer_id", "orders_status"];
const validDimensions = requestedDimensions.filter(
fieldId => fieldMap[fieldId] && fieldMap[fieldId].fieldType === "dimension"
);
query.dimensions = validDimensions;Always provide a filter structure, even if empty:
// Good
filters: { dimensions: { id: "root", and: [] } }
// Avoid
filters: {} // Missing required structureChoose appropriate limits:
// For UI display
query.limit = 500;
// For exports
query.limit = 5000;
// For sampling
query.limit = 100;