How to hide pages from the sidebar (Version 1.45.1).

I want to hide a page so it’s only accessible directly by URL, I’ve tried st-pages but I’ve been unable to make it work. Also is there a way to edit the CSS before it first loads, I know you can inject CSS using st.markdown but that loads after original CSS meaning the user will see the original CSS for a split second.

The only way (as far as I know) to hide pages but still keep them accessible by url is using CSS.

Here’s one way that hides pages using CSS by looking for the href that they point to:

import streamlit as st


def _main_page():
    st.write("Main Page")


def _page_2():
    st.write("Page 2")


def _page_3():
    st.write("Page 3")


def _page_4():
    st.write("Page 4")


def _page_5():
    st.write("Page 5")


main_page = st.Page(_main_page, title="Main Page", icon="🎈")
page_2 = st.Page(_page_2, title="Page 2", icon="❄️", url_path="page_2")
page_3 = st.Page(_page_3, title="Page 3", icon="🎉", url_path="page_3")

other_pages = [
    st.Page(_page_4, title="Page 4", icon="❄️", url_path="page_4"),
    st.Page(_page_5, title="Page 5", icon="🎉", url_path="page_5"),
]

pg = st.navigation({"Main": [main_page, page_2, page_3], "Other": other_pages})

pages_to_hide = st.multiselect(
    "Pages to hide", ["page_2", "page_3", "page_4", "page_5"], default=["page_2"]
)

for page in pages_to_hide:
    # Find links in the sidebar that have a href that ends with page_2, page_3, etc. and hide them
    st.html(f"""
    <style>
        [data-testid="stSidebarNavLink"][href$="{page}"] {{
            display: none;
        }}
    </style>
    """)

pg.run()

Also, if you have css like this that is in streamlit_app.py instead of in a page itself, it runs once and not every time you switch pages. Also, using st.html is also recommended over using st.markdown.

Two other solutions:

  1. You can hide the default navigation with position="hidden" and build a manual navigation widget with st.page_link.
  2. You can use st.context.url to check the path in the URL and insert it into your pages list/dict right before calling st.navigation so that it only appears when the user is trying to go there.
import streamlit as st


def _main_page():
    st.write("Main Page")


def _page_2():
    st.write("Page 2")


def _page_3():
    st.write("Page 3")


def _page_4():
    st.write("Page 4")


def _page_5():
    st.write("Page 5")


main_page = st.Page(_main_page, title="Main Page", icon="🎈")
page_2 = st.Page(_page_2, title="Page 2", icon="❄️", url_path="page_2")

page_3 = st.Page(_page_3, title="Page 3", icon="🎉", url_path="page_3")
page_4 = st.Page(_page_4, title="Page 4", icon="❄️", url_path="page_4")
page_5 = st.Page(_page_5, title="Page 5", icon="🎉", url_path="page_5")

pages = [main_page, page_2]
hidden_pages = {
    "page_3": page_3,
    "page_4": page_4,
    "page_5": page_5
}

path = st.context.url.split("/")[-1]
if path in hidden_pages:
    pages.append(hidden_pages[path])

pg = st.navigation(pages)

pg.run()

Also, here’s a feature request you can upvote: Add a page without showing it in the navigation menu · Issue #10738 · streamlit/streamlit · GitHub

1 Like

Thanks, I ended up going with an approach like this. I hid all pages with showSidebarNavigation = false then in each page I added
st.sidebar.title("Navigation") st.sidebar.page_link('View.py', label='**View** Posts') st.sidebar.page_link('pages/2_Upload.py', label='**Upload** New Post')
with this approach, my secret page doesn’t appear in the sidebar but it’s still accessible by URL.

It isn’t the best since it still takes a split second for the sidebar to appear but it works and is simple.