Docs

everywhere.yml

config/everywhere.yml describes your native app: its name, identity, appearance, native chrome, and build matrix. every install writes a starter with every section present and commented out. This page documents the keys.

A small example

config/everywhere.yml yaml
app:
  name: Notes
  bundle_id: com.example.notes   # set once, never change it
  version: "1.4.0"               # marketing version, shared across targets
  mode: local                    # local or remote
  entry_path: /notes

appearance:
  background_color:
    light: "#ffffff"
    dark: "#09090b"

menu:
  - label: Settings…
    path: /settings
    accelerator: CmdOrCtrl+,

tray:
  - label: New note
    path: /notes/new

Keys at a glance

Key What it does
app.name Window title and app bundle name.
app.bundle_id Reverse-DNS id. Names the app-data directory and the macOS bundle identifier. Set it once and never change it: renaming it moves your users' data.
app.version Marketing version (CFBundleShortVersionString). Shared across every target unless a platform overrides it. See Platforms and targets.
app.mode local (compile and ship the app) or remote (a shell around a deployed site). See Local vs Remote mode.
app.entry_path The route the window opens on launch. Defaults to /.
app.icon PNG used to generate app icons. Defaults to icon.png at the app root if one is there.
app.splash Custom boot splash (an HTML file) shown while the packaged server starts.
remote.url In remote mode, the deployed URL the shell opens.
remote.instances true lets users pick their server: the shell boots into remote.url, your hosted picker, until the page calls Everywhere.instance.set. See the bridge.
remote.instances_escape The mobile shells' built-in "back to start" action while re-rooted onto an instance. On by default with instances: true; set false to opt out.
appearance.tint_color Accent color. A hex string, or split by light: / dark:.
appearance.background_color Native window background. Same shape.
menu: Native menu-bar items. Each visits a route in your app.
tray: System tray items. Each visits a route or runs `action: show \
window: Desktop window chrome: title bar style, size, resizability. See Window chrome.
tabs: Native mobile tab bar: name, path, per-platform icons. See iOS.
rules: Extra Hotwire Native path-configuration rules (modals, pull-to-refresh), appended after the defaults. See iOS.
permissions: Native permissions the app may request: notifications, camera, location, biometrics. Camera, location and biometrics values are the sentence iOS shows in its prompt, and the build fails without one. See the bridge.
auth: Third-party sign-in. See Third-party sign-in.
updates: Self-hosted auto-updates: feed URL, channel, public key, and the publish-side bucket. See Updates.
deep_linking: Universal links and Android App Links. See Deep linking.
native.ios Swift from native/ios/ compiled into the shell. See Native extensions.
native.android Kotlin from native/android/, plus icon_font: and Maven packages:.
native.desktop Rust from native/desktop/, reachable as Everywhere.desktop.invoke.
build.ruby Ruby version to package.
build.targets The platforms you ship: the build matrix, written once.
build.capabilities Reserved for desktop OS-integration capabilities. Nothing reads it yet. For permissions the app requests at runtime, use permissions: above.
platforms.<os> Per-platform overrides of the app.* keys, plus Android's target_sdk.

Appearance

background_color takes a single hex string or a light/dark pair. The dark value applies whenever the OS reports dark mode, so the native window matches your app before the first frame paints:

config/everywhere.yml yaml
appearance:
  background_color:
    light: "#ffffff"
    dark: "#09090b"

tint_color, the accent color, takes the same shape.

menu: and tray: are lists of items. Every item has a label and a path. Menu items may add a keyboard accelerator; tray items may use action: show | quit in place of a path. Use separator for a divider. Picking an item visits that route in your app. No callbacks, no native code:

config/everywhere.yml yaml
menu:
  - label: Settings…
    path: /settings
    accelerator: CmdOrCtrl+,
  - separator
  - label: Documentation
    path: /help

tray:
  - label: Open Notes
    action: show
  - label: New note
    path: /notes/new
  - separator
  - label: Quit
    action: quit

Both also emit events your app can handle through the Bridge API.

Window chrome

Desktop only. On mobile the OS owns the frame, so mobile builds skip this section. Leave a key out and the shell keeps its own default.

config/everywhere.yml yaml
window:
  title_bar: overlay   # decorated (default) | overlay | frameless
  title: false         # draw the title text (default true)
  size: [1100, 750]    # initial inner size
  min_size: [600, 400]
  resizable: true
  drag_height: 28      # top strip that drags the window; 0 turns it off

overlay floats the macOS traffic lights over your content with no title text. Reserve room for them with the .everywhere-titlebar-inset class from everywhere/native.css. Windows and Linux have no such style, so the shell falls back to frameless there and your page draws its own controls through Everywhere.window.

Third-party sign-in

Providers refuse to run, or run badly, inside an app's web view. Declare which paths start a provider flow and the mobile shell hands them to the system browser instead:

config/everywhere.yml yaml
auth:
  oauth_paths:                # unanchored regexes, like rules:
    - ^/auth/                 # the default, and OmniAuth's convention
  scheme: com.example.app     # callback scheme (default: the iOS bundle id)
  cookies:
    except: ["_ga"]           # which cookies cross back in (default: all)
  # token_ttl: 60             # seconds a handoff token stays valid

Your app keeps its own auth. This declares nothing about providers, only which paths the shell must not open in its web view. The full flow is on the iOS page.

Updates

Self-hosted auto-updates. The client half (url, channel, public_key, auto, interval) is baked into the build; the s3: half is publish-side and never ships:

config/everywhere.yml yaml
updates:
  url: https://updates.example.com/notes
  channel: stable          # release channel this build polls
  public_key: "RWQf6LRC…"  # from `every updates keygen`
  auto: check              # off | check (notify) | download | install (silent)
  interval: 21600          # seconds between background checks (minimum 300)
  s3:
    bucket: my-app-releases
    endpoint: https://<account>.r2.cloudflarestorage.com
    region: auto
    # prefix: notes

url must be https, or the shell turns the updater off and the app ships looking fine while never updating. Both url and public_key must be set, or no update config reaches the shell at all. See App updates.

Deep linking

Declares the app's association with your web domains, so the OS hands matching URLs to the app instead of the browser. The gem serves both association files (/.well-known/apple-app-site-association and /.well-known/assetlinks.json) from this config:

config/everywhere.yml yaml
deep_linking:
  team_id: ABCDE12345           # derives the iOS app id from bundle_id
  paths: ["/*"]                 # which paths open the app (default: all)
  domains: ["www.example.com"]  # extra domains; the remote host is implicit
  android:
    package: com.example.notes
    sha256_cert_fingerprints: ["AB:CD:EF:…"]

Platforms and targets

build.targets is your build matrix, written once. every build, every release and every platform build all fan out over it:

config/everywhere.yml yaml
build:
  ruby: "4.0.6"
  targets:
    - macos-arm64
    - ios-arm64:testflight   # optional per-target distribution channel
    - android-arm64

An entry may carry a :channel suffix (os-arch:channel) picking that target's distribution channel. Targets without one use the build's default channel.

The macOS, iOS, and Android builders are live today. Windows and Linux are being built out. You can declare their targets now: each builds as it lands.

Per-platform overrides

app.* holds what every target shares. When one platform needs a different identity or version, and the App Store often wants its own bundle id, override just those keys under a top-level platforms: map keyed by OS (macos, ios, android, windows, linux):

config/everywhere.yml yaml
app:
  name: Notes
  bundle_id: com.example.notes   # shared default
  version: "1.4.0"               # shared marketing version
  mode: remote

remote:
  url: https://notes.example.com

build:
  targets: [macos-arm64, ios-arm64, android-arm64]

platforms:
  ios:
    bundle_id: com.example.notes.ios   # the App Store wants its own id
    version: "1.4.1"                   # and a bumped version
  android:
    bundle_id: com.example.notes.android

A target's OS selects the matching platforms.<os> block. Keys there override app.*; anything you leave out falls back to the shared value. Above, iOS ships com.example.notes.ios at 1.4.1, Android ships com.example.notes.android at the shared 1.4.0, and macOS ships the plain app.* defaults.

Local mode is desktop only. iOS and Android targets need remote mode: a phone cannot boot a compiled Rails server. See Local vs Remote mode.

platforms.android.target_sdk

The one override that is not an app.* key, and the only SDK level an app can set:

config/everywhere.yml yaml
platforms:
  android:
    bundle_id: com.example.notes.android
    target_sdk: 37    # ship ahead of a Play deadline

Google Play raises the minimum targetSdk for new uploads every August (see the support matrix). The Android shell tracks that floor, so normally you never touch this: upgrading the gem moves it. This covers the one case upgrading cannot, a deadline that lands before our next release does.

It only goes up. Setting it below the shell's tested level is refused, because a build under Play's floor still succeeds locally and is rejected months later at upload.

Raising it means accepting OS behaviour the shell has not been tested against. targetSdk is a contract with the operating system, not a preference: every behaviour change it opts into (edge-to-edge enforcement, permission semantics, background limits) lands on the native code inside the shell, not on your Rails app. Test the build on a device running that OS version before you ship it, and drop the key once a gem release catches up.

There is deliberately no compile_sdk. From API 37 on, a platform also needs a matching minor version (android-37.0, not android-37) and a Gradle plugin that knows the level, so moving it is a shell change rather than a config one.


It is the same file whether you build it yourself or on the Platform.

Rails · Hanami · Sinatra — built with Ruby