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.

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: ''

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.