| name | posit |
| title | Posit on Snowflake |
| description | Build and deploy data apps, dashboards, APIs, and reports using Posit tools
on Snowflake. Use when the user asks to build, create, or deploy any of:
Shiny (R or Python), Streamlit, Dash, FastAPI, Plumber, Quarto, R Markdown,
Jupyter, Panel, or Bokeh apps — especially with Snowflake data. Also
triggers for: "deploy to Posit Connect", "rsconnect", tidyverse, tidymodels,
orbital, querychat, chatlas, Positron, "publish my app", or any R/Python
data product targeting Snowflake. Do NOT use for Node.js, Next.js, React,
or custom JS/TS web apps — that is the snowflake-apps skill (Snowflake App
Runtime). Boundary: data science apps, dashboards, reports, and APIs in
R or Python deployed to Posit Connect → this skill; custom JavaScript web
apps on App Runtime → snowflake-apps. Do NOT use for pure SQL.
|
| metadata | {"author":"Posit PBC","version":2,"type":"partner","references":["references/connect-frameworks.md","references/snowflake-auth.md","references/r-conventions.md","references/python-conventions.md","references/package-management.md","references/orbital-patterns.md","references/connect-deploy.md","references/cortex-ai-tools.md","references/posit-packages.md","references/better-together.md"]} |
Posit on Snowflake
CRITICAL: Deploy to Posit Connect, NOT Streamlit-in-Snowflake
Every app this skill creates targets Posit Connect — NEVER Snowflake's
built-in Streamlit hosting (Streamlit-in-Snowflake / SiS). This is the
entire point of this skill. Even when the user says "build a Streamlit app,"
the output is a standalone Python Streamlit app that connects to Snowflake
via snowflake-connector-python and deploys to Posit Connect.
Do NOT:
- Create a Streamlit-in-Snowflake app (no
CREATE STREAMLIT SQL)
- Use the SiS
snowflake.snowpark.context connection pattern
- Deploy via Snowflake's native Streamlit hosting
- Use
st.connection("snowflake") (that's the SiS shortcut)
DO:
- Use
snowflake.connector.connect(...) with OAuth or env vars
- Generate a standalone
app.py + requirements.txt
- Deploy via
POSIT_DEPLOY procedure or rsconnect deploy CLI
- Target
https://connect (Posit Connect inside the Native App)
Why Posit Connect instead of Streamlit-in-Snowflake:
Connect gives you viewer-level RBAC passthrough, scheduled execution, email
delivery, git-backed publishing, content management across all 10 frameworks,
and a centralized hub for every data product. SiS runs Streamlit but nothing
else.
Build and deploy data products on Snowflake using any framework Posit Connect
supports. This skill covers the full lifecycle: connect to Snowflake data,
build the app, and deploy it to Posit Connect — with viewer-level RBAC
inherited from Snowflake automatically.
Supported frameworks
| Framework | Language | Best for | Reference |
|---|
| Shiny | R or Python | Interactive dashboards, NL chatbots | connect-frameworks.md |
| Streamlit | Python | Quick prototypes, data exploration | connect-frameworks.md |
| Dash | Python | Plotly-heavy analytics dashboards | connect-frameworks.md |
| FastAPI | Python | REST APIs, model endpoints | connect-frameworks.md |
| Plumber | R | REST APIs from R code | connect-frameworks.md |
| Quarto | R or Python | Reports, docs, parameterised dashboards | connect-frameworks.md |
| Panel | Python | HoloViz/PyViz dashboards | connect-frameworks.md |
| Bokeh | Python | Interactive visualisations | connect-frameworks.md |
| Jupyter | Python | Notebooks as scheduled reports | connect-frameworks.md |
| R Markdown | R | Legacy reports (prefer Quarto for new) | connect-frameworks.md |
Workflow
Step 1 — Identify the framework
If the user says "build an app" without specifying, ask which framework.
If they say "Shiny", ask "R or Python?" Default to Python if unclear.
If they describe a use case without naming a framework, recommend one:
- "Interactive dashboard" → Shiny (Python) or Streamlit
- "API endpoint" → FastAPI or Plumber
- "Scheduled report" → Quarto
- "ML model serving" → FastAPI + vetiver
- "NL chatbot over my data" → Shiny (R or Python) + querychat/chatlas
Step 1b — Detect needed Posit packages from the request
Map what the user is asking for to the Posit open-source package that does
it, and include that package automatically. The user does NOT have to name
the package — infer it from intent. See references/posit-packages.md for
the connection patterns and full code for each.
| User says / implies | Posit package(s) | Language |
|---|
| "ask questions about the data", "chat with", "natural language", "let users query" | querychat (NL→SQL over a table) | R or Python |
| "chatbot", "LLM", "AI assistant", "summarize", "generate text" | ellmer (R) / chatlas (Python) | R / Python |
| "complete LLM toolkit", "tool calling from R" | btw | R |
| "run an LLM over every row", "classify each record", "batch LLM" | mall | R or Python |
| "streaming chat UI in a Shiny app" | shinychat + ellmer/chatlas | R or Python |
| "RAG", "search my documents", "retrieval", "ground answers in a corpus" | ragnar (R) / raghilda (Python) + ellmer/chatlas | R / Python |
| "MCP server", "expose tools to an agent" | mcptools | R |
| "evaluate the LLM", "test prompt quality", "LLM evals" | vitals | R |
| "nice table", "formatted/publication table" | gt (R) / great_tables (Python) | R / Python |
| "interactive table", "sortable/filterable table" | reactable (R) / itables (Python) | R / Python |
| "interactive map", "geospatial" | leaflet | R or Python |
| "plots", "charts", "visualize" | ggplot2 (R) / plotnine or plotly (Python) | R / Python |
| "validate data", "data quality checks" | pointblank | R |
| "predict", "model", "classify", "score" | tidymodels + orbital (in-DB scoring) | R |
| "run predictions in the database" | orbital, tidypredict, modeldb | R |
| "serve a model", "model API", "monitor/version a model" | vetiver + pins | R or Python |
| "scheduled email", "email a report" | blastula (R) + Connect scheduling | R |
| "async", "scale concurrent users", "background jobs" | mirai | R |
| "dashboard layout", "theming/branding" | bslib + brand-yml | R or Python |
Full catalog: https://opensource.posit.co/software/ (365 projects). Default
LLM backend for ellmer/chatlas/querychat/mall is Snowflake Cortex — no
API key needed inside the Native App. Always add the chosen packages to
requirements.txt (Python) or library() calls (R), plus posit-sdk.
Step 2 — Establish Snowflake connection
Load references/snowflake-auth.md and generate the appropriate connection
code for the chosen language (R or Python).
Step 3 — Generate the app
Always create a dedicated project folder. Never write files to the
workspace root. Name the folder after the app using kebab-case:
<descriptive-name>/
├── app.py (or app.R, plumber.R, report.qmd, etc.)
├── requirements.txt (Python) or renv.lock (R)
└── README.md (brief: what it does, how to deploy)
Examples: loan-risk-dashboard/, sales-api/, quarterly-revenue-report/,
churn-streamlit-app/. Ask the user what they want to call it if it's
not obvious from the prompt.
Load references/connect-frameworks.md and use the scaffold template for the
chosen framework. Wire the Snowflake connection into the app. Use the user's
specified tables/data.
Bake fully-qualified table names directly into the app. You know exactly
which tables the user referenced at generation time — write
FROM <db>.<schema>.<table> into the code. Do NOT route table locations
through environment variables (the scaffold templates' DEMO_DATABASE /
DEMO_SCHEMA pattern is for the demo kit only); env-var indirection forces
manual per-content configuration in Connect and breaks the one-prompt deploy.
Reserve env vars for genuine secrets or runtime config — and when those are
needed, pass them in the deploy request's env_vars parameter so the watcher
sets them on the content automatically:
CALL <bridge>.POSIT_REQUEST_DEPLOY(
'streamlit', 'my-app', 'My App',
OBJECT_CONSTRUCT('app.py', '<code>', 'requirements.txt', '<deps>'),
OBJECT_CONSTRUCT('SOME_API_KEY', '<value>')
);
For R-specific conventions (tidyverse, ggplot2, dbplyr), also load
references/r-conventions.md.
For Python-specific conventions (pandas/Snowpark, Streamlit/Shiny/Dash/FastAPI
idioms, plotly/plotnine, requirements.txt essentials), also load
references/python-conventions.md.
Always ship a lockfile for reproducible deploys. Load
references/package-management.md. Python apps get a requirements.txt
(pinned via uv if available); R apps get an renv.lock (via
renv::snapshot() before delivery). Connect installs from Posit Package
Manager using these — never hardcode a package source URL in the app. An R
app without renv.lock, or a Python app without requirements.txt, is not
deployable; generate the lockfile as part of the project.
Step 4 — Add AI features (if requested)
If the user wants NL querying, chatbot, or AI-assisted features, load
references/cortex-ai-tools.md for querychat, chatlas, and Positron
Assistant patterns.
Step 5 — Add ML scoring (if requested)
If the user needs in-database ML predictions, load
references/orbital-patterns.md for the tidymodels/scikit-learn → SQL
translation workflow.
Step 6 — Deliver the app
Don't assume git, a repo, or deploy access exists. Detect what the
environment supports and use the highest-priority path that works. Ask the
user only when a path needs a value you don't have (repo URL, deploy choice).
Always present the delivery choice as THREE explicit options (not two).
When you elicit how to deliver the app, the choices the user sees must be:
- Push to my Git repo — commit + push so I can pull into Workbench,
iterate in Positron, and publish. (If no repo is detected, ask for the
repo URL/branch.)
- Deploy to Connect now — deploy via the POSIT_REQUEST_DEPLOY bridge,
one SQL call, returns a live URL.
- Write files locally — write the project folder so I can open/edit in
Positron without git.
Git must ALWAYS appear as its own option — never fold it into "write locally."
If the user picks Git and no repo is configured, ask for the repo URL and
branch, then init/commit/push. Then:
If the user chose GIT → deliver code via the repo. Try in
order:
- Git (preferred) — commit + push so the user pulls into Workbench and
opens in Positron. Version-controlled and clean.
- First detect a repo: is there a git remote configured (CLI/Desktop) or a
git-backed Snowsight Workspace?
- If a repo IS detected: commit and push.
- CLI/Desktop:
git add . && git commit -m "..." && git push
- Snowsight: commit through the Workspace's git integration
- If NO repo is detected: STOP and ASK the user before doing anything
else. Do not silently fall through to a local folder. Ask:
"I don't see a Git repository connected. Where would you like this?
- Push to a GitHub repo — give me the repo URL (and branch), and I'll
initialize, commit, and push. (If pushing fails on credentials,
I'll tell you what to set up.)
- Just write the files locally so you can open them in Positron."
Wait for the answer. Only fall through to option 2 below if the user
explicitly chooses local, or says they have no repo.
- If the user gives a repo URL:
git init (if needed), set the remote,
git add . && git commit && git push -u origin <branch>. If the push
fails on auth, tell the user to authenticate the machine (gh CLI login,
PAT, or SSH key) and that you'll retry — do NOT silently drop to local.
- Workbench home dir — if CoCo is running in a Workbench terminal
(CLI inside SPCS): write the project folder straight to the home
directory. No transfer needed; files are already where Positron sees them.
- Files in chat / local folder — only after the user has declined git.
Output or write the project files so they can open them in Positron.
For the ITERATE path, ALWAYS include a README.md in the project folder
with the exact local-setup commands, so pulling into Workbench → running
locally is one step. Use uv if available, venv otherwise. The README body:
# <App Title>
Snowflake data: <DATABASE.SCHEMA.TABLE the app reads>
## Run locally in Posit Workbench / Positron
Using uv (fast, recommended):
uv venv
source .venv/bin/activate
uv pip install -r requirements.txt
Or with the standard library venv:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Then run:
streamlit run app.py # (or: shiny run app.py / python app.py)
**Viewing the app:** In Posit Workbench, open it in the Positron **Viewer
pane** (or the Workbench-proxied URL) — NOT `localhost:<port>` in your laptop
browser, which can't reach the container and shows "localhost refused to
connect."
**Authenticating to Snowflake locally** — set environment variables for your
context before running:
In Posit Workbench (recommended — no browser needed):
export SNOWFLAKE_ACCOUNT=<your-account> # e.g. ORG-ACCOUNT
export SNOWFLAKE_USER=<your-login> # e.g. FIRST.LAST@COMPANY.COM
export SNOWFLAKE_PASSWORD=<a PAT> # Programmatic Access Token
On a local laptop (Cortex Desktop — opens a browser to sign in):
export SNOWFLAKE_ACCOUNT=<your-account>
export SNOWFLAKE_USER=<your-login>
# no password; a browser SSO window opens
When deployed to Connect, none of these are needed — the OAuth integration
provides the token automatically. No secrets file, ever. NOTE: browser SSO
(externalbrowser) does NOT work inside Workbench — use a PAT there.
## Deploy to Connect
Click Publish in Positron, or: rsconnect deploy <framework> .
Adjust the run command and "data" line to the actual framework and table.
The .venv/ is for LOCAL iteration only — never commit it or include it in
what deploys. Connect builds its own environment from requirements.txt.
Also include a .gitignore in the project so the local environment never
gets committed or bundled:
.venv/
__pycache__/
*.pyc
.Rproj.user/
.Rhistory
rsconnect-python/
.streamlit/secrets.toml
If the user wants to DEPLOY now → use the bridge. Try in order:
- POSIT_REQUEST_DEPLOY (the stage-deploy bridge) if it exists — works
from any surface, no privileges.
- rsconnect from a Workbench terminal if CoCo has shell access there.
- If neither is set up, tell the user the one-time bridge setup, OR fall
back to the ITERATE path above so they at least get the code and can
Publish from Positron.
Never dead-end. If the preferred path isn't available, silently fall to
the next one and tell the user what you did. The worst case is always
"here are the files" — never "I can't help."
Load references/connect-deploy.md for detailed patterns.
Procedure preference order (check in THIS order, stop at the first match):
POSIT_REQUEST_DEPLOY — always prefer this. Pure SQL, cannot fail on
network. It lives in ONE central bridge location per account (often
<db>.POSIT_BRIDGE); call it FULLY QUALIFIED from anywhere:
CALL <bridge_db>.<bridge_schema>.POSIT_REQUEST_DEPLOY(...).
Find it with: SHOW PROCEDURES LIKE 'POSIT_REQUEST_DEPLOY' IN ACCOUNT
POSIT_DEPLOY — ONLY if the user confirms the External Access
Integration is configured. If a call to POSIT_DEPLOY or POSIT_PUSH
fails with an error mentioning "External Access Integration" or
"secrets", do NOT retry it and do NOT try POSIT_PUSH (it has the same
dependency and will fail identically). Fall through to
POSIT_REQUEST_DEPLOY, or if that doesn't exist, tell the user the
one-time bridge setup below.
- Bash
rsconnect deploy — only from a Workbench terminal.
Deploy decision tree:
What does the user want?
├── Deploy to Connect (default)
│ → Is the deploy watcher set up? (check: SHOW TABLES LIKE 'POSIT_DEPLOY_REQUESTS')
│ ├── YES → CALL POSIT_REQUEST_DEPLOY(framework, app_name, title, files)
│ │ Then poll via SQL every ~10s until status changes:
│ │ SELECT status, result FROM POSIT_DEPLOY_REQUESTS
│ │ WHERE request_id = '<id>'
│ │ Report the URL from result when COMPLETE. This path needs
│ │ NO network access and NO admin — it works from every
│ │ CoCo surface (Snowsight, Desktop, CLI).
│ └── NO → Is POSIT_DEPLOY (direct EAI path) available?
│ ├── YES → CALL POSIT_DEPLOY(framework, app_name, title, files)
│ └── NO → Can CoCo run bash inside a Workbench terminal?
│ ├── YES → rsconnect deploy directly
│ └── NO → One-time setup: tell the user to run
│ scripts/stage-deploy-bridge.sql and deploy
│ scripts/deploy-watcher/ to Connect once.
│ Then this path works forever.
│
├── Code in Workbench IDE only (no deploy yet)
│ → CALL POSIT_STAGE_FILES(folder_name, files)
│ Then: snowsql -q "GET @POSIT_FILE_STAGE/<folder>/ file://~/<folder>/"
│
└── Both → CALL POSIT_REQUEST_DEPLOY (deploys) + give the GET command (files)
How the bridge works (so you can explain it to the user):
CoCo can't reach Connect directly \u2014 stored procedures run outside SPCS,
and PUT-to-stage is unavailable inside a stored procedure. The bridge
sidesteps both: POSIT_REQUEST_DEPLOY is a pure SQL INSERT that carries the
app files in the request row itself (a VARIANT column). A watcher agent
deployed ON Connect (inside SPCS, where https://connect resolves) polls the
POSIT_DEPLOY_REQUESTS table, reads the files straight from the row, and
deploys. No stage, no PUT, no network from CoCo. Deploy latency is the
watcher's schedule interval (typically ~1 minute).
To check if procedures exist:
SHOW PROCEDURES LIKE 'POSIT_%' IN SCHEMA <schema>;
If they don't exist, tell the user the one-time setup (in order of preference):
| Setup | What it enables | Privileges needed |
|---|
scripts/stage-deploy-bridge.sql + deploy scripts/deploy-watcher/ to Connect once | POSIT_REQUEST_DEPLOY — deploy from any CoCo surface, no network access needed | CREATE TABLE/PROCEDURE only. No admin. |
scripts/deploy-procedure.sql + an External Access Integration | POSIT_DEPLOY — direct synchronous deploy | SYSADMIN + ACCOUNTADMIN for the EAI |
| Procedure | What it does |
|---|
POSIT_REQUEST_DEPLOY | Stage files + queue a deploy request. The Connect watcher fulfills it. Primary path. |
POSIT_DEPLOY | Direct deploy via Connect API (requires EAI) |
POSIT_STAGE_FILES | Stage files for Workbench pickup only |
POSIT_PUSH | Stage + direct deploy (requires EAI) |
How to call them with the generated app:
After generating the app code in Step 3, pass ALL files as OBJECT_CONSTRUCT:
CALL POSIT_DEPLOY(
'streamlit', 'loan-risk-dashboard', 'Loan Risk Dashboard',
OBJECT_CONSTRUCT(
'app.py', '<full app code>',
'requirements.txt', 'streamlit>=1.36.0\nsnowflake-connector-python>=3.12.0'
)
);
CALL POSIT_STAGE_FILES(
'loan-risk-dashboard',
OBJECT_CONSTRUCT(
'app.py', '<full app code>',
'requirements.txt', 'streamlit>=1.36.0\nsnowflake-connector-python>=3.12.0'
)
);
CALL POSIT_PUSH(
'streamlit', 'loan-risk-dashboard', 'Loan Risk Dashboard',
OBJECT_CONSTRUCT(
'app.py', '<full app code>',
'requirements.txt', 'streamlit>=1.36.0\nsnowflake-connector-python>=3.12.0'
)
);
Framework names for POSIT_DEPLOY:
streamlit | shiny-python | dash | fastapi | bokeh | panel | quarto | shiny-r | plumber
Fallback — rsconnect CLI commands (if no procedure):
rsconnect deploy streamlit <folder>/ --name <app-name> --server https://connect
rsconnect deploy shiny <folder>/ --name <app-name> --server https://connect
rsconnect deploy dash <folder>/ --name <app-name> --server https://connect
rsconnect deploy fastapi <folder>/ --name <app-name> --server https://connect
rsconnect deploy bokeh <folder>/ --name <app-name> --server https://connect
rsconnect deploy quarto <folder>/ --name <app-name> --server https://connect
Never end a conversation with "go deploy this yourself." Either call
POSIT_DEPLOY, run rsconnect, or give a single copy-paste command.
Common Mistakes
- Wrapping the Snowflake connection in
@st.cache_resource — The Connect
OAuth token is resolved per session; caching the connection at app startup
captures an empty token and every request fails with "251005: User is
empty". Never cache the connection. Build it per query and cache the DATA
with @st.cache_data (keyed on the SQL) instead.
- Reading
os.environ["SNOWFLAKE_ACCOUNT"] before auth — Using bracket
access at the top of get_connection() crashes deployed content with a
KeyError before the OAuth integration ever runs, even though the integration
could supply what's needed. Always read with .get(), try the integration's
account first, and only require the env var on the off-Connect (local) path.
- Escaping quotes inside app source when building the deploy call — When
passing files to POSIT_REQUEST_DEPLOY, the code is the VALUE of a JSON/
VARIANT field; let the SQL/JSON layer handle escaping ONCE. Do not
backslash-escape quotes inside the Python/R source itself. A leaked
\' or
\" in source causes SyntaxError: unexpected character after line continuation character in the deployed app. Pass raw source; escape only
for the SQL string literal, never inside the code text.
- Using st.secrets / secrets.toml / st.connection for Snowflake — A
deployed app has no secrets file; this throws StreamlitSecretNotFoundError
at runtime. On Connect, get the Snowflake token from the OAuth integration
via posit-sdk (PositAuthenticator). See the CRITICAL connection section in
references/connect-frameworks.md and use that get_connection() verbatim.
Always add posit-sdk to requirements.txt.
- Retrying POSIT_DEPLOY / POSIT_PUSH after an EAI error — If either
fails mentioning "External Access Integration" or "secrets", both will
always fail in this environment. Switch to POSIT_REQUEST_DEPLOY
immediately. Suggest the user DROP the broken procedures so they stop
appearing as options.
- Deploying to Streamlit-in-Snowflake instead of Posit Connect — This
is the #1 mistake. CoCo's default is to create SiS apps. This skill
ALWAYS targets Posit Connect. Never use
CREATE STREAMLIT, never use
st.connection("snowflake"), never use snowflake.snowpark.context.
Always use snowflake.connector.connect(...) with explicit auth.
https://connect not resolving — This internal URL only works from
inside SPCS. If in CoCo Snowsight, give a single paste-able terminal
command — don't make the user figure out the deploy themselves.
- Dumping files in the workspace root — Always create a named project
folder for each app (e.g.,
loan-risk-dashboard/). Never write app.py
or app.R to the root — it gets unmanageable after two demos.
- Missing demo data — For demos, tell the user to run
$posit-prepare set up demo data first (or run
posit-prepare/scripts/setup-demo-data.sql in Snowsight).
- CoCo not available in Snowsight — CoCo in Snowsight requires
cross-region inference. CoCo CLI and Desktop do not.
dsn 'snowflake' not found — The snowflake DSN only exists inside
the Posit Team Native App. Outside it, use Pattern B or C from
references/snowflake-auth.md.
- Hardcoding credentials — Always use managed credentials or env vars.
- Wrong deploy command — Each framework has its own
rsconnect deploy
subcommand. Check references/connect-frameworks.md.
Demo mode (for Snowflake SEs)
When the user asks to see a demo, show a demo app, or run through a demo
scenario, use the assets in assets/:
-
assets/DEMO-RUNBOOK.md — Four scripted demos (3-5 min each) with
exact CoCo prompts and talking points. Load this when the user asks:
"show me the demo script", "what demos are available", or "walk me
through a demo".
-
assets/rbac-streamlit-app/app.py — Streamlit app with zero
data-filtering logic demonstrating Snowflake RBAC via Posit Connect
viewer OAuth. Show this when the user asks about RBAC, row-level
security, or the Streamlit demo. Deploy command:
rsconnect deploy streamlit assets/rbac-streamlit-app/ --name rbac-demo
-
assets/loan-dashboard/app.R — Shiny dashboard reading from the
orbital predictions view (2.2M rows). Show this when the user asks
about the loan demo, orbital demo, or ML scoring demo. Deploy command:
rsconnect::deployApp(appDir = "assets/loan-dashboard", appName = "loan-dashboard")
-
assets/example-nl-dashboard.R — Shiny app with querychat +
chatlas for natural language querying via Cortex. Show this when the
user asks about the NL demo, chatbot demo, or Cortex integration.
All demo apps read DEMO_DATABASE, DEMO_SCHEMA, and
SNOWFLAKE_WAREHOUSE from environment variables. Defaults:
SOL_ENG_DEMO, COCO_DEMO, DEFAULT_WH.