Skip to content

App Rules

acsandmann edited this page Aug 4, 2026 · 1 revision

App rules

App rules let Rift make decisions automatically when it discovers a window. A rule can send a window to a workspace, make it floating, give it an initial position or size, focus it, or tell Rift not to manage it at all.

Rules are configured in the [virtual_workspaces] section of ~/.config/rift/config.toml:

[virtual_workspaces]
enabled = true
auto_assign_windows = true
app_rules = []

Tip

App rules are evaluated when windows are discovered(or opened). If a title-based rule needs to react after an application changes its title, enable reapply_app_rules_on_title_change as described below.

How a rule works

Each item in app_rules has two kinds of fields:

  1. Match fields identify which windows the rule applies to.
  2. Action fields describe what Rift should do with a matching window.

For example:

app_rules = [
  { app_id = "com.apple.Terminal", workspace = "coding" },
  { app_id = "com.apple.Calculator", floating = true, size = { w = 420.0, h = 680.0 } },
]

The first rule sends Terminal windows to the workspace named coding. The second makes Calculator windows float and gives them an initial size.

Rules do not continuously force a window into a state after every manual change. Position and size are initial placement hints, and a floating window remains freely movable afterward.

Configuration fields

Match fields

All match fields supplied in one rule must match the same window. In other words, fields are combined with AND, not OR.

Field Match behavior Example
app_id Case-insensitive exact match against the application bundle identifier "com.apple.Safari"
app_name Case-insensitive fuzzy name match; the configured value and reported name may contain one another "Safari"
title_regex Case-insensitive regular expression matched against the window title `"^(Inbox
title_substring Case-insensitive literal substring matched against the window title "Preferences"
ax_role Case-sensitive exact match against the macOS Accessibility role "AXWindow"
ax_subrole Case-sensitive exact match against the macOS Accessibility subrole "AXDialog"

A rule with only app_id matches every discovered window belonging to that application. A rule with both app_id and title_substring matches only that app's windows whose titles contain the configured text.

Finding an application bundle identifier

Use mdls with the application bundle to inspect its identifier:

mdls -name kMDItemCFBundleIdentifier -r /Applications/Safari.app

Bundle identifiers are usually more stable than application names, so prefer app_id for rules that should survive renames or localization.

Titles and regular expressions

Use title_substring for straightforward title matching. Use title_regex when the title has a predictable pattern:

app_rules = [
  { app_id = "com.example.Editor", title_regex = "^project-[0-9]+$", workspace = "coding" },
]

The regex is case-insensitive. Invalid regular expressions are ignored and Rift logs a warning; the rest of the configuration can still load. TOML strings use backslashes for escaping, so prefer single-quoted TOML strings when that makes a pattern easier to read:

app_rules = [
  { title_regex = '^Notes: \\d+$', floating = true },
]

Action fields

Field Behavior
workspace Assigns the window to a workspace by 0-based index or by workspace name. If omitted, the active workspace is used.
floating Makes the matching window floating when true. Set it to false in a more specific rule to keep an exception tiled.
position Sets an initial normalized position for a floating window. x = 0.0, y = 0.0 is top-left; x = 1.0, y = 1.0 is bottom-right.
size Sets an initial size in logical pixels. w and h are optional, so either dimension can be left out.
focus Focuses the matching window after applying the rule. If necessary, Rift switches to the target workspace.
manage Controls whether Rift manages the window. false makes Rift ignore it completely; the default is true.

position and size are tables:

position = { x = 0.5, y = 0.25 }
size = { w = 900.0, h = 650.0 }

For a floating window, Rift applies the requested size and then resolves the normalized position against the available screen area. For a tiled window, a requested size is applied once after insertion into the layout rather than turning the window into a floating window.

Rule selection and precedence

Rift evaluates every rule that matches a window, then chooses one rule to provide the actions. The selection process is:

  1. Count the non-empty match fields in each matching rule. This is its specificity.
  2. The rule with the highest specificity wins.
  3. If rules have equal specificity, the rule that appears earlier in app_rules wins.

Action fields such as workspace, floating, size, and focus do not make a rule more specific. Specificity is based only on the match fields.

This makes a general rule plus a more specific exception easy to express:

app_rules = [
  # Two match fields: this wins for Terminal windows titled "ssh ...".
  { app_id = "com.apple.Terminal", title_regex = "^ssh ", workspace = "remote", floating = false },

  # One match field: other Terminal windows go to coding.
  { app_id = "com.apple.Terminal", workspace = "coding" },
]

The order of the rules matters when their specificity ties:

app_rules = [
  # This wins because it appears first when both rules match equally.
  { app_name = "Browser", workspace = "web" },
  { app_name = "Browser", workspace = "research" },
]

When designing exceptions, give the exception an additional match field whenever possible instead of relying on order alone.

Common recipes

Assign an application to a workspace

Workspace indexes are zero-based. This sends an application to the third workspace:

app_rules = [
  { app_id = "com.apple.Xcode", workspace = 2 },
]

Names are often easier to maintain. Define them in the same section and reference them from a rule:

[virtual_workspaces]
workspace_names = ["main", "web", "coding", "chat"]

app_rules = [
  { app_id = "com.microsoft.VSCode", workspace = "coding" },
  { app_id = "com.apple.Safari", workspace = "web" },
  { app_id = "com.hnc.Discord", workspace = "chat" },
]

Float a utility window

app_rules = [
  { app_id = "com.apple.Calculator", floating = true },
  { app_id = "com.raycast.macos", floating = true },
]

Float dialogs but tile the rest of an application

Match the dialog using its Accessibility subrole when possible:

app_rules = [
  { app_id = "com.example.Editor", ax_subrole = "AXDialog", floating = true },
  { app_id = "com.example.Editor", floating = false },
]

The dialog rule has two match fields and therefore wins over the general application rule for matching dialogs.

Create a centered floating window

Normalized coordinates describe the available travel space after the window's size is applied. 0.5, 0.5 centers the window:

app_rules = [
  { app_id = "com.example.Preview", floating = true, position = { x = 0.5, y = 0.5 }, size = { w = 800.0, h = 600.0 } },
]

Focus a newly discovered window

app_rules = [
  { app_id = "com.apple.Spotlight", focus = true },
]

Use focus = true carefully: applications that open helper windows frequently can cause Rift to switch workspaces unexpectedly.

Ignore an application completely

app_rules = [
  { app_id = "com.example.Overlay", manage = false },
]

An unmanaged window is invisible to Rift's tiling, floating, and workspace assignment logic. Use this for overlays, helper windows, or applications that should behave exactly as they do without a window manager.

Match a title that changes over time

[virtual_workspaces]
reapply_app_rules_on_title_change = true

app_rules = [
  { app_id = "com.example.Editor", title_regex = "^Build failed", workspace = "debug", focus = true },
]

With this option enabled, Rift re-evaluates a known window when its title changes. It is disabled by default so ordinary title updates do not repeatedly reassign or focus windows.

Troubleshooting

The rule never matches

  • Confirm the application bundle identifier with mdls.
  • Start with only app_id or app_name, then add title or Accessibility conditions one at a time.
  • Remember that all match fields are ANDed together.
  • Check title spelling and capitalization when using title_substring; matching is case-insensitive, but the text still needs to be present.
  • For dialogs and sheets, inspect the application's Accessibility attributes. AXDialog and AXSystemDialog are common subroles, but applications can report different values.

The window goes to the wrong workspace

  • Check that workspace indexes are zero-based.
  • Prefer a workspace name over a numeric index if the workspace list changes frequently.
  • Look for another matching rule with more match fields.
  • If two rules have equal specificity, remember that the earlier rule wins.
  • Ensure auto_assign_windows is enabled.

A title rule works only sometimes

Some applications create a window before its final title is available. Enable reapply_app_rules_on_title_change so Rift evaluates the rule again after title updates.

The rule does not appear to reload

Make sure the file is at ~/.config/rift/config.toml, check that the TOML is valid, and reload the configuration:

rift-cli execute config reload

You can also enable configuration hot reload in [settings]:

[settings]
hot_reload = true

Use rift-cli query windows to inspect the windows Rift currently knows about, including their application and title data. If a title_regex is invalid, Rift logs a warning and ignores that rule.

Related documentation