dataapp-development

Builds and fixes interactive data apps using Python or Streamlit so you can explore and share data.

Installation
Run `npx skills add "https://github.com/keboola/ai-kit" --skill "dataapp-development"` to install this skill, then follow its SKILL.md instructions for my next request.

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.

The systems behind these skills get built for partners every week.

Partner with us