Documentation

Getting Started

Install OpenPlait in TypeScript or Python, register a server-side datasource, and execute your first portable query.

Getting Started

OpenPlait gives applications one query and dataframe contract across different analytics and observability backends. The application still owns connection records, credentials, authorization, and presentation.

Requirements

  • TypeScript: Node.js 20.19+, npm 10+
  • Python: Python 3.9+
  • A ClickHouse (or Tempo / Loki / Prometheus / Jaeger) endpoint for adapters you enable

Prefer guided setup? Use the Cursor Agent Skills shipped in this repo. Language guides: TypeScript · Python.

git clone git@github.com:openlit/openplait.git
cd openplait

Install

Sources live under typescript/ and python/.

npm install @openplait/core @openplait/adapter-sdk \
  @openplait/runtime @openplait/adapter-clickhouse

For Tempo, replace the last package with @openplait/adapter-tempo. You can install multiple adapters in the same app.

Register a datasource

Construct adapters on the server. Never include passwords or tokens in query, dashboard, or browser-visible resources.

import { ClickHouseAdapter } from "@openplait/adapter-clickhouse";
import { DatasourceRegistry, OpenPlaitRuntime } from "@openplait/runtime";

const config = {
  url: process.env.CLICKHOUSE_URL!,
  username: process.env.CLICKHOUSE_USER,
  password: process.env.CLICKHOUSE_PASSWORD,
  database: "observability",
};

const registry = new DatasourceRegistry().register({
  name: "primary",
  kind: "ClickHouseDatasource",
  scope: "workspace",
  config,
  adapter: new ClickHouseAdapter(config),
});

export const runtime = new OpenPlaitRuntime(registry, {
  defaultTimeoutMs: 30_000,
});

Define a portable query

The Query IR is shared (apiVersion: openplait.io/v1alpha1). TypeScript uses typed objects; Python accepts the same document shape via parse_query.

import {
  OPENPLAIT_API_VERSION,
  type SemanticQuery,
} from "@openplait/core";

export const query: SemanticQuery = {
  apiVersion: OPENPLAIT_API_VERSION,
  kind: "Query",
  metadata: { name: "requests-by-service" },
  spec: {
    mode: "semantic",
    datasource: { kind: "ClickHouseDatasource", name: "primary" },
    input: { signal: "traces", entity: "otel.spans" },
    timeRange: {
      field: "timestamp",
      from: "${__from}",
      to: "${__to}",
    },
    select: [
      { field: "service.name", as: "service" },
      { aggregate: { function: "count" }, as: "requests" },
    ],
    groupBy: [{ field: "service.name" }],
    orderBy: [{ field: "requests", direction: "desc" }],
    limit: 20,
  },
};

Execute

const response = await runtime.execute({
  queries: [query],
  variables: {
    __from: "2026-08-05T00:00:00.000Z",
    __to: "2026-08-05T01:00:00.000Z",
  },
  audit: {
    requestId: "dashboard-7b9b4d",
    actorId: "user-42",
    tenantId: "workspace-a",
  },
});

for (const frame of response.result.frames) {
  console.log(frame.name, frame.length, frame.fields);
}

Every adapter returns the same typed, columnar dataframe shape. Continue with Core Concepts, or configure ClickHouse and Tempo in detail.