logoSemaphor

API Reference

Complete type definitions and manifest schema for custom visuals

Complete TypeScript types and manifest schema for building custom visuals and filters.


Visual Props

SingleInputVisualProps

Props passed to single-input custom visual components.

type SingleInputVisualProps = {
  /** Query results from the card's data source */
  data: Record<string, string | number | boolean>[];
 
  /** Component settings defined in manifest */
  settings?: Record<string, string | number | boolean>;
 
  /** Card metadata including title, type, and formatting */
  cardMetadata?: CardMetadata;
 
  /** Dashboard theme configuration */
  theme?: CustomCardTheme;
 
  /** Active filter definitions on the dashboard */
  filters?: DashboardFilter[];
 
  /** Current filter values (selections) */
  filterValues?: ActiveFilterValue[];
 
  /**
   * Pre-rendered inline filter components.
   * Render these wherever you want in your layout.
   * Always an array (empty if no inline filters configured).
   */
  inlineFilters?: ReactNode[];
 
};

MultiInputVisualProps

Props passed to multi-input custom visual components.

type MultiInputVisualProps = {
  /** Array of datasets, one per slot (indexed by tab position) */
  data: Record<string, string | number | boolean>[][];
 
  /** Global settings from the config card */
  settings?: Record<string, string | number | boolean>;
 
  /** Per-slot settings (aligned with data array) */
  slotSettings?: Array<Record<string, string | number | boolean> | undefined>;
 
  /** Dashboard theme configuration */
  theme?: CustomCardTheme;
 
  /** Active filter definitions on the dashboard */
  filters?: DashboardFilter[];
 
  /** Current filter values (selections) */
  filterValues?: ActiveFilterValue[];
 
  /**
   * Pre-rendered inline filter components.
   * Render these wherever you want in your layout.
   */
  inlineFilters?: ReactNode[];
 
  /** Tab metadata (titles, types, IDs) */
  tabMetadata?: TabMetadata;
 
  /** Rich metadata for each slot */
  cardMetadata?: CardMetadata[];
};

Filter Props

CustomFilterProps

Props passed to custom filter components for text and select filters.

type CustomFilterProps = {
  /** Available filter options from the query */
  options: TSelectedRecord[];
 
  /** Loading state for initial data fetch */
  isLoading: boolean;
 
  /** Fetching state for refetches */
  isFetching: boolean;
 
  /** Error state */
  isError: boolean;
 
  /** Currently selected values */
  selectedValues: TSelectedRecord[];
 
  /** Called when selection changes */
  onChange: (records: TSelectedRecord[]) => void;
 
  /** Called to clear all selections */
  onClear: () => void;
 
  /** Called to toggle select all (if supported) */
  onSelectAll?: (checked: boolean) => void;
 
  /** Current search query (for searchable filters) */
  searchQuery?: string;
 
  /** Called when search query changes */
  onSearchChange?: (query: string) => void;
 
  /** True when search is in progress */
  isSearching?: boolean;
 
  /** Dashboard theme configuration */
  theme?: CustomCardTheme;
 
  /** Plugin settings from manifest */
  settings?: Record<string, string | number | boolean>;
 
  /** True if filter is single-select mode */
  isSingleSelect?: boolean;
 
  /** True if all options are selected */
  allSelected?: boolean;
};

CustomDateFilterProps

Props passed to custom filter components for date filters.

type CustomDateFilterProps = {
  /** Current date range selection */
  dateRange: DateRange | undefined;
 
  /** Initial date range (for reset) */
  initialDateRange: DateRange | undefined;
 
  /** Loading state for initial data fetch */
  isLoading: boolean;
 
  /** Fetching state for refetches */
  isFetching: boolean;
 
  /** Error state */
  isError: boolean;
 
  /** Called when date range changes */
  onDateChange: (range: DateRange | undefined) => void;
 
  /** Called when relative date changes (e.g., "Last 7 days") */
  onRelativeDateChange?: (
    range: DateRange | undefined,
    meta: RelativeDateFilter
  ) => void;
 
  /** Called to clear the date selection */
  onClear: () => void;
 
  /** Called to reset to initial values */
  onReset?: () => void;
 
  /** Dashboard theme configuration */
  theme?: CustomCardTheme;
 
  /** Plugin settings from manifest */
  settings?: Record<string, string | number | boolean>;
};

TSelectedRecord

A selected record in a filter (value from filter options).

type TSelectedRecord = {
  value: string | number | boolean;
  label?: string;
};

DateRange

Date range type for date filters.

type DateRange = {
  from: Date | undefined;
  to?: Date | undefined;
};

RelativeDateFilter

Relative date filter configuration (e.g., "Last 7 days").

type RelativeDateFilter = {
  mode: 'last' | 'this' | 'previous' | 'between';
  unit: 'day' | 'week' | 'month' | 'quarter' | 'year';
  n?: number;
  complete?: boolean;
  toDate?: boolean;
  from?: number;
  to?: number;
};

Metadata Types

CardMetadata

Rich metadata about a card's configuration.

type CardMetadata = {
  /** Card type (kpi, bar, table, custom, etc.) */
  cardType: string;
 
  /** Card title */
  title: string;
 
  /** Card description */
  description?: string;
 
  /** KPI-specific configuration */
  kpiConfig?: {
    /** Comparison period metadata */
    comparisonMetadata?: ComparisonMetadataMap;
    /** KPI display options */
    options?: {
      /** True if lower values are better (e.g., costs) */
      lowerIsBetter?: boolean;
      /** Show sparkline trendline */
      showTrendline?: boolean;
      /** Show comparison value */
      showComparison?: boolean;
    };
    /** Legacy number formatting */
    formatNumber?: LegacyFormatNumber;
  };
 
  /** Number formatting configuration */
  formatConfig?: CustomVisualFormatConfig;
};

TabMetadata

Lightweight metadata about tabs in a multi-input visual.

type TabMetadata = {
  /** Tab titles (labels) */
  titles: string[];
 
  /** Card types per tab */
  cardTypes: string[];
 
  /** Card IDs per tab */
  cardIds: string[];
};

CustomVisualFormatConfig

Number formatting configuration for custom visuals.

type CustomVisualFormatConfig = {
  /** KPI number formatting */
  kpi?: {
    /** Primary value format */
    primary?: FormatOptions;
    /** Comparison value format */
    comparison?: FormatOptions;
    /** Conditional color ranges */
    colorRanges?: ColorRange[];
  };
 
  /** Chart axis formatting */
  axes?: {
    xAxis?: FormatOptions;
    yAxis?: FormatOptions;
    secondaryYAxis?: FormatOptions;
  };
 
  /** Data label formatting */
  dataLabels?: FormatOptions;
 
  /** Table column formatting */
  tables?: {
    /** Per-column format settings */
    columns?: Array<{
      id?: string;
      label?: string;
      position?: number;
      numberFormat?: ColumnNumberFormat | FormatOptions;
    }>;
    /** Column format lookup by ID */
    columnMap?: Record<string, { numberFormat?: ColumnNumberFormat | FormatOptions }>;
    /** Comparison column format */
    comparison?: FormatOptions;
    /** Default format for numeric columns */
    defaultNumberFormat?: FormatOptions;
  };
 
  /** Legacy format fields (for backward compatibility) */
  legacy?: {
    formatNumber?: LegacyFormatNumber;
    numberAxisFormat?: {
      decimalPlaces?: number;
      suffix?: string;
      currency?: string;
      locale?: string;
    };
  };
};

FormatOptions

Number formatting options.

type FormatOptions = {
  /** Format type */
  type?: 'auto' | 'number' | 'currency' | 'percent' | 'scientific' | 'date';
 
  /** Decimal places to display */
  decimalPlaces?: number;
 
  /** Currency code (e.g., 'USD', 'EUR') */
  currency?: string;
 
  /** Locale for formatting (e.g., 'en-US', 'de-DE') */
  locale?: string;
 
  /** Text prepended to value */
  prefix?: string;
 
  /** Text appended to value */
  suffix?: string;
 
  /** Enable automatic suffix (K, M, B) */
  useSuffix?: boolean;
 
  /** Show negative numbers in parentheses */
  negativeInParentheses?: boolean;
 
  /** Multiply value before display (e.g., 100 for percentages) */
  multiplyBy?: number;
 
  /** Date format string (e.g., 'MM/DD/YYYY') */
  dateFormat?: string;
};

ColorRange

Conditional color range for values.

type ColorRange = {
  /** Start of range (inclusive) */
  start: number;
 
  /** End of range (inclusive) */
  end: number;
 
  /** Color to apply (hex or CSS color) */
  color: string;
};

ColumnNumberFormat

Table column number format options.

type ColumnNumberFormat = {
  /** Number style */
  style: 'decimal' | 'currency' | 'percent';
 
  /** Currency code */
  currency: string;
 
  /** Locale for formatting */
  locale: string;
 
  /** Minimum decimal places */
  minimumFractionDigits: number;
 
  /** Maximum decimal places */
  maximumFractionDigits: number;
 
  /** Show data bar visualization */
  showDataBar: boolean;
 
  /** Data bar color */
  dataBarColor: string;
 
  /** Data bar minimum value (for scaling) */
  dataBarMinValue?: number;
 
  /** Data bar maximum value (for scaling) */
  dataBarMaxValue?: number;
};

LegacyFormatNumber

Legacy number format (for backward compatibility).

type LegacyFormatNumber = {
  decimalPlaces?: number;
  currency?: string;
  locale?: string;
  suffix?: string;
  enabled?: boolean | string;
  colorRanges?: ColorRange[];
  [key: string]: unknown;  // Allows additional legacy fields
};

ComparisonMetadataEntry

Metadata for a single comparison type.

type ComparisonMetadataEntry = {
  type: 'previous_period' | 'same_period_last_year' | 'start_vs_end' | 'target';
  displayName?: string;
  displayLabel?: string;
  currentPeriod?: { start: string; end: string };
  comparisonPeriod?: { start: string; end: string };
};

ComparisonMetadataMap

Map of comparison metadata entries keyed by column name.

type ComparisonMetadataMap = Record<string, ComparisonMetadataEntry>;

Theme Types

CustomCardTheme

Theme configuration passed to custom visuals.

type CustomCardTheme = {
  /** Color palette from dashboard theme */
  colors?: string[];
 
  /** Color mode */
  mode?: 'light' | 'dark' | 'system';
};

Filter Types

FilterOperation

Filter operation types.

type FilterOperation =
  | '='
  | '!='
  | '>'
  | '<'
  | '>='
  | '<='
  | 'in'
  | 'not in'
  | 'like'
  | 'not like'
  | 'between'
  | 'not between';

DashboardFilter

Simplified filter definition (metadata about a dashboard filter).

type DashboardFilter = {
  id: string;
  title: string;      // Display name
  column: string;     // Column being filtered
  table: string;      // Table containing the column
  dataType: string;   // 'text', 'number', 'date', etc.
  operation: FilterOperation;
};

ActiveFilterValue

Active filter value (current selection).

type ActiveFilterValue = {
  filterId: string;   // References DashboardFilter.id
  name: string;       // Display name
  operation: FilterOperation;
  valueType: 'string' | 'number' | 'date' | 'boolean';
  values: (string | number | boolean)[];
  relativeDateMeta?: RelativeDateFilter;
};

Manifest Schema

The components.config.ts file defines your plugin's components.

ComponentsConfig

Top-level configuration structure.

type ComponentsConfig = {
  /** Visual components (charts, KPIs, tables) */
  visuals: VisualConfig[];
 
  /** Filter components (optional) */
  filters?: FilterConfig[];
};

VisualConfig

Configuration for a visual component.

type VisualConfig = {
  /** Display name in the chart selector */
  name: string;
 
  /** React component export name */
  component: string;
 
  /** Component type (always 'chart' for visuals) */
  componentType: 'chart';
 
  /** Unique identifier for this visual */
  chartType?: string;
 
  /** Icon name from lucide-react */
  icon?: string;
 
  /** 'single' or 'multiple' data inputs */
  visualType?: 'single' | 'multiple';
 
  /** Minimum required inputs (multi-input only) */
  minInputs?: number;
 
  /** Maximum allowed inputs (multi-input only) */
  maxInputs?: number;
 
  /** Slot definitions (multi-input only) */
  slots?: SlotDefinition[];
 
  /** Global settings schema */
  settings?: Record<string, SettingConfig>;
 
  /** Per-slot settings schema (multi-input only) */
  slotSettings?: Record<string, SettingConfig>;
 
  /** Documentation for the visual */
  docs?: VisualDocumentation;
};

FilterConfig

Configuration for a filter component.

type FilterConfig = {
  /** Display name (must be unique) */
  name: string;
 
  /** React component export name */
  component: string;
 
  /** Unique filter type identifier */
  filterType?: string;
 
  /** Icon name from lucide-react */
  icon?: string;
 
  /** Supported data types (e.g., ['text', 'varchar']) */
  supportedDataTypes?: string[];
 
  /** Settings schema */
  settings?: Record<string, SettingConfig>;
 
  /** Documentation for the filter */
  docs?: FilterDocumentation;
};

SlotDefinition

Slot definition for multi-input visuals.

type SlotDefinition = {
  /**
   * Position identifier:
   * - number: exact position (0, 1, 2)
   * - "N+": position N and all subsequent
   * - "N-M": positions N through M
   */
  position: number | string;
 
  /** Tab label shown in editor */
  label: string;
 
  /** Tooltip description */
  description?: string;
 
  /** Expected card type(s) for this slot */
  expectedType?: string | string[];
 
  /** True if slot must have data */
  required?: boolean;
};

Position format examples:

PositionMeaning
0Exactly position 0 (first tab)
1Exactly position 1 (second tab)
"0+"All positions starting from 0
"1+"Position 1 and all subsequent
"0-2"Positions 0, 1, and 2 only

SettingConfig

Configuration for a single setting.

type SettingConfig = {
  /** Display title in the settings panel */
  title: string;
 
  /** Default value */
  defaultValue: string;
 
  /** UI control type */
  ui: 'input' | 'select';
 
  /** Options for select UI */
  options?: { label: string; value: string }[];
 
  /** Setting documentation */
  docs?: SettingDocumentation;
};

VisualDocumentation

Documentation for a visual component.

type VisualDocumentation = {
  /** What this visual does */
  description: string;
 
  /** Markdown describing expected data format */
  dataSchema: string;
 
  /** When to use this visual */
  useCases?: string[];
 
  /** Preview image (base64 or URL) */
  thumbnail?: string;
};

FilterDocumentation

Documentation for a filter component.

type FilterDocumentation = {
  /** What this filter does */
  description: string;
 
  /** When to use this filter */
  useCases?: string[];
};

SettingDocumentation

Documentation for a setting.

type SettingDocumentation = {
  /** What this setting controls */
  description?: string;
};

Helper Functions

Utility functions available in the plugin quickstart template.

parseKPIData

Parse KPI data using the segment convention.

type Data = Record<string, string | number | boolean>[];
 
function parseKPIData(data: Data): {
  currentValue: string | number | boolean | null;
  comparisonValue: string | number | boolean | null;
  trendlineData: Data;
  valueKey: string | undefined;
};

Usage:

const { currentValue, comparisonValue, trendlineData } = parseKPIData(data);
// currentValue: value from row where segment === 'current'
// comparisonValue: value from row where segment === 'comparison'
// trendlineData: rows where segment === 'trendline'

getPercentChange

Calculate percent change between two values.

function getPercentChange(
  current: number | undefined,
  comparison: number | undefined,
  isLowerBetter?: boolean
): {
  value: number | null;
  isPositive: boolean;
  isBetter: boolean;
  isNeutral: boolean;
};

Usage:

const change = getPercentChange(125000, 98000);
// { value: 27.55, isPositive: true, isBetter: true, isNeutral: false }
 
const costChange = getPercentChange(80000, 100000, true); // lower is better
// { value: -20, isPositive: false, isBetter: true, isNeutral: false }

formatKPIValue

Format a number using KPI format configuration.

function formatKPIValue(
  value: number | undefined,
  formatConfig?: FormatOptions | LegacyFormatNumber
): string;

Usage:

formatKPIValue(125000, { currency: 'USD', decimalPlaces: 0 });
// "$125,000"
 
formatKPIValue(0.2755, { type: 'percent', decimalPlaces: 1 });
// "27.6%"
 
formatKPIValue(1500000, { suffix: 'M', multiplyBy: 0.000001, decimalPlaces: 1 });
// "1.5M"

getComparisonLabel

Get display label for a comparison type.

function getComparisonLabel(metadata?: {
  type?: string;
  displayLabel?: string;
}): string;

Usage:

getComparisonLabel({ type: 'previous_period' });
// "vs Previous Period"
 
getComparisonLabel({ type: 'same_period_last_year' });
// "vs Same Period Last Year"
 
getComparisonLabel({ displayLabel: 'vs Budget' });
// "vs Budget"

Comparison types:

TypeDefault Label
previous_periodvs Previous Period
same_period_last_yearvs Same Period Last Year
start_vs_endChange Over Period
targetvs Target

formatDateRange

Format a date range for display.

function formatDateRange(
  start: string,
  end: string,
  displayTimezone?: string
): string;

Usage:

formatDateRange('2024-01-01', '2024-01-31');
// "Jan 1, 2024 - Jan 31, 2024"

formatDateShort

Format a date range with year de-duplication.

function formatDateShort(
  start: string,
  end: string,
  displayTimezone?: string
): string;

Usage:

formatDateShort('2024-01-01', '2024-01-31');
// "Jan 1 - Jan 31, 2024"
 
formatDateShort('2023-12-01', '2024-01-31');
// "Dec 1, 2023 - Jan 31, 2024"