One repo, many apps, one launcher. A suite bundles several Shiny apps, R or Python, into a single Electron binary. Users open the launcher, pick an app, and switch whenever they like through the Apps menu.
The launcher hands off to one backend at a time; picking a different app returns through the launcher.
Layout
A suite is a directory with a _shinyelectron.yml at the root and an apps/ folder. Each subfolder holds a normal Shiny app (app.R for R, app.py for Python).
R suite
The bundled demo at inst/demos/demo-r-app-suite/, read live:
A top-level apps array switches shinyelectron into suite mode. Each entry needs three fields (id, name, path) and accepts four optional ones (description, type, runtime_strategy, icon).
R suite config
Read live from inst/demos/demo-r-app-suite/_shinyelectron.yml:
app:name:"R Shiny Demo Suite"version:"1.0.0"build:type:"r-shiny"window:width:1100height:750apps:-id: dashboardname:"Dashboard"description:"Real-time analytics with live-updating stats and charts"path:"./apps/dashboard"-id: data-explorername:"Data Explorer"description:"Browse and analyze built-in R datasets"path:"./apps/data-explorer"-id: aboutname:"About"description:"System information and runtime details"path:"./apps/about"
Python suite config
Only build.type changes. Read live from inst/demos/demo-py-app-suite/_shinyelectron.yml:
app:name:"Python Shiny Demo Suite"version:"1.0.0"build:type:"py-shiny"window:width:1100height:750apps:-id: dashboardname:"Dashboard"description:"Analytics overview with live-updating stats and charts"path:"./apps/dashboard"-id: data-explorername:"Data Explorer"description:"Browse and analyze built-in datasets"path:"./apps/data-explorer"-id: aboutname:"About"description:"System information and runtime details"path:"./apps/about"
Fields in an apps entry
Field
Required
Description
id
Yes
Unique identifier (used in file paths and the apps manifest)
name
Yes
Display name shown on the launcher card
path
Yes
Relative path from the suite root to the app directory
description
No
Short text shown below the name on the launcher card
type
No
Override the default build.type for this specific app
runtime_strategy
No
Override the default build.runtime_strategy for this specific app
icon
No
Path to an icon image displayed on the launcher card
Every app inherits build.type and build.runtime_strategy unless it overrides them. Suites can mix languages (some r-shiny, some py-shiny) and mix strategies (one app on shinylive, another on system). Most suites stay uniform, but the overrides are there when you need them.
Mixed-strategy suites
A single suite can bundle apps with different runtime strategies. Set a suite-wide default in build.runtime_strategy, then override it per app where it makes sense. This is handy when one app runs fine in the browser (shinylive) while another needs a real process for packages that do not compile to WebAssembly.
# _shinyelectron.ymlapp:name:"Mixed Suite"version:"1.0.0"build:type:"r-shiny"runtime_strategy:"shinylive" # suite defaultapps:-id:"viewer"name:"Data Viewer"description:"Lightweight tables and charts"path:"./apps/viewer" # inherits shinylive from build.runtime_strategy-id:"modeler"name:"Model Fitter"description:"Needs packages that are not in WebR"path:"./apps/modeler"runtime_strategy:"system" # overrides the suite default
Per-app runtime_strategy: overrides the suite default for just that app. The other apps keep inheriting the default. There is one rule on mixing: within a single language, all native apps (system, bundled, auto-download) must use the same strategy. shinylive and container apps use their own backends and combine freely with anything. So a suite can hold a shinylive R app, a bundled R app, and a system Python app, but it cannot hold both a bundled R app and an auto-download R app, or a bundled R app and a system R app, because that is two native strategies for the same language. Export aborts with a message naming the conflicting apps if you cross that line. The usual platform caveats still apply (for example, bundled R needs a portable R build for the target OS).
How dependencies are handled
R apps can be scanned for dependencies; Python apps cannot. R lets you skip declaration entirely and rely on the source. Python always asks you to write a list, just in one of two files. Both languages can also use _shinyelectron.yml for explicit declarations, and lists from any source merge into one install plan.
R apps
Automatic. shinyelectron runs renv::dependencies() over each app directory. It picks up library(), require(), pkg::func(), and loadNamespace() references in the source. Base and recommended R packages (stats, utils, MASS, lattice, and friends) are excluded automatically.
Explicit. Add a dependencies: block to the suite-level _shinyelectron.yml:
Source-scan and declared packages merge. Set dependencies.auto_detect: false to skip the renv scan and install only the declared list.
Python apps
shinyelectron does not parse import statements: the module-name to package-name mapping is unreliable (import cv2 is opencv-python, import sklearn is scikit-learn). You declare what gets installed, in one of two places.
requirements.txt or pyproject.toml. A standard Python manifest at the suite root. All apps in the suite share one virtual environment. The demo’s requirements.txt:
shiny
numpy
matplotlib
That file covers every app in the suite.
_shinyelectron.yml. Add a python section to the same dependencies: block:
Manifest-file and YAML packages merge. Set dependencies.auto_detect: false to skip reading the manifest and install only the YAML list.
Exporting a suite
export() reads the config, sees an apps array with two or more entries, and switches to suite mode. No extra flags.
library(shinyelectron)# Export the bundled R demo suiter_suite <-example_app("suite")export(appdir = r_suite, destdir ="output-r-suite")# Export a Python suite from your own projectexport(appdir ="path/to/my-py-suite", destdir ="output-py-suite")
You do not pass app_type or runtime_strategy. The YAML owns those choices, through build.type and build.runtime_strategy.
All five runtime strategies (shinylive, system, bundled, auto-download, container) work with suites, exactly as they do with single apps. Per-app overrides let one suite mix them, subject to the one-native-strategy-per-language rule described above.
Launcher and app switching
The Electron window opens to a launcher page. Each app gets a card with its name, description, type badge, and icon (your image, or a generated initial).
Click a card: the backend for that app starts, and the window navigates to it. The Apps menu offers “Back to Launcher,” which stops the backend and returns to the card grid.
One runtime, one active app
A suite runs a single R or Python process. Switching apps follows four steps:
Stop the current backend.
Show the launcher.
User picks the next app.
Start a fresh backend for that app.
Only one app runs at a time. No parallel runtimes, no duplicated processes, no extra installs.
Building your own suite
From scratch:
Create a directory with an apps/ subfolder.
Put each Shiny app in its own subdirectory.
Write _shinyelectron.yml with an apps array listing every app.
For Python suites, add requirements.txt at the root.
Run export() on the suite directory.
# Minimal example: create suite structure, then exportexport(appdir ="my-suite", destdir ="my-suite-output")
The config file is the single source of truth. Two or more entries in apps, each pointing at a valid app, and shinyelectron builds the launcher, the menu, and the switching logic for you.