# Configuration Reference

This page documents the configuration keys that `prek` understands.

## Global config file

`prek` reads an optional user-level global config from:

- Linux and macOS: `~/.config/prek/prek.toml` (or `$XDG_CONFIG_HOME/prek/prek.toml` when `XDG_CONFIG_HOME` is set)
- Windows: `%APPDATA%\prek\prek.toml`

This file stores user-level `prek` settings and does not define project hooks.

### Global `update`

User-level defaults for [`prek update`](cli.md#prek-update):

| Key | Type | Default | CLI override |
| -- | -- | -- | -- |
| `update.cooldown_days` | integer days, `0` to `255` | `0` | [`--cooldown-days <DAYS>`](cli.md#prek-update) |
| `update.freeze` | boolean | `false` | [`--freeze`](cli.md#prek-update--freeze), which forces freezing on |
| `update.include_tags` | glob string or list of glob strings | empty | [`--include-tag`](cli.md#prek-update--include-tag) |
| `update.exclude_tags` | glob string or list of glob strings | empty | [`--exclude-tag`](cli.md#prek-update--exclude-tag) |

```toml
[update]
cooldown_days = 7
freeze = true
include_tags = "v*"
exclude_tags = ["*-{alpha,beta,rc}*"]
```

Each field is resolved independently with this precedence:

1. the corresponding CLI option, when provided
2. [project `update`](#update)
3. user-level global config
4. the default shown above

The cooldown age is computed from the tag creation timestamp for annotated tags, or from the tagged commit timestamp for lightweight tags. A value of `0` disables the cooldown check.

!!! tip "Cooldowns never downgrade"

    If the current `rev` is newer than the latest cooldown-eligible tag, [`prek update`](cli.md#prek-update) keeps the current `rev` instead of downgrading it.

!!! note "Compatibility alias"

    The legacy `auto_update` key is still accepted as an alias for `update`.

## Extension keys (`x-`)

Any key starting with `x-` (a lowercase `x` followed by a hyphen) is silently ignored by `prek` at any level of the configuration. This supports custom metadata without triggering unexpected key warnings.

## Top-level keys

### `repos` (required)

A list of hook repositories.

Each entry is one of:

- a remote repository (typically a git URL)
- `repo: local` for hooks defined directly in your repository
- `repo: meta` for built-in meta hooks
- `repo: builtin` for `prek`'s built-in fast hooks

See [Repo entries](#repo-entries).

### `priorities`

!!! note "prek-only"

    Priority aliases are a `prek` extension and do not exist in upstream `pre-commit`.

An optional mapping that declares configuration-local aliases for non-negative integer priorities.
A hook can use one of these aliases in its [`priority`](#priority) field instead of repeating the
integer.

- Type: mapping from string to non-negative integer
- Default: empty mapping
- Scope: the current project configuration only
- Aliases: non-empty, case-sensitive strings without whitespace

Different aliases may map to the same integer. Unused declarations are allowed. Referencing an
alias that is not declared in the current configuration is an error.

<a id="top-level-files"></a>

### `files`

Global *include* regex applied before hook-level filtering.

- Type: regex string (default, pre-commit compatible) **or** a prek-only glob pattern mapping
- Default: no global include filter

This is usually used to narrow down the universe of files in large repositories.

!!! note "What path is matched? (workspace + nested projects)"

    `files` (and `exclude`) are matched against the file path **relative to the project root** — i.e. the directory containing the configuration file.

    - For the root project, this is the workspace root.
    - For a nested project, this is the nested project directory.

    Example (workspace mode):

    - Root project config: `./.pre-commit-config.yaml`
    - Nested project config: `./nested/.pre-commit-config.yaml`

    For a file at `nested/excluded_by_project`:

    - Root project sees the path as `nested/excluded_by_project`
    - Nested project sees the path as `excluded_by_project`

    This matters most for anchored patterns like `^...$`.

!!! tip "Regex matching"

    When `files` / `exclude` are regex strings, they are matched with *search* semantics (the pattern can match anywhere in the path).
    Use `^` to anchor at the beginning and `$` at the end.

    `prek` uses the Rust [`fancy-regex`](https://github.com/fancy-regex/fancy-regex) engine.
    Most typical patterns are portable to upstream `pre-commit`, but very advanced regex features may differ from Python’s `re`.

!!! note "prek-only globs"

    In addition to regex strings, `prek` supports glob patterns via:

    - `files: { glob: "..." }` (single glob)
    - `files: { glob: ["...", "..."] }` (glob list)

    This is a `prek` extension. Upstream `pre-commit` expects regex strings here.

    For more information on the glob syntax, refer to the [globset documentation](https://docs.rs/globset/latest/globset/#syntax).

Examples:

=== "prek.toml"

    ```toml
    # Regex (portable to pre-commit)
    files = "\\.rs$"

    # Glob (prek-only)
    files = { glob = "src/**/*.rs" }

    # Glob list (prek-only; matches if any glob matches)
    files = { glob = ["src/**/*.rs", "crates/**/src/**/*.rs"] }
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    # Regex (portable to pre-commit)
    files: "\\.rs$"

    # Glob (prek-only)
    files:
      glob: "src/**/*.rs"

    # Glob list (prek-only; matches if any glob matches)
    files:
      glob:
        - "src/**/*.rs"
        - "crates/**/src/**/*.rs"
    ```

<a id="top-level-exclude"></a>

### `exclude`

Global *exclude* regex applied before hook-level filtering.

- Type: regex string (default, pre-commit compatible) **or** a prek-only glob pattern mapping
- Default: no global exclude filter

`exclude` is useful for generated folders, vendored code, or build outputs.

!!! note "What path is matched?"

    Same as [`files`](#top-level-files): the pattern is evaluated against the file path **relative to the project root** (the directory containing the config).

!!! note "prek-only globs"

    Like [`files`](#top-level-files), `exclude` supports `glob` (single glob or glob list) as a `prek` extension.
    For glob syntax details, see the [globset documentation](https://docs.rs/globset/latest/globset/#syntax).

Examples:

=== "prek.toml"

    ```toml
    # Regex (portable to pre-commit)
    exclude = "^target/"

    # Glob (prek-only)
    exclude = { glob = "target/**" }

    # Glob list (prek-only)
    exclude = { glob = ["target/**", "dist/**"] }
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    # Regex (portable to pre-commit)
    exclude: "^target/"

    # Glob (prek-only)
    exclude:
      glob: "target/**"

    # Glob list (prek-only)
    exclude:
      glob:
        - "target/**"
        - "dist/**"
    ```

Verbose regex example (useful for long allow/deny lists):

=== "prek.toml"

    ```toml
    # `(?x)` enables "verbose" regex mode (whitespace and newlines are ignored).
    exclude = """(?x)^(
      docs/|
      vendor/|
      target/
    )"""
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    # `(?x)` enables "verbose" regex mode (whitespace and newlines are ignored).
    exclude: |
      (?x)^(
        docs/|
        vendor/|
        target/
      )
    ```

### `fail_fast`

Stop the run after the first failing hook.

- Type: boolean
- Default: `false`

This is a global default; individual hooks can also set `fail_fast`.

### `default_language_version`

Map a language name to the default [`language_version`](#language_version) used by hooks of that language.

- Type: map
- Default: none (hooks fall back to `language_version: default`)

Example:

=== "prek.toml"

    ```toml
    default_language_version.python = "3.12"
    default_language_version.node = "20"
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    default_language_version:
      python: "3.12"
      node: "20"
    ```

`prek` treats [`language_version`](#language_version) as a version request (often a semver-like selector) and may install toolchains automatically. See [Difference from pre-commit](../diff.md).

### `default_stages`

Default [`stages`](#stages) used when a hook does not specify its own.

- Type: list of stage names
- Default: all stages

Allowed values:

- `manual`
- `commit-msg`
- `post-checkout`
- `post-commit`
- `post-merge`
- `post-rewrite`
- `pre-commit`
- `pre-merge-commit`
- `pre-push`
- `pre-rebase`
- `prepare-commit-msg`

See [Supported Git Hook Stages](#supported-git-hook-stages) for what each value means.

### `default_env`

<a id="prek-only-default-env"></a>

!!! note "prek-only"

    `default_env` is a `prek` extension and may not be recognized by upstream `pre-commit`.

Runtime environment variables to apply to every hook in this config.
If a hook sets the same variable in [`env`](#prek-only-env), the hook-level value wins.

- Type: map of string to string
- Default: none

Example:

=== "prek.toml"

    ```toml
    default_env = { UV_PYTHON = "", VIRTUAL_ENV = "" }
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    default_env:
      UV_PYTHON: ""
      VIRTUAL_ENV: ""
    ```

### `default_install_hook_types`

Default Git shim name(s) installed by [`prek install`](cli.md#prek-install) when you don’t pass `--hook-type`.

- Type: list of `--hook-type` values
- Default: `[pre-commit]`

This controls which Git shims are installed (for example `pre-commit` vs `pre-push`).
It is separate from a hook’s [`stages`](#stages), which controls when a particular hook is eligible to run.

Allowed values:

- `pre-commit`
- `pre-push`
- `commit-msg`
- `prepare-commit-msg`
- `post-checkout`
- `post-commit`
- `post-merge`
- `post-rewrite`
- `pre-merge-commit`
- `pre-rebase`

### `update`

!!! note "prek-only"

    This top-level key is a `prek` extension and is not recognized by upstream `pre-commit`.

Project settings for [`prek update`](cli.md#prek-update):

| Key | Type | Default or behavior |
| -- | -- | -- |
| `update.cooldown_days` | integer days, `0` to `255` | Inherited from the global config, or `0`. |
| `update.freeze` | boolean | Inherited from the global config, or `false`. |
| `update.include_tags` | glob string or list of glob strings | Inherited from the global config, or empty. Only consider matching tags. |
| `update.exclude_tags` | glob string or list of glob strings | Inherited from the global config, or empty. Ignore matching tags. |
| `update.repos` | map from repo to tag-filter fields | Override tag filters for a repository whose configured `repo` value exactly matches the map key. |

=== "prek.toml"

    ```toml
    [update]
    cooldown_days = 7
    freeze = false
    include_tags = "v*"
    exclude_tags = ["*-{alpha,beta,rc}*"]

    [update.repos."https://github.com/example/hooks"]
    include_tags = ["v1.*", "v2.*"]

    [update.repos."https://github.com/lycheeverse/lychee"]
    exclude_tags = ["nightly", "*-rc*", "*-dev*"]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    update:
      cooldown_days: 7
      freeze: false
      include_tags: "v*"
      exclude_tags: ["*-{alpha,beta,rc}*"]
      repos:
        "https://github.com/example/hooks":
          include_tags: ["v1.*", "v2.*"]
        "https://github.com/lycheeverse/lychee":
          exclude_tags: ["nightly", "*-rc*", "*-dev*"]
    ```

Each project-level field overrides the corresponding [global `update`](#global-update) field. Within `update.repos`, `include_tags` and `exclude_tags` are resolved independently: an omitted field inherits the project default, a present field replaces it, and `[]` explicitly clears it. This allows one repository to override only `include_tags` while still inheriting `exclude_tags`.

CLI filters have the highest precedence. `--include-tag` and `--exclude-tag` replace the configured effective defaults; `--repo-include-tag` then replaces the include filters for its named repository, while `--repo-exclude-tag` adds excludes for its named repository.

In workspace mode, `update` is scoped to the project config file that defines it and is not inherited by nested projects. Sub-projects use their own `update`, then the user-level global config, then built-in defaults. Repositories shared by multiple projects are fetched once but evaluated with each project's cooldown, freeze, and tag-filter settings.

!!! note "Compatibility alias"

    The legacy `auto_update` key is still accepted as an alias for `update`.

### `minimum_prek_version`

<a id="prek-only-minimum-prek-version-config"></a>

!!! note "prek-only"

    This key is a `prek` extension. Upstream `pre-commit` uses `minimum_pre_commit_version`, which `prek` intentionally ignores.

Require a minimum `prek` version for this config.

- Type: string (version)
- Default: unset

If the installed `prek` is older than the configured minimum, `prek` exits with an error.

Example:

=== "prek.toml"

    ```toml
    minimum_prek_version = "0.2.0"
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    minimum_prek_version: "0.2.0"
    ```

### `orphan`

<a id="prek-only-orphan"></a>

!!! note "prek-only"

    `orphan` is a `prek` workspace-mode feature and is not recognized by upstream `pre-commit`.

Workspace-mode setting to isolate a nested project from parent configs.

- Type: boolean
- Default: `false`

When `orphan: true`, files under this project directory are handled only by this project’s config and are not “seen” by parent projects.

Example:

=== "prek.toml"

    ```toml
    orphan = true

    [[repos]]
    repo = "https://github.com/astral-sh/ruff-pre-commit"
    rev = "v0.8.4"
    hooks = [{ id = "ruff" }]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    orphan: true
    repos:
      - repo: https://github.com/astral-sh/ruff-pre-commit
        rev: v0.8.4
        hooks:
          - id: ruff
    ```

See [Workspace Mode - File Processing Behavior](../workspace.md#file-processing-behavior) for details.

## Repo entries

Each item under `repos:` is a mapping that always contains a `repo:` key.

### Remote repository

Use this for hooks distributed in a separate repository.

Required keys:

- `repo`: repository location (commonly an https git URL)
- `rev`: version to use (tag, branch, or commit SHA)
- `hooks`: list of hook selections

Remote hook definitions live inside the hook repository itself in the
`.pre-commit-hooks.yaml` manifest (at the repo root). Your config only selects
hooks by `id` and optionally overrides options. See [Authoring Hooks](../authoring-hooks.md)
if you maintain a hook repository.

#### `repo`

Where to fetch hooks from.

In most configs this is a git URL.
`prek` also recognizes special values documented separately: `local`, `meta`, and `builtin`.

#### `rev`

The revision to use for the remote repository.

Use a tag or commit SHA for repeatable results.
If you use a moving target (like a branch name), runs may change over time.

#### `hooks`

The list of hooks to enable from that repository.

Each item must at least specify `id`.
You can also add hook-level options (filters, args, stages, etc.) to customize behavior.

Example:

=== "prek.toml"

    ```toml
    [[repos]]
    repo = "https://github.com/astral-sh/ruff-pre-commit"
    rev = "v0.8.4"
    hooks = [{ id = "ruff", args = ["--fix"] }]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    repos:
      - repo: https://github.com/astral-sh/ruff-pre-commit
        rev: v0.8.4
        hooks:
          - id: ruff
            args: [--fix]
    ```

Notes:

- For reproducibility, prefer immutable pins (tags or commit SHAs).
- [`prek update`](cli.md#prek-update) can help update [`rev`](#rev) values.

### `repo: local`

Define hooks inline inside your repository.

Keys:

- `repo`: must be `local`
- `hooks`: list of **local hook definitions** (see [Local hook definition](#local-hook-definition))

Example:

=== "prek.toml"

    ```toml
    [[repos]]
    repo = "local"
    hooks = [
      {
        id = "cargo-fmt",
        name = "cargo fmt",
        language = "system",
        entry = "cargo fmt",
        files = "\\.rs$",
      },
    ]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    repos:
      - repo: local
        hooks:
          - id: cargo-fmt
            name: cargo fmt
            language: system
            entry: cargo fmt
            files: "\\.rs$"
    ```

### `repo: meta`

Use `pre-commit`-style meta hooks that validate and debug your configuration.

`prek` supports the following meta hook ids:

- `check-hooks-apply`
- `check-useless-excludes`
- `identity`

Restrictions:

- `id` is required.
- `entry` is not allowed.
- `language` (if set) must be `system`.

You may still configure normal hook options such as [`files`](#hook-files-exclude), [`exclude`](#hook-files-exclude), [`stages`](#stages), etc.

Example:

=== "prek.toml"

    ```toml
    [[repos]]
    repo = "meta"
    hooks = [{ id = "check-useless-excludes" }]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    repos:
      - repo: meta
        hooks:
          - id: check-useless-excludes
    ```

### `repo: builtin`

<a id="prek-only-repo-builtin"></a>

!!! note "prek-only"

    `repo: builtin` is specific to `prek` and is not compatible with upstream `pre-commit`.

Use `prek`’s built-in fast hooks (offline, zero setup).

Restrictions:

- `id` is required.
- `entry` is not allowed.
- `language` (if set) must be `system`.

Example:

=== "prek.toml"

    ```toml
    [[repos]]
    repo = "builtin"
    hooks = [
      { id = "trailing-whitespace" },
      { id = "check-yaml" },
    ]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    repos:
      - repo: builtin
        hooks:
          - id: trailing-whitespace
          - id: check-yaml
    ```

For the list of available built-in hooks and the “automatic fast path” behavior, see [Built-in Fast Hooks](../builtin.md).

## Hook entries

Hook items under `repos[*].hooks` have slightly different shapes depending on the repo type.

### Remote hook selection

For a remote repo, the hook entry must include:

- `id` (required): selects the hook from the repository

All other hook keys are optional overrides (for example [`args`](#args), [`files`](#hook-files-exclude), [`exclude`](#hook-files-exclude), [`stages`](#stages), …).

!!! note "Advanced overrides"

    `prek` also supports overriding `name`, `entry`, and `language` for remote hooks.
    This can be useful for experimentation, but it may reduce portability to the original `pre-commit`.

### Local hook definition

For `repo: local`, the hook entry is a full definition and must include:

- `id` (required): stable identifier used by [`prek run <id>`](cli.md#prek-run) and selectors
- `name` (required): label shown in output
- `entry` (required): command to execute
- `language` (required): how `prek` sets up and runs the hook

### Builtin/meta hook selection

For `repo: builtin` and `repo: meta`, the hook entry must include `id`.
You can optionally provide `name` and normal hook options (filters, [`stages`](#stages), etc), but not `entry`.

## Supported Git Hook Stages

`prek` follows upstream [`pre-commit` behavior](https://pre-commit.com/#supported-git-hooks) for Git hook stages. The
[`stages`](#stages) option controls which hooks are eligible to run; the selected
stage controls what input the hook receives.

Stages with no repository file input do not have candidate filenames for
[`files`](#hook-files-exclude), [`exclude`](#hook-files-exclude),
[`types`](#hook-types), [`types_or`](#hook-types), or
[`exclude_types`](#hook-types). Hooks in those stages need
[`always_run: true`](#always_run) to run automatically.

| Stage | When it runs | Hook input |
| -- | -- | -- |
| `manual` | Only when selected explicitly, for example with `prek run --hook-stage manual`. | Repository file paths selected by the `prek run` file mode. |
| `commit-msg` | During commit message validation. | Git's commit message file, not repository file paths. |
| `post-checkout` | After a checkout has occurred. | No repository file input. |
| `post-commit` | After a commit has already succeeded. | No repository file input. |
| `post-merge` | After a successful merge. | No repository file input. |
| `post-rewrite` | After a command rewrites history, such as amend or rebase. | No repository file input. |
| `pre-commit` | Before a commit is finalized. | Repository file paths from the staged contents; unstaged changes are temporarily stashed while hooks run. |
| `pre-merge-commit` | After a merge succeeds but before the merge commit is created. | Repository file paths from the staged merge result. |
| `pre-push` | During `git push`. | Repository file paths changed in the push range. |
| `pre-rebase` | Before a rebase starts. | No repository file input. |
| `prepare-commit-msg` | Before the commit message editor opens or before the commit message is finalized. | Git's commit message file, not repository file paths. |

## Common hook options

These keys can appear on hooks (remote/local/builtin/meta), subject to the restrictions above.

### `id`

The stable identifier of the hook.

- For remote hooks, this must match a hook id defined by the remote repository.
- For local hooks, you choose it.

`id` is also used for CLI selection (for example [`prek run <id>`](cli.md#prek-run) and [`PREK_SKIP`](environment-variables.md#prek_skip)).

!!! note "Hook ids containing `:`"

    If your hook id contains `:` (for example `id: lint:ruff`), `prek run lint:ruff`
    will not select that hook. `prek` interprets `lint:ruff` as the selector
    `<project-path>:<hook-id>`, with project `lint` and hook `ruff`.
    To select the hook id `lint:ruff`, add a leading `:` and run
    `prek run :lint:ruff`.

### `name`

Human-friendly label shown in output.

- Required for `repo: local` hooks.
- Optional as an override for remote/meta/builtin hooks.

### `entry`

The command line to execute for the hook.

- Required for `repo: local` hooks.
- Optional override for remote hooks.
- Not allowed for `repo: meta` and `repo: builtin`.

If [`pass_filenames`](#pass_filenames) is `true`, `prek` appends matching filenames to this command when running.

### `shell`

<a id="prek-only-shell"></a>

!!! note "prek-only"

    `shell` is a `prek` extension and may not be recognized by upstream `pre-commit`.

Run `entry` through a predefined shell adapter.

- Type: one of `sh`, `bash`, `pwsh`, `powershell`, `cmd`
- Default: `null` (run `entry` directly without a shell)

When `shell` is omitted, `prek` preserves the default no-shell behavior: it parses `entry` into argv, invokes the command directly, and appends [`args`](#args) and matching filenames as process arguments.

When `shell` is set, `entry` is treated as source for that shell. `prek` writes the source to a temporary script file, runs it with the selected shell adapter, and passes hook [`args`](#args) followed by matching filenames as script arguments.

| `shell` | Adapter command | Script arguments |
| -- | -- | -- |
| `bash` | `bash --noprofile --norc -eo pipefail <script>` | `"$@"` |
| `sh` | `sh -e <script>` | `"$@"` |
| `pwsh` | `pwsh -NoProfile -NonInteractive -File <script>` | `$args` |
| `powershell` | `powershell -NoProfile -NonInteractive -File <script>` | `$args` |
| `cmd` | `cmd /D /E:ON /V:OFF /S /C CALL <script>` | `%*` |

=== "prek.toml"

    ```toml
    [[repos]]
    repo = "local"
    hooks = [
      {
        id = "test-all",
        name = "test-all",
        language = "system",
        entry = """
        uv run --python=3.10 --isolated pytest
        uv run --python=3.11 --isolated pytest
        """,
        shell = "bash",
        pass_filenames = false,
      },
    ]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    repos:
      - repo: local
        hooks:
          - id: test-all
            name: test-all
            language: system
            entry: |
              uv run --python=3.10 --isolated pytest
              uv run --python=3.11 --isolated pytest
            shell: bash
            pass_filenames: false
    ```

??? note "Unsupported languages"

    `shell` is rejected for language backends that do not run `entry` through
    the shell-aware entry resolver, and for `repo: meta` and `repo: builtin`
    hooks.

    | Language | Why `shell` is unsupported |
    | -- | -- |
    | `docker`, `docker_image` | `entry` participates in container image or entrypoint selection instead of direct host process execution. |
    | `dart` | Dart package config injection requires `entry` to resolve to a direct `dart` command. |
    | `fail` | `entry` is the failure message body. |
    | `julia`, `rust` | `entry` participates in install/runtime package resolution and is split before execution. |
    | `pygrep` | `entry` is the regex pattern. |
    | `r` | `entry` must use the R backend's `Rscript -e <expr>` or `Rscript <file>` forms. |

### `language`

How `prek` should run the hook (and whether it should create a managed environment).

- Required for `repo: local` hooks.
- Optional override for remote hooks.
- Not allowed (except as `system`) for `repo: meta` and `repo: builtin`.

Common values include `system`, `python`, `node`, `php`, `rust`, `golang`, `ruby`, and `docker`.

See [Language Support](../languages.md) for per-language behavior, supported values, and [`language_version`](#language_version) details.

!!! note "Language name aliases"

    For compatibility with upstream `pre-commit`, the following legacy language names are also accepted:

    - `unsupported` is treated as `system`
    - `unsupported_script` is treated as `script`

### `alias`

An alternate identifier for selecting the hook from the CLI.

If set, you can run the hook via either [`prek run <id>`](cli.md#prek-run) or [`prek run <alias>`](cli.md#prek-run).

### `args`

Extra arguments appended to the hook’s `entry`.

- Type: list of strings

Example:

=== "prek.toml"

    ```toml
    hooks = [{ id = "ruff", args = ["--fix"] }]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    hooks:
      - id: ruff
        args: [--fix]
    ```

### `env`

<a id="prek-only-env"></a>

!!! note "prek-only"

    `env` is a `prek` extension and may not be recognized by upstream `pre-commit`.

Extra runtime environment variables for the hook process.

- Type: map of string to string

Values override the existing process environment (including variables such as `PATH`).
They are applied when the hook runs, not when `prek` installs or prepares the hook environment.

!!! note "Runtime only"

    `env` cannot be used to control hook installation or environment preparation.
    Keeping it runtime-only means the installed hook environment does not depend on
    ambient variables from the invoking shell. If those variables affected
    installation, changing one of them would imply that `prek` may need to
    recreate the hook environment.

For remote hooks, `env` may also be set by the hook author in
`.pre-commit-hooks.yaml`. Values from the project configuration are merged with
manifest values and override duplicate keys.
Values set directly on a hook override matching [`default_env`](#default_env) values.

For `docker` / `docker_image` hooks, these variables are passed into the container rather than being applied to the container runtime command.

Example:

=== "prek.toml"

    ```toml
    [[repos]]
    repo = "local"
    hooks = [
      {
        id = "cargo-doc",
        name = "cargo doc",
        language = "system",
        entry = "cargo doc --all-features --workspace --no-deps",
        env = { RUSTDOCFLAGS = "-Dwarnings" },
        pass_filenames = false,
      },
    ]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    repos:
      - repo: local
        hooks:
          - id: cargo-doc
            name: cargo doc
            language: system
            entry: cargo doc --all-features --workspace --no-deps
            env:
              RUSTDOCFLAGS: -Dwarnings
            pass_filenames: false
    ```

<a id="hook-files-exclude"></a>

### `files` / `exclude`

Filters applied to candidate filenames.

- `files` selects which files are eligible for the hook.
- `exclude` removes files matched by `files`.

If you use both global and hook-level filters, the effective behavior is “global filter first, then hook filter”.

By default (and for compatibility with upstream `pre-commit`), these are regex strings.
As a `prek` extension, you can also specify globs using `glob` or a glob list.

See [Top-level `files`](#top-level-files) and [Top-level `exclude`](#top-level-exclude) for syntax notes and examples.

<a id="hook-types"></a>

### `types` / `types_or` / `exclude_types`

File-type filters based on [`identify`](https://pre-commit.com/#filtering-files-with-types) tags.

!!! tip

    Use [`prek util identify <path>`](cli.md#prek-util-identify) to see how prek tags a file when you’re troubleshooting `types` filters.

Compared to regex-only filtering ([`files` / `exclude`](#hook-files-exclude)), tag-based filtering is often easier and more robust:

- tags can match by **file extension** *and* by **shebang** (for extensionless scripts)
- you can easily exclude things like **symlinks** or **binary files**

Common tags include:

- `file`, `text`, `binary`, `symlink`, `executable`

- language-ish tags such as `python`, `rust`, `javascript`, `yaml`, `toml`, ...

- `types`: all listed tags must match (logical AND)

- `types_or`: at least one listed tag must match (logical OR)

- `exclude_types`: tags that disqualify a file

How these combine:

- [`files` / `exclude`](#hook-files-exclude), `types`, and `types_or` are combined with **AND**.
- Tags within `types` are combined with **AND**.
- Tags within `types_or` are combined with **OR**.

Defaults:

- `types`: `[file]` (matches all files)
- `types_or`: `[]`
- `exclude_types`: `[]`

These filters are applied in addition to regex filtering.

Examples:

=== "prek.toml"

    ```toml
    [[repos]]
    repo = "local"
    hooks = [
      # AND: must be under `src/` AND have the `python` tag
      {
        id = "lint-py",
        name = "Lint (py)",
        language = "system",
        entry = "python -m ruff check",
        files = "^src/",
        types = ["python"],
        exclude_types = ["symlink"]
      },

      # OR: match any of the listed tags under `web/`
      {
        id = "lint-web",
        name = "Lint (web)",
        language = "system",
        entry = "npm run lint",
        files = "^web/",
        types_or = ["javascript", "jsx", "ts", "tsx"]
      },
    ]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    repos:
      - repo: local
        hooks:
          - id: lint-py
            name: Lint (py)
            language: system
            entry: python -m ruff check
            files: ^src/
            types: [python]
            exclude_types: [symlink]

          - id: lint-web
            name: Lint (web)
            language: system
            entry: npm run lint
            files: ^web/
            types_or: [javascript, jsx, ts, tsx]
    ```

If you need to match a path pattern that doesn’t align with a hook’s default `types` (common when reusing an existing hook in a nonstandard way), override it back to “all files” and use [`files`](#hook-files-exclude):

=== "prek.toml"

    ```toml
    [[repos]]
    repo = "meta"
    hooks = [
      {
        id = "check-hooks-apply",
        types = ["file"],
        files = "\\.(yaml|yml|myext)$"
      },
    ]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    repos:
      - repo: meta
        hooks:
          - id: check-hooks-apply
            types: [file]
            files: \.(yaml|yml|myext)$
    ```

### `always_run`

Run the hook even when no files match.

- Type: boolean
- Default: `false`

This is commonly used for hooks that check repository-wide state (for example, running a test suite) rather than operating on specific files.

### `pass_filenames`

Controls whether `prek` appends the matching filenames to the command line.

- Type: boolean or positive integer
- Default: `true` which passes all matching filenames

Set `pass_filenames: false` for hooks that don’t accept file arguments (or that discover files themselves).

Set `pass_filenames: n` (a positive integer) to limit each invocation to at most `n` filenames. When there are more matching files than `n`, `prek` splits them across multiple batches. A batch is one hook command invocation over a subset of the matched filenames. Batches may run concurrently up to `PREK_CONCURRENT_BATCHES` unless [`require_serial`](#require_serial) is `true`. This is useful for tools that can only process a limited number of files at once.

Prek will automatically limit the number of filenames to ensure command lines don’t exceed the OS limit, even when `pass_filenames: true`.

!!! note "prek-only"

    `pass_filenames: n` with a positive integer is a `prek` extension. Upstream `pre-commit` only accepts a boolean value.

### `stages`

Declare which stages a hook is eligible to run in.

- Type: list of stage names
- Default: all stages

Allowed values:

- `manual`
- `commit-msg`
- `post-checkout`
- `post-commit`
- `post-merge`
- `post-rewrite`
- `pre-commit`
- `pre-merge-commit`
- `pre-push`
- `pre-rebase`
- `prepare-commit-msg`

For behavior of each stage and whether it operates on repository files, see
[Supported Git Hook Stages](#supported-git-hook-stages).

When you run [`prek run --hook-stage <stage>`](cli.md#prek-run), only hooks configured for that stage are considered.

### `groups`

Tag a hook with user-defined run groups.

!!! note "prek-only"

    `groups` is a `prek` extension. Upstream `pre-commit` does not support selecting hooks by group.

- Type: list of strings
- Default: `[]`

Groups are arbitrary labels used by [`prek run --group <group>`](cli.md#prek-run--group),
[`prek run --require-group <group>`](cli.md#prek-run--require-group), and
[`prek run --no-group <group>`](cli.md#prek-run--no-group).
Group names cannot be empty or contain whitespace.

`groups` is a project configuration field. If it appears in a remote
`.pre-commit-hooks.yaml` manifest, `prek` ignores it.

Examples:

=== "prek.toml"

    ```toml
    [[repos]]
    repo = "local"
    hooks = [
      {
        id = "format",
        name = "Format Python",
        language = "system",
        entry = "ruff format",
        groups = ["format", "ci"],
      },
      {
        id = "lint",
        name = "Lint Python",
        language = "system",
        entry = "ruff check",
        groups = ["lint", "ci"],
      },
    ]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    repos:
      - repo: local
        hooks:
          - id: format
            name: Format Python
            language: system
            entry: ruff format
            groups: [format, ci]

          - id: lint
            name: Lint Python
            language: system
            entry: ruff check
            groups: [lint, ci]
    ```

Run only the `ci` group:

```bash
prek run --all-files --group ci
```

Run only hooks belonging to both `lint` and `ci`:

```bash
prek run --all-files --require-group lint --require-group ci
```

Run everything except formatters:

```bash
prek run --all-files --no-group format
```

If a hook matches both `--group` and `--no-group`, `--no-group` wins.

Repeated `--group` values use union semantics, while repeated
`--require-group` values use intersection semantics. When combined, a hook must
match at least one `--group`, every `--require-group`, and no `--no-group`.

For example, consider these hook groups:

| Hook | Groups |
| -- | -- |
| `ty` | `lint-only`, `fast`, `local` |
| `ruff-format` | `format`, `fast`, `local` |
| `mypy` | `lint-only`, `slow`, `ci` |
| `black` | `format`, `slow`, `ci` |

```bash
prek run --all-files --require-group fast --group format --group lint-only
```

This represents `fast AND (format OR lint-only)`, so it selects exactly `ty`
and `ruff-format`. Reordering the options does not change the selection.

When any group selector is used without `--stage`, group filtering is not
constrained by hook stage. `prek run` collects normal file input for the manual
command and runs every matching hook that can use that input. Hooks configured
only for `commit-msg` and/or `prepare-commit-msg` require Git's message file
argument, so they are ignored unless run in the corresponding hook stage. If
every matching hook is ignored this way, `prek run` warns and fails.

### `require_serial`

Force a hook to run without parallel batches (one in-flight process for that hook at a time).

- Type: boolean
- Default: `false`

This is useful for tools that use global caches/locks or otherwise can’t handle concurrent execution.

### `priority`

<a id="prek-only-priority"></a>

!!! note "prek-only"

    `priority` controls `prek`'s scheduler and does not exist in upstream `pre-commit`.

Each hook can set an explicit `priority` that controls when it runs and with which hooks it may
execute in parallel. The value may be either a non-negative integer or a [priority
alias](#priorities) declared by the current configuration.

Scope:

- `priority` is evaluated **within a single configuration file** and is compared across **all hooks in that file**, even if they appear under different `repos:` entries.
- `priority` does **not** coordinate across different config files. In workspace mode, each project’s config file is scheduled independently.
- Numeric priorities and aliases can be mixed. Aliases resolve to their declared integer before scheduling.

Hooks run in ascending priority order: **lower `priority` values run earlier**. Hooks that share the same `priority` value run concurrently, subject to `PREK_CONCURRENT_HOOKS`.

When `priority` is omitted, `prek` assigns an implicit value based on hook order to preserve sequential behavior.

Example:

=== "prek.toml"

    ```toml
    [priorities]
    format = 0
    checks = 10
    tests = 20

    [[repos]]
    repo = "local"
    hooks = [
      {
        id = "format",
        name = "Format",
        language = "system",
        entry = "python3 -m ruff format",
        always_run = true,
        priority = "format",
      },
      {
        id = "lint",
        name = "Lint",
        language = "system",
        entry = "python3 -m ruff check",
        always_run = true,
        priority = "checks",
      },
      {
        id = "tests",
        name = "Tests",
        language = "system",
        entry = "just test",
        always_run = true,
        priority = "tests",
      },
    ]
    ```

=== ".pre-commit-config.yaml"

    ```yaml
    priorities:
      format: 0
      checks: 10
      tests: 20

    repos:
      - repo: local
        hooks:
          - id: format
            name: Format
            language: system
            entry: python3 -m ruff format
            always_run: true
            priority: format

          - id: lint
            name: Lint
            language: system
            entry: python3 -m ruff check
            always_run: true
            priority: checks

          - id: tests
            name: Tests
            language: system
            entry: just test
            always_run: true
            priority: tests
    ```

!!! danger "Parallel hooks modifying files"

    If two hooks run in the same priority group and both mutate the same files (or depend on shared state), results are undefined.
    Use separate priorities to avoid overlap.

!!! note "Hooks modifying files without a non-zero exit code"

    If a hook modifies files without emitting a non-zero exit code (e.g. `ruff format`), the priority group as a whole will fail.
    It is not possible for prek to attribute the failure to a specific hook in the group which modified files.
    Use separate priorities for clearer failure attribution.

!!! note "`require_serial` is different"

    [`require_serial`](#require_serial) set to `true` prevents concurrent batches of the *same hook*.
    It does not prevent other hooks from running alongside it; use a unique `priority` if you need exclusivity.

!!! note "Priority aliases are not hook groups"

    Priority aliases control scheduling. [`groups`](#groups) select which hooks run and have no
    ordering or concurrency meaning.

### `fail_fast`

Hook-level fail-fast behavior.

- Type: boolean
- Default: `false`

If `true`, a failure in this hook stops the run immediately.

### `verbose`

Print hook output even when the hook succeeds.

- Type: boolean
- Default: `false`

### `log_file`

Write hook output to a file when the hook fails (and also when `verbose: true`).

- Type: string path

### `description`

Free-form description shown in listings / metadata. Its first line is also included in failure and
verbose run details.

- Type: string

### `language_version`

Choose the language/toolchain version request for this hook.

- Type: string
- Default: `default`

If not set, `prek` may use [`default_language_version`](#default_language_version) for the hook’s language.

!!! note "prek-only"

    `language_version` is treated as a **version request**, not a single pinned value. For languages that use semver requests, you can specify ranges (for example `^1.2`, `>=1.5, <2.0`).

    Special values:

    - `default`: use the language’s default resolution logic.
    - `system`: do not download a new toolchain. A compatible toolchain already managed by prek may still be reused, and project metadata can further constrain the required version.

    Language-specific behavior:

    - Python: passed to the Python resolver (for example `python3` or `python3.12`). May trigger toolchain download.
    - Node: passed to the Node resolver (for example `20`, `18.19.0`). May trigger toolchain download.
    - Go: uses Go version strings such as `1.22.1` (downloaded if missing).
    - Rust: supports rustup toolchains such as `stable`, `beta`, `nightly`, or versioned toolchains.
    - Other languages: parsed as a semver request and matched against the installed toolchain version.

    Examples:

    === "prek.toml"

        ```toml
        hooks = [
          { id = "ruff", language = "python", language_version = "3.12" },
          { id = "eslint", language = "node", language_version = "20" },
          { id = "cargo-fmt", language = "rust", language_version = "stable" },
          { id = "my-tool", language = "system", language_version = "system" },
        ]
        ```

    === ".pre-commit-config.yaml"

        ```yaml
        hooks:
          - id: ruff
            language: python
            language_version: "3.12"

          - id: eslint
            language: node
            language_version: "20"

          - id: cargo-fmt
            language: rust
            language_version: stable

          - id: my-tool
            language: system
            language_version: system
        ```

### `additional_dependencies`

Extra dependencies for hooks that run inside a managed environment (for example Python or Node hooks).

- Type: list of strings

If you set this for a language that doesn’t support dependency installation, `prek` fails with a configuration error.

### `minimum_prek_version`

<a id="prek-only-minimum-prek-version-hook"></a>

!!! note "prek-only"

    This is a `prek`-specific requirement gate. Upstream `pre-commit` does not have a hook-level minimum version key.

Require a minimum `prek` version for this specific hook.

- Type: string (version)
- Default: unset

For process environment controls, see the [Environment Variable Reference](environment-variables.md).
