Functions for working with explores (data models) and extracting fields, dimensions, and metrics.
Explores represent data models in Lightdash, containing tables with their dimensions and metrics. These utility functions help you extract, filter, and manipulate fields from explores, build field maps for efficient lookups, and work with items (fields, table calculations, and custom dimensions).
IMPORTANT: Lightdash uses two distinct formats for identifying fields, and choosing the wrong format is a common source of errors.
Field ID (returned by getItemId()): Format is "table_field" using underscore separators
"customers_customer_id""customers_customer__id" for a field named customer.idField Reference (returned by getFieldRef()): Format is "table.field" using dot separators
"customers.customer_id"Common Pitfall: Using field references (dot notation) in metric queries will cause errors. Always use field IDs (underscore notation) for queries. Use convertFieldRefToFieldId() to convert between formats if needed.
Gets all compiled fields (dimensions and metrics) from an explore.
function getFields(explore: Explore): CompiledField[];Returns an array of all compiled dimensions and metrics from all tables in the explore.
Note: CompiledField is a union type CompiledDimension | CompiledMetric. These are "compiled" versions of Field, Dimension, and Metric types that include generated SQL and additional runtime information. The "Compiled" prefix indicates that these fields have been processed by the ExploreCompiler and are ready for query execution. You can use type guards like isDimension() and isMetric() to discriminate between the types.
Example:
import { getFields } from '@lightdash/common';
const fields = getFields(explore);
// Returns: [CompiledDimension, CompiledDimension, ..., CompiledMetric, CompiledMetric, ...]
fields.forEach(field => {
console.log(`${field.table}.${field.name}: ${field.label}`);
});Gets all compiled dimensions from an explore.
function getDimensions(explore: Explore): CompiledDimension[];Returns an array of all compiled dimensions from all tables in the explore.
Example:
import { getDimensions } from '@lightdash/common';
const dimensions = getDimensions(explore);
// All dimensions across all tables
const stringDimensions = dimensions.filter(d => d.type === DimensionType.STRING);
const dateDimensions = dimensions.filter(d => d.type === DimensionType.DATE);Gets all compiled metrics from an explore.
function getMetrics(explore: Explore): CompiledMetric[];Returns an array of all compiled metrics from all tables in the explore.
Example:
import { getMetrics } from '@lightdash/common';
const metrics = getMetrics(explore);
// All metrics across all tables
const countMetrics = metrics.filter(m => m.type === MetricType.COUNT);
const sumMetrics = metrics.filter(m => m.type === MetricType.SUM);Gets all visible fields (non-hidden dimensions and metrics) from an explore.
function getVisibleFields(explore: Explore): CompiledField[];Returns an array of all fields where hidden is not true.
Example:
import { getVisibleFields } from '@lightdash/common';
const visibleFields = getVisibleFields(explore);
// Only fields that should be shown in the UIFinds a specific field by its field ID within an explore.
function findFieldByIdInExplore(
explore: Explore,
id: FieldId
): Field | undefined;Parameters:
explore: The explore to searchid: Field ID in the format "tableName_fieldName" (note: underscore separator, not dot)Returns the field if found, or undefined if not found.
Note: Field IDs use underscores as separators. If a field name itself contains dots, they are replaced with double underscores. For example, a field customer_id in the customers table has ID customers_customer_id, and a field customer.id would have ID customers_customer__id.
Example:
import { findFieldByIdInExplore } from '@lightdash/common';
const field = findFieldByIdInExplore(explore, 'customers_customer_id');
if (field) {
console.log(`Found: ${field.label}`);
}The ItemsMap type is a record mapping field IDs to all queryable items in an explore.
type ItemsMap = Record<
string,
Field | TableCalculation | CustomDimension | Metric
>;
type Item = ItemsMap[string];An ItemsMap contains all items that can be used in queries and visualizations, including regular fields, table calculations, and custom dimensions. Field IDs serve as keys (e.g., "customers_customer_id") and map to their full definitions.
Example:
import { type ItemsMap, getItemMap } from '@lightdash/common';
const itemsMap: ItemsMap = getItemMap(explore, additionalMetrics, tableCalculations, customDimensions);
// Access a specific item
const revenueMetric = itemsMap['customers_total_revenue'];
if (revenueMetric) {
console.log(`${revenueMetric.label}: ${revenueMetric.table}`);
}
// Iterate through all items
Object.entries(itemsMap).forEach(([fieldId, item]) => {
console.log(`${fieldId}: ${getItemLabel(item)}`);
});Creates a mapping of field IDs to fields (dimensions and metrics).
function getFieldMap(
explore: Explore,
additionalMetrics?: AdditionalMetric[]
): Record<string, CompiledField | AdditionalMetric>;Parameters:
explore: The explore containing fieldsadditionalMetrics: Optional array of additional (ad-hoc) metrics to includeReturns a record mapping field IDs (e.g., "customers_total_revenue") to their field definitions.
Example:
import { getFieldMap } from '@lightdash/common';
const fieldMap = getFieldMap(explore, additionalMetrics);
const revenueField = fieldMap['customers_total_revenue'];
if (revenueField && isMetric(revenueField)) {
console.log(`${revenueField.label}: ${revenueField.type}`);
}Creates a comprehensive mapping of all queryable items including fields, table calculations, custom dimensions, and additional metrics.
function getItemMap(
explore: Explore,
additionalMetrics?: AdditionalMetric[],
tableCalculations?: TableCalculation[],
customDimensions?: CustomDimension[]
): ItemsMap;Parameters:
explore: The explore containing fieldsadditionalMetrics: Optional array of additional metricstableCalculations: Optional array of table calculationscustomDimensions: Optional array of custom dimensionsReturns an ItemsMap (record) containing all items that can be used in queries and visualizations.
Example:
import { getItemMap } from '@lightdash/common';
const itemsMap = getItemMap(
explore,
metricQuery.additionalMetrics,
metricQuery.tableCalculations,
metricQuery.customDimensions
);
// Access any item by ID
const item = itemsMap['customers_customer_id'];
if (item) {
console.log(`${item.name}: ${getItemLabel(item)}`);
}
// Use for formatting values
metricQuery.dimensions.forEach(dimId => {
const dimension = itemsMap[dimId];
if (dimension) {
const formattedValue = formatItemValue(dimension, rawValue);
}
});Extracts all dimensions from an items map.
function getDimensionsFromItemsMap(
itemsMap: ItemsMap
): Record<string, Dimension | CustomDimension>;Example:
import { getDimensionsFromItemsMap } from '@lightdash/common';
const dimensions = getDimensionsFromItemsMap(itemsMap);Extracts all filterable dimensions from an items map.
function getFilterableDimensionsFromItemsMap(
itemsMap: ItemsMap
): Record<string, FilterableDimension>;Returns only dimensions that can be used in filters (excludes dimensions with unsupported types).
Example:
import { getFilterableDimensionsFromItemsMap } from '@lightdash/common';
const filterableDimensions = getFilterableDimensionsFromItemsMap(itemsMap);
// Use for building filter UIsExtracts all metrics from an items map.
function getMetricsFromItemsMap(
itemsMap: ItemsMap,
filter: (value: ItemsMap[string]) => boolean = () => true
): Record<string, Metric>;Parameters:
itemsMap: The items map to extract fromfilter: Filter function to select specific metrics (defaults to all metrics)Example:
import { getMetricsFromItemsMap, MetricType } from '@lightdash/common';
// Get all metrics
const allMetrics = getMetricsFromItemsMap(itemsMap);
// Get only sum metrics
const sumMetrics = getMetricsFromItemsMap(itemsMap,
metric => metric.type === MetricType.SUM
);Extracts all table calculations from an items map.
function getTableCalculationsFromItemsMap(
itemsMap?: ItemsMap
): Record<string, TableCalculation>;Example:
import { getTableCalculationsFromItemsMap } from '@lightdash/common';
const tableCalcs = getTableCalculationsFromItemsMap(itemsMap);Extracts all fields, metrics, table calculations, and custom dimensions from a metric query and returns them as an ItemsMap.
function getFieldsFromMetricQuery(
metricQuery: MetricQuery,
explore: Explore
): ItemsMap;Parameters:
metricQuery: The metric query containing field referencesexplore: The explore to extract fields fromReturns an ItemsMap containing all dimensions, metrics, additional metrics, table calculations, and custom dimensions referenced in the query.
Example:
import { getFieldsFromMetricQuery } from '@lightdash/common';
// Get all items used in a specific query
const queryItems = getFieldsFromMetricQuery(metricQuery, explore);
// Format values from query results
resultRows.forEach(row => {
Object.keys(row).forEach(fieldId => {
const item = queryItems[fieldId];
if (item) {
const formatted = formatItemValue(item, row[fieldId]);
console.log(`${getItemLabel(item)}: ${formatted}`);
}
});
});Functions for finding and replacing custom metrics with standard metrics.
Compares a metric with a custom metric to determine if they match exactly or if the custom metric can be replaced with the standard metric.
function compareMetricAndCustomMetric({
metric,
customMetric
}: {
metric: Metric;
customMetric: AdditionalMetric;
}): {
isExactMatch: boolean;
isSuggestedMatch: boolean;
};Returns:
isExactMatch: True if all properties match (SQL, type, format, filters, etc.)isSuggestedMatch: True if essential properties match (SQL, type, format, filters) but labels may differExample:
import { compareMetricAndCustomMetric } from '@lightdash/common';
const comparison = compareMetricAndCustomMetric({
metric: standardMetric,
customMetric: additionalMetric
});
if (comparison.isExactMatch) {
console.log('Perfect match - can replace without changes');
} else if (comparison.isSuggestedMatch) {
console.log('Suggested match - logic matches but labels differ');
}Finds custom metrics that can be replaced with standard metrics from the explore.
function findReplaceableCustomMetrics({
customMetrics,
metrics
}: {
customMetrics: AdditionalMetric[];
metrics: Metric[];
}): ReplaceableFieldMatchMap;Returns: A map of custom metric IDs to their replacement options:
match: Exact match that can replace the custom metricsuggestedMatches: Array of suggested matches with similar logicExample:
import { findReplaceableCustomMetrics, getMetrics } from '@lightdash/common';
const replaceableMetrics = findReplaceableCustomMetrics({
customMetrics: metricQuery.additionalMetrics,
metrics: getMetrics(explore)
});
Object.entries(replaceableMetrics).forEach(([customMetricId, match]) => {
if (match.match) {
console.log(`${match.label} can be replaced with ${match.match.fieldLabel}`);
} else if (match.suggestedMatches.length > 0) {
console.log(`${match.label} has ${match.suggestedMatches.length} suggested replacements`);
}
});Converts a replaceable field match map to a format suitable for chart field replacement operations.
function convertReplaceableFieldMatchMapToReplaceFieldsMap(
replaceableFieldMap: ReplaceableFieldMatchMap
): ReplaceCustomFields[string]['customMetrics'];Example:
import {
findReplaceableCustomMetrics,
convertReplaceableFieldMatchMapToReplaceFieldsMap
} from '@lightdash/common';
const replaceableMetrics = findReplaceableCustomMetrics({
customMetrics,
metrics
});
const replaceMap = convertReplaceableFieldMatchMapToReplaceFieldsMap(
replaceableMetrics
);
// Use replaceMap in chart update operationsConverts replaceable custom fields for multiple charts into a format for batch replacement operations.
function convertReplaceableFieldMatchMapToReplaceCustomFields(
replaceableCustomFields: ReplaceableCustomFields
): ReplaceCustomFields;Example:
import {
findReplaceableCustomMetrics,
convertReplaceableFieldMatchMapToReplaceCustomFields
} from '@lightdash/common';
// Find replaceable custom metrics across multiple charts
const replaceableByChart = {
[chart1.uuid]: {
customMetrics: findReplaceableCustomMetrics({
customMetrics: chart1.metricQuery.additionalMetrics,
metrics: explore.metrics
})
},
[chart2.uuid]: {
customMetrics: findReplaceableCustomMetrics({
customMetrics: chart2.metricQuery.additionalMetrics,
metrics: explore.metrics
})
}
};
// Convert for batch operations
const replaceFields = convertReplaceableFieldMatchMapToReplaceCustomFields(
replaceableByChart
);
// Apply replacements to all charts at oncefunction getItemId(
item: ItemsMap[string] | AdditionalMetric | Pick<Field, 'name' | 'table'>
): string;
function getItemLabel(item: Item): string;
function getItemLabelWithoutTableName(item: Item): string;
function getItemType(
item: ItemsMap[string] | AdditionalMetric
): DimensionType | MetricType | TableCalculationType;Example:
import { getItemId, getItemLabel, getItemType } from '@lightdash/common';
const itemId = getItemId(item);
// Returns field ID like "customers_revenue"
const label = getItemLabel(item);
// Returns full label like "Customers Revenue"
const labelShort = getItemLabelWithoutTableName(item);
// Returns just "Revenue"
const itemType = getItemType(item);
// Returns DimensionType or MetricTypefunction isNumericType(type: DimensionType | MetricType | TableCalculationType): boolean;
function isNumericItem(
item: Field | AdditionalMetric | TableCalculation | CustomDimension | undefined
): boolean;
function isStringDimension(
item: Field | AdditionalMetric | TableCalculation | CustomDimension | undefined
): boolean;
function isDateItem(
item: Field | AdditionalMetric | TableCalculation | CustomSqlDimension | undefined
): boolean;Example:
import {
isNumericItem,
isStringDimension,
isDateItem
} from '@lightdash/common';
if (isNumericItem(item)) {
// Can apply numeric formatting, aggregations
}
if (isStringDimension(item)) {
// Can use string filters like startsWith, contains
}
if (isDateItem(item)) {
// Can use date filters like inThePast, inTheNext
}function getItemIcon(
item: Field | TableCalculation | AdditionalMetric | CustomDimension
): string;
function getItemColor(
item: Field | TableCalculation | AdditionalMetric | CustomDimension
): string;Returns icon name and color appropriate for the item type (dimension, metric, table calculation).
Example:
import { getItemIcon, getItemColor } from '@lightdash/common';
const icon = getItemIcon(item);
const color = getItemColor(item);
// Use for UI renderingGets all item IDs referenced in a metric query.
function itemsInMetricQuery(metricQuery?: MetricQuery): string[];Returns an array of all field IDs used in dimensions, metrics, and table calculations (mapped to names).
Example:
import { itemsInMetricQuery } from '@lightdash/common';
const allItems = itemsInMetricQuery(metricQuery);
// Returns: ["customers_customer_id", "customers_name", "customers_revenue", ...]
// Validate all items exist
const itemsMap = getItemMap(explore, ...);
const missingItems = allItems.filter(id => !itemsMap[id]);
if (missingItems.length > 0) {
console.warn('Missing items:', missingItems);
}Gets a human-friendly label for a date-grouped dimension, removing the time frame suffix.
function getDateGroupLabel(axisItem: ItemsMap[string]): string | undefined;For date or timestamp dimensions with grouping (e.g., "Order created day"), removes the time frame suffix to return just "Order created".
Example:
import { getDateGroupLabel } from '@lightdash/common';
const dimension = itemsMap['orders.created_at'];
const label = getDateGroupLabel(dimension);
// If dimension.label is "Order created day", returns "Order created"Gets the appropriate axis name for a chart axis, considering series configuration and axis settings.
function getAxisName({
isAxisTheSameForAllSeries,
selectedAxisIndex,
axisReference,
axisIndex,
axisName,
series,
itemsMap
}: {
isAxisTheSameForAllSeries: boolean;
selectedAxisIndex: number;
axisReference: 'yRef' | 'xRef';
axisIndex: number;
axisName?: string;
series?: Series[];
itemsMap: ItemsMap | undefined;
}): string | undefined;Parameters:
Returns the axis name, using explicit name if provided, date group label for date dimensions, or series name for single-series charts.
Example:
import { getAxisName } from '@lightdash/common';
const yAxisName = getAxisName({
isAxisTheSameForAllSeries: true,
selectedAxisIndex: 0,
axisReference: 'yRef',
axisIndex: 0,
axisName: cartesianConfig.axes.y?.label,
series: chartConfig.series,
itemsMap
});
// Returns appropriate Y-axis label for chartBeyond the high-level extraction and lookup functions, the field module provides numerous utility functions and type guards for working with fields, metrics, dimensions, and related types.
Converts a word to sentence case (first letter uppercase, rest lowercase).
function capitalize(word: string): string;Note: This function always converts the entire string to sentence case. It does not preserve all-caps text.
Example:
import { capitalize } from '@lightdash/common';
const result1 = capitalize('customer');
// Returns: "Customer"
const result2 = capitalize('CUSTOMER');
// Returns: "Customer" (not "CUSTOMER")Converts a technical field name (snake_case, camelCase, etc.) into a human-friendly display name.
function friendlyName(text: string): string;This function intelligently splits words and converts to sentence case (first letter capitalized, rest lowercase).
Example:
import { friendlyName } from '@lightdash/common';
const name1 = friendlyName('customer_id');
// Returns: "Customer id"
const name2 = friendlyName('totalRevenue');
// Returns: "Total revenue"
const name3 = friendlyName('SKU123code');
// Returns: "Sku 123 code"Generates default SQL template for a column reference.
function defaultSql(columnName: string): string;Example:
import { defaultSql } from '@lightdash/common';
const sql = defaultSql('customer_id');
// Returns: "${TABLE}.customer_id"These utilities convert between field reference formats and field IDs. Understanding when to use each format is crucial for working with Lightdash's field system.
Converts a field reference string (dot-separated) to a field ID (underscore-separated).
function convertFieldRefToFieldId(
fieldRef: string,
fallbackTableName?: string
): FieldId;Parameters:
"table.field" or just "field"Returns: Field ID string in the format "table_field"
Throws: CompileError if the reference is invalid and no fallback table name is provided
This function is primarily used when processing table calculations that reference fields using the "table.field" syntax and need to convert them to field IDs for lookups in ItemsMap or for use in metric queries.
Example:
import { convertFieldRefToFieldId } from '@lightdash/common';
// Convert standard field reference
const id1 = convertFieldRefToFieldId('customers.customer_id');
// Returns: "customers_customer_id"
// Use fallback table name when reference lacks table
const id2 = convertFieldRefToFieldId('revenue', 'sales');
// Returns: "sales_revenue"
// Convert field reference in table calculation
const tableCalc = {
name: 'revenue_growth',
displayName: 'Revenue Growth',
sql: '(${orders.revenue} - ${orders.previous_revenue}) / ${orders.previous_revenue}'
};
// Extract and convert field references from the SQL
const fieldRef1 = 'orders.revenue';
const fieldRef2 = 'orders.previous_revenue';
const fieldId1 = convertFieldRefToFieldId(fieldRef1); // "orders_revenue"
const fieldId2 = convertFieldRefToFieldId(fieldRef2); // "orders_previous_revenue"
// Use field IDs to look up fields
const revenueField = itemsMap[fieldId1];
const previousRevenueField = itemsMap[fieldId2];Error Handling:
import { convertFieldRefToFieldId } from '@lightdash/common';
try {
// This will throw because 'field' doesn't include table name
const id = convertFieldRefToFieldId('field');
} catch (error) {
console.error(error.message);
// "Table calculation contains an invalid reference: field.
// References must be of the format "table.field""
}
// Instead, provide fallback table name
const id = convertFieldRefToFieldId('field', 'my_table');
// Returns: "my_table_field"Common Use Cases:
Processing Table Calculations: When table calculations reference fields using SQL syntax like ${table.field}, convert these references to field IDs for validation and lookups.
Filter Conversion: Converting filter references from API or configuration that use dot notation to field IDs for internal processing.
SQL Template Processing: When parsing SQL templates that contain field references and need to map them to actual field objects.
Gets a field reference string from a field object, returning the dot-separated format.
function getFieldRef(field: Pick<Field, 'table' | 'name'>): FieldRef;Parameters:
table and name properties (can be a full Field object or any object with these properties)Returns: Field reference string in the format "table.field" (using dot separator)
Type Definition:
type FieldRef = string;This function creates a field reference string that uses dot notation, which is commonly used in SQL generation, DBT model references, and template variables. This format is distinct from field IDs which use underscores.
Note: This returns a different format than getItemId(). Use getFieldRef() when you need dot-separated references (for SQL, templates, DBT), and use getItemId() when you need underscore-separated IDs (for queries, filters, lookups).
Example:
import { getFieldRef } from '@lightdash/common';
// Get reference from a field
const field = {
table: 'customers',
name: 'customer_id',
label: 'Customer ID',
// ... other field properties
};
const ref = getFieldRef(field);
// Returns: "customers.customer_id"
// Use in SQL template generation
const sqlTemplate = `SELECT ${getFieldRef(field)} FROM ${field.table}`;
// Generates: "SELECT customers.customer_id FROM customers"
// Get references for all dimensions in a query
const dimensionRefs = metricQuery.dimensions
.map(dimId => itemsMap[dimId])
.filter(isDimension)
.map(dim => getFieldRef(dim));
// Returns: ["customers.customer_id", "customers.name", "orders.order_date"]Comparison with getItemId():
import { getFieldRef, getItemId } from '@lightdash/common';
const field = { table: 'orders', name: 'created_at' };
const fieldRef = getFieldRef(field);
// Returns: "orders.created_at" (dot separator)
const fieldId = getItemId(field);
// Returns: "orders_created_at" (underscore separator)
// Use fieldRef for:
// - SQL generation: SELECT ${fieldRef} FROM ...
// - DBT references: ref('${fieldRef}')
// - Template variables: ${fieldRef}
// Use fieldId for:
// - ItemsMap lookups: itemsMap[fieldId]
// - Metric query fields: { dimensions: [fieldId] }
// - Filter targets: { target: { fieldId } }Common Use Cases:
SQL Generation: When building SQL queries and need to reference fields in the format table.field.
Template Variable Creation: When creating template variables for use in custom SQL or table calculations like ${table.field}.
DBT Model References: When generating references to DBT models using dot notation.
API Responses: When returning field identifiers in a human-readable format that clearly shows table ownership.
Logging and Debugging: When displaying field information where the table context is important for clarity.
Working with Both Functions:
import { getFieldRef, convertFieldRefToFieldId, getItemMap } from '@lightdash/common';
// Round-trip conversion
const field = { table: 'customers', name: 'revenue' };
// Convert to reference format
const fieldRef = getFieldRef(field);
// "customers.revenue"
// Convert back to ID format
const fieldId = convertFieldRefToFieldId(fieldRef);
// "customers_revenue"
// Use ID to lookup full field definition
const itemsMap = getItemMap(explore);
const fullField = itemsMap[fieldId];
// Common pattern: Extract field references from SQL, convert to IDs, lookup fields
const tableSql = '${customers.revenue} + ${customers.costs}';
const fieldRefs = tableSql.match(/\$\{([^}]+)\}/g)
?.map(match => match.slice(2, -1)) || [];
// ["customers.revenue", "customers.costs"]
const fieldIds = fieldRefs.map(ref => convertFieldRefToFieldId(ref));
// ["customers_revenue", "customers_costs"]
const fields = fieldIds
.map(id => itemsMap[id])
.filter(Boolean);
// [Field, Field] - actual field objectsGets the display label for a field (combines table label and field label).
function getFieldLabel(field: Field): string;Example:
import { getFieldLabel } from '@lightdash/common';
const label = getFieldLabel(field);
// Returns: "Customers Customer id"Type guards allow you to safely narrow the type of an item at runtime.
Checks if a value is a Field (dimension or metric).
function isField(field: any): field is Field;Returns true if the value has a fieldType property, indicating it's either a dimension or metric from the explore.
Example:
import { isField } from '@lightdash/common';
if (isField(value)) {
console.log(`Field: ${value.name} (table: ${value.table})`);
console.log(`Field type: ${value.fieldType}`); // 'dimension' or 'metric'
}Checks if a field is a Dimension.
function isDimension(
field: ItemsMap[string] | AdditionalMetric | undefined
): field is Dimension;Returns true if the field is a Field with fieldType === FieldType.DIMENSION.
Example:
import { isDimension, DimensionType } from '@lightdash/common';
if (isDimension(field)) {
console.log(`Dimension type: ${field.type}`);
if (field.type === DimensionType.DATE) {
console.log(`Time interval: ${field.timeInterval}`);
}
}Checks if a field is a Metric.
function isMetric(
field: ItemsMap[string] | AdditionalMetric | undefined
): field is Metric;Returns true if the field is a Field with fieldType === FieldType.METRIC.
Example:
import { isMetric, MetricType } from '@lightdash/common';
if (isMetric(field)) {
console.log(`Metric type: ${field.type}`);
if (field.filters) {
console.log(`Has ${field.filters.length} filters`);
}
}Checks if a field is a CompiledMetric (has compiledSql property).
function isCompiledMetric(
field: ItemsMap[string] | AdditionalMetric | undefined
): field is CompiledMetric;Example:
import { isCompiledMetric } from '@lightdash/common';
if (isCompiledMetric(field)) {
console.log(`Compiled SQL: ${field.compiledSql}`);
}Checks if a dimension can be used in filters.
function isFilterableDimension(
dimension: Dimension
): dimension is FilterableDimension;Returns true for dimensions with types: STRING, NUMBER, DATE, TIMESTAMP, or BOOLEAN.
Example:
import { isFilterableDimension } from '@lightdash/common';
if (isDimension(field) && isFilterableDimension(field)) {
// Can create filters for this dimension
}Checks if a field can be used in filters.
function isFilterableField(
field: Dimension | ItemsMap[string]
): field is FilterableField;Returns true for filterable dimensions, custom SQL dimensions, metrics, and table calculations.
Example:
import { isFilterableField } from '@lightdash/common';
if (isFilterableField(field)) {
// Show filter options in UI
}Checks if an item can be used in filters.
function isFilterableItem(
item: ItemsMap[string] | TableCalculation
): item is FilterableItem;Example:
import { isFilterableItem } from '@lightdash/common';
if (isFilterableItem(item)) {
// Can apply filters to this item
}Checks if a value is a CustomDimension.
function isCustomDimension(value: any): value is CustomDimension;Example:
import { isCustomDimension } from '@lightdash/common';
if (isCustomDimension(item)) {
console.log(`Custom dimension: ${item.name}`);
}Checks if a value is a CustomBinDimension (binned dimension).
function isCustomBinDimension(value: any): value is CustomBinDimension;Example:
import { isCustomBinDimension } from '@lightdash/common';
if (isCustomBinDimension(item)) {
console.log(`Bin type: ${item.binType}`);
console.log(`Bin number: ${item.binNumber}`);
}Checks if a value is a CustomSqlDimension (SQL-based custom dimension).
function isCustomSqlDimension(value: any): value is CustomSqlDimension;Example:
import { isCustomSqlDimension } from '@lightdash/common';
if (isCustomSqlDimension(item)) {
console.log(`Custom SQL: ${item.sql}`);
}Checks if a value is a CompiledCustomSqlDimension (has compiledSql property).
function isCompiledCustomSqlDimension(
value: any
): value is CompiledCustomSqlDimension;Example:
import { isCompiledCustomSqlDimension } from '@lightdash/common';
if (isCompiledCustomSqlDimension(item)) {
console.log(`Compiled SQL: ${item.compiledSql}`);
}Checks if a value is a TableCalculation.
function isTableCalculation(
item: ItemsMap[string] | AdditionalMetric | Pick<Field, 'table' | 'name'>
): item is TableCalculation;Example:
import { isTableCalculation } from '@lightdash/common';
if (isTableCalculation(item)) {
console.log(`Table calculation: ${item.displayName}`);
}Checks if a table calculation is SQL-based (has sql property).
function isSqlTableCalculation(
calc: TableCalculation
): calc is TableCalculation & { sql: string };Example:
import { isSqlTableCalculation } from '@lightdash/common';
if (isTableCalculation(item) && isSqlTableCalculation(item)) {
console.log(`SQL: ${item.sql}`);
}Checks if a table calculation is template-based (has template property).
function isTemplateTableCalculation(
calc: TableCalculation
): calc is TableCalculation & { template: TableCalculationTemplate };Example:
import { isTemplateTableCalculation } from '@lightdash/common';
if (isTableCalculation(item) && isTemplateTableCalculation(item)) {
console.log(`Template type: ${item.template.type}`);
}Parses a string into a MetricType enum value.
function parseMetricType(metricType: string): MetricType;Throws an error if the metric type is invalid.
Example:
import { parseMetricType } from '@lightdash/common';
const type = parseMetricType('sum');
// Returns: MetricType.SUM
const type2 = parseMetricType('count_distinct');
// Returns: MetricType.COUNT_DISTINCTChecks if a metric is a non-aggregate type (STRING, NUMBER, DATE, TIMESTAMP, or BOOLEAN).
function isNonAggregateMetric(field: Field): boolean;Example:
import { isNonAggregateMetric } from '@lightdash/common';
if (isMetric(field) && isNonAggregateMetric(field)) {
// This is a non-aggregate metric (dimension-like)
}Array of metric types that are calculated after aggregation.
/**
* Metric types that are calculated after aggregation (post-calculation metrics).
* These include percentage comparisons and running totals.
*/
const PostCalculationMetricTypes: MetricType[] = [
MetricType.PERCENT_OF_PREVIOUS,
MetricType.PERCENT_OF_TOTAL,
MetricType.RUNNING_TOTAL,
];Checks if a metric type is a post-calculation type (PERCENT_OF_PREVIOUS, PERCENT_OF_TOTAL, or RUNNING_TOTAL).
function isPostCalculationMetricType(type: MetricType): boolean;Example:
import { isPostCalculationMetricType, MetricType } from '@lightdash/common';
if (isPostCalculationMetricType(MetricType.RUNNING_TOTAL)) {
// This metric is calculated after aggregation
}Checks if a metric is a post-calculation metric.
function isPostCalculationMetric(field: Field): boolean;Example:
import { isPostCalculationMetric } from '@lightdash/common';
if (isMetric(field) && isPostCalculationMetric(field)) {
// This metric is calculated after aggregation
}Checks if a string is a valid Format enum value.
function isFormat(value: string | undefined): value is Format;Example:
import { isFormat } from '@lightdash/common';
if (isFormat(field.format)) {
// format is a valid Format enum value
}Finds the compact configuration for a given compact type or alias.
function findCompactConfig(
compactOrAlias: CompactOrAlias
): CompactConfig | undefined;Example:
import { findCompactConfig } from '@lightdash/common';
const config = findCompactConfig('K');
// Returns config for Compact.THOUSANDS
const config2 = findCompactConfig('millions');
// Returns config for Compact.MILLIONSGets the available compact options for a given format type.
function getCompactOptionsForFormatType(
type: CustomFormatType
): Compact[];Returns different compact options based on the format type (bytes, currency, etc.).
Example:
import { getCompactOptionsForFormatType, CustomFormatType } from '@lightdash/common';
const options = getCompactOptionsForFormatType(CustomFormatType.BYTES_SI);
// Returns: [Compact.KILOBYTES, Compact.MEGABYTES, ...]
const options2 = getCompactOptionsForFormatType(CustomFormatType.NUMBER);
// Returns: [Compact.THOUSANDS, Compact.MILLIONS, ...]Checks if an item can be summed/totaled.
function isSummable(item: Item | undefined): boolean;Returns true for numeric dimensions and COUNT/SUM metrics that aren't percentages or date parts.
Example:
import { isSummable } from '@lightdash/common';
if (isSummable(item)) {
// Show column totals in table
}Updates an explore with a new or modified dimension.
function replaceDimensionInExplore(
explore: Explore,
dimension: CompiledDimension
): Explore;Example:
import { replaceDimensionInExplore } from '@lightdash/common';
const updatedExplore = replaceDimensionInExplore(explore, modifiedDimension);Checks if formatting can be applied to a custom metric based on dimension type.
function canApplyFormattingToCustomMetric(
item: Dimension,
customMetricType: MetricType
): boolean;Sorts items prioritizing dimensions and date items for X axis selection.
function sortedItemsForXAxis(
itemsMap: ItemsMap | undefined
): ItemsMap[string][];Sorts items prioritizing numeric metrics for Y axis selection.
function sortedItemsForYAxis(
itemsMap: ItemsMap | undefined
): ItemsMap[string][];Example:
import { sortedItemsForXAxis, sortedItemsForYAxis } from '@lightdash/common';
// Get best candidates for chart axes
const xAxisCandidates = sortedItemsForXAxis(itemsMap);
const yAxisCandidates = sortedItemsForYAxis(itemsMap);
// Use first items as defaults
const defaultXField = xAxisCandidates[0];
const defaultYField = yAxisCandidates[0];import {
isField,
isDimension,
isMetric,
isCustomDimension,
isTableCalculation,
isFilterableItem,
type ItemsMap
} from '@lightdash/common';
function analyzeItem(item: ItemsMap[string]) {
if (isField(item)) {
if (isDimension(item)) {
console.log(`Dimension: ${item.label} (${item.type})`);
} else if (isMetric(item)) {
console.log(`Metric: ${item.label} (${item.type})`);
}
} else if (isCustomDimension(item)) {
console.log(`Custom dimension: ${item.name}`);
} else if (isTableCalculation(item)) {
console.log(`Table calculation: ${item.displayName}`);
}
if (isFilterableItem(item)) {
console.log('Can be filtered');
}
}import {
getFields,
getDimensions,
getMetrics,
getItemMap,
DimensionType,
MetricType,
} from '@lightdash/common';
// Get all fields
const allFields = getFields(explore);
console.log(`Total fields: ${allFields.length}`);
// Get dimensions by type
const dimensions = getDimensions(explore);
const stringDims = dimensions.filter(d => d.type === DimensionType.STRING);
const dateDims = dimensions.filter(d => d.type === DimensionType.DATE);
// Get metrics by type
const metrics = getMetrics(explore);
const countMetrics = metrics.filter(m => m.type === MetricType.COUNT);
const aggregateMetrics = metrics.filter(m =>
[MetricType.SUM, MetricType.AVERAGE, MetricType.MAX].includes(m.type)
);
// Build comprehensive item map
const itemsMap = getItemMap(
explore,
metricQuery.additionalMetrics,
metricQuery.tableCalculations,
metricQuery.customDimensions
);import {
getItemMap,
findFieldByIdInExplore,
isMetric,
isDimension,
type MetricQuery,
} from '@lightdash/common';
// Find specific fields
const customerIdField = findFieldByIdInExplore(explore, 'customers_customer_id');
const revenueField = findFieldByIdInExplore(explore, 'customers_total_revenue');
if (!customerIdField || !revenueField) {
throw new Error('Required fields not found');
}
// Build query
const query: MetricQuery = {
exploreName: explore.name,
dimensions: [customerIdField.name],
metrics: [revenueField.name],
sorts: [{ fieldId: revenueField.name, descending: true }],
limit: 100,
};
// Get items for result formatting
const itemsMap = getItemMap(explore);Compares a compiled metric with a custom (additional) metric to determine if they match or should be suggested as replacements.
/**
* Compare a metric and custom metric to find matches
* @param params - Object containing metric and customMetric to compare
* @returns Object with isExactMatch and isSuggestedMatch booleans
*/
function compareMetricAndCustomMetric(params: {
metric: Metric;
customMetric: AdditionalMetric;
}): { isExactMatch: boolean; isSuggestedMatch: boolean };Usage:
import { compareMetricAndCustomMetric } from '@lightdash/common';
const compiledMetric: Metric = {
fieldType: FieldType.METRIC,
name: 'total_revenue',
label: 'Total Revenue',
table: 'orders',
sql: '${TABLE}.revenue',
type: MetricType.SUM,
// ... other metric properties
};
const customMetric: AdditionalMetric = {
table: 'orders',
name: 'revenue_sum',
label: 'Total Revenue',
type: MetricType.SUM,
sql: '${TABLE}.revenue',
};
const comparison = compareMetricAndCustomMetric({
metric: compiledMetric,
customMetric,
});
if (comparison.isExactMatch) {
console.log('Metrics match exactly - can replace custom metric with compiled metric');
} else if (comparison.isSuggestedMatch) {
console.log('Metrics match partially - suggest replacement to user');
}Analyzes a list of custom metrics to find which ones can be replaced with compiled metrics from the explore.
/**
* Find custom metrics that can be replaced with compiled metrics
* @param params - Object containing customMetrics and metrics arrays
* @returns Map of custom metric IDs to replacement information
*/
function findReplaceableCustomMetrics(params: {
customMetrics: AdditionalMetric[];
metrics: Metric[];
}): ReplaceableFieldMatchMap;
type ReplaceableFieldMatchMap = {
[fieldId: string]: {
fieldId: string;
label: string;
match: {
fieldId: FieldId;
fieldLabel: string;
} | null;
suggestedMatches: Array<{
fieldId: FieldId;
fieldLabel: string;
}>;
};
};Usage:
import { findReplaceableCustomMetrics, getMetrics } from '@lightdash/common';
const customMetrics: AdditionalMetric[] = [
{
table: 'orders',
name: 'custom_revenue',
label: 'Revenue',
type: MetricType.SUM,
sql: '${TABLE}.amount',
},
{
table: 'orders',
name: 'custom_count',
label: 'Order Count',
type: MetricType.COUNT,
sql: '${TABLE}.order_id',
},
];
const compiledMetrics = getMetrics(explore);
const replaceableMetrics = findReplaceableCustomMetrics({
customMetrics,
metrics: compiledMetrics,
});
// Example result:
// {
// 'orders_custom_revenue': {
// fieldId: 'orders_custom_revenue',
// label: 'Revenue',
// match: {
// fieldId: 'orders_total_revenue',
// fieldLabel: 'Total Revenue'
// },
// suggestedMatches: []
// },
// 'orders_custom_count': {
// fieldId: 'orders_custom_count',
// label: 'Order Count',
// match: null,
// suggestedMatches: [
// { fieldId: 'orders_count', fieldLabel: 'Number of Orders' }
// ]
// }
// }Converts a replaceable field match map (from findReplaceableCustomMetrics) into a simpler replacement map.
/**
* Convert replaceable field match map to replacement map
* @param replaceableFieldMap - Map of replaceable fields with match information
* @returns Simplified map of custom metric IDs to replacement field IDs
*/
function convertReplaceableFieldMatchMapToReplaceFieldsMap(
replaceableFieldMap: ReplaceableFieldMatchMap
): ReplaceCustomFields[string]['customMetrics'];Usage:
import {
findReplaceableCustomMetrics,
convertReplaceableFieldMatchMapToReplaceFieldsMap
} from '@lightdash/common';
const replaceableMetrics = findReplaceableCustomMetrics({
customMetrics,
metrics: compiledMetrics,
});
// Convert to simple replacement map (only exact matches)
const replacementMap = convertReplaceableFieldMatchMapToReplaceFieldsMap(replaceableMetrics);
// Example result:
// {
// 'orders_custom_revenue': {
// replaceWithFieldId: 'orders_total_revenue'
// }
// }
// Note: Only includes metrics with exact matches, not suggested matchesConverts replaceable custom fields across multiple charts into a structured replacement map.
/**
* Convert replaceable custom fields to replacement structure
* @param replaceableCustomFields - Map of chart UUIDs to replaceable custom fields
* @returns Structured replacement map for bulk operations
*/
function convertReplaceableFieldMatchMapToReplaceCustomFields(
replaceableCustomFields: ReplaceableCustomFields
): ReplaceCustomFields;
type ReplaceableCustomFields = {
[chartUuid: string]: {
customMetrics: ReplaceableFieldMatchMap;
};
};
type ReplaceCustomFields = {
[chartUuid: string]: {
customMetrics: {
[customMetricId: string]: {
replaceWithFieldId: string;
};
};
};
};Usage:
import {
findReplaceableCustomMetrics,
convertReplaceableFieldMatchMapToReplaceCustomFields
} from '@lightdash/common';
// Find replaceable metrics across multiple charts
const replaceableFieldsPerChart = {
'chart-uuid-1': {
customMetrics: findReplaceableCustomMetrics({
customMetrics: chart1CustomMetrics,
metrics: compiledMetrics,
}),
},
'chart-uuid-2': {
customMetrics: findReplaceableCustomMetrics({
customMetrics: chart2CustomMetrics,
metrics: compiledMetrics,
}),
},
};
// Convert to replacement structure
const replacements = convertReplaceableFieldMatchMapToReplaceCustomFields(
replaceableFieldsPerChart
);
// Use replacements to update multiple charts at once
// Example result:
// {
// 'chart-uuid-1': {
// customMetrics: {
// 'orders_custom_revenue': { replaceWithFieldId: 'orders_total_revenue' }
// }
// },
// 'chart-uuid-2': {
// customMetrics: {
// 'orders_custom_count': { replaceWithFieldId: 'orders_count' }
// }
// }
// }Common Workflow - Replacing Custom Metrics:
import {
getMetrics,
findReplaceableCustomMetrics,
convertReplaceableFieldMatchMapToReplaceCustomFields,
} from '@lightdash/common';
// 1. Get compiled metrics from explore
const compiledMetrics = getMetrics(explore);
// 2. Find replaceable custom metrics across all charts
const replaceableMetricsPerChart: ReplaceableCustomFields = {};
for (const chart of charts) {
const customMetrics = chart.metricQuery.additionalMetrics || [];
if (customMetrics.length > 0) {
replaceableMetricsPerChart[chart.uuid] = {
customMetrics: findReplaceableCustomMetrics({
customMetrics,
metrics: compiledMetrics,
}),
};
}
}
// 3. Convert to replacement structure
const replacements = convertReplaceableFieldMatchMapToReplaceCustomFields(
replaceableMetricsPerChart
);
// 4. Apply replacements to charts
for (const [chartUuid, replacement] of Object.entries(replacements)) {
// Update chart to use compiled metrics instead of custom metrics
applyFieldReplacements(chartUuid, replacement);
}Problem: Using dot notation (field references like "customers.customer_id") in metric queries instead of underscore notation (field IDs like "customers_customer_id").
Solution: Always use getItemId() to get field IDs for queries. Use convertFieldRefToFieldId() if you have a field reference that needs conversion.
// ❌ Wrong - will cause errors
const query: MetricQuery = {
dimensions: ['customers.customer_id'], // Field reference - wrong!
// ...
};
// ✓ Correct - use field IDs
const query: MetricQuery = {
dimensions: [getItemId(customerIdField)], // Field ID - correct!
// ...
};Problem: Using getFields() and then repeatedly searching the array for specific fields, resulting in O(n) performance.
Solution: Use getFieldMap() or getItemMap() for O(1) field lookups by ID.
// ❌ Inefficient - O(n) lookup per field
const fields = getFields(explore);
const field1 = fields.find(f => getItemId(f) === 'customers_name');
const field2 = fields.find(f => getItemId(f) === 'customers_email');
// ✓ Efficient - O(1) lookup per field
const fieldMap = getFieldMap(explore);
const field1 = fieldMap['customers_name'];
const field2 = fieldMap['customers_email'];Problem: Creating an ItemsMap without including additionalMetrics or tableCalculations from the query, causing missing field errors when formatting results.
Solution: Always pass additionalMetrics, tableCalculations, and customDimensions to getItemMap() when working with query results.
// ❌ Incomplete - missing additional metrics
const itemsMap = getItemMap(explore);
const formattedValue = formatItemValue(itemsMap[additionalMetricId], value);
// Error: itemsMap[additionalMetricId] is undefined
// ✓ Complete - includes all field types
const itemsMap = getItemMap(
explore,
metricQuery.additionalMetrics,
metricQuery.tableCalculations,
metricQuery.customDimensions
);
const formattedValue = formatItemValue(itemsMap[additionalMetricId], value);Problem: Attempting to use uncompiled field objects returned from raw table definitions instead of compiled fields from explores.
Solution: Always use compiled fields from explores (returned by getFields(), getDimensions(), getMetrics()) which include generated SQL and are ready for queries.
// ❌ Wrong - using raw uncompiled field
const rawTable: Table = explore.tables['customers'];
const rawDimension = rawTable.dimensions['customer_id']; // Not compiled!
// ✓ Correct - using compiled field
const compiledFields = getDimensions(explore);
const compiledDimension = compiledFields.find(d => d.name === 'customer_id');