---
title: "Quickstart: Tinybird CLI"
meta:
    description: Follow this step-by-step tutorial to get started with Tinybird.
---

# Quickstart: Tinybird CLI

Follow these steps to install the Tinybird CLI, build your first data project in a Tinybird branch, and deploy it to Tinybird Cloud.

See [Core concepts](/forward/get-started/concepts) for a complete overview of Tinybird.

{% callout type="note" %}
This quickstart is based on the Tinybird CLI and datafiles. If you're looking for a different approach with SDKs, 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:

- A Tinybird account. You can create one at [cloud.tinybird.co](https://cloud.tinybird.co).
- Git.

## Deploy a new project in five minutes

### Install Tinybird CLI

Run the following command to install the Tinybird CLI:

{% snippet title="install-tinybird-forward" /%}

{% callout type="tip" %}
**Already have Tinybird Classic CLI installed?** Both Classic and Forward CLIs use the `tb` command. Run `which tb` to check if you already have a `tb` binary installed. If you do, see [Managing CLI versions](/forward/install-tinybird/migrate#install-the-tinybird-forward-cli) to avoid conflicts.
{% /callout %}

### Create a project

Create a new directory and initialize the project:

```shell
mkdir tinybird-cli-quickstart
cd tinybird-cli-quickstart
git init

tb init
```

This creates the standard project folders (`datasources/`, `pipes/`, `endpoints/`, `fixtures/`, `tests/`, and others), plus other useful resources like CI/CD templates or Agent Skills. It also creates a `tinybird.config.json` containing your project settings, like the folder where the project is located and the default development environment.

```json
{
  "dev_mode": "branch",
  "include": [
    "tinybird"
  ]
}
```

{% callout type="caution" %}
If you log in during this step, a file called `.tinyb` is created. This contains your auth details, so never commit it to git.
{% /callout %}

First things first, you need a data source. 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.

Create the `.datasource` file. If you want to ingest from Kafka or S3 instead of the Events API, add a `.connection` file and reference it in the `.datasource`:

{% tabs initial="Events" %}
{% tab label="Events" %}
```tb {% title="datasources/page_views.datasource" %}
DESCRIPTION >
    Page view tracking data

SCHEMA >
    `timestamp` DateTime `json:$.timestamp`,
    `session_id` String `json:$.session_id`,
    `pathname` String `json:$.pathname`,
    `referrer` Nullable(String) `json:$.referrer`

ENGINE "MergeTree"
ENGINE_SORTING_KEY "pathname, timestamp"
```
{% /tab %}
{% tab label="Kafka" %}
```tb {% title="connections/my_kafka.connection" %}
TYPE kafka
KAFKA_BOOTSTRAP_SERVERS {{ tb_secret("KAFKA_BOOTSTRAP_SERVERS", "localhost:9092") }}
KAFKA_SECURITY_PROTOCOL SASL_SSL
KAFKA_SASL_MECHANISM PLAIN
KAFKA_KEY {{ tb_secret("KAFKA_KEY") }}
KAFKA_SECRET {{ tb_secret("KAFKA_SECRET") }}
```
```tb {% title="datasources/page_views.datasource" %}
DESCRIPTION >
    Page view tracking data

SCHEMA >
    `timestamp` DateTime `json:$.timestamp`,
    `session_id` String `json:$.session_id`,
    `pathname` String `json:$.pathname`,
    `referrer` Nullable(String) `json:$.referrer`

ENGINE "MergeTree"
ENGINE_SORTING_KEY "pathname, timestamp"

KAFKA_CONNECTION_NAME my_kafka
KAFKA_TOPIC page_views
KAFKA_GROUP_ID {{ tb_secret("KAFKA_GROUP_ID") }}
```
{% /tab %}
{% tab label="S3" %}
```tb {% title="connections/my_s3.connection" %}
TYPE s3
S3_REGION us-east-1
S3_ROLE_ARN {{ tb_secret("S3_ARN") }}
```
```tb {% title="datasources/page_views.datasource" %}
DESCRIPTION >
    Page view tracking data

SCHEMA >
    `timestamp` DateTime `json:$.timestamp`,
    `session_id` String `json:$.session_id`,
    `pathname` String `json:$.pathname`,
    `referrer` Nullable(String) `json:$.referrer`

ENGINE "MergeTree"
ENGINE_SORTING_KEY "pathname, timestamp"

IMPORT_CONNECTION_NAME my_s3
IMPORT_BUCKET_URI s3://my-bucket/page_views/*.ndjson
IMPORT_SCHEDULE @auto
```
{% /tab %}
{% /tabs %}

Data Sources define the schema and properties of the database tables where you store your data. For more information, see [Data Source files](/forward/dev-reference/datafiles/datasource-files).

### Create an endpoint

Create a new file `endpoints/top_pages.pipe`:

```sql {% title="endpoints/top_pages.pipe" %}
TOKEN top_pages_read READ

DESCRIPTION >
    Get the most visited pages

NODE aggregated
SQL >
    %
    SELECT
        pathname,
        count() AS views
    FROM page_views
    WHERE timestamp >= {{DateTime(start_date, '2026-01-01 00:00:00')}}
      AND timestamp <= {{DateTime(end_date, '2026-12-31 23:59:59')}}
    GROUP BY pathname
    ORDER BY views DESC
    LIMIT {{Int32(limit, 10)}}

TYPE endpoint
```

### Build the project

Checkout 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
tb build
```

#### Git integration

You may have noticed that the quickstart example had a `git checkout -b tinybird-intro`. Tinybird detects you're on a git project and creates a branch with the git branch name in your Tinybird development environment.

### Ingest data and query

#### Ingest

Send events using the Events API with `curl`:

```shell
TB_HOST=$(cat .tinyb | jq -r ".host")
tb --branch=tinybird-intro token copy "admin"
TB_TOKEN="<paste-your-admin-token-here>"

curl -X POST "$TB_HOST/v0/events?name=page_views&token=$TB_TOKEN" \
  -d '{"timestamp": "2026-01-15 10:00:00", "session_id": "s1", "pathname": "/home", "referrer": "https://google.com"}
{"timestamp": "2026-01-15 10:01:00", "session_id": "s2", "pathname": "/docs", "referrer": null}
{"timestamp": "2026-01-15 10:02:00", "session_id": "s1", "pathname": "/pricing", "referrer": "/home"}'
```

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

#### Query

Query the endpoint with `curl`. All Tinybird API calls require a **token** for authentication.

```shell
curl -X GET "$TB_HOST/v0/pipes/top_pages.json?token=$TB_TOKEN"
```

```txt
{
  "meta": [
    { "name": "pathname", "type": "String" },
    { "name": "views", "type": "UInt64" }
  ],
  "data": [
    { "pathname": "/home", "views": 1 },
    { "pathname": "/docs", "views": 1 },
    { "pathname": "/pricing", "views": 1 }
  ]
}
```

### Deploy to Tinybird Cloud

```shell
tb deploy
```

Open the project in Tinybird Cloud:

```shell
tb --cloud open
```

## 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
```

Create a new Data Source for the materialized rollup in `datasources/daily_page_views.datasource`:

```tb {% title="datasources/daily_page_views.datasource" %}
DESCRIPTION >
    Daily page view counts populated by a materialized view

SCHEMA >
    `date` Date,
    `pathname` String,
    `views` AggregateFunction(count)

ENGINE "AggregatingMergeTree"
ENGINE_SORTING_KEY "date, pathname"
```

Create the materialized view in `pipes/daily_page_views_mv.pipe`:

```sql {% title="pipes/daily_page_views_mv.pipe" %}
DESCRIPTION >
    Materialized rollup of page views by day and pathname

NODE aggregate
SQL >
    SELECT
        toDate(timestamp) AS date,
        pathname,
        countState() AS views
    FROM page_views
    GROUP BY date, pathname

TYPE materialized
DATASOURCE daily_page_views
```

Update the endpoint in `endpoints/top_pages.pipe` to read from the materialized Data Source:

```sql {% title="endpoints/top_pages.pipe" %}
TOKEN top_pages_read READ

DESCRIPTION >
    Get the most visited pages from MV-backed datasource

NODE aggregated
SQL >
    %
    SELECT
        pathname,
        countMerge(views) AS views
    FROM daily_page_views
    WHERE date >= toDate({{DateTime(start_date, '2026-01-01 00:00:00')}})
      AND date <= toDate({{DateTime(end_date, '2026-12-31 23:59:59')}})
    GROUP BY pathname
    ORDER BY views DESC
    LIMIT {{Int32(limit, 10)}}

TYPE endpoint
```

Build against the branch:

```bash
tb build
```

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

## Next steps

- Familiarize yourself with Tinybird concepts. See [Core concepts](/forward/get-started/concepts).
- Learn about datafiles, like .datasource and .pipe files. See [Datafiles](/forward/dev-reference/datafiles).
- Get data into Tinybird from a variety of sources. See [Get data in](/forward/get-data-in).
- Learn about authentication and securing your Endpoints. See [Tokens](/forward/administration/tokens).
- Browse the Tinybird CLI commands reference. See [Commands reference](/forward/dev-reference/commands).
