| | |

Prettier 14 ๐ŸŽจ The –check Flag โ€” CI enforcement & exit codes

โฑ๏ธ Estimated read: 9 min | |

The --check flag is the CI counterpart to --write. It does not modify any files. It reads the files, compares the formatted output to the original content, and reports the files that would change. If any file would change, Prettier exits with a non-zero status code, which is what a CI pipeline uses to fail the build. The --write flag is for the developer’s machine; the --check flag is for the gate that prevents unformatted code from merging.

This chapter covers the exit code contract, the difference between --check and --write in a pipeline, the --list-different alias that produces the same exit code, and the CI configuration that runs the check with the project’s pinned Prettier version.

Key point: --check exits with code 1 when any file is unformatted. The output names the files and tells the developer to run --write. The exit code is the gate; the output is the diagnostic.


Why the check flag exists

The CI gate problem. A pre-commit hook can be bypassed with --no-verify. An editor integration can be misconfigured or disabled. The only check that cannot be bypassed by the developer’s local environment is the one that runs in CI, on the server, with the project’s own dependency tree . The --check flag is the mechanism that makes that check possible without modifying the repository.

The write problem. Running --write in CI would modify the files in the runner, but the modifications would be discarded when the runner exits. The pipeline would pass, and the unformatted code would still be merged. The --check flag is the correct choice because it produces a failure without producing a change.

The pinned version problem. The Prettier documentation for CI recommends using a pinned version of Prettier in the repository . The --check run in CI must use the same version as the developer’s local run, or the check may fail on formatting differences that are not the developer’s fault. The npm ci command installs the exact version from the lockfile, which is why the CI configuration should use it.


a. The exit code contract

The --check flag returns exit code 0 when every file is formatted and exit code 1 when at least one file is not. The output names the unformatted files and prints a message that tells the developer to run --write.

# Check all files in the current directory
npx prettier . --check

# Check a specific file
npx prettier src/app.js --check

A successful check produces a message that all files are formatted and exits 0. A failed check produces a list of the files that would change and exits 1. The CI pipeline step fails when the exit code is non-zero, which is the entire mechanism.

The --list-different flag is a legacy alias that produces the same behavior: it lists the files that differ and exits non-zero . The --check flag is the current name and the one that the documentation uses. Both flags are suitable for CI, but --check is the recommended form.


b. The CI configuration

The CI pipeline runs npm ci to install the exact dependencies from the lockfile, then runs the check script. The npm ci command is important because it installs the version that the developer tested against, not a version that the CI runner happens to resolve.

# .github/workflows/lint.yml (excerpt)
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'
- run: npm ci
- run: npm run format:check

The package.json script is the abstraction that keeps the command consistent across environments:

{
  "scripts": {
    "format:check": "prettier . --check"
  }
}

The local developer runs the same script, which means the check that passes locally is the check that runs in CI . The only difference is the environment.

A GitHub Actions workflow that runs the check on every push to the main branch and every pull request is the standard configuration. The workflow fails when the check fails, which blocks the merge .


c. The glob and the ignore file

The --check command uses the same file resolution as --write. The directory argument recurses, the glob argument is expanded by Prettier, and the .prettierignore file filters the result. A CI check that runs prettier . --check covers the entire project, subject to the ignore file.

The glob syntax for multiple extensions must not contain spaces. **/*.{js,ts,css} works; **/*.{js, ts, css} matches nothing because the space becomes part of the pattern . This is the most common reason a check script reports zero files and exits 0 even when unformatted files exist.


Complete Example Session

// ============================================
// PART 1: THE PACKAGE SCRIPT
// ============================================
{
  "scripts": {
    "format": "prettier . --write",
    "format:check": "prettier . --check"
  }
}
# ============================================
# PART 2: THE LOCAL CHECK
# ============================================
npm run format:check

# Output when all files are formatted:
# Checking formatting...
# All matched files use Prettier code style!
# ============================================
# PART 3: THE FAILING CHECK
# ============================================
npm run format:check

# Output:
# Checking formatting...
# src/app.js
# src/utils.js
# Code style issues found in 2 files. Run Prettier with --write to fix.
# Exit code: 1
# ============================================
# PART 4: THE WRITE FIX
# ============================================
npm run format

# The files are reformatted.
# The check passes on the next run.
# ============================================
# PART 5: THE CI WORKFLOW
# ============================================
name: Lint & Format
on:
  push:
    branches: [main]
  pull_request:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run format:check
# ============================================
# PART 6: THE EXIT CODE
# ============================================
# Check passes โ†’ exit 0 โ†’ CI step passes
# Check fails โ†’ exit 1 โ†’ CI step fails
# ============================================
# PART 7: THE GLOB
# ============================================
# Correct: "**/*.{js,ts,css}"
# Wrong:   "**/*.{js, ts, css}" (spaces)
# ============================================
# PART 8: THE IGNORE FILE
# ============================================
# .prettierignore
# dist/
# coverage/
# node_modules/
# ============================================
# PART 9: THE PINNED VERSION
# ============================================
# package.json: "prettier": "3.9.4"
# npm ci installs the exact version.
# ============================================
# PART 10: THE DECISION
# ============================================
# Local โ†’ --write
# CI โ†’ --check
# Both โ†’ same script, same version

The ten parts covered the package script, the local check, the failing check, the write fix, the CI workflow, the exit code, the glob, the ignore file, the pinned version, and the decision.


Quick Reference

The flags

FlagModifies filesExit code on unformatted
--checkNo1
--list-differentNo1 (legacy alias)
--writeYes0

The CI configuration

StepCommand
Installnpm ci
Checknpm run format:check
Fix locallynpm run format

The exit codes

CodeMeaning
0All files formatted
1At least one file unformatted

The glob rule

PatternResult
**/*.{js,ts,css}Matches
**/*.{js, ts, css}Matches nothing

Best Practices

โœ… Do This:

# Use --check in CI
npm run format:check                                         # โœ…
# Use npm ci to install the pinned version
npm ci                                                       # โœ…
// Keep the script consistent between local and CI
"format:check": "prettier . --check"                         // โœ…
// Quote the glob and omit spaces
prettier "**/*.{js,ts,css}" --check                          // โœ…

โŒ Don’t Do This:

# Use --write in CI
prettier . --write                                           # โŒ (changes discarded)
# Rely on a global Prettier in CI
prettier . --check                                           # โŒ (version drift)
// Use spaces in the glob
prettier "**/*.{js, ts, css}" --check                        // โŒ (no match)

Common Pitfalls

PitfallWhy It HappensFix
CI passes despite unformatted files--write used instead of --checkUse --check and the exit code
Check reports no filesGlob has spacesRemove the spaces
CI fails on formatting that passes locallyDifferent Prettier versionUse npm ci with the lockfile
Check is slowThe whole node_modules is scannedAdd node_modules to .prettierignore
Check fails on generated filesNot ignoredAdd the paths to .prettierignore

Real-World Examples

1. The check script

{ "scripts": { "format:check": "prettier . --check" } }

2. The local run

npm run format:check

3. The CI step

- run: npm ci && npm run format:check

4. The failing output

Code style issues found in 2 files. Run Prettier with --write to fix.

5. The fix

npm run format

6. The glob

prettier "**/*.{js,ts}" --check

7. The ignore file

# .prettierignore
dist/

8. The pinned version

{ "devDependencies": { "prettier": "3.9.4" } }

9. The exit code

echo $?  # 1 after a failing check

10. The decision

Local โ†’ --write; CI โ†’ --check

Visual

The check vs write

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  --check                                                     โ”‚
โ”‚    Read file โ”€โ”€โ–บ format โ”€โ”€โ–บ compare โ”€โ”€โ–บ report              โ”‚
โ”‚    No file is modified.                                       โ”‚
โ”‚    Exit 1 if any file differs.                               โ”‚
โ”‚                                                              โ”‚
โ”‚  --write                                                     โ”‚
โ”‚    Read file โ”€โ”€โ–บ format โ”€โ”€โ–บ write โ”€โ”€โ–บ exit 0                โ”‚
โ”‚    Files are modified.                                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The CI gate

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Push โ”€โ”€โ–บ CI runner โ”€โ”€โ–บ npm ci โ”€โ”€โ–บ format:check             โ”‚
โ”‚                                      โ”‚                       โ”‚
โ”‚                                      โ”œโ”€โ”€ exit 0 โ”€โ”€โ–บ pass     โ”‚
โ”‚                                      โ”‚                       โ”‚
โ”‚                                      โ””โ”€โ”€ exit 1 โ”€โ”€โ–บ fail     โ”‚
โ”‚                                                              โ”‚
โ”‚  The fail blocks the merge.                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The version alignment

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Developer                    CI                              โ”‚
โ”‚    package-lock.json โ”€โ”€โ–บ npm ci โ”€โ”€โ–บ same version             โ”‚
โ”‚                                                              โ”‚
โ”‚  The check that passes locally is the check that runs in CI.โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The glob spaces

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  "**/*.{js,ts,css}"     โ†’ matches                           โ”‚
โ”‚  "**/*.{js, ts, css}"   โ†’ no match (spaces)                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Flag--check
Modifies filesNo
Exit code on unformatted1
Legacy alias--list-different
CI installnpm ci
CI scriptformat:check
Glob ruleNo spaces in braces
Version requirementPinned in the lockfile

Key takeaways:

  • --check is the CI gate. It reports unformatted files and exits with code 1, which fails the pipeline step. It does not modify any files, which is the correct behavior for a CI runner that discards its changes.
  • --write is for the developer’s machine. It reformats the files in place and exits 0. The developer runs it locally, and the CI runs --check on the same files.
  • The version must be pinned. The CI pipeline uses npm ci to install the exact version from the lockfile. A global Prettier or an npx run without a local install would use a different version and produce a check that does not match the developer’s environment .
  • The glob must not contain spaces. **/*.{js,ts,css} matches the three extensions. **/*.{js, ts, css} matches nothing because the space becomes part of the pattern . A check script that reports zero files is usually a glob with spaces.
  • The .prettierignore file filters the check. The check runs on the files that the ignore file does not exclude. A generated file that should not be checked must be added to the ignore file, not to a separate exclusion list.

Remember: The --check flag is the difference between a formatter that the developer uses and a formatter that the repository enforces. The local run uses --write, the CI run uses --check, and the exit code is the gate. Use npm ci to align the version, quote the glob and omit the spaces, and keep the script consistent between the developer’s machine and the CI runner. The check that passes locally is the check that runs in CI, and the exit code is the signal that the code is ready to merge.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!