---
title: "Quickstart: TypeScript SDK"
meta:
    description: Build Tinybird projects using the TypeScript SDK and typed resources.
---

# Quickstart: TypeScript SDK

This quickstart walks through creating a Tinybird project using the TypeScript SDK. You define Data Sources and Endpoints in TypeScript, sync them to Tinybird, and use a typed client for ingest and queries.

{% callout type="note" %}
Looking for other ways to start? See [Quickstarts](/forward/quickstarts).
{% /callout %}

If you use a coding agent like Cursor, Claude Code, Amp, or Open Code, install Tinybird agent skills so your agent understands Tinybird project structure and workflows:

```shell
npx skills add tinybirdco/tinybird-agent-skills
```

## Before you begin

To get started, you need the following:

- Node.js 20 LTS or later
- TypeScript 4.9 or later

## SDK Installation

We recommend installing the SDK globally so you can run the `tinybird` CLI directly.

{% tabs initial="pnpm" %}

{% tab label="pnpm" %}

```shell
pnpm add -g @tinybirdco/sdk
```

{% /tab %}

{% tab label="yarn" %}

```shell
yarn global add @tinybirdco/sdk
```

{% /tab %}

{% tab label="npm" %}

```shell
npm install @tinybirdco/sdk --global
```

{% /tab %}

{% tab label="bun" %}

```shell
bun add --global @tinybirdco/sdk
```

{% /tab %}

{% /tabs %}

You can also add it just to your repository:

{% tabs initial="pnpm" %}

{% tab label="pnpm" %}

```shell
pnpm add @tinybirdco/sdk
```

{% /tab %}

{% tab label="yarn" %}

```shell
yarn add @tinybirdco/sdk
```

{% /tab %}

{% tab label="npm" %}

```shell
npm install @tinybirdco/sdk
```

{% /tab %}

{% tab label="bun" %}

```shell
bun add @tinybirdco/sdk
```

{% /tab %}

{% /tabs %}

To follow the quickstart, install it in the repository so that commands work exactly as they are in the examples.

## Initialize a Tinybird TypeScript project

Create a new directory and initialize the project:

```shell
mkdir tinybird-typescript-quickstart
cd tinybird-typescript-quickstart
git init
pnpm add @tinybirdco/sdk

npx tinybird init
```

During initialization, the CLI authenticates you against Tinybird Cloud and can optionally add CI/CD workflow files.

This creates:

- `tinybird.config.json` configuration file
- `lib/tinybird.ts` for Data Sources, Pipes, Connectors, and Client definitions
- `.env.local` for authentication against your Tinybird Cloud Workspace

## Check your config file

In `tinybird.config.json`, the default `"include"` is `lib/tinybird.ts` to generate resources from that file. The default `"devMode"` is `"branches"` to develop in Tinybird Cloud Branches, although you can use `"local"` too with Tinybird Local.

```json
{
  "include": [
    "lib/tinybird.ts"
  ],
  "token": "${TINYBIRD_TOKEN}",
  "baseUrl": "${TINYBIRD_URL}",
  "devMode": "branches"
}
```

## Check your Tinybird token

Create a `.env.local` file with your Tinybird token if it doesn't exist yet:

```shell
TINYBIRD_TOKEN=p.your_token_here
TINYBIRD_URL={% user("apiHost", "https://api.tinybird.co") %}
```

The CLI reads `.env.local` automatically.

## Inspect resource definitions

### Data sources

Check the Data Source definition in `lib/tinybird.ts`. The schema and engine stay the same regardless of how you ingest data. The difference is how data arrives: via the Events API, from a Kafka topic, or from an S3 bucket.

{% tabs initial="Events" %}
{% tab label="Events" %}
```ts
import { defineDatasource, engine, t, type InferRow } from "@tinybirdco/sdk";

export const pageViews = defineDatasource("page_views", {
  description: "Page view tracking data",
  schema: {
    timestamp: t.dateTime(),
    session_id: t.string(),
    pathname: t.string(),
    referrer: t.string().nullable(),
  },
  engine: engine.mergeTree({
    sortingKey: ["pathname", "timestamp"],
  }),
});

export type PageViewsRow = InferRow<typeof pageViews>;
```
{% /tab %}
{% tab label="Kafka" %}
```ts
import { defineKafkaConnection, defineDatasource, engine, t, type InferRow } from "@tinybirdco/sdk";

const myKafka = defineKafkaConnection("my_kafka", {
  bootstrapServers: "your-broker:9092",
  securityProtocol: "SASL_SSL",
  saslMechanism: "PLAIN",
  key: '{{ tb_secret("KAFKA_KEY") }}',
  secret: '{{ tb_secret("KAFKA_SECRET") }}',
});

export const pageViews = defineDatasource("page_views", {
  description: "Page view tracking data",
  schema: {
    timestamp: t.dateTime(),
    session_id: t.string(),
    pathname: t.string(),
    referrer: t.string().nullable(),
  },
  engine: engine.mergeTree({
    sortingKey: ["pathname", "timestamp"],
  }),
  kafka: {
    connection: myKafka,
    topic: "page_views",
    groupId: '{{ tb_secret("KAFKA_GROUP_ID") }}',
    autoOffsetReset: "latest",
  },
});

export type PageViewsRow = InferRow<typeof pageViews>;
```
{% /tab %}
{% tab label="S3" %}
```ts
import { defineS3Connection, defineDatasource, engine, t, type InferRow } from "@tinybirdco/sdk";

const myS3 = defineS3Connection("my_s3", {
  region: "us-east-1",
  arn: '{{ tb_secret("S3_ARN") }}',
});

export const pageViews = defineDatasource("page_views", {
  description: "Page view tracking data",
  schema: {
    timestamp: t.dateTime(),
    session_id: t.string(),
    pathname: t.string(),
    referrer: t.string().nullable(),
  },
  engine: engine.mergeTree({
    sortingKey: ["pathname", "timestamp"],
  }),
  s3: {
    connection: myS3,
    bucketUri: "s3://my-bucket/page_views/*.ndjson",
    schedule: "@auto",
  },
});

export type PageViewsRow = InferRow<typeof pageViews>;
```
{% /tab %}
{% /tabs %}

### API Endpoints

Check the API Endpoint definition in `lib/tinybird.ts`:

```ts
import {
  defineEndpoint,
  node,
  p,
  t,
  type InferParams,
  type InferOutputRow,
} from "@tinybirdco/sdk";

export const topPages = defineEndpoint("top_pages", {
  description: "Get the most visited pages",
  params: {
    start_date: p.dateTime().describe("Start of date range"),
    end_date: p.dateTime().describe("End of date range"),
    limit: p.int32().optional(10).describe("Number of results"),
  },
  nodes: [
    node({
      name: "aggregated",
      sql: `
        SELECT
          pathname,
          count() AS views
        FROM page_views
        WHERE timestamp >= {{DateTime(start_date)}}
          AND timestamp <= {{DateTime(end_date)}}
        GROUP BY pathname
        ORDER BY views DESC
        LIMIT {{Int32(limit, 10)}}
      `,
    }),
  ],
  output: {
    pathname: t.string(),
    views: t.uint64(),
  },
});

export type TopPagesParams = InferParams<typeof topPages>;
export type TopPagesOutput = InferOutputRow<typeof topPages>;
```

### Typed client

Check the client in `lib/tinybird.ts`:

```ts
import { Tinybird } from "@tinybirdco/sdk";

export const tinybird = new Tinybird({
  datasources: { pageViews },
  pipes: { topPages },
});
```

## Build the project

Check out a new git branch:

```shell
git add tinybird.config.json && git commit -m "Initial commit" && git checkout -b tinybird-intro
```

Run a build to create your resources in your Tinybird development environment:

```shell
npx tinybird build
```

If you want your changes to sync automatically while you work, run dev mode instead:

```shell
npx tinybird dev
```

### About the development flow

Your default target (local or branch) is set in `tinybird.config.json`. You can override it with flags on the command line.

`tinybird build` syncs to a Tinybird branch, either in Cloud or in Tinybird Local.

You can also pass a flag to explicitly use the desired development environment.

```shell
npx tinybird build --local # Tinybird Local Branches
# or
npx tinybird build --branch  # Tinybird Cloud Branches
```

If you haven't installed Tinybird Local yet, follow [Install Tinybird Local](/forward/install-tinybird/local).

When you're ready to promote changes:

- Use `tinybird build` to push to a branch (good for review or CI).
- To deploy to your main Tinybird Cloud Workspace, we strongly recommend CI/CD, but you can use `tinybird deploy` directly.

#### Git integration

You may have noticed that the quickstart example had a `git checkout -b tinybird-intro`. If Tinybird detects you are in a Git repository, it creates a branch with the git branch name in your Tinybird development environment.

## Ingest data and query

### Ingest

Use the typed client to send events via the Events API:

```ts
import { tinybird } from "./lib/tinybird";

await tinybird.ingest.pageViews({
  timestamp: new Date(),
  pathname: "/home",
  session_id: "abc123",
  referrer: "https://google.com",
});
```

{% callout type="note" %}
If you configured a Kafka or S3 connector, data is ingested automatically after you deploy. You don't need to call `ingest` manually.
{% /callout %}

### Query

Query the endpoint from your application code:

```ts
const result = await tinybird.query.topPages({
  start_date: new Date("2026-01-01"),
  end_date: new Date(),
  limit: 5,
});

result.data.forEach((row) => {
  console.log(row.pathname, row.views);
});
```

## Iterate in a branch

Add a materialized view in another branch, test the app against that branch, and deploy to Tinybird Cloud when you're finished.

```bash
git checkout -b materialization
```

Edit `lib/tinybird.ts` to add a new Data Source, a new materialized view, and update the Endpoint to read from the materialized view.

```ts
import {
  defineDatasource,
  defineEndpoint,
  defineMaterializedView,
  engine,
  node,
  p,
  t,
} from "@tinybirdco/sdk";

export const pageViews = defineDatasource("page_views", {
  description: "Page view tracking data",
  schema: {
    timestamp: t.dateTime(),
    session_id: t.string(),
    pathname: t.string(),
    referrer: t.string().nullable(),
  },
  engine: engine.mergeTree({
    sortingKey: ["pathname", "timestamp"],
  }),
});

export const dailyPageViews = defineDatasource("daily_page_views_ds", {
  description: "Daily page view counts populated by a materialized view",
  schema: {
    date: t.date(),
    pathname: t.string(),
    views: t.simpleAggregateFunction("sum", t.uint64()),
  },
  engine: engine.aggregatingMergeTree({
    sortingKey: ["date", "pathname"],
  }),
});

export const dailyPageViewsMv = defineMaterializedView("daily_page_views_mv", {
  datasource: dailyPageViews,
  nodes: [
    node({
      name: "aggregate",
      sql: `
        SELECT
          toDate(timestamp) AS date,
          pathname,
          count() AS views
        FROM page_views
        GROUP BY date, pathname
      `,
    }),
  ],
});

export const topPages = defineEndpoint("top_pages", {
  description: "Get the most visited pages from MV-backed datasource",
  params: {
    start_date: p.dateTime(),
    end_date: p.dateTime(),
    limit: p.int32().optional(10),
  },
  nodes: [
    node({
      name: "aggregated",
      sql: `
        SELECT
          pathname,
          sum(views) AS views
        FROM daily_page_views_ds
        WHERE date >= toDate({{DateTime(start_date)}})
          AND date <= toDate({{DateTime(end_date)}})
        GROUP BY pathname
        ORDER BY views DESC
        LIMIT {{Int32(limit, 10)}}
      `,
    }),
  ],
  output: {
    pathname: t.string(),
    views: t.uint64(),
  },
});
```

Build against the branch:

```bash
npx tinybird build
```

Once validated, you can deploy to Tinybird Cloud with `npx tinybird deploy` if you haven't set up a CI/CD script.

## Next steps

- Review the SDK repository for advanced usage. See [tinybird-sdk-typescript](https://github.com/tinybirdco/tinybird-sdk-typescript).
- Review the TypeScript SDK CLI reference. See [TypeScript SDK CLI commands](/forward/dev-reference/commands/typescript-sdk-cli).
- Learn about core Tinybird concepts. See [Core concepts](/forward/get-started/concepts).
- Configure deployments and branches. See [Development workflow](/forward/development-workflow).
