Hi, I was wondering if there was a way to store some fields in the browser local storage of a client and access them through streamlit. I want to be able to repopulate certain fields if the user closes my form and opens it after some time.
If this isn’t feasible is using cookies my best option and is there a good way of setting and updating cookie keys via streamlit?
Hi @pramodith, welcome to the forum. There’s st.experimental_set_query_params and st.experimental_get_query_params that allows you to send field information via query parameters in the browser’s URL bar.
Hey @dataprofessor thanks for the pointer but I’m afraid I’m looking for something else. In my application the user should be able to close the url to my application, but on returning certain fields of the user need to be restored. The application does not have a login feature so we want to store these fields in the clients browser via local storage or through cookies.
Sir, I had a similar doubt. Can these be used for this? Or is there another way? I have not deployed my app yet, if I deploy it, will I be able to keep track of some ss variables from all the open instances at a central location?
However, when I try running this I get the following error :
streamlit.errors.DuplicateWidgetID: There are multiple identical st.extra_streamlit_components.CookieManager.cookie_manager widgets with key='set'.
To fix this, please make sure that the key argument is unique for
each st.extra_streamlit_components.CookieManager.cookie_manager you create.
I’m not sure which key is not unique in this case.
I’ve just built a synchronous way to access localStorage from Streamlit using websockets (streamlit-ws-localstorage), feel free to try it out.
I struggled for a couple of days with localStorage access (and authentication), and thought it would be easier to build a websocket based synchronous communication itself. The code is simple, just import the module and use it like this:
import streamlit as st
from streamlit_ws_localstorage import injectWebsocketCode, getOrCreateUID
# Main call to the api, returns a communication object
conn = injectWebsocketCode(hostPort='linode.liquidco.in', uid=getOrCreateUID())
st.write('setting into localStorage')
ret = conn.setLocalStorageVal(key='k1', val='v1')
st.write('ret: ' + ret)
st.write('getting from localStorage')
ret = conn.getLocalStorageVal(key='k1')
st.write('ret: ' + ret)
Here is a demo of fetching saved info in the browser:
the “key” means the wiget_id ,not key in dict. when you use the wiget like get,set,delete, you should not use same Key, you can apply like this:key=“0”, “1”,“2”,etc,it works
I tried your code verbatim. But it throws an error:
ssl.SSLCertVerificationError: This app has encountered an error. The original error message is redacted to prevent data leaks. Full error details have been recorded in the logs (if you're on Streamlit Cloud, click on 'Manage app' in the lower right of your app).
Traceback:
File "/home/adminuser/venv/lib/python3.9/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 535, in _run_script
exec(code, module.__dict__)
File "/mount/src/xxxxx/xxxxx.py", line 8, in <module>
ret = conn.setLocalStorageVal(key='k1', val='v1')
File "/home/adminuser/venv/lib/python3.9/site-packages/streamlit_ws_localstorage/__init__.py", line 99, in setLocalStorageVal
result = self.sendCommand(json.dumps({ 'cmd': 'localStorage_set_key', 'key': key, 'val': val }))
File "/home/adminuser/venv/lib/python3.9/site-packages/streamlit_ws_localstorage/__init__.py", line 90, in sendCommand
self.loop.run_until_complete(query(future1))
File "/usr/local/lib/python3.9/asyncio/base_events.py", line 647, in run_until_complete
return future.result()
File "/home/adminuser/venv/lib/python3.9/site-packages/streamlit_ws_localstorage/__init__.py", line 80, in query
async with websockets.connect("wss://" + self.hostPort + "/?uid=" + self.uid, ssl=ssl_context) as ws:
File "/home/adminuser/venv/lib/python3.9/site-packages/websockets/legacy/client.py", line 629, in __aenter__
return await self
File "/home/adminuser/venv/lib/python3.9/site-packages/websockets/legacy/client.py", line 647, in __await_impl_timeout__
return await self.__await_impl__()
File "/home/adminuser/venv/lib/python3.9/site-packages/websockets/legacy/client.py", line 651, in __await_impl__
_transport, _protocol = await self._create_connection()
File "/usr/local/lib/python3.9/asyncio/base_events.py", line 1090, in create_connection
transport, protocol = await self._create_connection_transport(
File "/usr/local/lib/python3.9/asyncio/base_events.py", line 1120, in _create_connection_transport
await waiter
File "/usr/local/lib/python3.9/asyncio/sslproto.py", line 534, in data_received
ssldata, appdata = self._sslpipe.feed_ssldata(data)
File "/usr/local/lib/python3.9/asyncio/sslproto.py", line 188, in feed_ssldata
self._sslobj.do_handshake()
File "/usr/local/lib/python3.9/ssl.py", line 945, in do_handshake
self._sslobj.do_handshake()
from streamlit_javascript import st_javascript
def local_storage_get(key):
return st_javascript(f"localStorage.getItem('{key}');")
def local_storage_set(key, value):
value = json.dumps(value, ensure_ascii=False)
return st_javascript(f"localStorage.setItem('{key}', JSON.stringify('{value}');")
The only problem was, that due to asynchronous nature of streamlit components, st_javascript did not provide return value of js code immidiatelly. Instead, it reloads some number of times before providing the value. And in case of None, you just don’t know if it returns empty value because you js code returns null or because the code execution hasn’t finished yet. So, the following code didn’t work
if "token" not in st.session_state:
st.session_state.token = local_storage_get("token")
It just saved 0 - initial value of st_javascript component.
So, i build my own version of this lib, that allows distinguishing between “not ready yet” and “returns none” states.
Brief explanation:
It returns [] if the code execution is in progress and [] if it finishes. So, you can just st.stop() until result is not empty or for unblocking case, just check it before setting session_state value
if "token" not in st.session_state:
if result := local_storage_get("token"):
st.session_state.token = result[0]
Hey @toolittlecakes I wanted to try this out but both links in your post seem to go to the original repo? is there a link to your fork somewhere? Thanks!
EDIT: Nvm, i found it from guessing your github username for anyone else curious:
Based on the work of @toolittlecakes and the streamlit_js library, I wrote a small gist for an st_local_storage that abstracts reading and writing keys to local storage to a dict / session_state like interface. Let me know how it is if you try it out!
This works pretty well when saving one value to local storage.
I gave it a try even within the same file you had shared. It look like it saves the value for the first key but not the second key.
Any idea why this may occur?
import json
from typing import Any
import uuid
import streamlit as st
from streamlit_js import st_js_blocking
KEY_PREFIX = "st_localstorage_"
# Keep track of a UUID for each key to enable reruns
if "_ls_unique_keys" not in st.session_state:
st.session_state["_ls_unique_keys"] = {}
_ls_keys = st.session_state["_ls_unique_keys"]
class StLocalStorage:
"""An Dict-like wrapper around browser local storage.
Values are stored JSON encoded."""
def __getitem__(self, key: str) -> Any:
if key not in _ls_keys:
_ls_keys[key] = str(uuid.uuid4())
code = f"""
// The UUID changes on save, which causes this to rerun and update
console.debug('{_ls_keys[key]}');
return JSON.parse(localStorage.getItem('{KEY_PREFIX + key}'));
"""
result = st_js_blocking(code)
if result:
return json.loads(result)
return None
def __setitem__(self, key: str, value: Any) -> None:
value = json.dumps(value, ensure_ascii=False)
_ls_keys[key] = str(uuid.uuid4())
code = f"localStorage.setItem('{KEY_PREFIX + key}', JSON.stringify('{value}'));"
return st_js_blocking(code)
def __delitem__(self, key: str) -> None:
_ls_keys[key] = str(uuid.uuid4())
code = f"localStorage.removeItem('{KEY_PREFIX + key}');"
return st_js_blocking(code)
def __contains__(self, key: str) -> bool:
val = self.__getitem__(key)
if val:
return True
return False
st_local_storage = StLocalStorage()
if __name__ == "__main__":
st.title("st_local_storage basic example")
"Any values you save will be available after leaving / refreshing the tab"
key = st.text_input("Key")
value = st.text_input("Value")
test_key = st.text_input("TestKey") # new test key to save after first key
if st.button("Save"):
st_local_storage[key] = value
st_local_storage[test_key] = value + " test"
if key:
st.write(f"Current value of {key} is: {st_local_storage[key]}")
st.write(f"Current value of {test_key} is: {st_local_storage[test_key]}")
if st.button("Delete"):
del st_local_storage[key]
st.rerun()
Hey @dkn-vtl thanks for reporting that, I hadn’t seen it but I do hit the same behavior.
I looked into it further, I think it’s because the “blocking” behavior provided by streamlit-js (which this relies on) is actually calling st.stop() and then triggering a rerun when the JS update finishes.
The button state would be reset on that following rerun, so it doesn’t execute again. I suspect a similar problem would happen (maybe worse) if you used local_storage inside a callback.
I’m not sure if there’s a way to fix this in the current runtime model besides working around it in your app code :\
Thanks for the quick reply, Joshua! I think I was able to get this to work by modifying the __setitem__() method to use st_js() instead of st_js_blocking() and adding a unique key.
Not sure if this is best practice, but it appears to work for my use case. Hope it may help someone else.
Nice, that approach seems to mostly work for me too although it occasionally causes the app to “reset” (all widgets lose their state) especially on save. Not sure if that’s some race condition or something else. I updated the gist. Thanks @dkn-vtl !
Thanks for stopping by! We use cookies to help us understand how you interact with our website.
By clicking “Accept all”, you consent to our use of cookies. For more information, please see our privacy policy.
Cookie settings
Strictly necessary cookies
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
Performance cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
Functional cookies
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
Targeting cookies
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.