Skip to content

Free SEO audit (seo:audit) ​

php artisan seo:audit answers one question in one command, for free: what's wrong with my SEO right now? It iterates your HasSEO models in-process — no queue, no license, no network — and prints a per-page pass / warn / fail table with a summary.

bash
php artisan seo:audit
+--------------+--------+----------------------------+
| Page         | Status | Findings                   |
+--------------+--------+----------------------------+
| Post #1      | PASS   | —                          |
| Post #2      | WARN   | notice title_too_short     |
| Post #3      | FAIL   | critical missing_title     |
|              |        | warning missing_description|
+--------------+--------+----------------------------+

3 page(s) — 1 passed, 1 warned, 1 failed
4 issue(s) — 1 critical, 1 warning, 2 notice

What it checks ​

The audit runs only the metadata execution class — the checks that are resolvable from the model and the resolver alone, with no page fetch:

CheckCodes
Title / description present (fallback-aware)missing_title, missing_description
OG image present (fallback-aware)missing_og_image
Title / description lengthtitle_too_long, title_too_short, description_too_long, description_too_short
Duplicate title / description across the siteduplicate_title, duplicate_description
Robots conflicts & suspicious noindexrobots_conflict_indexing, robots_conflict_following, noindex_warning
Canonical format / cross-domain / shared / insecureinvalid_canonical, cross_domain_canonical, shared_canonical, insecure_canonical
Answer-readiness (AEO) — article structured dataaeo_missing_author, aeo_article_missing_date
Focus keyword set (opt-in)missing_focus_keyword
hreflang alternates (core registry, when a page has any)hreflang_invalid_code, hreflang_duplicate_code, hreflang_missing_self

Most codes also appear in the Pro scan, but the registries are separate. In particular, core uses hreflang_missing_self, while Pro uses hreflang_missing_self_reference; hreflang_duplicate_code is a core notice and a Pro warning. Do not assume identical coverage or severity from a shared name. blank_explicit_override belongs to the core registry. Length uses the editor's per-script budget — 60/160 characters for Latin text, ~30/80 for CJK, counted in graphemes and measured against the resolved value, suffix included — so the audit never contradicts the character counters in the Filament editor. The hreflang checks run on the list after the seo.hreflang policies (the same list the tags and the sitemap emit); reciprocity needs a crawl and stays in Pro.

The answer-readiness (AEO) checks fire only when a page declares article-type JSON-LD (Article, BlogPosting, NewsArticle, …) that is missing a signal that makes the article legible in structured data — an author entity (explicit authorship / provenance), or a datePublished / dateModified (an explicit timeline). A page without an article is never flagged, so the audit stays quiet where AEO doesn't apply. They are advisory (notice-level) and held out of the Pro 0–100 score.

What it does not check — the capability boundary ​

A free in-process audit can never equal the full Pro scan, and the command says so on every run. It does not run:

  • Rendered-HTML checks — missing_h1, multiple_h1, missing_image_alt, thin_content, mixed_content. These need the page's served HTML.
  • Live-canonical network checks — canonical_target_broken / _redirect / _noindex. These need an outbound (guarded) fetch.
  • The numerical 0–100 score. The score is a Pro feature, persisted on the scan-result record with a versioned rubric — see SEO score.

Those ship in the Pro scan — see the full issue registry.

Choosing what to audit ​

By default the command audits the models listed under seo.audit.models, falling back to seo.sitemap.models:

php
// config/seo.php
'audit' => [
    'models' => [
        \App\Models\Post::class,
        \App\Models\Page::class,
    ],
],

Or pass models explicitly:

bash
php artisan seo:audit --model="App\Models\Post" --model="App\Models\Page"

Options ​

OptionEffect
--model=A HasSEO model class to audit (repeatable). Overrides config.
--locale=Resolve SEO data in this locale (defaults to the app locale).
--limit=Max records to audit per model (0 = all).
--issues-onlyOnly list pages that have at least one issue.
--strictExit with a non-zero status when any issue is found — for CI.
--jsonEmit machine-readable JSON (pages, summary, coverage) instead of the table.

CI gate ​

--strict turns the audit into a build check:

bash
php artisan seo:audit --strict

It exits 1 if any page warns or fails, 0 when every audited page passes.

JSON ​

bash
php artisan seo:audit --json
json
{
  "pages": [
    {
      "model": "App\\Models\\Post",
      "key": 3,
      "label": "Post #3",
      "url": "https://example.com/blog/...",
      "status": "fail",
      "issues": [
        { "code": "missing_title", "severity": "critical", "field": "title", "message": "Page is missing a title tag." }
      ]
    }
  ],
  "summary": { "pages": 3, "passed": 1, "warned": 1, "failed": 1, "issues": 4, "by_severity": { "critical": 1, "warning": 1, "notice": 2 } },
  "skipped": [],
  "coverage": { "executes": "metadata", "note": "...", "reference": "https://rankbeam.dev/pro/scan-issues" }
}

Focus keywords ​

The missing_focus_keyword notice is off by default. It only fires once you opt into the focus-keyword workflow:

php
// config/seo.php
'keywords' => [
    'enabled' => true,
],

The Pro scan reads the same flag, so the audit, the scan, and the Pro editor nag always agree. Set a page's keywords with the Filament focus-keyword field or $model->saveSEO(['focus_keywords' => [['keyword' => 'laravel seo', 'is_primary' => true]]]).

When a value isn't what you expect: seo:explain ​

seo:audit tells you what's wrong; seo:explain tells you why a field resolved the way it did — which layer (config / default / computed / explicit) set each value, what it overrode, and what post-processing (title suffix, canonical strip, indexing guard) changed afterwards. Reach for it when an audit finding or a rendered tag is surprising:

bash
php artisan seo:explain "App\Models\Post" 42

rankbeam/laravel-seo is released under the MIT License.