Introducing multipage apps! 📄

Quickly and easily add more pages to your Streamlit apps

Posted in Announcement, June 2 2022

So, you built a Streamlit app that became super useful, but then you got overloaded with feature requests. You kept adding more and more features until it felt too cluttered. You tried splitting the content across several pages by using st.radio or st.selectbox to choose which “page” to run.

It worked! But maintaining the code got harder. You were limited by the st.selectbox UI and couldn’t customize page titles with st.set_page_config or navigate between them via URLs. 🤯

Sound familiar?

We wanted to find a simple solution for this, so today, we’re excited to introduce…

Native support for multipage apps!

In this post, we’ll show you how to use this new feature and share tips and tricks on getting the most out of it.

Want to jump right in? Update Streamlit to the newest version and see the streamlit hello demo app and repo for inspiration. Read more on how to get started in our docs.

Using multipage apps

Building a multipage app is easy! Just follow these steps:

1. Create a main script named streamlit_app.py.

2. In the same folder, create a new pages folder.

3. Add new .py files in the pages folder. Your filesystem will look like this:

my_app
├── streamlit_app.py    <-- Your main script
└── pages
    ├── page_2.py       <-- New page 2!
    └── page_3.py       <-- New page 3!

4. Run streamlit run streamlit_app.py as usual.

That’s it!

The streamlit_app.py script will now correspond to your app's main page. You’ll see the other scripts from the pages folder in the sidebar page selector.

Converting an existing app into a multipage app

Let’s say you built a multipage app by using st.selectbox and want to convert it to the multipage app functionality. In your current app, the selectbox picks which page to display, and each “page” is written as a function.

If your folder name is ~/my_app , your code will look like this:

# Contents of ~/my_app/streamlit_app.py
import streamlit as st
def main_page():
    st.markdown("# Main page 🎈")
    st.sidebar.markdown("# Main page 🎈")
def page2():
    st.markdown("# Page 2 ❄️")
    st.sidebar.markdown("# Page 2 ❄️")
def page3():
    st.markdown("# Page 3 🎉")
    st.sidebar.markdown("# Page 3 🎉")
page_names_to_funcs = {
    "Main Page": main_page,
    "Page 2": page2,
    "Page 3": page3,
}
selected_page = st.sidebar.selectbox("Select a page", page_names_to_funcs.keys())
page_names_to_funcs[selected_page]()

To convert your app to a multipage app, follow these steps:

1. Upgrade Streamlit to the newest version: pip install streamlit --upgrade

2. Add a new pages folder inside of  ~/my_app.

3. Create three new files inside of ~/my_app :

  • main_page.py
  • pages/page_2.py
  • pages/page_3.py

4. Move the contents of the main_page, page2, and page3 functions into their corresponding new files:

# Contents of ~/my_app/main_page.py
import streamlit as st
st.markdown("# Main page 🎈")
st.sidebar.markdown("# Main page 🎈")
# Contents of ~/my_app/pages/page_2.py
import streamlit as st
st.markdown("# Page 2 ❄️")
st.sidebar.markdown("# Page 2 ❄️")
# Contents of ~/my_app/pages/page_3.py
import streamlit as st
st.markdown("# Page 3 🎉")
st.sidebar.markdown("# Page 3 🎉")

5. Remove the original streamlit_app.py file.

6. Run streamlit run main_page.py and view your shiny new multipage app!

Tips and tricks

We didn’t specify an order for pages 2 and 3, but they displayed correctly anyway. Why? 🤔  Because they’re ordered alphabetically by default.

But what if you want to display them in a different order?

Just add numerical prefixes in front of the files in the pages/ folder and rename them pages/02_page_2.py and pages/03_page_3.py. The names won’t include these prefixes—they’re used only for sorting.

You can also add emojis! 🥳  Try renaming the script files to:

  • 01_🎈_main_page.py
  • pages/02_❄️_page2.py
  • pages/03_🎉_page3.py

Bonus features: new dataframe UI, horizontal radio buttons, and more!

Want to make your multipage apps look even cooler? 😎

Good news!

We launched more new features in Streamlit’s 1.10 release. Among them are the redesigned st.dataframe (based on glide-data-grid) and the horizontal radio buttons. Check out the release notes for more info.

Wrapping up

And that’s it for the intro to multipage apps! Adding more pages to your apps is now easier than ever. To start using multipage apps today, upgrade to the latest version of Streamlit:

pip install --upgrade streamlit

Have any questions or want to share a cool app you made? Join us on the forum, tag us on Twitter, or let us know in the comments below. 🎈


This is a companion discussion topic for the original entry at https://blog.streamlit.io/introducing-multipage-apps/
10 Likes

Hi! Great news!

This will be my first multipage app. I would like to create a single database connection to leave open that each individual page needs to be able to access. Where should I put this function, for instance, in the example above?

Hi @chrish1!

We’re excited that you’re building your first multipage app!

In order to open a single database connection that’s shared for all your viewers, you will want to use the @st.experimental_singleton decorator on top of your connection function. And you can then call it in your page scripts - the connection will be shared across all pages automagically! More about our cache primitives here

Hope that helps!

3 Likes

Congratulations, this is great news! I was wondering if you might push out to version 2 with this one… there will be a pretty high bar for that!

I get the “Unsupported browser” notification when trying to open the demo app. All other Streamlit apps work just fine for me. I have added an exception in my browser to allow third party cookies, cleared cookies completely, but nothing worked so far…

1 Like

I face the same issue as @EnriR89 . Tried to open the app with Chrome and Safari, all third party options set as the documentation suggests. I also never had such issue before.

1 Like

Seems like a general issue?

This is great news! I noticed there are some problems with imports though. My streamlit code is structured in this way:

k2_oai
├── dashboard
│  ├── components/
│  ├── pages
│  │  ├── 02_metadata_explorer.py
│  │  ├── 03_obstacle_annotator.py
│  │  └── 04_obstacle_detection.py
│  ├── __init__.py
│  ├── app.py # <- main page here
│  └── utils.py
├── io/
├── utils/
├── __init__.py
├── experimental_metrics.py
├── obstacle_detection.py
└── pipelines.py

And now there are plenty of import errors because stuff in app.py and pages in pages apparently fail to recognize imports. For example, my app.py had the following import: from k2_oai.dashboard.components import login. Now this does not work and I had to replace it with from .components import login. In this peculiar instance it’s not a big deal at all, but this actually messes up with all the other modules (e.g. utils) and all other modules in k2_oai such as io.

3 Likes

hey there!
great news!

Up to now I was using the workaround st.sidebar.radio().

Question: I have a bunch of apps that require a password. So, right after st.set_page_config() I’d use st.sidebar.text_input(type='password') and the user’d type the password ONCE and then can view any page as (s)he wishes. Now, with this new multipage-app possibility, it seems to me that I’d have to implement the password in every page, resulting in the user having to type the password multiple times, which is highly inconvenient, or am I missing something?

Thanks!

Clem

EDIT: after testing, it seems that typing the password once in the main_app.py is enough, in the sense that the password persists across pages! :white_check_mark:

3 Likes

Hi, cool update, althought it did mess up with my project (my fault, I should have fixed the version numbers). But anyway, I already had my app beeing multi-page, using simple buttons to set a session state which then defined which of the different pages to load on the next refresh. Now, I do not want the user to just go to any page he likes, but keep the navigation as it is. How can I get rid of the sidebar that shows all the pages? And how should I move to another page by the press of a button?

1 Like

Hi there! Great upgrade! Thanks!

There is any way to put something (App Title, image, etc.) before side bar page selector?

Regards!

4 Likes

Dope! Can’t wait to try it out!~

Hi @BirdBotASA!

We’re so excited you’re going to try out multipage apps!

Let us know how it goes and we can help in any way or answer any questions. Would love to see what you build!

I am not sure but seems like there is incompatibility with dependencies.
I got the following error when using streamlit run command:
File “f:\anaconda3\lib\runpy.py”, line 193, in run_module_as_main
main”, mod_spec)
File “f:\anaconda3\lib\runpy.py”, line 85, in run_code
exec(code, run_globals)
File "f:\Anaconda3\Scripts\streamlit.exe_main
.py", line 4, in
File "f:\anaconda3\lib\site-packages\streamlit_init
.py", line 70, in
from streamlit.delta_generator import DeltaGenerator as _DeltaGenerator
File “f:\anaconda3\lib\site-packages\streamlit\delta_generator.py”, line 168, in
DataFrameSelectorMixin,
File “f:\anaconda3\lib\site-packages\streamlit\delta_generator.py”, line 303, in DeltaGenerator
def getattr(self, name: str) → Callable[…, NoReturn]:
File “f:\anaconda3\lib\typing.py”, line 755, in getitem
return self.getitem_inner(params)
File “f:\anaconda3\lib\typing.py”, line 251, in inner
return func(*args, **kwds)
File “f:\anaconda3\lib\typing.py”, line 774, in getitem_inner
result = _type_check(result, msg)
File “f:\anaconda3\lib\typing.py”, line 135, in _type_check
raise TypeError(f"Plain {arg} is not valid as type argument")
TypeError: Plain typing.NoReturn is not valid as type argument

Is it possible to nest multipage apps? And if so is there a code example for reference? Thanks!

1 Like

Is it possible to create a deeplink? Now I have something like https://share.streamlit.io/rcsmit/streamlit_scripts/main/menu_streamlit.py?choice=2

Hi @EnriR89, @tschmelzer,

That’s correct. There was an issue in yesterday’s Cloud release, but at this point it should be resolved. Let us know if you run into any related issues!

1 Like

Hi @baggiponte, could you file a GitHub issue with an example that minimally reproduces the issue? Thanks!

Hi @Christian_Geissler,

We were thinking about including ways to navigate between multipage app pages as part of the initial release of this feature, but we eventually decided to not do this in order to keep the scope down. We are aware that people would find this useful and may decide to build it in the future. Feel free to file a GitHub issue requesting the feature.

To get rid of the sidebar nav component showing all of an app’s pages, there’s a (hidden) ui.hideSidebarNav config option that you can set to true. Note that we chose to hide this by default as we thought it would be rarely used, but we may change its visibility in case we see it used frequently.

4 Likes

Hi @rraires,

There’s currently no way of doing this, but the feature request has come up a few times, so it’s likely that we’ll add this functionality in the future. Feel free to create a GitHub issue requesting it, as doing so will make it more likely that we add it to our roadmap sooner.

1 Like