Last quarter I shipped a JSON API field called recipient_adress. It sat in production for three weeks before anyone noticed, and by then a mobile client was already reading it — so the typo was a contract I couldn't break. Peck is a PHP spell checker that catches exactly this: it scans the identifiers and comments in your codebase the way Pint scans formatting, and flags the misspellings before they harden into a public API.
It comes from the team behind Pest and Pint, builds on GNU Aspell, and drops into a Laravel project in about two minutes. Here's how I wire it up, tune out the noise, and run it on every push.
Install Peck and GNU Aspell#
Peck leans on GNU Aspell for the actual dictionary lookups, so Aspell has to be on the machine first. This is the step people miss — Peck errors out immediately without it. Install the binary and, on Debian, the English dictionary package for your platform:
# macOS (Homebrew) — bundles a dictionary
brew install aspell
# Debian / Ubuntu — note the separate English dictionary package
sudo apt-get install aspell aspell-en
Then pull Peck in as a dev dependency and scaffold its config. Because Peck is still on 0.x releases, I pin it so a minor bump can't silently change what CI flags overnight:
composer require peckphp/peck:^0.3 --dev
./vendor/bin/peck --init
--init drops a starter peck.json in your project root — that's the file you'll spend most of your tuning time in. Peck slots in next to the other CLI tools that read your codebase, the same way you might automate your Laravel upgrade refactors with Rector, except Peck reads your prose, not your syntax.
Run your first Peck spell check#
With Aspell installed and the config scaffolded, run the checker:
./vendor/bin/peck
On a real codebase the first run is brutal. Peck walks every filename, class, method, property, and comment, and its first pass surfaces a wall of "mistakes" — most of them domain words, vendor names, and abbreviations it doesn't know yet. This is the same first-run shock you get when you point PHPStan at level 10 on an untyped app: the noise isn't bugs, it's the tool meeting your vocabulary for the first time.
Each finding points at the file, the identifier, and Aspell's suggested corrections — roughly like this:
✗ adress → address, dress, adrenal
at app/Http/Controllers/RecipientController.php
property $recipient_adress
✗ recieve → receive, relieve, reprieve
at app/Services/PaymentService.php
method recievePayment()
There's an escape hatch — ./vendor/bin/peck --ignore-all swallows every finding in the run — but I don't reach for it. It hides the real typos along with the false ones. The durable fix is to teach Peck your vocabulary in peck.json.
Tune out false positives with peck.json#
Every project has words that look like typos but aren't: Stripe, Nginx, middleware, your product name, a client's surname. You don't want to argue with Aspell about them on every run, so you list them once. Here's a trimmed peck.json from a Laravel app:
{
"preset": "laravel",
"language": "en_GB",
"ignore": {
"words": [
"stripe",
"nginx",
"middleware",
"dto"
],
"paths": [
"app/Legacy",
"database/migrations"
]
}
}
The laravel preset pre-loads the framework's own vocabulary — eloquent, artisan, casts and friends — so you're not ignoring them by hand. It's the only preset that ships today.
I'm in the UK, so I set "language": "en_GB". Out of the box Peck uses Aspell's en_US dictionary, which flags colour, behaviour, and organisation as mistakes — infuriating if your whole team spells them the British way. The catch: the dictionary has to be installed. Run aspell dump dicts to confirm en_GB is there before you rely on it.
ignore.words is your allow-list of domain terms; ignore.paths skips whole directories — generated code, a legacy corner you're not ready to clean, third-party migrations. Ignoring a path is a blunt instrument, and it's the same trade-off as adopting a PHPStan baseline on a legacy app: use it to draw a line around the mess you'll deal with later, not as a permanent mute button. Keep the ignore list short and honest.
Run the Peck spell checker in CI#
A spell checker only earns its keep if it runs on every push — otherwise the typos are back within a sprint. First, give yourself a composer script so the command is identical locally and in CI:
{
"scripts": {
"test:typos": "peck",
"test": [
"@test:typos",
"@php artisan test"
]
}
}
Now composer test:typos runs the checker on its own, and it's folded into composer test alongside the rest of your suite.
In CI the same rule applies as on your laptop: no Aspell, no Peck. GitHub's Ubuntu runners don't ship the Aspell dictionary, so install it before the checker runs:
name: typos
on: [push, pull_request]
jobs:
peck:
runs-on: ubuntu-latest
continue-on-error: true # advisory while you tune peck.json
steps:
- uses: actions/checkout@v4
- name: Install Aspell
run: sudo apt-get update && sudo apt-get install -y aspell aspell-en
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
- name: Check for typos
run: composer test:typos
If you already run a matrix or sharded pipeline, this is just one more job — the same wiring behind cutting Laravel CI time with Pest sharding or running a full PHP × database test matrix. Keep it as a separate job so a typo failure is obvious at a glance and doesn't get buried in a test log.
Note the continue-on-error: true. Because Peck is pre-1.0, I run this job non-blocking for the first week or two so the team can see findings and curate peck.json before red crosses start blocking merges.
Gotchas and Edge Cases#
The Debian dictionary is a separate package. Installing aspell without aspell-en gives you the binary but no English dictionary, and Peck then has nothing to check against. Always install both on Linux; Homebrew bundles a dictionary on macOS.
The en_US default trips up British spelling. If your team writes en_GB and you skip the language key, Peck will "correct" every colour and initialise in the codebase. Set the language explicitly and confirm the dictionary exists with aspell dump dicts.
Peck is genuinely young. v0.3.0 is the current release as I write this, and the maintainers explicitly say it's "not yet ready for production use." The output and options are still moving, so pin an exact version if you want zero surprises and re-read the changelog before you bump.
--ignore-all is a footgun. It silences real typos alongside the false ones. Use it once to get a green baseline if you must, then delete the noise from peck.json by hand rather than leaning on the flag.
Fixing a typo is sometimes a breaking change. Renaming a misspelled public method, route name, or API field breaks whatever depends on it — which is the whole argument for running Peck early, before anything downstream binds to the mistake. On a large codebase, scope runs with the --path option to keep them fast.
Wrapping Up#
Peck fills a gap the rest of the toolchain leaves open: Pint fixes your formatting, PHPStan checks your types, and until now nothing was reading your actual words. Install Aspell, pin peckphp/peck, curate a short peck.json, and run it as its own CI job — advisory first, blocking once the noise is gone.
From here, if you want the rest of your static-analysis story tightened up, PestStan brings that same type safety to your Pest tests — a natural next step for a codebase that now spells everything correctly.
FAQ#
What is Peck in PHP?
Peck is a command-line spell checker for PHP codebases, from the team behind Pest and Pint. It scans filenames, class names, method and property names, comments, and docblocks, then flags words that look misspelled and suggests corrections. It's aimed at stopping typos before they harden into public method names, routes, or API fields. As of v0.3.0 the maintainers still consider it pre-production, so treat it as an assistant rather than a gatekeeper.
How do I install Peck in a Laravel project?
Install GNU Aspell first, because Peck depends on it — brew install aspell on macOS, or sudo apt-get install aspell aspell-en on Debian and Ubuntu. Then require the package as a dev dependency with composer require peckphp/peck --dev and scaffold a config file with ./vendor/bin/peck --init. Run ./vendor/bin/peck to perform your first check. The laravel preset in peck.json pre-loads the framework's own vocabulary so you start with less noise.
How do I ignore words or paths in Peck?
Add an ignore block to your peck.json. Under ignore.words list the domain terms, product names, and abbreviations you want Peck to accept; under ignore.paths list directories or files to skip entirely, such as generated code or a legacy folder. Keep the list short and deliberate, because every ignored word is a real typo you might be hiding. The --ignore-all flag swallows a whole run at once, but it silences genuine mistakes too, so I avoid it beyond a one-off baseline.
Can I run Peck in CI?
Yes, and that's where it pays off. Add a composer script such as "test:typos": "peck" so the command is identical locally and on the runner, then add a job that installs Aspell before invoking it — GitHub's runners don't ship the dictionary by default. Because Peck is still pre-1.0, I run the job as advisory (non-blocking) for the first couple of weeks so the team can curate peck.json before typo failures start blocking merges.
Does Peck need Aspell installed?
Yes. Peck delegates the actual dictionary lookups to GNU Aspell, so the Aspell binary and an English dictionary must be present on any machine that runs it — your laptop and every CI runner. On Debian and Ubuntu that means installing both aspell and aspell-en; the Homebrew formula bundles a dictionary on macOS. Without Aspell, Peck cannot run at all.