Thanks for your interest in helping improve Streamlit! 🎉
If you are looking for Streamlit's documentation, go here instead: https://docs.streamlit.io
Important
We have paused accepting pull requests from outside the Streamlit maintainer team. AI coding tools have increased PR volume beyond what we can review sustainably, while detailed issues give maintainers a more effective starting point for improving Streamlit.
There are still many valuable ways to contribute to Streamlit and help shape what we build next.
Search existing issues first. If the bug has not already been reported, open a bug report that includes:
- A clear, descriptive title.
- What you expected to happen and what happened instead.
- Numbered, specific steps that reproduce the problem.
- The smallest self-contained Streamlit app that demonstrates the problem, when possible.
- Your Streamlit and Python versions, operating system, and browser for frontend issues.
- Relevant tracebacks, logs, screenshots, or screen recordings.
Minimal, reproducible examples help maintainers investigate bugs much more quickly. For security vulnerabilities, follow our security policy instead of opening a public issue.
Search existing feature requests first. If your idea has not already been suggested, open a feature request that includes:
- The problem or use case you want to solve.
- The outcome you would like, including an example API or mockup when useful.
- Alternatives and workarounds you have tried.
- Any examples, references, or other context that clarify the request.
Detailed feature requests help us understand community needs and prioritize our roadmap.
- Add relevant details to existing issues, such as a new reproduction, environment information, use case, or workaround.
- Answer open questions from maintainers when you have firsthand knowledge that can help.
- React with a 👍 to the initial post of bugs and feature requests that matter to you. Please use reactions instead of adding "+1" or "same" comments.
- Review open specs and comment with concrete feedback about the use case, proposed API, design, or tradeoffs.
- Build a custom Streamlit Component and publish it to PyPI so the community can use it.
- Contribute reusable utilities to the community-maintained streamlit-extras project.
- Help other users in the Streamlit community forum or on Stack Overflow.
Help us keep GitHub issues and PR threads readable and actionable.
- Keep comments relevant and constructive. Add new information (a minimal repro, logs, screenshots, answers to maintainer questions, or review follow-ups).
- Avoid low-signal replies like "+1" / "same". Use GitHub reactions instead.
- No spam or promotion. Do not post recruiting links, unsolicited links to third-party products/services, link-only replies, or repeated off-topic advertising.
- Avoid duplicate comments that only restate existing points (including copy/paste or light paraphrasing). If you have nothing new to add, consider using a reaction instead of commenting.
Maintainers may hide, edit, or delete comments that don’t contribute to the discussion, lock threads, limit interactions, and take other moderation actions when needed. Repeated or severe abuse may result in restrictions or bans. See our Code of Conduct.
The rest of this guide covers local development, testing, and pull requests for the Streamlit maintainer team.
- Properly fill out the PR template with concrete details (not placeholders).
- Keep PRs narrowly scoped. If your changes are broad, split them into smaller, reviewable PRs.
- Address prior review feedback before requesting another review cycle.
- Please respond to requested changes or maintainer questions within 14 days. If you need more time, leave a short status comment.
- If you open additional PRs while prior feedback remains unaddressed, maintainers may pause or close review of newer PRs until earlier feedback is handled.
- Repeated non-response may result in newer PRs being deprioritized or closed.
We welcome responsible use of AI-assisted tools in this repository. AI can help contributors move faster, but it does not replace author ownership. If you open a PR, you are responsible for the correctness, scope, testing, and maintainability of that change.
To protect maintainer bandwidth and keep the process fair for active contributors, maintainers may take stricter action when a contributor repeatedly submits low-quality or non-responsive PRs, spams issues/PRs with low-signal or duplicative comments, or posts promotional/spam links. This may include deprioritizing reviews, requesting that existing feedback be resolved first, closing PRs, removing disruptive content, locking threads, limiting interactions, or other GitHub moderation actions. When necessary, we also reserve the right to protect the project and community up to and including banning users in severe or repeated abuse cases.
This repository includes skills and subagents in .claude/ usable with Claude Code and Cursor to assist AI coding agents with common development tasks. Skills are invoked automatically based on their description, but can also be triggered manually via /skill-name (e.g., /checking-changes).
| Skill | When to use |
|---|---|
checking-changes |
After making backend or frontend changes, before committing |
assessing-external-test-risk |
When reviewing branch or PR changes to decide whether @pytest.mark.external_test coverage is needed for externally hosted or embedded scenarios |
debugging-streamlit |
When testing code changes, investigating bugs, or checking UI behavior |
discovering-make-commands |
To list available make commands for build, test, lint, or format tasks |
fixing-streamlit-ci |
When CI checks fail and you need to diagnose and fix errors |
fixing-flaky-e2e-tests |
When E2E tests fail intermittently, show timeout errors, have snapshot mismatches, or exhibit browser-specific failures |
implementing-feature |
When you have a spec folder, URL, or GitHub issue to implement end-to-end, or want the next papercut enhancement selected automatically |
understanding-streamlit-architecture |
When debugging cross-layer issues, understanding how features work end-to-end, or onboarding to the codebase |
creating-pull-requests |
When changes are ready to be submitted as a PR with proper labels and formatting |
addressing-pr-review-comments |
When a PR has reviewer feedback to address, including inline and general PR comments |
updating-internal-docs |
After significant codebase changes to review and update internal documentation |
sharing-pr-agent-artifacts |
When you have agent-generated artifacts (specs, plans) relevant for the current PR to share for reviewing |
writing-spec |
When designing new API commands, widgets, or significant changes that need team review before implementation |
finalizing-pr |
When changes are ready to merge — runs quality checks, simplifies code, and creates/updates the PR |
generating-changelog |
When preparing release notes between two git tags |
improving-frontend-coverage |
When you want to systematically improve frontend test coverage with high-value test cases |
improving-python-coverage |
When you want to systematically improve Python test coverage with high-value test cases |
reviewing-readability |
When reviewing a PR, branch, or changes for comment, docstring, and naming readability — produces findings with concrete proposed rewrites |
reviewing-pr-description |
When reviewing a PR's title and description for clarity and conciseness |
Subagents run autonomously in a fresh context, which optimizes for context size and cost. They can be triggered manually via /subagent-name (e.g., /reviewing-local-changes).
| Subagent | When to use |
|---|---|
reviewing-local-changes |
When you want a code review of the current branch's changes for quality, security, best practices, and product/API alignment |
simplifying-local-changes |
When you want to simplify and refine code for clarity and maintainability |
fixing-pr |
When a PR needs CI fixes, merge-conflict resolution against its base branch, review feedback handling, and validation before merge |
qa-testing-feature |
After implementing a feature to perform comprehensive QA testing before finalizing a PR |
Streamlit ships user-facing skills with the library under lib/streamlit/.agents/skills/ (for example, developing-with-streamlit). When adding or editing those skills, follow lib/streamlit/.agents/skills/AGENTS.md.
Check out Streamlit's style guide. We use oxfmt, oxlint, ESLint, and Ruff to format and lint code, but some things go beyond what auto-formatters and linters can do. So please take a look!
# Some Apple dev tools (developer.apple.com/downloads)
$ xcode-select --install
# Install Homebrew
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Install the Protobuf compiler
$ brew install protobuf
# (Recommended) Install GitHub CLI - used by AI agents for PR and issue management
$ brew install gh
# (Recommended) Install ripgrep - used by AI agents for fast log/code search
$ brew install ripgrepInstalling nvm
We recommend that you manage your nodejs installation with nvm.
Install nvm now using those instructions. In step 2 ("Grab the code") you will install the Node version pinned in .nvmrc and enable Corepack (Yarn ships via Corepack).
# Install some essentials
$ sudo apt-get update
$ sudo apt-get install -y sudo make build-essential curl git rsync unzip protobuf-compiler
# Install nvm (Node version from .nvmrc is installed after cloning)
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
$ source ~/.bashrc
# Install uv for Python
$ curl -LsSf https://astral.sh/uv/install.sh | sh
# (Recommended) Install GitHub CLI - used by AI agents for PR and issue management
# See https://cli.github.com/ for installation instructions
# (Recommended) Install ripgrep - used by AI agents for fast log/code search
$ sudo apt-get install -y ripgrep# Install some essentials
$ sudo dnf install -y make gcc-c++ curl git rsync unzip protobuf-compiler
# Install nvm (Node version from .nvmrc is installed after cloning)
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
$ source ~/.bashrc
# Install uv for Python
$ curl -LsSf https://astral.sh/uv/install.sh | sh
# (Recommended) Install GitHub CLI - used by AI agents for PR and issue management
# See https://cli.github.com/ for installation instructions
# (Recommended) Install ripgrep - used by AI agents for fast log/code search
$ sudo dnf install -y ripgrepNote
If you're not on Debian, Ubuntu, or macOS, make python-init skips playwright install --with-deps. That command only officially supports those platforms. Browser binaries still download fine. If e2e tests later complain about missing system libraries, grab them through your package manager. See Playwright's system requirements for the list. To force or skip the deps step, set INSTALL_PLAYWRIGHT_DEPS=true or false.
Streamlit's development setup is pretty Mac- and Linux-centric. If you're doing Streamlit development on Windows, we suggest using our devcontainer via Github Codespaces or locally via VS Code. Alternatively, you can also spin up a Linux VM (e.g. via VirtualBox, which is free); or your own Linux Docker image; or using Microsoft's WSL ("Windows Subsystem for Linux").
(You probably already know how to do this, but just in case...)
First fork the repo via the UI on Github and then do the following:
git clone https://github.com/${YOUR_NAME}/streamlit.git
cd streamlit
git remote add remote https://github.com/streamlit/streamlit.git
git checkout develop
git checkout -b ${BRANCH_NAME}From the repo root, install the Node version pinned in .nvmrc and enable Corepack:
nvm install
corepack enable
# Re-activate the pinned version in new shells with `nvm use`,
# or make it your default with `nvm alias default $(cat .nvmrc)`.On macOS you may need to brew install corepack depending on how you installed Node.
We use uv to manage Python dependencies and virtual environments. Use a version allowed by [tool.uv].required-version in pyproject.toml. If you don't have uv installed, you can install it with:
curl -LsSf https://astral.sh/uv/install.sh | shThe committed uv.lock is the source of truth for development and normal CI environments. It does not lock dependencies for users installing the published Streamlit wheel; those remain governed by the ranges in lib/pyproject.toml.
The virtual environment and dependencies will be automatically created and managed when you run make all-dev in the next step. uv creates a .venv directory in the repository root.
You can also install one explicit final environment:
| Environment | Command |
|---|---|
| Runtime only | PYTHON_DEPENDENCY_GROUP=runtime make python-init |
| Unit/E2E tests | PYTHON_DEPENDENCY_GROUP=test make python-init |
| Development (tests plus lint, types, and release tools) | make python-init |
| Integration tests | PYTHON_DEPENDENCY_GROUP=integration make python-init |
These commands exact-sync the same .venv, so switching selections mutates it. Set a distinct UV_PROJECT_ENVIRONMENT path for each selection if you need simultaneous environments.
The basic developer workflow is that you run a React development server (default port 3000) in one terminal and run Streamlit CLI commands in another terminal.
make all-devmake frontendThe easiest way to start the dev server from the terminal, is to run:
make frontend-devNote
This server defaults to port 3000 rather than 8501 (i.e. Streamlit's production port), but you can change it with VITE_PORT (or PORT), for example: VITE_PORT=3002 make frontend-dev.
To point the frontend dev server at a different backend, set DEV_SERVER_BACKEND_URL, for example: DEV_SERVER_BACKEND_URL=http://localhost:8502 make frontend-dev.
The server automatically updates when frontend code changes (hot-reloading).
Open another terminal and run Streamlit using uv run:
# Run any Streamlit command, such as:
uv run streamlit helloSince we use that awesome dev server above, when you change any JS/CSS code everything should automatically just work without the need to restart any of the servers.
When you modify Python code, you should kill the old Streamlit server, if any (ctrl-c on the terminal) and then restart it.
If you ever modify our protobufs, you'll need to run the command below to compile the protos into libraries that can be used in Python and JS:
make protobufmake initImportant
If your change updates frontend/yarn.lock (for example, after adding or upgrading dependencies), run cd frontend && yarn dedupe before committing. Our scripts/check_yarn_dedupe.sh hook enforces this locally (via pre-commit) and in CI, so handling it upfront keeps your PR green.
For Python dependencies, edit the relevant pyproject.toml, run uv lock, review the generated lock diff, and commit both files. Use uv lock --upgrade-package <package> for a targeted upgrade, uv lock --upgrade for a full compatible upgrade, and uv lock --check to verify manifest consistency. Do not hand-edit uv.lock.
You should always write unit tests and end-to-end tests! This is true for new features, but also for bugs; this way when you fix a bug you can be sure it will not show up again. So bug-fixing is actually a great way to increase our test coverage where it actually matters.
-
Run all with:
make python-tests
-
Run a specific test file with:
uv run pytest lib/tests/streamlit/the_test_name.py
-
Run a specific test inside a test file with:
uv run pytest lib/tests/streamlit/the_test_name.py -k test_that_something_works
-
Integration tests need the
integrationdependency group (Snowflake, Polars, Pydantic, and others). Some also need Snowflake credentials described in test utils. They are skipped by default.Install the integration environment, then run
make python-integration-tests. Do not use a bareuv run pytest --require-integration: a defaultuv runre-syncs to thedevgroup and drops integration-only packages.PYTHON_DEPENDENCY_GROUP=integration make python-init make python-integration-tests
-
Run all with:
make frontend-tests
-
Run specific tests:
cd frontend yarn workspace @streamlit/lib test src/path/to/test_file.test.ts
NOTE: Making changes to a react component may cause unit snapshot tests (which are designed to catch unintended changes to jsx/tsx components) to fail. Once you've double-checked that all of the changes in the failing snapshot test are expected, you can follow the prompts that appear after running make frontend-tests to update the snapshots, check them into source control, and include them in your PR.
You can find information about our e2e testing setup here.
We've set up various formatting, linting, and type-checking rules that our Continuous Integration checks to maintain code quality and consistency. Before merging a Pull Request, all formatting and linting rules must be satisfied and passed successfully.
For Python, we use ruff for formatting & linting and mypy plus ty for type-checking.
To format all Python code & sort the imports, run the following command:
make python-format
Alternatively, you can use uv run ruff format directly.
To run the linter, use the command below:
make python-lint
Alternatively, you can use uv run ruff check directly.
For type-checking, run:
make python-typesFor Javascript/Typescript, we utilize oxfmt, oxlint, ESLint, and Knip.
To format your code, run this command:
make frontend-format
To initiate the linting process, use this command:
make frontend-lintFor type-checking, run:
make frontend-typesTo check for unused exports and unused dependencies / devDependencies, run:
make frontend-knipKnip also fails on undeclared (unlisted) imports: every direct import must be listed in that workspace's package.json. See frontend/AGENTS.md.
For development in VS Code, we recommend installing the extensions listed in .vscode/extensions.json and for an optimized configuration you can use the VS-Code settings from .devcontainer/devcontainer.json.
Tip
For Cursor users: We suggest enabling the "Include third-party skills, subagents, and other configs" setting in Cursor's preferences to take full advantage of all available agent skills and configs.
When Streamlit's pre-commit detects that one of the linters has failed, it automatically lints the files and does not allow the commit to pass. Please review the changes after lint has failed and commit them again, the second commit should pass, because the files were linted after trying to do the first commit.
But you can run pre-commit hooks manually as needed.
-
Run all checks on your staged files by using:
uv run pre-commit run
-
Run all checks on all files by using:
uv run pre-commit run --all-files
def test_streamlit_version(self):
"""Test streamlit.__version__."""
self.assertEqual(__version__, get_version())
AssertionError: '1.11.0' != '1.11.1'
- 1.11.0
? ^
+ 1.11.1
? ^To fix this make sure your Python environment is set up correctly. Try running make python-init to reinstall locked development dependencies, or delete the .venv directory and run make all-dev again to recreate the environment.
Local protoc must be at least 3.20 and no newer than the Python protobuf
package in uv.lock. make protobuf only checks the 3.20 floor; a newer
compiler fails when generated modules are imported. The compiler does not need
to match that package exactly: Python can run code generated by an older
compiler on a newer protobuf package. Compare protoc --version with
uv run python -c "import google.protobuf as p; print(p.__version__)".
Drop the leading major version to convert: Python protobuf 6.33.6
corresponds to protoc 33.6, and any protoc at or below that is fine.
CI's 26.1 is an example of an older compiler that still works with the
current lockfile. If generation already works, keep that compiler; do not
replace a working install just to match CI. When changing that CI version,
update PROTOC_SHA256 in .github/actions/make_init/action.yml to the
sha256sum of the linux-x86_64 zip.
-
Too old (
Error: protoc version X is < 3.20): install a newer compiler from the official installation instructions, still no newer than the Pythonprotobufversion inuv.lock. Homebrew's unversionedprotobufformula may be ahead of the lockfile. -
Too new (
google.protobuf.runtime_version.VersionErrorwhen generated modules are imported): switch to a compiler at or below the locked package, then re-runmake protobuf. On macOS, runbrew search protobuf@for a versioned formula matching the lock's minor version (e.g.protobuf@33forprotobuf6.33.x). If one exists:brew install protobuf@33 export PATH="$(brew --prefix protobuf@33)/bin:$PATH" make protobuf
The
PATHexport is session-scoped; a latermake protobufin a new shell will pick Homebrew's unversionedprotocagain unless thatPATHis persisted. If Homebrew has no matching formula, copyPROTOC_VERSIONfrom.github/actions/make_init/action.ymland install that GitHub release zip (it is an archive, not aprotocbinary). Use the asset for your OS and architecture (osx-aarch_64,osx-x86_64,linux-x86_64, orlinux-aarch_64), extractbin/protoc, and put it first onPATH:PROTOC_VERSION=26.1 # copy from action.yml PROTOC_ZIP="protoc-${PROTOC_VERSION}-osx-aarch_64.zip" curl -fOL "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/${PROTOC_ZIP}" sudo unzip -o "$PROTOC_ZIP" -d /usr/local bin/protoc sudo unzip -o "$PROTOC_ZIP" -d /usr/local 'include/*' rm "$PROTOC_ZIP" which protoc # should be /usr/local/bin/protoc make protobuf
/usr/local/binmust precede Homebrew'sprotoconPATH. -
ImportError: cannot import name 'runtime_version'fromgoogle.protobufmeans the Python environment is out of sync (the lockedprotobufpackage already includesruntime_version). Reinstall withmake python-init.
To reproduce CI or release-generated output, use the compiler version configured in
.github/actions/make_init/action.yml.
Add a dependency only when it provides meaningful value that cannot easily be replicated with an in-house implementation. Each dependency increases the risk of supply-chain attacks, breakage from incompatible new versions, and conflicts with other dependencies in users' environments.
We aim to only introduce dependencies in this project that have reasonable restrictions and comply with various laws.
Normal validation uses uv.lock for deterministic dependencies. CI separately tests the published minimum runtime versions without project synchronization, and the weekly update-python-lock.yml workflow upgrades the complete compatible graph and opens a PR when it changes. This preserves explicit minimum- and newest-compatible coverage without making unrelated PRs depend on upstream release timing.
If uv.lock conflicts during a merge, regenerate it instead of hand-merging:
git checkout origin/develop -- uv.lock
uv lockPackage name or version changes in lib/pyproject.toml also require uv lock, because the editable package identity is recorded in the lock.