Customize and AdminCommands and Actions
Customize and Admin

Custom Commands and Actions

cmux.json is where you teach cmux your own routines: shell commands you run often, workspaces that open with a preset arrangement of terminals and browsers, and actions you can bind to shortcuts, tab bar buttons, and the Command Palette. Keep one at .cmux/cmux.json inside a project, at ~/.config/cmux/cmux.json for all projects, or both.

Where cmux reads cmux.json

A project file, a user-wide file, or both can supply entries. For project files, cmux starts at the current directory and also checks each parent directory above it.

  • Project: ./.cmux/cmux.json (found by searching upward from the working directory; wins over the user-wide file)
  • Fallback local: ./cmux.json (still supported for existing repos)
  • User-wide: ~/.config/cmux/cmux.json (shared by every project; supplies whatever a project file leaves undefined)
Local actions and commands override global entries with the same ID or name.

Edit cmux.json, then press Cmd+Shift+, or run cmux reload-config to apply changes.

If a project or global config has a schema error, cmux falls back to the next valid config and shows a cmux.json Schema Error row in Command Palette. Select it to open the config file.

Shell commands

The smallest entry is a name plus a command string. Picking it in the Command Palette types that string into whichever terminal has focus and runs it.

cmux.json
{
  "commands": [
    {
      "name": "Format Changed Files",
      "description": "Run the formatter on files that differ from main",
      "keywords": [
        "fmt",
        "prettier",
        "style"
      ],
      "command": "git diff --name-only main -- '*.ts' '*.tsx' | xargs npx prettier --write"
    },
    {
      "name": "Reset Local Database",
      "keywords": [
        "db",
        "wipe"
      ],
      "command": "cd \"$(git rev-parse --show-toplevel)\" && ./scripts/db-reset.sh",
      "confirm": true
    }
  ]
}

Shell command fields

  • name: Required, non-blank label you pick from the Command Palette
  • command: The shell text cmux types into the focused terminal and runs
  • description: Optional secondary text shown alongside the entry
  • keywords: More words that should match this entry when you search the Command Palette
  • confirm: Set to true to get a confirmation prompt each time before it runs

A shell command executes wherever the focused terminal happens to be. When the command needs a predictable starting point, begin it with cd "$(git rev-parse --show-toplevel)" && to move to the top of the Git repository, or with cd /some/folder && to pin it to a fixed folder.

Commands that open a workspace

Give a command a workspace object in place of a command string and it opens a new workspace instead, arranged as a tree of split panes holding terminals and browsers. An entry needs exactly one of the two.

cmux.json
{
  "commands": [
    {
      "name": "API Stack",
      "keywords": [
        "api",
        "server",
        "logs"
      ],
      "restart": "confirm",
      "workspace": {
        "name": "API",
        "cwd": "./services/api",
        "color": "#10b981",
        "env": {
          "PORT": "8080"
        },
        "layout": {
          "direction": "vertical",
          "split": 0.65,
          "children": [
            {
              "direction": "horizontal",
              "children": [
                {
                  "pane": {
                    "surfaces": [
                      {
                        "type": "terminal",
                        "name": "Server",
                        "command": "go run ./cmd/api",
                        "focus": true
                      }
                    ]
                  }
                },
                {
                  "pane": {
                    "surfaces": [
                      {
                        "type": "terminal",
                        "name": "Worker",
                        "command": "go run ./cmd/worker",
                        "env": {
                          "QUEUE": "default"
                        }
                      }
                    ]
                  }
                }
              ]
            },
            {
              "pane": {
                "surfaces": [
                  {
                    "type": "terminal",
                    "name": "Logs",
                    "cwd": "/var/log",
                    "command": "tail -F system.log"
                  },
                  {
                    "type": "browser",
                    "name": "Health",
                    "url": "http://localhost:8080/healthz"
                  }
                ]
              }
            }
          ]
        }
      }
    }
  ]
}

Fields of a workspace object

  • name: Title for the workspace; falls back to the command name
  • cwd: Base directory for the workspace; each tab cwd resolves against it
  • layout: The root node of the layout tree described below
  • color: Sidebar color, as #RRGGBB hex or a workspace color name
  • env: Environment variables inherited by every shell in the workspace
  • setup: Bootstrap command sent to the workspace's first terminal before that terminal's own command

When the workspace is already open

Set restart on the command to decide what happens if a workspace with the same name already exists:

  • "new": Create a new workspace (default)
  • "confirm": Prompt first, then replace it if you agree
  • "recreate": Replace it right away with no prompt
  • "ignore": Switch to the existing workspace

Describing a layout

A layout is a tree. Every inner node splits its area in two, and every leaf is a pane that holds tabs.

Split nodes

A split divides its area into two parts:

  • direction: "horizontal" or "vertical"
  • children: An array of exactly two nodes, each of which is another split or a pane
  • split: Where the divider sits, as a fraction of the area. Defaults to 0.5; values outside 0.1 to 0.9 are clamped

Pane nodes

A pane is a leaf of the tree. Its surfaces array lists the tabs it opens with, and it must contain at least one entry.

Surfaces

Each entry in a pane's surfaces array opens one tab, which is either a terminal or a browser:

  • type: "terminal" or "browser"
  • name: Tab title to show in place of the default
  • focus: Set to true to put keyboard focus on this tab once the layout is built
  • command: Terminals only: shell text to run as soon as the terminal opens
  • url: Browsers only: the page to load
  • cwd: Starting directory for this tab, resolved with the rules below
  • env: An object of extra environment variable names and values

How cwd values resolve

  • ~/path: resolved inside your home folder
  • A path that starts with /: taken literally
  • ./subdir: appended to the workspace directory
  • . or unset: inherits the workspace directory

File structure

The action registry is a nightly feature. Install the latest nightly build before using actions, shortcut, or ui.surfaceTabBar.buttons.

commands still define reusable shell commands and workspace layouts. Nightly builds add an actions registry. Actions are the public IDs shared by the surface tab bar, the Command Palette, and action-level shortcuts.

cmux.json
{
  "actions": {
    "cmux.newTerminal": {
      "type": "agent",
      "agent": "claude",
      "title": "Claude Code",
      "subtitle": "Start Claude Code in a new tab"
    },
    "lint": {
      "type": "command",
      "title": "Lint",
      "subtitle": "Run the linter in a fresh tab",
      "keywords": [
        "eslint",
        "check"
      ],
      "command": "npm run lint",
      "shortcut": [
        "cmd+k",
        "cmd+l"
      ],
      "icon": {
        "type": "symbol",
        "name": "checkmark.seal"
      }
    },
    "scratch-dir": {
      "type": "command",
      "title": "Scratch Directory",
      "command": "cd \"$(mktemp -d)\"",
      "target": "currentTerminal",
      "palette": false,
      "icon": {
        "type": "emoji",
        "value": "📝",
        "scale": 0.85
      }
    },
    "api-stack": {
      "type": "workspaceCommand",
      "title": "API Stack",
      "commandName": "API Stack"
    }
  },
  "ui": {
    "surfaceTabBar": {
      "buttons": [
        "cmux.newTerminal",
        "cmux.splitRight",
        "lint",
        {
          "action": "scratch-dir",
          "tooltip": "cd into a new temp folder"
        }
      ]
    }
  },
  "commands": [
    {
      "name": "API Stack",
      "keywords": [
        "api"
      ],
      "workspace": {
        "cwd": "./services/api",
        "layout": {
          "pane": {
            "surfaces": [
              {
                "type": "terminal",
                "name": "Server",
                "command": "go run ./cmd/api"
              }
            ]
          }
        }
      }
    }
  ]
}

Nightly action registry

actions maps stable IDs to runnable behavior. Use the built-in IDs cmux.newTerminal, cmux.newBrowser, cmux.splitRight, and cmux.splitDown to override the defaults. Use your own IDs for project-specific tools.

palette defaults to true. Set it to false to keep an action out of Command Palette while still making it available to the surface tab bar or a shortcut. shortcut uses the same syntax as settings shortcuts, for example cmd+shift+c or ["cmd+k", "cmd+c"].

ui.surfaceTabBar.buttons replaces the default button list when present. Leave out a built-in ID to hide it. Icons always use an object shape: { "type": "symbol", "name": "play.circle" }, { "type": "emoji", "value": "🧪", "scale": 0.9 }, or { "type": "image", "path": "./icons/codex.svg" }. Image paths are relative to the config file. Emoji scale is optional and defaults to 1. SVG, PDF, PNG, JPEG, GIF, TIFF, BMP, HEIC, HEIF, WebP, AVIF, ICO, and ICNS are supported.

Each button entry can be either an action ID string or a button object. Use a button object when you want the same action with a different surface label, icon, or tooltip. The resolved button title is also used as the trust prompt title.

Put any approval or permission flags directly in the command string you actually want to run. The default action target is newTabInCurrentPane, so the common pattern is to open a new terminal tab in the current pane and start Codex, Claude Code, or OpenCode there.

Project-local actions show up in the surface tab bar and Command Palette immediately. The first run still prompts for trust. Trust is per exact action fingerprint, not per repo. Project-local image icons stay locked until that action is trusted.

Custom actions and Command Palette

An actions entry is the reusable thing cmux runs. Use actions when the same behavior should be available from the Command Palette, surface tab bar, shortcuts, or the plus-button menu. Keep commands for reusable shell commands and workspace layouts. Set palette to false when an action should stay out of Command Palette.

Action types

  • "command": Run shell text in a terminal. Use target to choose the current terminal or a new tab in the current pane.
  • "agent": Start a coding agent in a new terminal tab. Any CLI name works (claude, codex, opencode, or your own binary), with optional args.
  • "builtin": Alias a built-in cmux action such as cmux.newTerminal, cmux.newBrowser, cmux.splitRight, or cmux.splitDown. Copy built-ins: cmux.copyWorkingDirectory copies the terminal's working directory, cmux.copyProjectRoot copies the root of its git repository (or the working directory outside one), and cmux.copyScreen copies the visible screen. In an SSH, cloud, or remote tmux workspace, cmux.copyProjectRoot copies the remote working directory, because cmux can't look up the git repository on the other host.
  • "workspaceCommand": Run a named workspace definition from commands. Use this for multi-pane layouts, custom working directories, and startup commands.
  • "workspace": Create a workspace from an inline workspace definition, with optional restart behavior. Offered in the new-workspace plus-button menu automatically.
  • "setting": Change one setting in your global cmux.json, for example toggle a true/false setting or cycle through a list of values. See Setting actions below.
  • "settingPreset": Apply a named group of settings from settingPresets in one step.

Action fields

  • title: Command Palette row title, surface button label, menu item title, and trust prompt title unless an entry overrides it.
  • subtitle / description: Command Palette secondary text. description is accepted as an alias for subtitle.
  • keywords: Extra search terms for Command Palette.
  • palette: Defaults to true for custom actions. Set false to hide the action from Command Palette while keeping it callable elsewhere.
  • shortcut: Optional action shortcut, using the same single-stroke or two-stroke chord syntax as settings shortcuts.
  • target: For command and agent actions only. Use currentTerminal or newTabInCurrentPane.
  • confirm: Ask before running the action.
  • newWorkspaceMenu: Set true to offer any action in the new-workspace plus-button menu, or false to hide a workspace action from it.

Command Palette behavior

Command Palette reads the resolved action registry. Custom action IDs are added as rows when palette is not false. Legacy commands are added automatically as custom rows unless an action with the same generated ID already exists. Built-in commands keep their normal palette labels, but overriding a built-in ID such as cmux.newTerminal changes the behavior behind that shared entrypoint.

Setting actions

A setting action names a settings path and exactly one of set (write a value), toggle: true (flip a true/false setting), cycle (move to the next value in a list, wrapping around), or unset: true (remove the key from cmux.json, so the value from the Settings window or the default applies). toggle and cycle start from the value cmux is using, including one you set in the Settings window. A settingPreset action applies an entry from settingPresets, a partial cmux.json whose nested objects merge key by key. cmux checks each change against the schema before saving, keeps your comments and other keys, and applies it right away. Setting actions and presets only run from your global cmux.json and the packs it references; project configs can't declare them.

~/.config/cmux/cmux.json
{
  "actions": {
    "scroll.cycle": {
      "type": "setting",
      "title": "Cycle Scroll Speed",
      "path": "terminal.scrollSpeed",
      "cycle": [1.0, 1.4, 1.8]
    },
    "editor.wrap": {
      "type": "setting",
      "title": "Toggle Editor Word Wrap",
      "path": "fileEditor.wordWrap",
      "toggle": true
    },
    "sidebar.quiet": {
      "type": "settingPreset",
      "title": "Quiet Sidebar",
      "preset": "sidebar.quiet"
    }
  },
  "settingPresets": {
    "sidebar.quiet": {
      "sidebar": { "showPorts": false, "showPullRequests": false, "showLog": false }
    }
  }
}

The same changes work from a terminal: cmux config set terminal.scrollSpeed 1.4, cmux config toggle fileEditor.wordWrap, and cmux config preset sidebar.quiet.

With confirm: true, cmux asks before saving and shows the equivalent cmux config command. Paths split on every ".", so a key that itself contains "." (such as a workspaceGroups.byCwd entry for ~/src/app.web) can't be changed this way. Edit it in cmux.json directly.

Custom plus button actions

Use ui.newWorkspace.action to override what the plus button does. Use ui.newWorkspace.contextMenu (or the rightClick alias) to define the ordered right-click menu. Menu entries can be action IDs, action objects, or { "type": "separator" }. When it is not set, the menu lists cmux.newWorkspace (⌘N), cmux.newCloudWorkspace (⌘Y), cmux.newCloudMachine (⇧⌘Y), cmux.newTerminal (⌘T), cmux.newBrowser (⇧⌘L), each showing its current shortcut. New Cloud Workspace uses the last selected Cloud workspace's machine, then the first machine in the right-hand sidebar; when no usable machine exists, it opens New Cloud Machine. New Cloud Machine provisions a machine deliberately. Cloud rows appear only when Cloud Machines is enabled.

cmux.json
{
  "actions": {
    "worktree-agents": {
      "type": "workspaceCommand",
      "title": "Agents in New Worktree",
      "commandName": "Agents in New Worktree",
      "icon": {
        "type": "symbol",
        "name": "arrow.triangle.branch"
      }
    }
  },
  "ui": {
    "newWorkspace": {
      "action": "worktree-agents",
      "contextMenu": [
        "worktree-agents",
        {
          "type": "separator"
        },
        {
          "action": "cmux.newWorkspace",
          "title": "Blank Workspace"
        },
        {
          "action": "cmux.newBrowser",
          "title": "Browser"
        }
      ]
    }
  },
  "commands": [
    {
      "name": "Agents in New Worktree",
      "description": "Branch off a new Git worktree, then run Codex and Claude Code in it side by side",
      "workspace": {
        "name": "Worktree",
        "layout": {
          "direction": "vertical",
          "split": 0.7,
          "children": [
            {
              "direction": "horizontal",
              "children": [
                {
                  "pane": {
                    "surfaces": [
                      {
                        "type": "terminal",
                        "name": "Codex",
                        "command": "marker=\"${TMPDIR:-/tmp}/cmux-wt-${CMUX_WORKSPACE_ID:-local}\"; echo \"Waiting for the worktree...\"; until [ -s \"$marker\" ]; do sleep 0.25; done; cd \"$(cat \"$marker\")\" && exec codex --yolo"
                      }
                    ]
                  }
                },
                {
                  "pane": {
                    "surfaces": [
                      {
                        "type": "terminal",
                        "name": "Claude",
                        "command": "marker=\"${TMPDIR:-/tmp}/cmux-wt-${CMUX_WORKSPACE_ID:-local}\"; echo \"Waiting for the worktree...\"; until [ -s \"$marker\" ]; do sleep 0.25; done; cd \"$(cat \"$marker\")\" && exec claude --dangerously-skip-permissions"
                      }
                    ]
                  }
                }
              ]
            },
            {
              "pane": {
                "surfaces": [
                  {
                    "type": "terminal",
                    "name": "Setup",
                    "command": "marker=\"${TMPDIR:-/tmp}/cmux-wt-${CMUX_WORKSPACE_ID:-local}\"; rm -f \"$marker\"; root=$(git rev-parse --show-toplevel) || exit 1; branch=\"wt-$(date +%m%d-%H%M%S)\"; wt=\"$root/../$(basename \"$root\")-$branch\"; git -C \"$root\" worktree add -b \"$branch\" \"$wt\" && printf \"%s\\n\" \"$wt\" > \"$marker\" && cd \"$wt\" && exec \"${SHELL:-/bin/zsh}\" -l",
                    "focus": true
                  }
                ]
              }
            }
          ]
        }
      }
    }
  ]
}

This example makes the normal plus click run the worktree-agents action. The workspace command from commands uses a visible setup terminal to create the Git worktree first. Codex and Claude start at the same time, wait for the workspace-specific state file, then cd into the created directory before exec.

Workspace layouts

A workspace layout is stored as a workspace action, embedding a full workspace definition directly in the action so it needs no separate commands entry. It supports the same workspace fields plus setup, a bootstrap command sent to the first terminal before that terminal's own command:

cmux.json
{
  "actions": {
    "pair-review": {
      "type": "workspace",
      "title": "Pair Review",
      "icon": {
        "type": "symbol",
        "name": "person.2.wave.2"
      },
      "restart": "ignore",
      "newWorkspaceMenu": true,
      "workspace": {
        "name": "Review",
        "cwd": "~/src/app",
        "setup": "git pull --ff-only",
        "layout": {
          "direction": "horizontal",
          "split": 0.55,
          "children": [
            {
              "pane": {
                "surfaces": [
                  {
                    "type": "terminal",
                    "name": "Diff",
                    "command": "git log -p -n 5",
                    "focus": true
                  }
                ]
              }
            },
            {
              "pane": {
                "surfaces": [
                  {
                    "type": "terminal",
                    "name": "Codex",
                    "command": "codex"
                  },
                  {
                    "type": "terminal",
                    "name": "Shell"
                  }
                ]
              }
            }
          ]
        }
      }
    }
  }
}

Workspace layouts appear in the plus-button right-click menu automatically. Set newWorkspaceMenu to false to hide one, or to true on any other action to add it to the menu.

You can also create these without writing JSON: right-click the plus button and choose Save Workspace as Layout to capture the current workspace's splits, directories, running agents, and browser tabs into ~/.config/cmux/cmux.json. They are stored as entries in the actions block. Customize Workspace Layouts opens that file in your editor.

Default for new workspaces

Use the Default for New Workspace submenu to pick which saved workspace layout plain New Workspace should run, or choose None to return to a blank terminal. The save dialog also has a Use as default for new workspaces checkbox. The setting is ui.newWorkspace.action; a project-local .cmux/cmux.json value wins over ~/.config/cmux/cmux.json.

Full example

cmux.json
{
  "actions": {
    "cmux.newTerminal": {
      "type": "command",
      "title": "Codex",
      "command": "codex --yolo",
      "shortcut": "cmd+t",
      "icon": {
        "type": "image",
        "path": "./icons/codex.svg"
      }
    },
    "review": {
      "type": "agent",
      "agent": "claude",
      "args": "--permission-mode plan",
      "title": "Plan with Claude",
      "shortcut": "cmd+shift+p",
      "icon": {
        "type": "symbol",
        "name": "list.bullet.clipboard"
      }
    },
    "storybook": {
      "type": "workspaceCommand",
      "commandName": "Storybook",
      "newWorkspaceMenu": true
    },
    "typecheck": {
      "type": "command",
      "title": "Typecheck",
      "command": "npx tsc --noEmit",
      "target": "newTabInCurrentPane",
      "confirm": false,
      "icon": {
        "type": "emoji",
        "value": "🔎"
      }
    }
  },
  "ui": {
    "surfaceTabBar": {
      "buttons": [
        "cmux.newTerminal",
        "review",
        {
          "action": "typecheck",
          "title": "tsc"
        },
        "cmux.newBrowser",
        "cmux.splitDown"
      ]
    },
    "newWorkspace": {
      "contextMenu": [
        "storybook",
        {
          "type": "separator"
        },
        "cmux.newTerminal",
        "cmux.newBrowser"
      ]
    }
  },
  "commands": [
    {
      "name": "Storybook",
      "description": "Component explorer with a live preview beside it",
      "keywords": [
        "ui",
        "components",
        "stories"
      ],
      "workspace": {
        "name": "Storybook",
        "cwd": "./packages/ui",
        "color": "#a855f7",
        "layout": {
          "direction": "horizontal",
          "split": 0.4,
          "children": [
            {
              "direction": "vertical",
              "split": 0.5,
              "children": [
                {
                  "pane": {
                    "surfaces": [
                      {
                        "type": "terminal",
                        "name": "Storybook",
                        "command": "npx storybook dev -p 6006 --no-open",
                        "focus": true
                      }
                    ]
                  }
                },
                {
                  "pane": {
                    "surfaces": [
                      {
                        "type": "terminal",
                        "name": "Tests",
                        "command": "npx vitest --watch",
                        "env": {
                          "CI": "false"
                        }
                      }
                    ]
                  }
                }
              ]
            },
            {
              "pane": {
                "surfaces": [
                  {
                    "type": "browser",
                    "name": "Preview",
                    "url": "http://localhost:6006"
                  }
                ]
              }
            }
          ]
        }
      }
    },
    {
      "name": "Install Dependencies",
      "keywords": [
        "install",
        "deps"
      ],
      "command": "npm ci",
      "confirm": true
    },
    {
      "name": "Open Changelog",
      "keywords": [
        "changes",
        "release"
      ],
      "command": "${EDITOR:-vi} CHANGELOG.md"
    }
  ]
}