Omnibus
200 skills · 1230 min
Omnibus
Skill 161 of 200
Write Streamlit app source code that runs well in a PostHog sandbox — the posthog_apps.query() bridge for reading PostHog data, the packages baked into the sandbox image, caching…
3 minutes · 576 words · 7 sections
Install
npx skills add PostHog/skills --skill writing-streamlit-appsnpx skills add PostHog/skills/plugin marketplace add PostHog/skillsThe first command installs just this skill, by the name in its SKILL.md; the second installs the whole repository.
The source you write becomes app.py at the root of a sandboxed Streamlit 1.31 runtime.
Deployment mechanics (create/start/share) are the managing-streamlit-apps skill; this one is about the code.
posthog_apps.query()The one and only data door is the in-sandbox bridge:
import posthog_apps
df = posthog_apps.query("SELECT event, count() FROM events GROUP BY event LIMIT 10")RuntimeError on failure. The message is deliberately generic (“Query execution failed”) — the bridge does not return query internals to the sandbox, so you cannot diagnose a bad query from inside the app. Catch it and render with st.error(str(e)) so viewers get a message instead of a stack trace, and test queries in the SQL editor where real errors are visible.import posthog does NOT exist in the sandbox — the module is posthog_apps, deliberately distinct from the posthog-python SDK’s name.LIMIT results, and aggregate in HogQL rather than pulling raw events into pandas.Streamlit reruns the whole script top to bottom on every widget interaction. Two consequences:
Cache every bridge call — uncached, one slider drag re-fires every query:
@st.cache_data(ttl=300, show_spinner="Running query...")
def run_query(hogql: str) -> pd.DataFrame:
return posthog_apps.query(hogql)Use st.session_state for anything that must survive reruns — accumulated selections, pagination cursors, “last refreshed” stamps. Module-level variables reset on every interaction.
Widgets drive parameters naturally, but never interpolate a free-text widget value into HogQL.
The bridge runs your query with the version author’s data access, and anyone who can view the app drives those widgets — a raw st.text_input spliced into a query hands viewers the author’s access to write their own.
Constrain the input instead: pick from a fixed list you control (st.selectbox over known values), or coerce to a type that can’t carry SQL (int(days), a date from st.date_input), and validate before it reaches the query.
st.set_page_config(page_title=..., layout="wide") first — the default narrow layout wastes most of the screen for data apps.st.columns for side-by-side metrics, st.tabs for alternate views, st.expander for detail sections; st.metric for headline numbers.st.plotly_chart(fig, use_container_width=True) with plotly express is the reliable default; st.dataframe(df, use_container_width=True) for tables. matplotlib/seaborn also work via st.pyplot.The image ships Python 3.11 with: streamlit 1.31, pandas, numpy, polars, plotly, matplotlib, seaborn, scipy, scikit-learn, pyarrow, duckdb, requests, beautifulsoup4, lxml, sqlalchemy, aiohttp.
There is no way to add dependencies: the sandbox never runs pip (a deliberate security posture — no arbitrary package code at boot), and a requirements.txt in an uploaded zip is tolerated but dropped. Only import what’s listed above.
app.py is the entry point. Via the MCP set-source flow your source IS app.py. Helper modules and data files can ship next to it through the files (text) and assets (base64) maps; import utils works because Streamlit puts the app directory on sys.path, but the process does not run inside that directory, so open data files via Path(__file__).parent / "data/events.parquet", never a bare relative path. Keep bundled data small: it is sent inline as JSON on every set-source call.st.session_state.os.environ — anything there belongs to the sandbox runtime, not your app. Design around posthog_apps.query() as the data source.import pandas as pd
import plotly.express as px
import posthog_apps
import streamlit as st
st.set_page_config(page_title="Events overview", layout="wide")
st.title("Events overview")
Write Streamlit app source code that runs well in a PostHog sandbox — the posthog_apps.query() bridge for reading PostHog data, the packages baked into the sandbox image, caching and session state across Streamlit reruns, layout and chart patterns, and the app.py entry point with any helper modules and data files bundled beside it. Use when authoring or debugging the Python source of a PostHog Streamlit app, when a query inside an app fails, or when asked to "write a streamlit app that shows PostHog data".
The verbatim description from this skill’s front matter — the string an agent matches on to decide whether to load it.
main, last pushed 24 September 2026.SKILL.md, not by matching a directory convention. 2 distinct layouts observed: skills/omnibus/*/SKILL.md, skills/posthog/all/skills/*/SKILL.md.h1 and no skipped levels:.claude-plugin/marketplace.json by PostHog, declaring 6 plugins. It is read for editorial metadata only — never as the skill index, which is always the repository tree./PostHog/skills.md, and each skill at its own .md URL.