Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Prose Linter Laravel Package

beyondcode/laravel-prose-linter

Lint the prose in your Laravel Blade views: this package checks spelling, grammar, and writing style, flags unclear or inconsistent wording, and runs as an Artisan command you can drop into CI so your app's copy stays polished, consistent, and readable.

View on GitHub
Deep Wiki
Context7

Getting Started

Install as a dev dependency. This is authoring/CI tooling — it should never reach a production image:

composer require --dev beyondcode/laravel-prose-linter

Before you write any code, check two things.

  1. The framework constraint. The only reason v3.1.0 (2023‑02‑16) exists is "Add Laravel 10 support." On Laravel 11/12 you will hit a Composer resolution wall. Your options, in order of preference: stay off it on 11+, fork and bump the illuminate/* constraints (the package is tiny), or vendor src/ into your own dev-tooling namespace. Do not reach for --ignore-platform-reqs; that hides the incompatibility until something breaks at runtime.
  2. The underlying analyzer. This package is a thin Laravel wrapper, and proselint (Python) does the actual checking. If that's the case, every developer machine and every CI image needs it:
pipx install proselint && proselint --version

Pin the proselint version in your CI image. Rule drift between local and CI is the single most common "works on my machine" complaint with tools like this.

Read these files in order: README.md → src/ (the Artisan command signature and, critically, its exit-code contract) → the published config. There is no runtime API you'll call from application code; the command and the test helpers are the integration.

Publish config, clear caches, confirm discovery.

php artisan vendor:publish --tag=prose-config   # confirm the tag in the README
php artisan config:clear
php artisan list | grep prose

Auto-discovery handles registration. If the command is missing, it's a cache/discovery problem, not a config/app.php problem.

First run: one directory you already trust.

php artisan prose:lint --path=resources/views/marketing

First impressions decide adoption. If run #1 dumps 400 findings from inline email CSS and minified partials, the tool is uninstalled within the hour. Prove signal on a narrow, curated folder before going wide.

Best first use case is lang/, not views. Translation files are UI copy written by a developer at 23:00, reviewed by nobody, and shipped to every user. A lint pass over lang/en/*.php catches double spaces, inconsistent capitalization, hedging, and the awkward phrasing that only appears under release pressure. If the package exposes test assertions (per the description, mailable/notification linting), add one assertion to an existing feature test before touching CI at all — cheapest possible way to find out whether findings are useful.

Implementation Patterns

Config scoping is where the entire value lives. Start from a whitelist, never from the default scan set.

// config/prose.php
'directories' => [
    resource_path('views/marketing'),
    lang_path('en'),
    // resource_path('views/emails'),  // only after markup is excluded
],
'exclude' => [
    resource_path('views/vendor'),
    resource_path('views/components/ui'),
    public_path(),
],

Exclude vendor views, compiled assets, JS/Vue mount points disguised as Blade, and any template where the majority of the file is markup or <style>.

Baseline, then ratchet. This is the workflow that survives:

  1. Run once over the full surface, archive the output as your baseline.
  2. Configure rule toggles and ignore lists for the classes of finding you accept.
  3. Add the CI step with continue-on-error: true.
  4. After a week of low noise, promote to blocking on changed files only:
git diff --name-only origin/main...HEAD -- 'resources/views/**/*.blade.php' 'lang/**/*.php' \
  | xargs -r -n1 php artisan prose:lint --path=

Verify whether the command accepts a single file path or only directories; if the latter, run per-directory and filter the output.

Use the test assertions as the tight loop. A mailable's copy gets linted in the same run as its subject line, so a typo fails next to its cause rather than in a separate CI stage nobody reads. Put prose assertions in the same test class as the mailable/notification, not in a distant "lint" suite.

One checked-in analyzer config for everyone. proselint reads ~/.proselintrc from the user's home directory by default, which means every developer silently runs different rules. Set PROSELINTRC (or the equivalent --config flag) to a repo-root file, in local scripts and CI alike. Disable the checks that fight your product voice rather than blanket-ignoring individual lines.

Keep a reviewed allowlist for domain terms. Product names, feature names, and acronyms will trip style rules forever. Maintain a checked-in ignore list with a comment per entry, a reason, and a review date. An allowlist without a reviewer becomes a graveyard within two quarters.

Prefer a pre-commit hook over CI. Run on staged *.blade.php and lang/** files via CaptainHook or Husky. The developer fixes the sentence while it's still in their head; CI is only the backstop.

Gotchas and Tips

  • The package is archived. No maintainer, no release since 2023‑02‑16, 114 stars, 0 dependents. Pin the exact version, name an owner, and decide fork vs. replace before your next Laravel major. MIT licensing makes a fork cheap if it proves valuable.
  • Laravel 11+ is unverified. Laravel 10 is the declared ceiling. Test in a throwaway branch; don't infer compatibility from "it installs."
  • The Python dependency is the hidden cost. Pin proselint in your Docker/CI image and fail fast with a clear message if it's absent, otherwise the command errors obscurely.
  • Config resolution bites in CI. ~/.proselintrc does not exist on a fresh runner. Always point at the checked-in config explicitly.
  • Prose and code don't mix. {{ $user->name }}, @if, @foreach, @include, :attribute placeholders, and HTML entities all generate false positives. Test against a real file with nested directives to see how much the wrapper strips.
  • Commented-out copy gets linted. {{-- --}} blocks and dead markup still count as text. Exclude or delete stale copy rather than ignoring it line by line.
  • Both branches of conditional copy are linted. Copy behind a feature flag that no user will ever see still produces findings. Decide whether that's a feature (catch dead copy) or noise (exclude the file).
  • Email templates are the worst offender. Inline styles, table layouts, and <style> blocks. Either exclude views/emails at first or lint only the plain-text variants.
  • Non-English copy produces nonsense. These are English heuristics. Scope lang/ to en first and validate findings on each additional locale before wiring it in.
  • Read the exit-code contract in src/. If every finding is a hard failure, start with continue-on-error: true and do not promote to blocking until false positives are under control.
  • Debug at the layer below. Running proselint directly on a file tells you instantly whether a miss is a proselint limitation, a Blade-stripping bug, or a config problem.
  • Performance scales with the directory tree, not traffic. Full scans on a big repo are slow; keep to per-changed-file or scoped directory runs.
  • If the ignore list grows faster than the codebase, the tool doesn't fit your voice. Say so and remove it — a linter everyone has trained themselves to ignore is worse than no linter, because it manufactures the illusion of a quality gate.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core