Plushie is configured at three levels: environment variables for deployment and CI, application config for project-wide defaults, and runtime options on Plushie.start_link/2 for per-instance control. Most projects need minimal configuration. Build or download the binary and go.

Environment variables

VariablePurpose
PLUSHIE_BINARY_PATHExplicit path to the renderer binary. Overrides all other binary resolution.
PLUSHIE_SOURCE_PATHPath to a local plushie-renderer checkout for source builds.
PLUSHIE_TEST_BACKENDTest backend: mock (default), headless, or windowed.
PLUSHIE_UPDATE_SCREENSHOTSWhen set to any value, update screenshot golden files instead of comparing.
PLUSHIE_UPDATE_SNAPSHOTSWhen set to any value, update tree-hash snapshot files instead of comparing.
RUST_LOGRenderer log verbosity. Set to plushie=debug for full protocol logging. Overrides the :log_level runtime option when set.

PLUSHIE_BINARY_PATH

Forces a specific renderer binary. Useful in CI where the binary is pre-built and stored at a known path, or in production deployments where the binary ships alongside the release. If set but pointing to a missing file, resolution raises immediately rather than falling through to other paths.

PLUSHIE_SOURCE_PATH

Points to a local checkout of the plushie-renderer repository. When set, mix plushie.build uses local path dependencies instead of crates.io, and the dev server watches .rs files for hot reload. The typical setup is a sibling directory:

git clone https://github.com/plushie-ui/plushie-renderer ../plushie-renderer
PLUSHIE_SOURCE_PATH=../plushie-renderer mix plushie.build

RUST_LOG

Controls the renderer's log output using the standard env_logger format. The Bridge forwards this to the renderer process at startup. Common values:

  • plushie=debug - full protocol and rendering detail
  • plushie=info - connection events and major state changes
  • plushie=warn - warnings only (default-like)

When RUST_LOG is set, the :log_level option on start_link/2 is ignored. Combine with --json for full wire protocol visibility:

RUST_LOG=plushie=debug mix plushie.gui MyApp --json

Application config

Set in config/*.exs under config :plushie. A typical development setup looks like this:

# config/dev.exs
import Config

config :plushie, code_reloader: true

Most projects only need :code_reloader in dev. The remaining options are for customising builds, binary paths, and test backends.

Binary and build

KeyTypeDefaultPurpose
:binary_pathString.t()nilExplicit binary path. The env var PLUSHIE_BINARY_PATH takes precedence if set.
:source_pathString.t()nilRust source checkout path. The env var PLUSHIE_SOURCE_PATH takes precedence if set.
:build_nameString.t()"app-renderer"Custom binary name for mix plushie.build output.
:build_profile:release | :debug:debugCargo build profile. :release enables optimisations.
:artifacts[:bin] | [:bin, :wasm][:bin]Which artifacts mix plushie.build and mix plushie.download install.
:bin_fileString.t()nilOverride binary output path for build/download.
:wasm_dirString.t()nilOverride WASM output directory for build/download.

Development

KeyTypeDefaultPurpose
:code_reloaderboolean() | keyword()falseEnable hot code reloading in dev.
:test_backend:mock | :headless | :windowed:mockTest backend selection.

Code reloader options

When :code_reloader is set to a keyword list, these options are supported:

KeyTypeDefaultPurpose
:debounce_msinteger()100Milliseconds to wait after a file change before recompiling. Prevents rapid recompilation during multi-file saves.
:rebuild_artifacts[:bin] | [:bin, :wasm][:bin]Which Rust artifacts to rebuild when .rs files change. Add :wasm to also rebuild the WASM renderer.
:dirs[String.t()]Mix project's elixirc_pathsDirectories to watch for Elixir file changes. Override if your source lives outside the standard paths.

Rust file changes (.rs, Cargo.toml) use a separate debounce of 200ms (not configurable) because Cargo builds are heavier and benefit from more coalescing.

Plushie.start_link/2 starts a Plushie application. The first argument is the app module (implementing Plushie.App), and the second is an optional keyword list of options:

Plushie.start_link(MyApp, binary: Plushie.Binary.path!(), daemon: true)

Common options

OptionTypeDefaultPurpose
:app_optsterm()[]Forwarded to app.init/1. Use this to pass startup configuration to your app.
:binaryString.t()auto-resolvedPath to the renderer binary. Usually resolved automatically via Plushie.Binary.path!/0.
:nameatom()PlushieSupervisor registration name. The Runtime registers as {name}.Runtime, the Bridge as {name}.Bridge.
:daemonboolean()falseKeep running after the last window closes. In both modes, update/2 receives %SystemEvent{type: :all_windows_closed}. Without daemon, the runtime shuts down after that update. With daemon, it continues running so you can open new windows.

Transport

OptionTypeDefaultPurpose
:transport:spawn | :stdio | {:iostream, pid}:spawnHow the SDK communicates with the renderer. See transport modes.
:format:msgpack | :json:msgpackWire protocol format. JSON is human-readable, useful for debugging.

Advanced

OptionTypeDefaultPurpose
:code_reloaderboolean() | keyword()falseOverride the config-level code_reloader setting for this instance.
:log_levelatom():errorRenderer log level (:error, :warn, :info, :debug). Ignored if RUST_LOG is set.
:renderer_args[String.t()][]Extra CLI arguments appended to the renderer command. Only applies in :spawn transport mode.

App settings callback

The optional Plushie.App.settings/0 callback provides application-level defaults to the renderer at startup. These are sent once during the initial handshake and affect all windows.

defmodule MyApp do
  use Plushie.App
  import Plushie.UI

  def settings do
    [
      default_text_size: 16,
      theme: :dark,
      fonts: ["priv/fonts/inter.ttf"],
      default_event_rate: 60
    ]
  end

  def init(_opts), do: %{}
  def update(model, _event), do: model

  def view(_model) do
    window "main", title: "MyApp" do
      text("hello", "Hello")
    end
  end
end
KeyTypeDefaultPurpose
default_fontmap()system defaultDefault font specification (same format as font props).
default_text_sizenumber()renderer defaultDefault text size in pixels for all text widgets.
antialiasingboolean()trueEnable font antialiasing.
vsyncboolean()trueSynchronize frame presentation with the display refresh rate. Disabling can improve performance on some platforms.
scale_factornumber()1.0Multiplier on top of OS DPI scaling. Setting 2.0 on a 2x HiDPI display gives 4x physical pixels per logical pixel. Per-window overrides are available via the scale_factor window prop.
themeatom() | map()renderer defaultBuilt-in theme (:dark, :light, :nord, :system, etc.) or a custom palette. See Plushie.Type.Theme.
fonts[String.t()][]Paths to font files to load. Loaded fonts are available by family name in any widget's font: prop.
default_event_rateinteger()unlimitedMaximum events per second for coalescable event types (mouse moves, scroll, slider drags, animation frames). Set to 60 for most apps, lower for remote rendering. Per-subscription max_rate and per-widget event_rate override this.

Widget discovery

Native widgets are auto-detected via Plushie.Widget.WidgetProtocol protocol consolidation at compile time. Any module that implements the protocol and exports native_crate/0 is included in mix plushie.build automatically.

Plushie.WidgetRegistry exposes the discovered widgets. No configuration is needed. Add a native widget to your dependencies and it appears in the next build.

Pure Elixir widgets (those with view/2 or view/3 callbacks) do not require protocol consolidation or build system integration. They work with any renderer binary, including precompiled downloads.

Transport modes

The renderer communicates with the SDK over a language-agnostic wire protocol (MessagePack or JSON over a byte stream). The SDK supports three transport modes:

:spawn (default)

The SDK spawns the renderer as a child process via an Erlang Port. Communication happens over the process's stdin/stdout. This is the default for local development. mix plushie.gui uses this mode.

The Bridge manages the Port lifecycle: starting, monitoring, and restarting the renderer on crash (with exponential backoff). The renderer process inherits RUST_LOG from the environment.

:stdio

The renderer spawns the Elixir app (via plushie --exec "mix ..."). Communication happens over the BEAM's own stdin/stdout. The Elixir app reads events from stdin and writes snapshots/patches to stdout.

This mode is used by the renderer's --exec flag, which starts the renderer first, then launches the Elixir app as a subprocess. The :binary option is ignored since no subprocess is spawned from the Elixir side.

### {:iostream, pid}

A message-based adapter for custom transports. The pid is a process that bridges between the wire protocol and an arbitrary transport (SSH channel, TCP socket, Unix socket, WebSocket, etc.).

The adapter process must handle these messages:

DirectionMessageDescription
Bridge -> Adapter{:iostream_bridge, bridge_pid}Sent during init. The adapter stores the bridge pid for sending data back.
Bridge -> Adapter{:iostream_send, iodata}Protocol data to write to the transport.
Adapter -> Bridge{:iostream_data, binary}One complete protocol message received from the transport.
Adapter -> Bridge{:iostream_closed, reason}Transport closed. The bridge handles shutdown.

The adapter is responsible for framing, splitting the byte stream into complete protocol messages before sending {:iostream_data, ...}. Plushie.Transport.Framing provides frame encode/decode for length-prefixed byte streams (the standard wire framing).

This mode enables remote rendering: the Elixir app runs on one machine (server, embedded device, cloud), the renderer runs on another (desktop, laptop, mobile). The wire protocol is identical; only the transport layer differs. See the Shared State guide for a complete SSH example.

Test configuration

Plushie.Test.setup!/1 accepts options for the test session pool:

OptionTypeDefaultPurpose
:pool_nameatom()Plushie.TestPoolRegistration name for the session pool process.
:max_sessionsinteger()max(schedulers * 8, 128)Maximum concurrent test sessions. Higher values allow more parallel tests but use more renderer memory.

The test backend is selected via the PLUSHIE_TEST_BACKEND environment variable or config :plushie, :test_backend. See the Testing reference for backend setup and CI configuration.

See also