Changes in version 1.13.0.9000 New features - session$destroy() and session$onDestroy() are now available on module session proxies to clean up "dangling reactivity" when dynamic module UI is removed. Calling session$destroy() invokes all registered onDestroy() callbacks for that scope and its descendants, tearing down reactive values, expressions, and observers. A parent can also destroy a child module scope by id with session$destroy(id), so it can tear down a module using the same id it used to insert the UI (#4372). - New startApp() runs a Shiny app in non-blocking mode, returning a ShinyAppHandle object with stop(), status(), url(), and result() methods. When a new app is started, any previously running non-blocking app is automatically stopped. (#4349) - downloadButton() and downloadLink() gain a new enabled parameter. The default value, "auto", automatically enables the button/link when the download is ready. To opt-into manual state management (e.g., shinyjs::enable()), set enabled to FALSE (or TRUE). (#4119) Improvements - Output resize/visibility detection now uses native browser observers (ResizeObserver, IntersectionObserver) instead of relying on jQuery shown/hidden events and window.resize. This makes Shiny's client-side output-info pipeline (image/plot sizing, hidden-state tracking, theme reporting) work automatically in any layout — including CSS-only show/hide, third-party tab components, and non-Bootstrap frameworks — without requiring custom event hooks. This also introduces a shiny:themechange event for code that needs to trigger theme clientdata refreshes after changing surrounding visual theme context. (#3682) - conditionalPanel() no longer briefly flashes its contents on app start when the condition is initially FALSE. (#3505) - Removed InputBinding.dispose() from the JavaScript InputBinding class. This method was never called by Shiny's runtime, so any overrides were dead code. Use unsubscribe() for cleanup logic instead. (#4375) - Updated default HTTP headers for better security. Shiny now sends X-Content-Type-Options: nosniff instead of the legacy X-UA-Compatible header. This removes outdated Internet Explorer–specific behavior and adds a modern safeguard that prevents browsers from misinterpreting file types. (#4385) Bug fixes - Loading shiny no longer creates .Random.seed in the global environment as a side effect. (#4382) - need() now gives a clearer error when called without either a message or label argument, instead of the cryptic "argument "label" is missing, with no default". (thanks @chasemc and @sundrelingam, #2509) - Clarified varSelectInput() documentation to explain that the input returns a symbol for use with tidy evaluation, and fixed a grammatical typo. (#2334) Changes in version 1.13.0 (2026-02-20) New features - Shiny now supports interactive breakpoints when used with Ark (e.g. in Positron). (#4352) Bug fixes and minor improvements - Stack traces from render functions (e.g., renderPlot(), renderDataTable()) now hide internal Shiny rendering pipeline frames, making error messages cleaner and more focused on user code. (#4358) - Fixed an issue with actionLink() that extended the link underline to whitespace around the text. (#4348) Changes in version 1.12.1 (2025-12-09) New features - withOtelCollect() and localOtelCollect() temporarily control OpenTelemetry collection levels during reactive expression creation, allowing you to enable or disable telemetry collection for specific modules or sections of code. (#4333) Bug fixes and minor improvements - OpenTelemetry code attributes now include both the preferred attribute names (code.file.path, code.line.number, code.column.number) and the deprecated names (code.filepath, code.lineno, code.column) to follow OpenTelemetry semantic conventions while maintaining backward compatibility. The deprecated names will be removed in a future release after Logfire supports the preferred names. (#4325) - ExtendedTask now captures the OpenTelemetry recording state at initialization time rather than at invocation time, ensuring consistent span recording behavior regardless of runtime configuration changes. (#4334) - Timer tests are now skipped on CRAN. (#4327) Changes in version 1.12.0 (2025-12-03) OpenTelemetry support - Shiny now supports OpenTelemetry via {otel}. By default, if otel::is_tracing_enabled() returns TRUE, then {shiny} records all OpenTelemetry spans. See {otelsdk}'s Collecting Telemetry Data for more details on configuring OpenTelemetry. (#4269, #4300) - Supported values for options(shiny.otel.collect) (or Sys.getenv("SHINY_OTEL_COLLECT")): - "none" - No Shiny OpenTelemetry tracing. - "session" - Adds session start/end spans. - "reactive_update" - Spans for any synchronous/asynchronous reactive update. (Includes "session" features). - "reactivity" - Spans for all reactive expressions. (Includes "reactive_update" features). - "all" [default] - All Shiny OpenTelemetry tracing. Currently equivalent to "reactivity". - OpenTelemetry spans are recorded for: - session_start: Wraps the calling of the server() function. Also contains HTTP request within the attributes. - session_end: Wraps the calling of the onSessionEnded() handlers. - reactive_update: Signals the start of when Shiny knows something is to be calculated. This span ends when there are no more reactive updates (promises or synchronous) to be calculated. - reactive, observe, output: Captures the calculation (including any async promise chains) of a reactive expression (reactive()), an observer (observe()), or an output render function (render*()). - reactive debounce, reactive throttle: Captures the calculation (including any async promise chains) of a debounce()d or throttle()d reactive expression. - reactiveFileReader, reactivePoll: Captures the calculation (including any async promise chains) of a reactiveFileReader() or reactivePoll(). - ExtendedTask: Captures the calculation (including any async promise chains) of an ExtendedTask. - OpenTelemetry Logs are recorded for: - Set reactiveVal - When a reactiveVal() is set - Set reactiveValues $ - When a reactiveValues() element is set - Fatal or unhandled errors - When an error occurs that causes the session to end, or when an unhandled error occurs in a reactive context. Contains the error within the attributes. To unsanitize the error message being collected, set options(shiny.otel.sanitize.errors = FALSE). - Set ExtendedTask - When an ExtendedTask's respective reactive value (e.g., status, value, and error) is set. - add to queue - When an ExtendedTask is added to the task queue. - All OpenTelemetry logs and spans will contain a session.id attribute containing the active session ID. New features - updateActionButton() and updateActionLink() now accept values other than shiny::icon() for the icon argument (e.g., fontawesome::fa(), bsicons::bs_icon(), etc). (#4249) Bug fixes and minor improvements - Showcase mode now uses server-side markdown rendering with the {commonmark} package, providing support for GitHub Flavored Markdown features (tables, strikethrough, autolinks, task lists). While most existing README.md files should continue to work as expected, some minor rendering differences may occur due to the change in markdown processor. (#4202, #4201) - debounce(), reactiveFileReader(), reactivePoll(), reactiveValues(), and throttle() now attempt to retrieve the assigned name for the default label if the srcref is available. If a value cannot easily be produced, a default label is used instead. (#4269, #4300) - The default label for items described below will now attempt to retrieve the assigned name if the srcref is available. If a value can not easily be produced, a default label will be used instead. This should improve the OpenTelemetry span labels and the reactlog experience. (#4269, #4300) - reactiveValues(), reactivePoll(), reactiveFileReader(), debounce(), throttle(), observe() - Combinations of bindEvent() and reactive() / observe() - Combination of bindCache() and reactive() - updateActionButton() and updateActionLink() now correctly render HTML content passed to the label argument. (#4249) - updateSelectizeInput() no longer creates multiple remove buttons when options = list(plugins="remove_button") is used. (#4275) - dateRangeInput()/updateDateRangeInput() now correctly considers the time zones of date-time objects (POSIXct) passed to the start, end, min and max arguments. (thanks @ismirsehregal, #4318) Breaking changes - The return value of actionButton() and actionLink() now wraps label and icon in an additional HTML container element. This allows updateActionButton() and updateActionLink() to distinguish between the label and icon when making updates, and allows spacing between label and icon to be more easily customized via CSS. Changes in version 1.11.1 (2025-07-03) This is a patch release primarily for addressing the bugs introduced in v1.11.0. Bug fixes - Fixed an issue where InputBinding implementations that don't pass a value to their subscribe callback were no longer notifying Shiny of input changes. (#4243) - updateActionButton() and updateActionLink() once again handle label updates correctly. (#4245) Changes in version 1.11.0 (2025-06-24) Improvements - When auto-reload is enabled, Shiny now reloads the entire app when support files, like Shiny modules, additional script files, or web assets, change. To enable auto-reload, call devmode(TRUE) to enable Shiny's developer mode, or set options(shiny.autoreload = TRUE) to specifically enable auto-reload. You can choose which files are watched for changes with the shiny.autoreload.pattern option. (#4184) - When busy indicators are enabled (i.e., useBusyIndicators()), Shiny now: - Shows a spinner on recalculating htmlwidgets that have previously rendered an error (including req() and validate()). (#4172) - Shows a spinner on tableOutput(). (#4172) - Places a minimum height on recalculating outputs so that the spinner is always visible. (#4172) - Shiny now uses {cli} instead of {crayon} for rich log messages. (thanks @olivroy, #4170) - renderPlot() was updated to accommodate changes in ggplot2 v4.0.0. (#4226) - When adding the new tab via insertTab() or bslib::nav_insert(), the underlying JavaScript no longer renders content twice. (#4179) New features - textInput(), textAreaInput(), numericInput() and passwordInput() all gain an updateOn option. updateOn = "change" is the default and previous behavior, where the input value updates immediately whenever the value changes. With updateOn = "blur", the input value will update only when the text input loses focus or when the user presses Enter (or Cmd/Ctrl + Enter for textAreaInput()). (#4183) - textAreaInput() gains a autoresize option, which automatically resizes the text area to fit its content. (#4210) - The family of update*Input() functions can now render HTML content passed to the label argument (e.g., updateInputText(label = tags$b("New label"))). (#3996) - ExtendedTask now catches synchronous values and errors and returns them via $result(). Previously, the extended task function was required to always return a promise. This change makes it easier to use ExtendedTask with a function that may return early or do some synchronous work before returning a promise. (#4225) - The callback argument of Shiny.js' InputBinding.subscribe() method gains support for a value of "event". This makes it possible for an input binding to use event priority when updating the value (i.e., send immediately and always resend, even if the value hasn't changed). (#4211) Changes - Shiny no longer suspends input changes when any or