dataapp-development
Builds and fixes interactive data apps using Python or Streamlit so you can explore and share data.
Installation
Paste this into Claude Code, Cursor, or any agent that can run commands.
SKILL.mdShow the author's original SKILL.md
---
name: dataapp-development
description: Use when building, modifying, deploying, or debugging Keboola Apps (Streamlit or Python/JS). Covers the full lifecycle — choosing app type, the Python/JS prod-app-and-drafts model (never create a second app), configuring keboola-config/, storage access (RO workspace, RW Query Service, input mapping), authentication, DuckDB caching for performance, default Keboola styling, dashboard patterns, optional Kai chat integration, the three client paths (MCP-only, Claude Code with filesystem, kbagent CLI), and deploying from a private GitHub repo through the organization's GitHub connection instead of a PAT or SSH key.
---
# Keboola App Development
This skill covers the full lifecycle of Keboola Apps (formerly "Data Apps"): choosing the right app type, building locally, deploying, and debugging. It supports both **Streamlit** apps and **Python/JS** apps, and three client paths (MCP-only, Claude Code with filesystem access, and `kbagent` CLI).
The skill is a router — it does not contain all the guidance itself. The `references/` files load on demand based on the task at hand. Read the decision tree below, then load only the references you need.
**A client with its own extras skill** (Kai in the Builder loads `dataapp-building-kai-extras`) follows that skill's reading list instead of this tree, and opens a reference here only when that skill points at it.
## Decision tree
Answer these questions in order. Each answer routes to the right reference.
### 1. What is the task?
| Task | Where to look first |
|---|---|
| Build a new app from scratch | `references/choosing-app-type.md` → type-specific reference → `references/deployment-paths.md`. **Python/JS: read `references/python-js-prod-and-drafts.md` first** — a prod app usually exists already |
| Anything that will call `modify_python_js_data_app` | `references/python-js-prod-and-drafts.md`, before the call |
| Modify an existing app (add feature, fix bug) | `references/dev-workflow.md` for the change loop. Python/JS: continue the app's draft — `references/python-js-prod-and-drafts.md` |
| See what a draft actually shows | `references/dev-workflow.md` §Verify a draft — a preview link opened in your own browser |
| Read, describe, or push from a clone you did not just make | `references/existing-clone.md` |
| Deploy or redeploy | `references/deployment-paths.md` |
| Deploy from a private GitHub repo, or about to ask the user for a PAT or SSH key | `references/github-organization-auth.md` — offer the organization connection first |
| Debug a deployment or runtime issue | `references/troubleshooting.md` |
| Migrate an existing app between types | `references/choosing-app-type.md` + `references/streamlit-apps.md` + `references/python-js-apps.md` |
### 2. Which app type?
If unsure → `references/choosing-app-type.md`. Short version:
- **Streamlit** — fastest when the team is Python-only and the UI is mostly sidebar + main pane. Read `references/streamlit-apps.md`.
- **React + Vite + Express in one Node container** — the Python/JS default. Start from `templates/react-vite-app/`; everything the browser loads is bundled. Read `references/python-js-apps.md`.
- **Combined Python + Node** — only when you genuinely need a Python backend (ML model, existing Python codebase). Read `references/python-js-apps.md` (multi-server section).
### 3. Which client path?
`references/deployment-paths.md` covers all three:
- **Path A — Claude Desktop / web (MCP-only, no filesystem):** Streamlit via `modify_streamlit_data_app` / `deploy_data_app`; Python/JS via `modify_python_js_data_app` + `create_python_js_data_app_git_credential` + git + `deploy_data_app` (see `references/python-js-prod-and-drafts.md`; needs a git-capable runner).
- **Path B — Claude Code / local agent with filesystem + MCP:** Edit files locally, push to the app's git repo (Keboola-managed or customer-provided), deploy via MCP or kbagent. A customer's private GitHub repo connects through organization auth, no PAT (`references/github-organization-auth.md`).
- **Path C — CLI agent (`kbagent`):** Full lifecycle via `kbagent data-app` command group.
### 4. Any cross-cutting concerns?
| Concern | Reference |
|---|---|
| Reading from / writing to Keboola Storage (RO default uses DuckDB cache) | `references/storage-access.md` + `references/duckdb-caching.md` |
| Per-user data: the app user token, the Storage token, row-level security, simple queries, per-user caches, background jobs | `references/storage-access.md` §The app user token, §Row-level security |
| Securing the app (login, SSO, OAuth) | `references/authentication.md` |
| Cutting DWH costs and speeding up read-only dashboards (default for RO apps) | `references/duckdb-caching.md` |
| Styling — default Keboola palette and footer | `references/styling-guide.md` |
| Styling — shadcn/ui, dark mode, ECharts on top of the template | `references/styling-react-bundled.md` |
| Building a dashboarding-style app | `references/dashboard-patterns.md` |
| Adding a natural-language assistant to the app | `references/kai-integration.md` |
| Source repos / canonical docs / UI navigation | `references/glossary.md` |
## Templates index
| Template | Use when |
|---|---|
| `templates/streamlit/` | New Streamlit app, code or git deployment. |
| `templates/python-app/` | New Python-only Python/JS app (Flask or similar). |
| `templates/react-vite-app/` | New Python/JS app — the default (React + Vite + Tailwind + Express). |
| `templates/python-node-app/` | Adding a Python backend next to the default template's Node server. |
| `templates/duckdb-cache/` | Adding the DuckDB caching pattern to an existing Python or Node app. |
## Need authoritative Keboola docs?
The Keboola MCP server exposes a `docs_query` tool that searches the official Keboola Connection documentation. When this skill's references don't answer a question — UI navigation specifics, edge cases, recent platform changes — call `docs_query` to get the canonical answer rather than guessing or relying on stale memory.
## Hard rules (apply to every task)
1. **Never commit `.streamlit/secrets.toml`** or any file with real credentials. Add to `.gitignore` before committing.
2. **RO workspace before input mapping.** New apps default to the RO workspace pattern. Input mapping is discouraged — see `references/storage-access.md`.
3. **Apps must handle `POST /`** on the root path. Keboola POSTs to `/` on startup; the React + Vite template answers it in nginx. Streamlit handles this natively; Flask needs `methods=["GET", "POST"]`; Express needs `app.all('/')`.
4. **No `pip install` in Python apps.** The base image blocks PEP 668. Use `uv sync` driven by `pyproject.toml`. All Python supervisord commands must use `uv run`.
5. **Never declare `[program:nginx]`** in `keboola-config/supervisord/`. Nginx is managed by the base image.
6. **Validate data first, code second.** When using Keboola MCP, call `get_table` and `query_data` to confirm schema before writing SQL. If the project has a semantic layer, check it first via `search_semantic_context` / `get_semantic_context` and ground the query in those definitions rather than inventing the calculation. See `references/dev-workflow.md`.
7. **Pick one Keboola path per session.** Before any project-mutating call, run BOTH detection checks (`which kbagent` + scan for `mcp__*[Kk]eboola*` tools) and ask the user which to use if more than one is present. Don't silently pick. See `references/deployment-paths.md` §Pick one path per session.
8. **MCP-only flows: compose source directly into the tool call.** When the chosen path is `modify_streamlit_data_app`, the `source_code` argument **is** the deployment artifact — don't pre-write a local copy. See `references/deployment-paths.md` Path A.
9. **For local-dev credentials: pre-fill what you can, ask for what's missing, then offer to run.** Never grep the filesystem or scan unrelated env vars for tokens. See `references/storage-access.md` §Getting the env vars for local development.
10. **An empty id means create.** For any Keboola tool where an empty `configuration_id` switches update into create (`modify_python_js_data_app`, `modify_streamlit_data_app`), the id is the decision, not a detail. Never send an empty one without saying in your reply that you are creating a new resource and why no existing one fits. For Python/JS apps a create also provisions a git repo that can never be attached later; a draft comes from `create_python_js_data_app_draft`, never from this tool. See `references/python-js-prod-and-drafts.md`.
11. **Say what Storage access grants, before you turn it on.** The workspace is scoped to the whole project, not to the tables the app uses — the app can read every table in the project's Storage. Tell the user that in your own words when you enable `storage_access`, and say it again if the app is `no-auth`, where anyone with the URL reaches data through it. Enabling it is fine and usually what they want; enabling it silently is not. See `references/storage-access.md`.
12. **A draft runs on the prod app's config.** It copies the prod config when it is created, and a git merge moves source code only. So config (`storage_access`, `storage` writable tables, `authentication_type`, image) goes on the prod app, before the draft is created; after a change, create the draft again on the same branch. A legacy draft's own config is lost when you convert it: say what changes. See `references/python-js-prod-and-drafts.md` §2.
13. **Log metadata, never data.** App logs can leak end-user PII. Log row counts, column names, redacted SQL, which branch of the code answered — never cell values, row contents, response bodies, or raw request values. ``console.debug(`${rows.length} rows, columns: ${Object.keys(rows[0] ?? {})}`)`` ✓ — `console.debug(rows)` or a logged customer email ✗.
14. **Nothing loads from a CDN at runtime.** Every script, stylesheet and font the browser loads is bundled at build time or served by the app itself — never `<script src="https://cdn…">`. A CDN outage, a compromised package or a page's content policy would break the app from outside it. New Python/JS apps start from `templates/react-vite-app/`, which already bundles React, Tailwind and Recharts. An app already on another stack keeps it: edit it in its own idiom, add no new CDN dependency, and move it to the template only on the user's yes — offer that when a change would rewrite most of the frontend anyway.
15. **Every Vite app carries `kaiPreview()`.** It is how Keboola's app preview knows the app rendered, and how the user points you at a part of the page. When you change an existing Vite app whose `vite.config` lacks it, add it in that same change, without telling the user unless it fails; an older inline `kaiPreviewErrorHandler` does not count, `kaiPreview()` replaces it. See `references/python-js-apps.md` §Keboola's app preview.
16. **Never share query results across users.** In a workspace governed by row-level security, Query Service returns each user's own rows. A server-side cache keyed without the user gives them to the next user. Key every such cache by `currentUserKey()` (React + Vite template), and keep `app.use(kbc.server.expressMiddleware())` in `server/index.ts`. See `references/storage-access.md` §Row-level security.
17. **The app user token travels only with the app's Storage token, picked per request.** A Query Service call for a user sends `X-Kbc-App-User-Token` and `X-StorageApi-Token`; a call with no user sends only the Storage token. Choose the credential per request through a provider, never once for a shared client. The React + Vite template does this. See `references/storage-access.md` §The app user token.
18. **Row-level security is not supported in Streamlit yet.** When the app needs per-user data or row-level security, or will run on a governed workspace, do not build a Streamlit app: build a Python/JS app from `templates/react-vite-app/`. Streamlit gets the user token once, when its WebSocket opens, and the token expires after 5 minutes; its shared `@st.cache_data` would also give one user's rows to another. See `references/streamlit-apps.md` §Row-level security — not supported yet.
19. **A Node app queries Query Service through `@keboola/api-client/sdk/dataAppServer`.** Move an existing Node app over (no `initializeApp()` in `server/kbcQuery.ts`) when its prod config has `runtime.workspace.rlsPolicies`, when it or its log shows `Row-level security does not allow this query` (that app does not forward the user token: this is the cause, not the policy), when the user wants per-user data or row-level security, or when you change its server or data code. Do it on a draft like any change, compare what its `runQuery` returned with the SDK's typed, lower-case records and adapt the callers, and tell the user in one line that the app now queries as the signed-in user. The draft has no row-level security, so its preview shows the data but not the filtering. See `references/storage-access.md` §Row-level security, "An existing Node app".
20. **Read queries are one plain `SELECT`; preprocess in a transformation.** Every read query the app sends to Query Service is one plain `SELECT` on Storage bucket tables, even where nothing is governed yet; work a plain `SELECT` cannot do goes into a Keboola transformation whose output table the app reads. A workspace governed by row-level security refuses anything else, and every write. See `references/storage-access.md` §Row-level security, "Simple queries only".
Ships with 64 supporting files:
- references/authentication.md
- references/choosing-app-type.md
- references/dashboard-patterns.md
- references/deployment-paths.md
- references/dev-workflow.md
- references/duckdb-caching.md
- references/existing-clone.md
- references/github-organization-auth.md
- references/glossary.md
- references/kai-integration.md
- references/python-js-apps.md
- references/python-js-prod-and-drafts.md
- references/storage-access.md
- references/streamlit-apps.md
- references/styling-guide.md
- references/styling-react-bundled.md
- references/troubleshooting.md
- templates/duckdb-cache/nodejs/duck.js
- templates/duckdb-cache/python/cache.py
- templates/duckdb-cache/README.md
- … and 44 more
Mirrored from the author's public source. Install counts from the open skills registry.