Run Your First Data Integration
Finish Install Tabsdata and Connect an AI Agent first, and make sure the server is running.
This quickstart uses the Tabsdata MCP integration. To deploy and run Tabsdata without an LLM, switch to the developer doc mirror.
We will be building a Tabsdata Workflow that:
- ingests a local
persons.csvinto a Tabsdata table calledpersons - builds a second table called
peoplethat drops theagecolumn and adds afull_namecolumn - writes
peopleback to your file system aspeople.jsonl
tdk examples extract --name quickstart-ai --dir qs-example
Creates qs-example/, which contains the source data persons.csv and two connection files.
Log in, then start your agent from the same terminal:
- Claude Code
- Codex
tdk login --server localhost --user admin --password tabsdata
claude
tdk login --server localhost --user admin --password tabsdata
codex
Give the following prompt to your LLM:
Build a tabsdata workflow that
1. ingests `qs-example/input-data/persons.csv`
2. creates a new column for the full name and drops the age column,
3. writes the result as a .jsonl to the `output-data` folder
More info can be found under `qs-example/README.md`
qs-example/input-data/persons.csv
2. creates a new column for the full name and drops the age column,
3. writes the result as a .jsonl to the output-data folder
More info can be found under qs-example/README.mdRead 1 file, listed 1 directory (ctrl+o to expand)
persons.csv has first_name, last_name and age. Three functions cover this: a publisher for the CSV, a transformer for the column changes, and a subscriber for the .jsonl. Getting the connection and publisher guides.
Called tabsdata 3 times (ctrl+o to expand)
Writing , then a source collection pointed at input-data.
Called tabsdata 2 times (ctrl+o to expand)
Registering the publisher that produces persons.
@publisher(
source=LocalFileSrc(paths=["persons.csv"], format=SrcFileFormat.CSV),
output_tables=["persons"],
)
def ingest_persons(rows: TableFramesSpec) -> TableFrameSpec:
return rows[0] if rows else None
Called tabsdata 2 times (ctrl+o to expand)
Registered and committed. Now the transformer — full_name from first_name and last_name, age dropped.
@transformer(
input_tables=["persons"],
output_tables=["people"],
)
def build_people(persons: TableFrameSpec) -> TableFrameSpec:
return persons.with_columns(
(col("first_name") + " " + col("last_name")).alias("full_name"),
).drop("first_name", "last_name", "age")
Called tabsdata 2 times (ctrl+o to expand)
Registered and committed. Last, the subscriber writing people out as .jsonl. Getting the connection and subscriber guides.
Called tabsdata 3 times (ctrl+o to expand)
Writing , then a destination collection pointed at output-data.
Called tabsdata 2 times (ctrl+o to expand)
Registering the subscriber.
@subscriber(
tables=["people"],
destination=LocalFileDst(path="people.jsonl", format=DstFileFormat.NDJSON),
)
def export_people(people: TableFrameSpec) -> None:
pass
Called tabsdata 3 times (ctrl+o to expand)
Registered. The publisher's commit triggered the transformer, which triggered the subscriber, all in one execution plan — committed, 0 failures.
people.jsonl is live in qs-example/output-data/:
┌──────────────┐ │ full_name │ ├──────────────┤ │ Ada Lovelace │ ├──────────────┤ │ Grace Hopper │ └──────────────┘
After your LLM finishes processing your request, you should have a new people.jsonl file in the qs-example/output-data/ folder.
Go to http://localhost:2457 and log in with admin / tabsdata. You'll see your project, its
collections, and the tables the flow just produced.