Hi everyone! I’m facing a need. I have my Streamlit app working fine but I noticed a behaviour I would like to tackle.
My Streamlit app is a multipage app and I’m using an entry point to dynamically add pages based on some condition.
The setup:
when the condition is false I add pages A, B, C (using streamlit.Page class)
when the condition is true I add pages A, B, D (also using streamlit.Page class)
The problem. If I directly access to the url http://<>/A or http://<>/B everything is fine because A and B pages are added in both situations. If I directly access to http://<>/C and for some reason the condition is true then I get the popup saying You have requested page, but no corresponding file was found in the app's pages/ directory
which makes sense of course. But also would be great to be able to customise this behaviour.
Is there a way to customise this behaviour? I was looking for something like add the requested page in the navigation and make the page redirect but it looks like is impossible because I cannot find the requested page in the streamlit.session_state.
I think it could work also to be able to have a default page if the requested one is not in the navigation instead of having the popup.
That’s bad. If a user already went on a page /A so it knows it exists, it would be good to personalise the message at least (for example, in case the navigation menu changes because user is not logged in). Even worse if the user bookmarked the page.
The user would be confused if it logs in and then found out the page is actually there.
It would be very easy to add a custom logic if it was possible to get the user requested url (I don’t mean through javascript, because you need to wait the page is rendered which is not the case if you are doing this in the entry point acting like a router). Is it so complex to expose the requested url in the session or in the client retrieved by st.runtime.get_instance().get_client(get_script_run_ctx().session_id)?
In that case, I would put all the pages in the navigation definition and create a custom navigation menu based on whether the user is logged in. If the user attempts to enter an unavailable page, we can show an error message and then redirected them to another page. Here is small example:
Code:
from itertools import compress
from time import sleep
import streamlit as st
### Login helpers
if "logged_in" not in st.session_state:
st.session_state.logged_in = False
def login():
st.session_state.logged_in = True
def logout():
st.session_state.logged_in = False
### Page definitions
def page_main():
st.title("Main page")
def page_a():
st.title("Page A")
def page_b():
st.title("Page B")
def page_c():
if not st.session_state.logged_in:
st.error("You are not logged in. Redirecting to main page...")
sleep(2)
st.switch_page(pages[0])
return
st.title("Page C")
def page_d():
if st.session_state.logged_in:
st.error("You are logged in. Redirecting to main page...")
sleep(2)
st.switch_page(pages[0])
return
st.title("Page D")
pages = [
st.Page(page_main, title="Main"),
st.Page(page_a, title="A"),
st.Page(page_b, title="B"),
st.Page(page_c, title="C"),
st.Page(page_d, title="D"),
]
nav = st.navigation(pages=pages, position="hidden")
### App layout
with st.sidebar:
# Dummy login/logout system
st.toggle("Logged in?", on_change=logout if st.session_state.logged_in else login)
# Custom navigation depending on login:
st.header("Navigation")
if st.session_state.logged_in:
mask = [True, True, True, True, False] # Hide page D
else:
mask = [True, True, True, False, True] # Hide page C
for page in compress(pages, mask):
st.page_link(page)
nav.run()
In the example, page C is hidden when the user is not logged in, and page D is hidden when they are logged in. You can select which pages to show in the navigation by tweaking the mask in the code.
Oh yeah, I didn’t see that detail. Ok, I absolutely need to know how this works! ahah
How do you modify the pages list after this is set? I can see you call once st.navigation passing all the pages.
I understand this
for page in compress(pages, mask):
st.page_link(page)
is the piece of code which does the trick but how does it work? I don’t see you reassigning the pages to st.navigation (which I think you cannot even do because you can call it once during the page run). Could you explain me how this works please? I understand the for loop loops over only the pages you want to show thanks to the filter applied by the compress function, but I don’t get how the pages are reassigned to the st.navigation
We could split what st.navigation does into two separate tasks:
It declares all the pages that will be available in the app.
[Optional] It generates a navigation menu in the sidebar.
Step 1 is mandatory, and we pass all the possible pages the app can have. This way Streamlit knows that all of those exist and point to valid URLs. Step 2 runs by default, however, in this example, we opt out of Step 2 and hide the default navigation that would list all the pages using the position argument:
nav = st.navigation(pages=pages, position="hidden")
At this point, we have an app without a pages navigation menu, however, the URLs for all those pages will be valid! If we do want a page menu, we have to explicitly build it. That is what the for loop does in the example.
I understand the “show” part is done by st.page_link(page), right? How does it work? Is st.page_link basically changing internally some attributes on the page object which is the same object passed to st.navigation? What I am missing is how the show part reflect on st.navigation
I think I got it. The pages passed to st.navigation are just to tell streamlit the allowed url, as you pointed out. Then, what you do is to show the menu not through the st.navigation anymore but adding the links using st.page_link using it inside with st.sidebar
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.