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
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:
appearance:
background_color:
light: "#ffffff"
dark: "#09090b"
tint_color, the accent color, takes the same shape.
Menus and the tray
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:
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.
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:
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:
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:
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:
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):
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:
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.