Skip to content

Configuration

Create one of these in your project root:

  • .stagelint.yml
  • .stagelint.yaml
  • .stagelint.json
  • .stagelint.jsonc
  • .stagelint.json5

If a directory has more than one, the first in this list is used.

The format is a map of glob patterns to commands. A pattern takes a command as a string, a command as an object, or a list of either to run in sequence:

.stagelint.yml
yaml
'*.md': prettier --write

'*.go':
  command: go vet ./...
  pass_filenames: false

'*.ts':
  - eslint --fix
  - command: tsc --noEmit
    pass_filenames: false

Matching files are appended to each command as absolute paths.

Glob patterns

A pattern with no / matches by basename at any depth, so *.ts covers both app.ts and src/app.ts. Any / anchors it: src/*.ts matches only files directly in src, and a leading / or ./ pins it to the top level, so ./*.ts excludes src/app.ts.

Matching is case sensitive, so *.ts does not match App.TS.

SyntaxMatches
*zero or more characters, never /
**zero or more directories, as a whole path component
?exactly one character, never /
[abc]one of a, b or c
[a-z]one character in the range
[!abc]one character other than a, b or c
{a,b}either a or b, each of which may itself be a pattern
[*]a literal *, and likewise for the other characters above

When several patterns match the same file, their tasks run in sequence, in declaration order. That is how you order dependent commands.

Extglobs match nothing

Extglob syntax, such as the negation !(*.ts) or +(...), is not supported. The pattern is accepted but matches no file, so its task never runs rather than failing. A negation is usually better written as two overlapping patterns.

If nothing matches, or no config file is found at all, stagelint prints a warning and exits successfully.

Task options

The object form takes two keys.

KeyDefaultDescription
commandrequiredThe command line to run.
pass_filenamestrueWhether matching files are appended as arguments. Set false for commands that scan the project themselves, such as tsc --noEmit or go vet ./....

Command syntax

Commands are split into arguments using POSIX shell rules on all platforms, so quote any argument containing spaces or backslashes.

yaml
'*.ts': prettier --write --config "config/my prettier.json"

Commands run directly, not through a shell, so shell syntax has no special meaning and reaches the command as plain arguments. Use a list to run commands in sequence, or wrap the command in sh -c 'tool "$@"' _ when you need a shell, where the trailing _ becomes $0 so the files start at $1 and "$@" covers all of them. Without it the first file is taken as $0 and never linted.

Monorepos

Place config files at any level in the repo. Each staged file uses the nearest config file above it.

text
.
├── .stagelint.yml
└── packages/
    ├── api/
    │   ├── .stagelint.yml
    │   └── main.go         # uses packages/api/.stagelint.yml
    └── web/
        └── app.ts          # uses .stagelint.yml

Config files do not merge, so main.go gets only the patterns in packages/api/.stagelint.yml, not the root ones.

A config file's own directory is the base for everything in it. Patterns match relative to that directory, and commands run with it as their working directory, so packages/api/.stagelint.yml matches paths under packages/api and runs its commands there.

Command resolution

Commands resolve to the tools installed in your project where available, so you do not have to prefix them with npx, uv run or a path. Any of these directories is added to PATH:

  • node_modules/.bin
  • .venv/bin, or .venv/Scripts on Windows
  • vendor/bin

stagelint looks for them in the config file's own directory, and in every directory above it up to the repository root. So '*.ts': eslint --fix runs the eslint your lockfile pins rather than whatever is installed globally. In a monorepo, a config in packages/web picks up its own node_modules/.bin before the repository root's, so hoisted and non-hoisted layouts both work.

If no project-local copy exists, the command resolves through your normal PATH as usual.