Bookmark Button

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 250

from starlette.requests import Request

from shiny import App, ui

def app_ui(request: Request):
    return ui.page_fluid(
        ui.row(
            ui.column(
                12,
                ui.input_radio_buttons(
                    "letter", "Choose a letter", choices=["A", "B", "C"], inline=True
                ),
                ui.input_bookmark_button(),
            ),
            {"class": "vh-100 justify-content-center align-items-center px-5"},
        ).add_class("text-center")
    )

def server(input, output, session):
    pass

app = App(app_ui, server, bookmark_store="url")
from shiny.express import app_opts, session, ui

app_opts(bookmark_store="url")

ui.input_radio_buttons("letter", "Choose a letter", choices=["A", "B", "C"])
ui.input_bookmark_button()  

# Update the browser's URL in place instead of showing a modal.
@session.bookmark.on_bookmarked
async def _(url: str):
    await session.bookmark.update_query_string(url)
from starlette.requests import Request

from shiny import App, ui

# The UI must be a function to ensure that each user restores their own UI values.
def app_ui(request: Request):
    return ui.page_fluid(
        ui.input_radio_buttons("letter", "Choose a letter", choices=["A", "B", "C"]),
        ui.input_bookmark_button(),  
    )

def server(input, output, session):
    # Update the browser's URL in place instead of showing a modal.
    @session.bookmark.on_bookmarked
    async def _(url: str):
        await session.bookmark.update_query_string(url)

app = App(app_ui, server, bookmark_store="url")
No matching items

Relevant Functions

  • ui.input_bookmark_button
    ui.input_bookmark_button(label='Bookmark...', *, icon=MISSING, width=None, disabled=False, id=BOOKMARK_ID, title="Bookmark this application's state and get a URL for sharing.", **kwargs)

No matching items

Details

A bookmark button lets users save the current state of an app’s inputs and share it as a URL. It is an action button with a default link icon and label that, when clicked, calls session.bookmark() for you.

Bookmarking must be enabled on the app itself before the button will do anything:

  1. Enable bookmarking by passing bookmark_store="url" (or "server") to App() in Core, or by calling app_opts(bookmark_store="url") in Express. In Core, the UI must also be a function that accepts a request argument (e.g. def app_ui(request): ...) so that each user’s bookmarked state can be restored independently.

  2. Add ui.input_bookmark_button() to the UI of your app. Where you call this function will determine where the button will appear within the app’s layout.

  3. Optionally customize the label, icon, width, or title (tooltip) parameters of ui.input_bookmark_button().

Clicking the button saves the current input values and, by default, shows a modal with a URL that restores them. With bookmark_store="url" the input values are encoded directly in the URL’s query string; with "server" they are saved on the server and the URL contains only an identifier.

To update the browser’s address bar in place instead of showing a modal, register a session.bookmark.on_bookmarked() callback that calls session.bookmark.update_query_string(), as the Express and Core examples above do:

@session.bookmark.on_bookmarked
async def _(url: str):
    await session.bookmark.update_query_string(url)
NoteBookmarking in the embedded preview

The live preview above runs inside an embedded Shinylive frame, so the bookmark URL it produces points at the frame, not at a page you can share, and reloading this documentation page will not restore the app’s state. The button still works — click it to see the bookmark modal — but to experience saving and restoring state end to end, run the example in its own browser tab (e.g. open the Express or Core code in the Shinylive editor) or run the app locally.

Restoring state

Saving state is only half of bookmarking. When a user opens a bookmark URL, Shiny restores the saved inputs automatically, but anything else your app derives from them — reactive values, computed state, dynamically created UI — you restore yourself with @session.bookmark.on_restore (or on_restored, which runs after the inputs are in place). To keep a value out of the bookmark entirely, add its id to session.bookmark.exclude.

Choosing a store matters here too: bookmark_store="url" encodes every saved input into the query string, so it needs no server state but produces long URLs and exposes the values to anyone with the link. bookmark_store="server" writes the state to disk on the server and puts only a short identifier in the URL, which keeps values private but means bookmarks are tied to that server’s storage.

For the full picture — restore callbacks, excluding inputs, storing state on the server, and bookmarking inside modules — see:

Variations

Multiple bookmark buttons

Shiny only bookmarks automatically for the button’s default id. To use more than one bookmark button (or one inside a module), give each a custom id, add those ids to session.bookmark.exclude so they are not saved as state, and call await session.bookmark() yourself from a reactive.effect.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 300

from starlette.requests import Request

from shiny import App, reactive, ui

# The UI must be a function to ensure that each user restores their own UI values.
def app_ui(request: Request):
    return ui.page_fluid(
        ui.input_radio_buttons("letter", "Choose a letter", choices=["A", "B", "C"]),
        ui.input_bookmark_button("Bookmark (top)", id="bookmark_top"),
        ui.input_bookmark_button("Bookmark (bottom)", id="bookmark_bottom"),
    )

def server(input, output, session):
    # A custom `id` means Shiny no longer bookmarks on click automatically, so
    # exclude the button ids from the saved state and trigger the bookmark yourself.
    session.bookmark.exclude.append("bookmark_top")
    session.bookmark.exclude.append("bookmark_bottom")

    @reactive.effect
    @reactive.event(input.bookmark_top, input.bookmark_bottom)
    async def _():
        await session.bookmark()

    @session.bookmark.on_bookmarked
    async def _(url: str):
        await session.bookmark.update_query_string(url)

app = App(app_ui, server, bookmark_store="url")
from shiny import reactive
from shiny.express import app_opts, input, session, ui

app_opts(bookmark_store="url")

ui.input_radio_buttons("letter", "Choose a letter", choices=["A", "B", "C"])
ui.input_bookmark_button("Bookmark (top)", id="bookmark_top")  
ui.input_bookmark_button("Bookmark (bottom)", id="bookmark_bottom")  

# A custom `id` means Shiny no longer bookmarks on click automatically, so exclude
# the button ids from the saved state and trigger the bookmark yourself.
session.bookmark.exclude.append("bookmark_top")  
session.bookmark.exclude.append("bookmark_bottom")

@reactive.effect
@reactive.event(input.bookmark_top, input.bookmark_bottom)
async def _():
    await session.bookmark()  

@session.bookmark.on_bookmarked
async def _(url: str):
    await session.bookmark.update_query_string(url)
from starlette.requests import Request

from shiny import App, reactive, ui

# The UI must be a function to ensure that each user restores their own UI values.
def app_ui(request: Request):
    return ui.page_fluid(
        ui.input_radio_buttons("letter", "Choose a letter", choices=["A", "B", "C"]),
        ui.input_bookmark_button("Bookmark (top)", id="bookmark_top"),  
        ui.input_bookmark_button("Bookmark (bottom)", id="bookmark_bottom"),  
    )

def server(input, output, session):
    # A custom `id` means Shiny no longer bookmarks on click automatically, so
    # exclude the button ids from the saved state and trigger the bookmark yourself.
    session.bookmark.exclude.append("bookmark_top")  
    session.bookmark.exclude.append("bookmark_bottom")

    @reactive.effect
    @reactive.event(input.bookmark_top, input.bookmark_bottom)
    async def _():
        await session.bookmark()  

    @session.bookmark.on_bookmarked
    async def _(url: str):
        await session.bookmark.update_query_string(url)

app = App(app_ui, server, bookmark_store="url")
No matching items

Variation Showcase

A live kitchen sink of all possible parameters for the Bookmark Button in Shiny Core and Shiny Express.

No matching items

See also: Action Button