---
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 settings you need are the app name and its slug, which `init_config()` writes for you:

```yaml
app:
  name: "My App"
  slug: "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"     # Display name: window title, About dialog, installed app
  slug: "my-application"     # Identity: package name, app ID, user data, file names
  version: "1.0.0"           # Application version (used in package metadata)
  description: "Quarterly sales explorer"   # About dialog and installer metadata
  author: "Jane Doe <jane@example.org>"     # Or a map with name, email, and url
  homepage: "https://example.org/sales"     # http:// or https:// only
  copyright: "Copyright 2026 Example Inc."  # About dialog and installer 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
    prune: true             # Bundled only: remove package tests, R manuals and news
  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 icons, used when icon is not set (rarely needed):
# icons:
#   mac: "branding/icon.icns"
#   win: "branding/icon.ico"
#   linux: "branding/icon.png"

nodejs:
  version: null              # Node.js version (null = latest LTS)

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.
:::

## File paths

Paths in `_shinyelectron.yml` are relative to the directory that holds the file: the app directory, or the suite root of a [multi-app suite](multi-app-suites.html). The R working directory does not matter, so `export("path/to/app", "output")` finds the same files from anywhere. `~` expands to your home directory, and absolute paths are used as they are. These keys name files:

- `icon` and the entries of `icons`
- `splash.image`
- `tray.icon`
- `signing.win.certificate_file`
- `installer.license_file`
- the entries of `dependencies.r.local_packages`
- `path` and `icon` in each `apps` entry

Path arguments follow the usual R rule instead: `export(icon = "icon.png")` looks for `icon.png` in the working directory. `build_electron_app()` also uses the paths in its `config` argument as given, relative to the working directory.

When one of these files does not exist, the message names the key and the full path that was checked. A missing app icon stops `export()`, just as a missing `icon` argument does, and so does a missing `installer.license_file` or `dependencies.r.local_packages` entry. A missing splash image, tray icon, or launcher icon gives a warning, and the build uses the default instead. A missing `signing.win.certificate_file` gives a warning when `export()` signs a Windows build.

Paths that the app uses later on the end user's machine, such as `log_dir`, `dependencies.r.lib_path`, and the host side of `container.volumes`, are not resolved: they are used there as written.

## Section reference

### `app`

Application metadata.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `name` | string | Directory name | Display name: the window title, the About dialog, and the installed app (the macOS app, the Windows Start Menu entry). It never sets the slug |
| `slug` | string | Directory name | The app's identity: lowercase letters, digits, and hyphens (see below) |
| `version` | string | `"1.0.0"` | Version number for the built application |
| `description` | string | `null` | Shown in Help > About and written to the installer metadata; `null` uses `"<slug> - Shiny Electron App"` |
| `author` | string or map | `null` | npm-style `"Name <email> (url)"`, where the email and URL are optional, or a map with `name`, `email`, and `url`. Help > About shows the name and, with an email, an Email button |
| `homepage` | string | `null` | An `http://` or `https://` URL. Adds a Visit Website button to Help > About and a support link to the Windows uninstall entry |
| `copyright` | string | `null` | Shown in Help > About, the macOS About panel, and the Windows file properties; `null` lets electron-builder write a notice with the year and the author |

Line breaks in `description`, `author`, and `copyright` become spaces, because the installer metadata holds a single line. The Windows installer script keeps the app name, the author's name, and the copyright in double-quoted strings, so their straight double quotes become typographic ones in the installer metadata, as electron-builder already does for the description. That script cannot hold a `$` at all: `export()` stops a Windows build when the app name, description, author, or copyright contains one, and warns when you build for other platforms. The same rule keeps electron-builder's `${...}` macros out of the copyright.

::: {.callout-important}
## Settle the slug before you ship

`name` is what people see; `slug` is what the operating system tracks. The slug names the package, the user data folder (window size, logs, and browser storage), and the installer files. Unless `installer.app_id` is set, it also gives the app ID, `com.shinyelectron.<slug>`, which Windows installers and macOS use to recognize an installed copy and update it. A build with a different slug is a different app: on Windows it installs next to the old one instead of updating it.

`app.name` sets only the display name. The slug comes from `app.slug`, else the `app_name` argument of `export()`, else the directory name, as in earlier releases, so renaming the app never changes its identity. An `app_name` argument with no ASCII letters or digits, such as one written only in Chinese characters, gives no slug, so the directory's is used. `export()` prints the slug next to the display name, and `init_config()` and `wizard()` write the directory's slug into the file, so the identity also survives a renamed or moved directory.

To give a new app a slug that matches its display name, set it before the first release:

```yaml
app:
  name: "Sales Dashboard"
  slug: "sales-dashboard"
```

Once an app has shipped, keep its slug.
:::

### `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` |

`export()` builds each listed platform for each listed architecture. Without `architectures`, every platform is built for the architecture of the machine running the build, so on an Apple Silicon Mac, `platforms: [win]` gives an arm64 Windows installer. The `platform` and `arch` arguments of `export()` override these lists, each only its own: with `architectures: [arm64]` in the file, `export(platform = "win")` builds Windows for 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, so `export()` stops when a target is `mac` on Windows or Linux. 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. A `bundled` or `auto-download` build targets one platform and one architecture, so it stops when `platforms` or `architectures` lists more than one. 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). A smaller PNG must still be at least 512×512 for macOS and 256×256 for Windows; Linux uses the PNG as it is. An `.icns` file works for every platform too, as long as it holds an image of 256×256 or more for Windows. An `.ico` file serves only Windows, and needs a 256×256 image: electron-builder cannot make a macOS or Linux icon from it. A platform that cannot use the icon, because of its format or its size, shows the default Electron icon. `export()` warns about it before anything is built, and `app_check()` reports it.

```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 instead of `icon`:

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

Paths are relative to the app directory, or to the suite root of a multi-app suite, whatever the working directory (see [File paths](#file-paths)). `export()` uses `icon` when it is set, and otherwise the `icons` entry for the first target platform. A build for several platforms ships that one icon to every platform that can use it, and `export()` warns about the `icons` entries it leaves out. To give each platform its own artwork, export each platform separately with the `platform` argument. The `icon` argument of `export()` overrides both, and is relative to the working directory. If the chosen file does not exist, `export()` stops with an error that names the key and the path it checked. 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 planned for a future release. It is not read yet, so setting it has no effect and is reported as an unknown key. Today, a missing Node.js aborts the build with guidance to run `install_nodejs()` or install Node.js manually.
:::

### `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, except `r.prune` and `r.local_packages`, which only the `bundled` strategy uses.

| 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 |
| `r.prune` | boolean | `true` | Remove package test suites and R's own tests, manuals, and news from the embedded runtime (see below) |
| `r.local_packages` | list of strings | `[]` | Package source folders or `.tar.gz` source tarballs to install into a bundled R library; see [Local R packages](#local-r-packages) |

#### Pruning the bundled R runtime

With the `bundled` strategy, shinyelectron trims the embedded R runtime after installing your packages, leaving out files that a running app does not use:

- the test suites (`tests/`, `testme/`, `tinytest/`) of every installed package, including R's base and recommended packages;
- R's own regression tests;
- R's PDF and HTML manuals and its news and FAQ files.

Everything else stays. Package code, data, help pages, `examples/`, `demo/`, NEWS files, and `include/` headers are kept, because some packages and apps read them at runtime: `shinyjs::runExample()` reads its examples, a "What's new" panel may show a package's `NEWS.md`, and `Rcpp::sourceCpp()` needs the headers. R's license files (`COPYING`, and `COPYRIGHTS` with the notices that binary distributions of R must include), its author list, and the mirror lists that `utils::getCRANmirrors()` reads are kept too.

Pruning is on by default. To ship the runtime exactly as installed, turn it off:

```yaml
dependencies:
  r:
    prune: false
```

Write `true` or `false` without quotes. A quoted `"true"` or `"false"` is read as the matching value with a warning; any other value stops the build before anything is downloaded.

#### Local R packages

`r.local_packages` installs R packages that are not on a repository, such as your own in-house packages, into the R library of a `bundled` build. List package source folders (the folder that holds `DESCRIPTION`) or source tarballs as `R CMD build` creates them:

```yaml
build:
  runtime_strategy: "bundled"

dependencies:
  r:
    local_packages:
      - ../mypkg                         # a source folder next to the app
      - ../vendor/otherpkg_1.2.0.tar.gz  # a source tarball
```

- Only R apps that use the `bundled` strategy can use local packages. `export()` stops with an error when the key is set for any other strategy, or for a suite without a bundled R app.
- Relative paths resolve against the folder that holds `_shinyelectron.yml`: the app directory, or the suite root for a multi-app suite. A suite reads `local_packages` only from its root `_shinyelectron.yml`, not from the config files of its apps.
- `export()` checks every entry before it downloads anything. Zip files and binary builds (such as the `.tgz` files CRAN publishes for macOS) are rejected, because local packages are compiled for the bundled R.
- A source folder is first built into a tarball in a temporary directory with the bundled R's `R CMD build`, so nothing is compiled or written inside it. The build runs after the package's dependencies are installed, so help pages whose `\Sexpr` macros need the package at build time work too.
- The `Depends`, `Imports`, and `LinkingTo` packages of each local package are installed from `r.repos` first. `Remotes:` fields are not read, so list a dependency that is not on a repository under `local_packages` too, or add its repository to `r.repos`.
- Local packages install in dependency order among themselves, so the order of the list does not matter. Local packages that depend on each other in a cycle stop the build.
- A local package replaces a repository package with the same name.
- Each package must load in a fresh R session from the bundled library, or the build stops with the load error and the last lines of the install output. Building or installing one package may take up to 30 minutes.
- Packages with C, C++, or Fortran code need a compiler toolchain that works with the bundled R on the build machine, such as the Xcode Command Line Tools on macOS or Rtools on Windows.
- Compiled code is linked on the build machine. System libraries that a package links against, such as Homebrew libraries found by its `configure` script or named in `~/.R/Makevars`, are not copied into the app, so they must also be installed on your users' machines.
- Keep package sources outside the app directory: everything in the app directory is copied into the app.

#### 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"
```

`env` values are written into the built app, so keep passwords and tokens out of them; see [Secrets and per-user credentials](security.html#secrets-and-per-user-credentials).

### `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 | Path to the app directory, relative to the suite root |
| `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 | Image for the app's launcher card, relative to the suite root |

**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. Any other value should be an absolute path that the app's user can write to: a relative path, including one that starts with `~`, resolves against the process working directory, and if the directory cannot be created no log file is written |
| `log_level` | string | `"info"` | Logging verbosity: `"debug"`, `"info"`, `"warn"`, or `"error"` |

**Example:**

```yaml
logging:
  log_level: "debug"        # log_dir is unset, so logs go to userData/logs
```

The same two keys are also accepted under `app` as `app.log_dir` and `app.log_level`, which is where earlier versions of `init_config()` put them. If both places set a key to different values, the `logging` value wins and shinyelectron warns.

### `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 |
| `startup_timeout` | integer (ms) | `180000` | Maximum milliseconds to wait for the R, Python, or container server to answer before showing a startup error |
| `shutdown_timeout` | integer (ms) | `10000` | Maximum milliseconds to wait for backend teardown before force-quitting or installing an update |
| `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 |

### `signing`

Code signing for release builds. Passwords and other credentials never go in this file; electron-builder reads them from environment variables. See [Code Signing and Distribution](code-signing.html) for the certificates, the variables, and the setup on each platform.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `sign` | boolean | `false` | Sign the builds `export()` makes; `export(sign = TRUE)` signs even when this is `false` |
| `mac.identity` | string | `null` | Signing identity, e.g. `"Developer ID Application: Your Name (TEAMID)"`; `null` lets electron-builder find one |
| `mac.team_id` | string | `null` | Apple Team ID, needed to notarize with an Apple ID; the build passes it to electron-builder as `APPLE_TEAM_ID`, and a team ID already set in `APPLE_TEAM_ID` wins |
| `mac.notarize` | boolean | `false` | Notarize with Apple; signed builds also notarize when `APPLE_ID` and `APPLE_APP_SPECIFIC_PASSWORD` are set |
| `win.certificate_file` | string | `null` | Path to a `.pfx` or `.p12` certificate; `null` uses `WIN_CSC_LINK` or `CSC_LINK` |
| `linux.gpg_sign` | boolean | `false` | Reserved for GPG-signing the AppImage; has no effect yet |

### `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` uses `"com.shinyelectron.<slug>"` |
| `license_file` | string | `null` | Path to a license file, relative to the app directory, that the Windows installer shows before installing. Plain text (UTF-8), `.rtf`, or `.html` |
| `one_click` | boolean | `true` | Use a one-click installer on Windows; set to `false` for a wizard-style installer |
| `allow_to_change_installation_directory` | boolean | `null` | `true` adds a wizard page where users choose the installation folder. Requires `one_click: false` |
| `per_machine` | boolean | `null` | `true` installs for all users, which needs administrator rights on every install and update. `false` behaves like unset: the one-click installer installs for the current user, and the wizard still lets the user choose |

**Example:**

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

::: {.callout-warning}
## Decide on `per_machine` before the first release

A per-machine install goes to Program Files, and every later update shows an administrator prompt. Standard users cannot write to Program Files, and R Shiny runs an app with its own folder as the working directory, so an app that writes files next to `app.R` fails for them. Turning `per_machine` on for an app that has already shipped per-user moves the next install there too. A standard user who approves that prompt with an administrator's credentials can end up with the old per-user copy still installed next to the new one.
:::

::: {.callout-tip}
## Build Windows installers on Windows

Build the Windows installer on a Windows machine, as the [GitHub Actions](github-actions.html) workflow does. Installers built on macOS have been reported to cut `.txt` and `.rtf` license pages short.
:::

## Common recipes

Each recipe sets `slug` next to `name`. When you copy one into an app that has already shipped, use the slug it shipped under, which is its directory name unless you set another.

### Development setup

Fast iteration while you work:

```yaml
app:
  name: "My App (Dev)"
  slug: "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"
  slug: "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"
```

Run this recipe on a Mac: macOS apps build only on macOS, and `export()` stops when `platforms` lists `mac` on Windows or Linux. To build each installer on its own operating system instead, pass `platform` and `arch` on each CI runner, as the [GitHub Actions](github-actions.html) workflow does; the arguments override these lists.

### Native R app with bundled runtime

Ship R inside the app:

```yaml
app:
  name: "Analytics Tool"
  slug: "analytics-tool"
  version: "1.0.0"

build:
  type: "r-shiny"
  runtime_strategy: "bundled"

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

A bundled build embeds R for one platform and architecture, so this recipe leaves out `platforms` and `architectures`, and each build targets the machine it runs on. Build on each operating system you ship to, as the [GitHub Actions](github-actions.html) workflow does.

### Multi-app suite

Several apps behind one launcher:

```yaml
app:
  name: "My App Suite"
  slug: "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. When none is left, the build targets the current platform or architecture.
- Window dimensions under 100 pixels warn and use defaults.
- Invalid port numbers warn and use `3838`.
- A `lifecycle.startup_timeout` or `lifecycle.shutdown_timeout` that is not a whole number of milliseconds from 1000 to 2147483647 warns and uses the default.
- An `app.description` or `app.copyright` that is not text, or an `app.author` that is neither a string nor a map of `name`, `email`, and `url`, warns and is left out.

Keys that shinyelectron does not recognize, such as a misspelled `widht` or a key placed in the wrong section, are reported in a single warning that lists each one by its dotted path (for example `window.widht`), and the build goes on without them. Entries under `apps` and the contents of maps such as `container.env` are not checked.

The `installer` settings are stricter. `one_click`, `allow_to_change_installation_directory`, and `per_machine` take `true` or `false`: a quoted `"true"` or `"false"` is read with a warning, and any other value stops the build. `allow_to_change_installation_directory: true` needs `one_click: false`, and a `license_file` that does not exist also stops the build. electron-builder would otherwise reject these late in the Windows build, after the runtime download.

`dependencies.r.prune` is stricter. A quoted `"true"` or `"false"` is read as the matching value with a warning, but any other value stops the build with an error before anything is downloaded, because falling back to the default could remove files you meant to keep.

`app.homepage` is stricter: a value that is not an `http://` or `https://` URL stops the build, because the About dialog opens it in the browser. So is a `$` in the app name, `app.description`, `app.author`, or `app.copyright` when the build includes Windows, because the Windows installer cannot hold one; for other platforms it only warns.

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.
