Imported from streamlit/streamlit (
lib/streamlit/AGENTS.md). Install upstream withnpx skills add streamlit/streamlit --skill streamlit. Copyright stays with the author.
Streamlit Lib Python Guide
Tips and guidelines specific to the development of the Streamlit Python library, not applicable to scripts and e2e tests.
FIPS Compatibility
- Production code must remain compatible with Python/OpenSSL environments running in FIPS mode.
- For non-security hashing, use
streamlit.util.create_fast_hasher(incremental hashing) orcalc_hash(one-shot string/bytes hashing) instead of callinghashlibdirectly.- Direct use of
hashlib.md5,sha1,blake2b,blake2s, andhashlib.newis banned by lint (ruffTID251). - The shared
streamlit.utilhelpers are the only sanctioned direct callers, guarded with# noqa: TID251.
- Direct use of
- FIPS-approved constructors (e.g.
hashlib.sha256) remain allowed for genuine security needs. - Update
lib/tests/streamlit/fips_test.pywhen changing hashing behavior.
Logging
Use our logger (a standard Python logger) with an appropriate level:
from streamlit.logger import get_logger
_LOGGER: Final = get_logger(__name__)
Log diagnostics that a developer or coding agent needs to see: important API
misuse, ignored parameters that change behavior, degraded or incomplete
output, and unexpected fallbacks. Agents and CLI users only see the console;
in-app st.warning / st.error / st.exception from library code are
invisible to them unless they are also logged.
Do not over-log. Console noise hides real issues. The library may handle potentially non-ideal API usage silently when it is not important enough to surface.
Use stack_info=True when the user call site matters. Prefer
_LOGGER.warning("%s", message, stack_info=True) when message may contain
%. Raised StreamlitAPIException subclasses are already logged by the
uncaught-exception handler; do not double-log those.
In-app UI vs console-only
Do not use st.warning, st.error, or st.exception from library internals
as the only signal. If you show one of those, also log the same message.
- Stay silent when a quirk can be handled without it mattering to the developer or agent (benign coercion, unused optional kwargs that never applied, other smart fallbacks).
- Log only when the command still works but a developer or agent should
know (explicitly provided
sample_rateon non-numpy audio, number-input format that does not match the value type). - Log and show UI when the app is actually misbehaving or data is
incomplete (widget inside a cached function, display commands in
refresh_mode="background", JSON keys dropped,st.echocannot read the source file). Usest.exception(StreamlitAPIWarning)when the in-app stack should point at user code. - Deprecations: use
show_deprecation_warning(), which already logs.
Metrics
- Use
gather_metricsonly for publicst.*APIs. Never use it for internal methods or functions.
Streamlit Backend Performance Hot Paths
Changes to these high-fan-out internals can affect every command, message, session, or rerun. Keep work in them minimal, and add or extend performance coverage when modifying these areas:
- Element creation and enqueueing (
delta_generator.py, element-ID calculation, public-command metrics): Avoid extra validation, hashing, protobuf copies, or context work perst.*call. - ForwardMsg hashing, caching, and serialization (
runtime/forward_msg_cache.py, protobuf transport): Messages can be serialized for hashing and again for transport. Avoid extra copies or passes over large payloads, including on cache hits. - Delta queueing and WebSocket flushing (
runtime/forward_msg_queue.py,runtime.py, WebSocket handlers): Preserve delta coalescing, bounded queues, and event-loop responsiveness; message count and flush cadence directly affect throughput and backpressure. - Script reruns and session/widget state (
script_runner.py,runtime/state/session_state.py): Avoid additional full-state scans, expensive equality checks, unstable widget IDs, or cleanup work repeated for every rerun and session. st.cache_dataandst.cache_resource(runtime/caching/): Hits still hash arguments;st.cache_dataalso copies and unpickles results. Be careful with large keys/results, serialization, validation, replayed messages, and lock scope.- Dataframe/Arrow and streaming paths (
dataframe_util.py,elements/arrow.py,elements/write.py): Avoid dataframe conversions and copies, repeated Arrow serialization, per-cell styling work, and many tiny streaming updates that repeatedly rebuild growing payloads.
Embedded agent skills
User-facing skills ship under lib/streamlit/.agents/skills/ (for example, developing-with-streamlit). Keep them current as features land; follow lib/streamlit/.agents/skills/AGENTS.md.
Unit Tests
We use the unit tests to cover internal behavior that can work without the web / backend
counterpart and the e2e tests to test the entire system. We aim for high unit test
coverage (95% or higher) of our Python code in lib/streamlit.
- Under
lib/tests/streamlit, add a new test file - Preferably in the mirrored directory structure as the non-test files.
- Naming:
my_example_test.py - Anti-regression checks: Where practical, go beyond the happy path by covering a plausible failure mode or edge case (invalid input, boundary condition, absent side effect). Do not add assertions that are logically implied by an earlier assertion — e.g., if you assert
x is True, assertingx is not Falseis a tautology and adds no value. Seelib/tests/AGENTS.mdfor detailed guidance and examples. - Coverage exclusions: Use
# pragma: no coverfor code that cannot reasonably be tested, such as import fallbacks for optional dependencies, "should never happen" defensive checks, or platform-specific unreachable paths. Include a brief reason, e.g.,# pragma: no cover - optional depor# pragma: no cover - defensive.
Typing Tests
We have typing tests in lib/tests/streamlit/typing for our public API to catch
typing errors in parameters or return types by using mypy, ty, and assert_type.
- These are NOT pytest tests — they are checked by mypy and ty, never executed at runtime.
- All assertions and imports go inside
if TYPE_CHECKING:blocks. - Do not use
def test_*()functions. Import mixin methods directly (e.g.LayoutsMixin().expander). For module-level objects such asst.context,st.user,st.bottom, andst.App, importstreamlit as st. - Always include
from __future__ import annotationsat the top. - Overloads discriminated on
boolneed an explicit fallback overload for non-literal values, because mypy does not expandboolintoLiteral[True] | Literal[False]. String-Literaldiscriminators do not need that fallback; mypy expands union arguments, so assert the union result directly. Cover both the literal cases and the non-literal case. - Check other typing tests in the
lib/tests/streamlit/typingdirectory for inspiration (e.g.radio_types.py,file_uploader_types.py). - Intentional invalid calls need a suppression for each checker that reports an
error:
# type: ignore[...](mypy) and# ty: ignore[...](ty). Place each suppression where its checker reports the diagnostic; use the same line when possible. Add a checker's comment only when that checker actually errors — ty'sunused-ignore-commentrule is disabled, so a superfluous suppression is silently kept. - A valid call whose asserted type mypy accepts but ty rejects may use
# ty: ignore[type-assertion-failure]. Add a short note saying what ty infers instead, so the suppression can be removed once ty catches up. - For dict-like return values backed by
AttributeDictionary/ReadOnlyAttributeDictionarysubclasses (e.g. dataframe/chart selection state,ButtonColumnclick state,st.data_editoredit state), and for module-level objects that support both notations (st.context,st.user,st.user.tokens), assert both attribute and bracket access (e.g.state.selection.rowsandstate["selection"]["rows"], orst.context.timezoneandst.context["timezone"]). Use a separateTypedDict(*Input) for values users assign (e.g.selection_default), not the returned attribute-dictionary class.
Docstrings for Public API
All public-facing API methods (st.* namespace) use NumPy-style docstrings (Numpydoc) with
reStructuredText directives. Follow these guidelines:
-
Follow existing patterns: Match the style of docstrings for similar parameters or functions in the codebase to ensure consistency.
-
Raw docstrings vs escaping: If you need to include a backslash in the docstring, prefer a raw docstring (
r"""...""") over escaping. -
Sections: Always include
ParametersandExamplessections. Include aReturnssection only when the function returns a value that users need to understand and use in their application logic (e.g., widgets likest.buttonreturnbool). Display elements that returnDeltaGenerator(e.g.,st.markdown,st.metric) omit theReturnssection since it's an implementation detail. Use.. note::for important caveats. -
Parameter descriptions: Start with the type (e.g.,
label : str), then describe purpose and behavior. Explicitly state defaults in prose, e.g.,"If this is ``None`` (default), ...". The first line is a noun phrase giving the definition. The remainder of the description should be in complete sentences. -
Inline code: Use double backticks (
``) for code literals, parameter values, andNone/True/False. -
Literal options: List multi-option parameters (e.g.,
type : "primary", "secondary") with bullet points describing each option. -
Cross-references: Link to
st.markdownfor Markdown capabilities using RST substitution (see existing docstrings for the pattern). -
Examples: Use
.. code-block:: pythonfor examples. Where possible, make the examples fully executable (beginning with import statements), label the filename, and end with.. output::directive and a URL with a reasonable name (e.g.,https://doc-<example-description>.streamlit.app/). The output directive should include a height of at least 200px. Adjust the height to avoid scrolling where reasonable. Try to keep examples shorter than 600px. Always include a full empty line after an RST directive... code-block:: python :filename: streamlit_app.py import streamlit as st.. output:: https://doc-example.streamlit.app height: 200px
Exception handling
User-facing API errors raised from st.* commands belong in
streamlit.errors. Prefer existing reusable exception types over raising a
generic StreamlitAPIException with a one-off message. Do not raise native
ValueError, TypeError, or RuntimeError for user-facing st.*
validation. Optional-dependency import failures may stay native
(st.connection ModuleNotFoundError, st.pyplot matplotlib ImportError).
Non-fatal library diagnostics (ignored parameters, degraded output, cached
widget misuse) should not be raised. Log them only when they are important
for the developer or coding agent to see; otherwise handle them silently.
Add in-app st.warning / st.error / st.exception only when the app is
actually wrong or data is incomplete — see Logging.
StreamlitAPIException: base for malformed user interaction with the Streamlit API. Prefer a more specific subclass when one fits. When a bareStreamlitAPIExceptionis still the right type, pass a stable kebab-caseerror_idthat is unique per distinct error (reuse the same id when the same error is raised from multiple sites; for examplefailed-loading-secrets-file). It is stored on the exception and appended in uncaught-exception telemetry (StreamlitAPIException:<error_id>). Do not put widget keys, file paths, or free-text values inerror_id.StreamlitValueError(parameter, valid_values, *, detail=None): use when a parameter receives an invalid value from a known set of options, or a short open-ended constraint (for example"a positive duration"). For a closed[min, max]interval, useStreamlitValueOutOfRangeErrorinstead.valid_valuesis the user-facing list of supported values.parameteris appended in uncaught-exception telemetry (StreamlitValueError:<parameter>); optionaldetailappears in the error message only. Example:raise StreamlitValueError("type", ["'primary'", "'secondary'", "'tertiary'"]).StreamlitValueOutOfRangeError(parameter, value, min_value, max_value, *, detail=None): use when a parameter is outside a closed[min, max]interval, including a dynamic max such aslen(options) - 1.parameteris appended in uncaught-exception telemetry (StreamlitValueOutOfRangeError:<parameter>); optionaldetailappears in the error message only. Example:raise StreamlitValueOutOfRangeError("index", index, 0, len(options) - 1).StreamlitMissingRequiredParameterError(parameter, *, detail=None): use when a required parameter is missing,None, or empty, including an empty sequence.parameteris appended in uncaught-exception telemetry (StreamlitMissingRequiredParameterError:<parameter>). Example:raise StreamlitMissingRequiredParameterError("label").StreamlitIncompatibleParametersError(first_use, second_use, *other_uses, *, explanation=None): use when two or more parameter uses cannot be combined. Passparameter=valuewhen the conflict depends on a value (wrap=False), or the bare parameter name when merely providing it conflicts (on_change). These strings appear only in the displayed error; uncaught-exception telemetry records only the exception type. Optionalexplanationis appended when the generic "cannot be used together" message needs more context. Example:raise StreamlitIncompatibleParametersError("wrap=False", "horizontal=False").StreamlitInvalidParameterTypeError(parameter, provided_type, expected_types, *, detail=None): use when a parameter has an unsupported type.parameteris appended in uncaught-exception telemetry (StreamlitInvalidParameterTypeError:<parameter>). Pass concise type names as strings; optionaldetailappears in the error message only. For example,raise StreamlitInvalidParameterTypeError("step", "str", ["int", "timedelta"]).- Prefer other shared validators/errors when they already exist for the
parameter, including:
StreamlitInvalidWidthError/StreamlitInvalidHeightError(layout sizing helpers)StreamlitInvalidColorErrorStreamlitValueBelowMinError/StreamlitValueAboveMaxError(widgetvaluevs user-configuredmin_value/max_value)StreamlitInvalidMinMaxError(min_valuecannot be greater thanmax_value;st.sliderswaps reversed bounds and raises this only for equal bounds;st.date_input/st.datetime_input/st.number_inputreject reversed bounds and allow equal bounds)StreamlitInvalidURLError(url, protocols)(st.logo(link=), page-config menu items). Pass the allowed schemes, for example["http", "https"].protocolsdefaults to("http", "https", "mailto").StreamlitInvalidFormCallbackError(form callback policy)StreamlitInvalidLayoutContextError(command used in a disallowed layout, form, dialog, or fragment context — including opening a second dialog in the same run, writing to a container across a parallel-fragment boundary, orst.rerun(scope="fragment")outside a fragment rerun)StreamlitDuplicateElementKey(duplicate userkey, includingst.form)StreamlitWidgetAlreadyInstantiatedError(session state assigned after the widget with that key is instantiated this run)StreamlitDefaultNotInOptionsError(default/index not in widgetoptions;st.tabsdefaultusesStreamlitValueErrorbecause this message is worded for widget options, not tab labels)StreamlitPageNotFoundError(missing page path,st.Pagefile,switch_page,page_link)StreamlitDataframeConversionError(value cannot be converted to a DataFrame, Arrow table, Series, or single-column list)
Reserve bare StreamlitAPIException for one-off cases that no shared type
covers and that users are expected to hit uncommonly (serialization failures
and similar). Always pass error_id at those remaining sites. The inventory
test in lib/tests/streamlit/errors_test.py fails if any production
StreamlitAPIException(...) omits error_id.
Theming and Layout
- Theming and layout calculations must be done in the frontend, not the Python backend.
- Do not use
get_option("theme.primaryColor")or similar theme options in backend code. This is unreliable because themes can be configured in multiple ways and the backend may not have access to the actual active theme. - Pixel-based or rem-based calculations (sizing, spacing, responsive layouts) must be handled on the frontend side where the rendering context is available.
- The backend should pass semantic data to the frontend; let the frontend handle all visual presentation logic.