---
title: "Configuration Guide"
vignette: >
  %\VignetteIndexEntry{Configuration Guide}
  %\VignetteEngine{quarto::html}
  %\VignetteEncoding{UTF-8}
knitr:
  opts_chunk:
    collapse: true
    comment: "#>"
---

```{r}
#| include: false
library(shinyelectron)
```

A `_shinyelectron.yml` file keeps your build settings next to your app. `export()` reads it automatically, so the function call stays short and the details live in version control where the rest of the project already does.

<figure><picture><source srcset="../man/figures/config-overview-dark.svg" media="(prefers-color-scheme: dark)" /><img src="../man/figures/config-overview.svg" alt="Overview of the _shinyelectron.yml configuration file. A title bar at the top reads _shinyelectron.yml. Below it, eight labeled cards arranged in two rows describe the main sections: app (display name and version), build (language, runtime strategy, target platforms and architectures), window (Electron window size), server (local Shiny port), icon (app icon, one PNG fanned out per OS), nodejs (build toolchain version), dependencies (r and python version pins for the bundled, auto-download, and container strategies; not used by shinylive or system), and container (Docker or Podman engine and image for the container strategy). A wider card underneath describes apps, the optional multi-app suite, where each entry may override the suite-level type or runtime_strategy so shinylive apps can coexist with bundled or system apps in the same build." style="width: 100%;" /></picture><figcaption>Every `_shinyelectron.yml` section at a glance: nine knobs that shape a different part of the build, from app metadata and target platforms down to the runtime and the optional multi-app launcher.</figcaption></figure>

## Why use one

Compare a fully specified call:

```{r}
#| eval: false
export(
  appdir = "my-app",
  destdir = "output",
  app_name = "My Application",
  app_type = "r-shiny",
  runtime_strategy = "shinylive",
  platform = c("mac", "win"),
  arch = c("x64", "arm64"),
  icon = "icons/icon.png"
)
```

With the same build driven by config:

```{r}
#| eval: false
export(appdir = "my-app", destdir = "output")
```

A config file pulls double duty. It is a set of arguments to `export()`, but it is also a short document telling anyone who clones the repo how the app is meant to be built.

## Getting a config file in place

The config file must be named `_shinyelectron.yml` and live at the root of your app directory:

```
my-shiny-app/
├── _shinyelectron.yml    # Configuration file
├── app.R                 # Your Shiny app
└── ...
```

Generate a starter template with `init_config()`:

```{r}
#| eval: false
init_config("path/to/my-app")
```

```
✔ Created configuration file: path/to/my-app/_shinyelectron.yml
ℹ Edit this file to customize your Electron app settings
```

The generated file carries the documented defaults you can edit in place. For most projects, the only setting you need to write yourself is the app name:

```yaml
app:
  name: "My App"
```

Everything else is filled in by defaults described below.

## Complete reference

Every available option, annotated. Each section is covered in more detail further down.

```yaml
# shinyelectron configuration file
# Documentation: https://r-pkg.thecoatlessprofessor.com/shinyelectron/

app:
  name: "My Application"     # Application display name
  version: "1.0.0"           # Application version (used in package metadata)

build:
  type: "r-shiny"            # Application language (autodetected if omitted)
  runtime_strategy: "shinylive"  # shinylive, auto-download, bundled, system, container
  platforms:                 # Target platforms
    - mac
    - win
    - linux
  architectures:             # Target architectures
    - x64
    - arm64

dependencies:               # Runtime version pins and system packages
  r:
    version: null           # null = maintained pin; "latest" = live query; "4.6.1" = exact
  python:
    version: null           # null = maintained pin; "latest" = live query; "3.12.0" = exact
  electron:
    version: null           # null = maintained pin; "latest" = live query from npm; "41.0.0" = exact
  # system_packages: extra apt packages baked into the container image
  # system_packages:
  #   - libgdal-dev
  #   - libpq-dev

window:
  width: 1200                # Default window width (pixels)
  height: 800                # Default window height (pixels)

server:
  port: 3838                 # Development server port

icon: "branding/icon.png"    # Single high-res source; electron-builder fans out to each platform

# Optional per-platform icon overrides (rarely needed):
# icons:
#   mac: "branding/icon.icns"
#   win: "branding/icon.ico"
#   linux: "branding/icon.png"

nodejs:
  version: null              # Node.js version (null = latest LTS)
  # auto_install: false      # Planned: auto-install Node.js if not found (not yet implemented)

container:                   # Used when runtime_strategy is "container"
  engine: "docker"           # "docker" or "podman"
  image: null                # Container image (null = use bundled Dockerfile)
  tag: null                  # null = resolved runtime version; "latest" for BYO image
  pull_on_start: true        # Pull latest image on app start
  volumes:                   # Host-to-container volume map (not a list)
    "/data": "/app/data"
  env:                       # Environment variable map (not a list)
    SHINY_LOG_LEVEL: "debug"

# Multi-app suite (2+ apps packaged in one Electron shell)
# apps:
#   - id: "dashboard"
#     name: "Dashboard"
#     path: "./apps/dashboard"
#     type: "r-shiny"                   # Optional per-app override (default: build.type)
#     runtime_strategy: "shinylive"     # Optional per-app override (default: build.runtime_strategy)
#     description: "Main dashboard"
#     icon: "icons/dash.png"
#   - id: "admin"
#     name: "Admin Panel"
#     path: "./apps/admin"
```

::: {.callout-note}
## How values get resolved

When `export()` runs, it merges three sources in priority order:

1. **Function arguments** passed directly to `export()`
2. **Config file** values from `_shinyelectron.yml`
3. **Built-in defaults**

A function argument always wins. So `export(appdir = "app", destdir = "out", app_name = "Override")` uses `"Override"` even if the config file says something else, and everything else falls through to the config or the defaults.
:::

## Section reference

### `app`

Application metadata.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `name` | string | Directory name | Display name shown in window title and system |
| `version` | string | `"1.0.0"` | Version number for the built application |

### `build`

The build process and its targets.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `type` | string | autodetect | Application language (see below). Autodetected from files in `appdir` if omitted. |
| `runtime_strategy` | string | `"shinylive"` | How the app's runtime reaches the user (see below) |
| `platforms` | list | Current platform | Target operating systems: `mac`, `win`, `linux` |
| `architectures` | list | Current arch | Target CPU architectures: `x64`, `arm64` |

**Valid `type` values:** `r-shiny` (an `app.R` or `ui.R`/`server.R` Shiny app) or `py-shiny` (an `app.py` Shiny for Python app).

**Valid `runtime_strategy` values:**

| Strategy | Description |
|----------|-------------|
| `shinylive` | Compiles to WebAssembly and runs in-browser via WebR or Pyodide (default) |
| `auto-download` | Downloads R or Python on first launch, caches for reuse |
| `bundled` | Embeds a portable R or Python runtime inside the app at build time |
| `system` | Uses R or Python already installed on the end user's machine |
| `container` | Runs the app inside a Docker or Podman container |

All five strategies work with both `r-shiny` and `py-shiny`. See the [Runtime Strategies](runtime-strategies.html) vignette for the full discussion.

::: {.callout-note}
## Cross-platform caveats

macOS apps build only on macOS. The `bundled` strategy ships a platform-specific runtime binary, so exporting for Windows from macOS is not supported for bundled builds. `auto-download`, `system`, and `container` sidestep that constraint. See the Runtime Strategies vignette for the full story.
:::

### `icon` and `icons`

A single top-level `icon` entry is the simplest and recommended shape. Point it at a high-resolution PNG (1024×1024 or larger) and electron-builder will generate the per-platform variants it needs (`.icns` for macOS, `.ico` for Windows, and the PNG itself for Linux).

```yaml
icon: "branding/icon.png"
```

If you genuinely need different artwork on different platforms (for example, a monochrome Windows icon alongside a full-color macOS icon), use the per-platform `icons` map as an override:

| Key | Type | Format | Description |
|-----|------|--------|-------------|
| `mac` | string | `.icns` | macOS icon (typically 512×512 or larger) |
| `win` | string | `.ico` | Windows icon (multi-resolution recommended) |
| `linux` | string | `.png` | Linux icon (512×512 recommended) |

Paths are relative to the app directory. When no icon is set at all, the build uses the default Electron icon.

::: {.callout-tip}
## Creating icons

Start from one 1024×1024 PNG. That single file is enough for the default `icon:` path; electron-builder handles the conversions. If you need hand-tuned `.icns` or `.ico` artwork, convert with
[iconutil](https://developer.apple.com/library/archive/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html)
on macOS, [ImageMagick](https://imagemagick.org/), or any online converter.
:::

### `window`

Electron window dimensions.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `width` | integer | `1200` | Window width in pixels (minimum 100) |
| `height` | integer | `800` | Window height in pixels (minimum 100) |

### `server`

Development server settings.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `port` | integer | `3838` | Port for the local Shiny server (1 to 65535) |

### `nodejs`

Node.js installation behavior.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `version` | string | `null` | Node.js version to install (`null` = latest LTS) |

::: {.callout-note}
## `auto_install` is planned, not yet active

An `auto_install` key is reserved for a future release. Today, a missing Node.js aborts the build with guidance to run `install_nodejs()` or install Node.js manually. Do not rely on `auto_install: true` having any effect.
:::

### `dependencies`

Controls runtime version pins and (for the container strategy) system-level apt packages.

#### Version keys

Each runtime has a `version` key under its own sub-section. Three values are accepted:

| Value | Effect |
|-------|--------|
| `null` or omitted | Uses the maintained latest pin shipped with shinyelectron |
| `"latest"` | Queries the upstream source at build time for the newest available version |
| A version string such as `"4.6.1"` | Uses that exact version for the build |

The maintained pins are updated with each shinyelectron release. Using `null` is recommended for most projects: you get a version known to work, and you can pin explicitly when you need a specific release.

A custom version must correspond to a build the upstream source actually publishes. The maintained pin resolves offline; other versions are resolved against the upstream releases at build time (portable-r for R, python-build-standalone for Python), so a very old patch version may no longer be available. If a pin cannot be resolved, the build aborts with a message pointing you back to the maintained default.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `r.version` | string | `null` | R version for `bundled`, `auto-download`, and `container` strategies |
| `python.version` | string | `null` | Python version for `bundled`, `auto-download`, and `container` strategies |
| `electron.version` | string | `null` | Electron (Chromium/Node) runtime version bundled in the desktop app; written as `^<version>` in `package.json` |

The support toolchain (`electron-builder`, `electron-updater`, `electron-log`) is pinned internally by shinyelectron and is not user-configurable via `_shinyelectron.yml`. Node.js is the build toolchain taken from the system PATH and is not bundled in the app; it is managed via the `nodejs` section below.

`r.version` and `python.version` apply only to the `bundled`, `auto-download`, and `container` strategies. They have no effect under `shinylive`, where the R or Python runtime is the WebR or Pyodide version shipped by the `shinylive` package (set upstream, not configurable here), or under `system`, where the app runs against the end user's installed R or Python.

#### Detection and extra packages

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `auto_detect` | boolean | `true` | Automatically scan the app source to detect required R or Python packages (applies to `bundled`, `auto-download`, and `container` strategies) |
| `extra_packages` | list of strings | `[]` | Additional packages to include beyond those auto-detected |

#### R-specific dependency options

These keys sit under `dependencies.r` and apply to the `bundled`, `auto-download`, and `system` strategies.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `r.packages` | list of strings | `[]` | R packages to install alongside the app |
| `r.repos` | list of strings | `["https://cloud.r-project.org"]` | CRAN-compatible repository URLs to use when installing R packages |
| `r.lib_path` | string | `null` | Custom library path where R packages are installed; `null` uses the default R library |

#### Python-specific dependency options

These keys sit under `dependencies.python` and apply to the `bundled`, `auto-download`, and `system` strategies.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `python.packages` | list of strings | `[]` | Python packages to install alongside the app |
| `python.index_urls` | list of strings | `["https://pypi.org/simple"]` | Package index URLs used when installing Python packages |

#### `system_packages`

A list of apt package names baked into the container image at build time. Used only when `runtime_strategy` is `"container"`.

For R apps, shinyelectron already queries the Posit Package Manager system-requirements service to auto-detect system libraries required by your R packages (for example, `libgdal-dev` for `sf`) and bakes them in automatically. `system_packages` is the escape hatch for anything not covered by that auto-detection.

For Python apps, auto-detection is not performed. `system_packages` is the only way to add system-level libraries to the image.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `system_packages` | list of strings | `null` | Extra apt packages baked into the container image (container strategy only) |

**Example:**

```yaml
dependencies:
  r:
    version: "4.6.1"        # pin to an exact R release
  python:
    version: null           # use maintained pin
  electron:
    version: "latest"       # resolve newest Electron from npm at build time
  system_packages:
    - libgdal-dev
    - libpq-dev
```

`system_packages` has no effect for `shinylive`, `bundled`, `auto-download`, or `system` strategies.

### `container`

Used when `runtime_strategy` is `"container"`. Ignored otherwise.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `engine` | string | `"docker"` | Container engine: `"docker"` or `"podman"` |
| `image` | string | `null` | Container image name (`null` = auto-select based on `app.type`) |
| `tag` | string | `null` | Image tag. For the bundled Dockerfile (`image: null`), defaults to the resolved runtime version (e.g. `"4.6.1"` for R). For a BYO `image`, defaults to `"latest"`. |
| `pull_on_start` | boolean | `true` | Pull the latest image when the app starts |
| `volumes` | map | `{}` | Host-to-container volume mounts (`host: container`) |
| `env` | map | `{}` | Environment variables (`KEY: value`) |

**Example:**

```yaml
container:
  engine: "docker"
  image: "rocker/shiny"
  tag: "4.4.1"
  pull_on_start: true
  volumes:
    "/data": "/app/data"
  env:
    SHINY_LOG_LEVEL: "DEBUG"
```

### `apps`

Defines a multi-app suite: two or more Shiny apps packaged in one Electron shell with a launcher screen. At least two entries are required. Any entry can override `build.type` or `build.runtime_strategy`, which is how mixed-strategy suites work. One constraint applies: within a language, all native apps (`system`, `bundled`, `auto-download`) must share one strategy; `shinylive` and `container` apps combine freely. Export aborts on a conflict. See the [Multi-App Suites](multi-app-suites.html) vignette for details.

| Key | Type | Required | Description |
|-----|------|----------|-------------|
| `id` | string | yes | Unique identifier (URL-safe) |
| `name` | string | yes | Display name in the launcher |
| `path` | string | yes | Relative path to the app directory |
| `type` | string | no | App type override (default: `build.type`) |
| `runtime_strategy` | string | no | Runtime strategy override (default: `build.runtime_strategy`) |
| `description` | string | no | Short description shown in the launcher |
| `icon` | string | no | Per-app icon path |

**Example:**

```yaml
apps:
  - id: "dashboard"
    name: "Dashboard"
    path: "./apps/dashboard"
    description: "Sales analytics dashboard"
  - id: "admin"
    name: "Admin Panel"
    path: "./apps/admin"
    type: "py-shiny"
    description: "User management"
```

See the [Multi-App Suites](multi-app-suites.html) vignette for mixed-strategy examples and launcher customization.

### `logging`

Controls where and how the app writes its log files.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `log_dir` | string | `null` | Directory for log files; `null` writes to the Electron app's `userData/logs` directory |
| `log_level` | string | `"info"` | Logging verbosity: `"debug"`, `"info"`, `"warn"`, or `"error"` |

**Example:**

```yaml
logging:
  log_dir: "/var/log/my-app"
  log_level: "debug"
```

### `lifecycle`

Controls the lifecycle window that fills the gap between launch and app readiness. See the [Customizations](customizations.html) guide for the visual options (splash image, preloader style, etc.). The keys below govern behavior and timeout rather than appearance.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `show_phase_details` | boolean | `true` | Show the phase-detail line under the preloader headline |
| `error_show_logs` | boolean | `true` | Show the collapsible error-log block in the error state |
| `shutdown_timeout` | integer (ms) | `10000` | Maximum milliseconds to wait for backend teardown before force-quitting |
| `custom_splash_html` | string | `null` | Raw HTML replacing the entire splash state; `null` uses the built-in splash |
| `custom_error_html` | string | `null` | Raw HTML replacing the entire error state; `null` uses the built-in error view |
| `prompt_before_install` | boolean | `false` | Prompt the user before installing missing R or Python packages |
| `prompt_runtime_version` | boolean | `false` | Show a runtime-version picker when multiple R or Python installations are detected |

### `installer`

Controls installer branding and behavior for the Windows (NSIS) installer produced by electron-builder.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `app_id` | string | `null` | Unique application identifier (e.g. `"com.example.myapp"`); `null` derives one from the app name |
| `license_file` | string | `null` | Path to a license file shown during installation (Windows NSIS only) |
| `one_click` | boolean | `true` | Use a one-click installer on Windows; set to `false` for a wizard-style installer |

**Example:**

```yaml
installer:
  app_id: "com.example.my-dashboard"
  license_file: "LICENSE.txt"
  one_click: false
```

## Common recipes

### Development setup

Fast iteration while you work:

```yaml
app:
  name: "My App (Dev)"
  version: "0.0.1"

window:
  width: 1000
  height: 700
```

### Production multi-platform build

For distribution across platforms:

```yaml
app:
  name: "Production App"
  version: "1.0.0"

build:
  type: "r-shiny"
  runtime_strategy: "shinylive"
  platforms:
    - mac
    - win
    - linux
  architectures:
    - x64
    - arm64

window:
  width: 1200
  height: 800

icon: "branding/icon.png"

nodejs:
  version: "22.11.0"
```

### Native R app with bundled runtime

Ship R inside the app:

```yaml
app:
  name: "Analytics Tool"
  version: "1.0.0"

build:
  type: "r-shiny"
  runtime_strategy: "bundled"
  platforms:
    - mac
    - win

dependencies:
  r:
    version: "4.4.1"
```

### Multi-app suite

Several apps behind one launcher:

```yaml
app:
  name: "My App Suite"
  version: "1.0.0"

build:
  type: "r-shiny"
  runtime_strategy: "auto-download"

apps:
  - id: "dashboard"
    name: "Dashboard"
    path: "./apps/dashboard"
    description: "Sales analytics"
  - id: "admin"
    name: "Admin Panel"
    path: "./apps/admin"
    type: "py-shiny"
    description: "User management"
```

## What happens when a value is invalid

shinyelectron validates values on read and degrades gracefully rather than aborting the build:

- Invalid `type` values warn and fall back to autodetect.
- Invalid `runtime_strategy` values warn and fall back to `shinylive`.
- Invalid platforms and architectures are dropped with a warning.
- Window dimensions under 100 pixels warn and use defaults.
- Invalid port numbers warn and use `3838`.

If the YAML itself fails to parse, shinyelectron warns and uses all defaults. This is deliberate: a broken config file should never block you from producing a build during development.

## Next steps

- **[Getting Started](getting-started.html)**: first-time user walkthrough.
- **[Runtime Strategies](runtime-strategies.html)**: deep dive on `build.runtime_strategy`.
- **[Node.js Management](nodejs-management.html)**: managing a local Node.js install.
- **[Troubleshooting](troubleshooting.html)**: diagnosing build issues.
