StreamlitAPIException: duplicate key, session_state cannot be modified (tested)
StreamlitAPIException is the parent of several Streamlit errors. The common ones: duplicate widgets without a unique key, the same key used twice, st.session_state.x = ... after the widget with key="x" was created, and assigning to a button's read-only state. Each fix is shown below, tested.
1. The four common StreamlitAPIExceptions
b1.py runs small apps with Streamlit's built-in AppTest and prints the exception each one raises:
from streamlit.testing.v1 import AppTest
cases = {
"duplicate widget (no key)": '''
import streamlit as st
st.text_input("Name")
st.text_input("Name")
''',
"duplicate key": '''
import streamlit as st
st.button("A", key="go")
st.button("B", key="go")
''',
"session_state after widget": '''
import streamlit as st
st.text_input("Name", key="name")
st.session_state.name = "Bob"
''',
"value + session_state": '''
import streamlit as st
st.session_state.n = 5
st.number_input("N", value=1, key="n")
''',
"set button state": '''
import streamlit as st
st.session_state.go = True
st.button("Go", key="go")
''',
}
for name, src in cases.items():
at = AppTest.from_string(src).run()
if at.exception:
e = at.exception[0]
print(f"{name}:\n {e.proto.type.split('.')[-1]}: {e.message.splitlines()[0]}")
else:
print(f"{name}: no exception")
Output (streamlit 1.66.1, Python 3.13.5; Streamlit's log lines on stderr hidden):
duplicate widget (no key):
StreamlitDuplicateElementId: There are multiple `text_input` elements with the same auto-generated ID. When this element is created, it is assigned an internal ID based on the element type and provided parameters. Multiple elements with the same type and parameters will cause this error.
duplicate key:
StreamlitDuplicateElementKey: There are multiple elements with the same `key='go'`. To fix this, please make sure that the `key` argument is unique for each element you create.
session_state after widget:
StreamlitWidgetAlreadyInstantiatedError: `st.session_state['name']` cannot be modified after the widget with that key is instantiated. Assign `st.session_state['name']` before creating the widget, or update it from an `on_change` or `on_click` callback, which runs before the widget is instantiated. If this is a read-only session state key (buttons, file and media inputs, `st.data_editor`, forms, chart or `ButtonColumn` selections), store the value under a different session state key.
value + session_state: no exception
set button state:
StreamlitValueAssignmentNotAllowedError: `st.session_state['go']` is read-only and cannot be assigned through session state. Use a different session state key for values you need to set.
- StreamlitDuplicateElementId: two widgets of the same type with the same label and parameters and no
key(typical inside a loop). Give each a uniquekey, e.g.key=f"name_{i}". - StreamlitDuplicateElementKey: you passed the same
keyto two elements, even of different types or labels. - StreamlitWidgetAlreadyInstantiatedError:
st.session_state.name = ...after the widget withkey="name"was created in this run. Set it before the widget, or in anon_change/on_clickcallback. - StreamlitValueAssignmentNotAllowedError: buttons (and file uploaders, forms,
st.data_editor) have read-only session state; store your own value under a different key. - Setting session state before a widget that also has
value=is not an exception: Streamlit logsThe widget with key "n" was created with a default value but also had its value set via the Session State API.and the widget shows the session state value (5, not 1).
2. The fixed app
b2.py:
from streamlit.testing.v1 import AppTest
import streamlit.errors as E
print("subclasses of StreamlitAPIException:",
all(issubclass(c, E.StreamlitAPIException) for c in (
E.StreamlitDuplicateElementId, E.StreamlitDuplicateElementKey,
E.StreamlitWidgetAlreadyInstantiatedError,
E.StreamlitValueAssignmentNotAllowedError)))
fixed = '''
import streamlit as st
st.text_input("Name", key="name_1") # unique keys
st.text_input("Name", key="name_2")
if "city" not in st.session_state: # set BEFORE the widget
st.session_state.city = "Paris"
st.text_input("City", key="city")
def reset(): # or change it in a callback
st.session_state.city = ""
st.button("Reset", key="reset", on_click=reset)
st.session_state.clicked = st.button("Go") # store button result under another key
st.write("city =", st.session_state.city)
'''
at = AppTest.from_string(fixed).run()
print("exceptions:", len(at.exception), "| city:", at.text_input(key="city").value)
at.button(key="reset").click().run()
print("after Reset click -> exceptions:", len(at.exception), "| city:", repr(at.text_input(key="city").value))
subclasses of StreamlitAPIException: True
exceptions: 0 | city: Paris
after Reset click -> exceptions: 0 | city: ''
- All four are subclasses of
streamlit.errors.StreamlitAPIException. - The pattern that avoids most of them: unique keys, initialize
st.session_statewithif "k" not in st.session_stateat the top, and change widget values only in callbacks, which run before the script re-runs. streamlit.testing.v1.AppTestreproduces these without a browser, so you can put a regression test for your app in pytest.
Related: Streamlit error message: st.error vs st.exception.
Frequently asked questions
What does "There are multiple elements with the same key" mean in Streamlit?
Two elements were given the same key argument. Keys must be unique across the whole app run.
How do I fix st.session_state cannot be modified after the widget with that key is instantiated?
Set the value before the widget is created, or change it inside an on_change/on_click callback.
Why can't I set a button value in st.session_state?
Button state is read-only. Store the result under a different session_state key instead.
Written with AI assistance. Measured on 2026-10-23 with streamlit 1.66.1 and Python 3.13.5 on Linux, using streamlit.testing.v1.AppTest. Scripts ran verbatim and the output was pasted unedited. PlainTables is our extension; Streamlit is free.