A minimal, runnable Atlan app you can clone, modify, and use as the
skeleton for your own. It does the smallest thing an Atlan app can do — take
a name, generate "Hello, {name}!" records, return a summary — so you can
focus on learning the shape of an Atlan app, not the business logic.
If you're here to build an integration that pulls metadata from your system into Atlan, start with this repo, get it running locally in two commands, then replace the toy logic with your real extraction.
An Atlan app is a small service that runs on the Atlan platform and does something useful with the metadata graph — pulling in metadata from a source system, publishing it back out, transforming or enriching it, reacting to events, automating a workflow.
The application-sdk is the
Python library Atlan provides for writing those workflows. This repo is
the "hello world" example built on top of it.
You need Python 3.11+ and uv.
The SDK handles everything else for you.
git clone https://github.com/atlanhq/atlan-hello-world-app.git
cd atlan-hello-world-app
uv sync # one-time: install deps
uv run python -m app.run_dev # boots the dev server on :8000The first run takes ~30 s while the SDK fetches the runtime binaries it needs (cached locally — subsequent runs are instant).
In a second terminal:
# Start a workflow
curl -X POST http://localhost:8000/workflows/v1/start \
-H "Content-Type: application/json" \
-d '{"name": "Atlan", "repeat_count": 3}'
# → {"success": true, "data": {"workflow_id": "hello-world-...", ...}}
# Fetch the result (use the workflow_id from the response above)
curl http://localhost:8000/workflows/v1/result/<workflow_id>
# → {"data": {"status": "completed",
# "result": {"message": "Hello, Atlan!", "record_count": 3, ...}}}If you see Hello, Atlan! come back, the app ran end-to-end. Hit
Ctrl-C in the first terminal to stop it.
The whole app is two tasks chained into one workflow:
┌──────────────────────┐
input ──────────▶ │ generate_greetings │ ──── writes greetings.jsonl
{name, └──────────────────────┘
repeat_count} │
▼
┌──────────────────────┐
│ summarize │ ──── reads file, picks last record
└──────────────────────┘
│
▼
{message, count, output_file}
Each task is just a Python async method on a class:
class HelloWorldApp(App):
name = "hello-world"
@task(timeout_seconds=60)
async def generate_greetings(self, input: GenerateGreetingsInput
) -> GenerateGreetingsOutput:
# ... your I/O here: HTTP call, SQL query, file read, anything ...
return GenerateGreetingsOutput(...)
@task(timeout_seconds=60)
async def summarize(self, input: SummarizeInput) -> SummarizeOutput:
# ... transform / aggregate ...
return SummarizeOutput(...)
async def run(self, input: HelloWorldInput) -> HelloWorldOutput:
# Orchestrates the tasks. Deterministic — no side effects here.
greetings = await self.generate_greetings(...)
summary = await self.summarize(...)
return HelloWorldOutput(...)Three rules to keep in mind:
- All I/O — network, disk, time — happens inside
@taskmethods. Therun()method must stay deterministic so the SDK can replay it safely on retry. - Every task takes one typed
Inputand returns one typedOutput. These are simple Pydantic-style dataclasses defined inapp/contracts.py. - The top-level workflow input (the JSON body of
POST /workflows/v1/start) is generated from a single source —contract/app.pkl— so the Atlan UI form and the Python dataclass can't drift apart.
That's the entire programming model. Open app/connector.py to see the
whole thing in ~150 lines.
The smallest path from this repo to a real connector:
- Rename. Replace
hello-world/HelloWorldApp/atlan-hello-world-appwith your connector's identifiers. Files to touch:pyproject.toml,atlan.yaml,app.yaml,Dockerfile,main.py,app/connector.py,app/run_dev.py,contract/app.pkl,app/generated/manifest.json, this README. - Describe your inputs. Edit
contract/app.pkl— what fields will your Atlan UI form ask the user for? Then runmake generateto regenerate the typed dataclass and the UI manifest. - Write your real tasks. Replace
generate_greetings(your extract) andsummarize(your transform) inapp/connector.py. Keep the shape: one@taskper discrete step, oneInputin, oneOutputout. - Pin the contract. Add a round-trip test to
tests/unit/test_contracts.pyfor every newInput/Outputyou define, so a future schema change can't silently break compatibility.
When all four are done you have a working Atlan connector. Open a PR and the Atlan team can walk through deployment.
uv run pytest tests/unit -qThe tests call @task methods directly with a fake context — no SDK
runtime needed. Fast, hermetic, and they run on every PR via
.github/workflows/tests.yaml. Add tests as you add tasks.
You'll see a few config files in the repo. Quick reference:
| File | What it is |
|---|---|
pyproject.toml |
Standard Python project file — pins atlan-application-sdk, declares dev tools. |
contract/app.pkl |
Single source of truth for the connector's input schema. Regenerates the UI form + Python dataclass via make generate. Edit when you want to change what the Atlan UI asks users for. |
atlan.yaml |
The app manifest. Tells the Atlan platform how to deploy your connector: its name, whether it autoscales, which platform components (object storage, secrets) it needs, what timeouts apply. Edit when you add a real I/O surface. |
app.yaml |
The image binding. Just app_name, app_image, app_port. CI fills in the image tag at deploy time — you rarely edit this. |
Dockerfile |
Production container. Extends registry.atlan.com/public/app-runtime-base:3 which ships uv, a non-root user, and the SDK runtime entrypoint, so this file stays a dozen lines. |
Makefile |
Shortcuts for the commands you'll use often: make generate, make test, make run, make lint. |
.env.example |
Lists the optional env vars the dev server understands. Copy to .env to override defaults. |
atlan-hello-world-app/
├── README.md ← you are here
├── pyproject.toml ← Python project + SDK pin
├── Dockerfile ← extends registry.atlan.com/public/app-runtime-base:3
├── atlan.yaml ← app manifest (deploy config)
├── app.yaml ← image binding (CI fills in)
├── Makefile ← generate / test / run / lint shortcuts
├── .env.example ← documented dev env vars
├── .pre-commit-config.yaml ← ruff + pyright
├── main.py ← container entry point
├── contract/
│ ├── app.pkl ← canonical input + UI schema
│ ├── PklProject ← Pkl deps (app-contract-toolkit)
│ └── PklProject.deps.json
├── app/
│ ├── contracts.py ← typed Input/Output for every task
│ ├── connector.py ← HelloWorldApp(App) + @task methods
│ ├── run_dev.py ← local dev server entry point
│ └── generated/ ← AUTO-GENERATED from contract/app.pkl
│ ├── _input.py
│ └── manifest.json
├── tests/
│ └── unit/
│ ├── test_contracts.py ← JSON round-trip pinning for every contract
│ └── test_connector.py ← task bodies tested with a fake context
└── .github/
└── workflows/
├── checks.yml ← pre-commit on every PR
└── tests.yaml ← unified unit tests (tests-passed gate)
| Symptom | Likely cause |
|---|---|
uv: command not found |
Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh |
make run hangs on first run for ~30 s |
Normal — SDK is downloading the workflow runtime binaries into ~/.cache/. Subsequent runs are instant. |
POST /workflows/v1/start returns 404 |
The dev server isn't up yet. Wait for Combined mode started: … in the dev-server log. |
Workflow stuck in RUNNING |
Check the dev-server log in the first terminal for the task's error message. |
make generate fails with pkl: command not found |
Install Pkl: https://pkl-lang.org. Only needed if you're editing contract/app.pkl. |
For SDK-level issues, file in atlanhq/application-sdk. For issues with this template, open one here.
Apache 2.0 — see LICENSE.