Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [5.24.3] - 2026-09-19

### Fixed

- **`wp-ops schema-audit` checked no pages at all on many sites.** The script appended paths starting with `/` to a site URL it had already given a trailing slash, so every request went to `https://example.com//path/`. Sites answer that with a 301, and the audit skipped every page as "not found", homepage included. On imagewize.com it checked nothing and reported 0 of 11.
- The script is rebuilt along the lines of the 5.24.2 `schema_audit` MCP fix. Pages come from the site's sitemap (`wp-sitemap.xml`, `sitemap_index.xml`, then `sitemap.xml`; only the page sitemaps when there's an index), capped by a new `--max-pages` flag (default 25). A site with no sitemap falls back to the common paths, and the output says so.
- URLs that don't return 200 are listed in their own "NOT CHECKED" section with the status code and redirect target, and never count toward the totals.
- The "PAGES NEEDING SCHEMA MARKUP" list in the report was always empty. Its `grep -c ... || echo "0"` fallback printed `0` twice, which broke the numeric test. The list is now built during the audit.
- Each page is fetched once instead of up to six times.
- `--pages` accepts full URLs as well as paths.
- JSON-LD blocks spanning several lines are now read, and `Person` is among the detected types. The script now needs `perl` for this, which is present on macOS and standard Linux servers.
- **The `schema_audit` MCP tool now detects schema types inside `@graph`.** It read only the top-level `@type`, so the Organization, WebSite and BreadcrumbList nodes that Yoast, Rank Math and The SEO Framework nest in an `@graph` array went uncounted. The imagewize.com homepage reported "None of the tracked types". The text fallback for JSON-LD that doesn't parse never matched either, because it lowercased the schema but not the type it searched for.

## [5.24.2] - 2026-09-19

### Fixed
Expand Down
19 changes: 14 additions & 5 deletions go/internal/catalog/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -4153,12 +4153,13 @@
{
"category": "wp-cli",
"key": "wp-cli/seo/schema-audit",
"description": "Check key pages for schema markup and validate implementation",
"description": "Check a site's pages (from its sitemap) for JSON-LD schema markup",
"script_path": "wp-cli/seo/schema-audit.sh",
"runs_on": "local",
"runs": "local",
"requires": [
"curl"
"curl",
"perl"
],
"doc": "wp-cli/seo/README.md",
"args": [
Expand All @@ -4178,15 +4179,23 @@
"required": false,
"default": "audits",
"description": "Output directory",
"raw": "--output optional {audits} Output directory"
"raw": "--output optional {audits} Output directory"
},
{
"name": "--pages",
"required_raw": "optional",
"required": false,
"default": "/,/about/,/contact/",
"description": "Comma-separated page paths to check",
"raw": "--pages optional {/,/about/,/contact/} Comma-separated page paths to check"
"description": "Comma-separated page paths or URLs (default: sitemap pages)",
"raw": "--pages optional {/,/about/,/contact/} Comma-separated page paths or URLs (default: sitemap pages)"
},
{
"name": "--max-pages",
"required_raw": "optional",
"required": false,
"default": "25",
"description": "Maximum number of sitemap pages to check",
"raw": "--max-pages optional {25} Maximum number of sitemap pages to check"
}
],
"examples": [
Expand Down
41 changes: 29 additions & 12 deletions mcp-server/src/tools/schemaAudit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,36 @@ function extractSchema(html: string): string[] {
* Check which schema types are present in JSON-LD content
*/
function checkSchemaTypes(rawSchema: string[]): SchemaTypeCheck[] {
return SCHEMA_TYPES.map((type) => ({
type,
found: rawSchema.some((schema) => {
// Check for @type field with the type value
try {
const parsed = JSON.parse(schema);
return parsed["@type"] === type || (Array.isArray(parsed["@type"]) && parsed["@type"].includes(type));
} catch {
// Fallback to string search if JSON parsing fails
return schema.toLowerCase().includes(`"@type":"${type}"`);
const found = new Set<string>();

// Every @type anywhere in the tree: SEO plugins nest their nodes in an
// "@graph" array rather than putting one @type at the top level
const collect = (node: unknown): void => {
if (Array.isArray(node)) {
node.forEach(collect);
} else if (node && typeof node === "object") {
for (const [key, value] of Object.entries(node)) {
if (key === "@type") {
(Array.isArray(value) ? value : [value]).forEach((t) => typeof t === "string" && found.add(t));
} else {
collect(value);
}
}
}
};

for (const schema of rawSchema) {
try {
collect(JSON.parse(schema));
} catch {
// Unparseable JSON-LD: fall back to matching "@type": "X" in the text
for (const type of SCHEMA_TYPES) {
if (new RegExp(`"@type"\\s*:\\s*(\\[[^\\]]*)?"${type}"`).test(schema)) found.add(type);
}
}),
}));
}
}

return SCHEMA_TYPES.map((type) => ({ type, found: found.has(type) }));
}

/**
Expand Down
29 changes: 16 additions & 13 deletions wp-cli/seo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,29 +165,32 @@ REPORT_DIR="reports" ./wp-cli/seo/redirect-audit.sh --url https://example.com

**Script:** `schema-audit.sh`

Validates JSON-LD schema markup presence and types across key WordPress pages.
Validates JSON-LD schema markup presence and types across a site's pages.

### Features

- Detect JSON-LD schema blocks in page HTML
- Identify specific schema types:
- Organization
- LocalBusiness
- Service
- Product
- WebSite
- BreadcrumbList
- Check key pages (homepage, services, contact, portfolio, about, shop)
- Generate summary report with recommendations
- Takes the page list from the site's sitemap: `wp-sitemap.xml` (WordPress core), `sitemap_index.xml` (Yoast, Rank Math, The SEO Framework), then `sitemap.xml`. From a sitemap index it reads only the page sitemaps, when there are any. The homepage always comes first.
- Caps the list at `--max-pages` (default 25) and says when the cap cut it short
- Falls back to common paths (`/about/`, `/contact/`, `/shop/`, ...) only when the site has no sitemap
- Detects JSON-LD blocks in page HTML, including ones spanning several lines or with extra attributes
- Identifies schema types anywhere in the JSON-LD, including inside `@graph`: Organization, LocalBusiness, Service, Product, WebSite, BreadcrumbList, Article, FAQPage, HowTo, Person
- Lists URLs that don't return 200 in their own section, with status code and redirect target. They never count as missing schema.
- Generates a summary report with the pages that need schema, plus recommendations

### Usage

```bash
# Audit schema on a site
# Audit the pages in the site's sitemap (first 25)
./wp-cli/seo/schema-audit.sh https://example.com

# Check more sitemap pages
./wp-cli/seo/schema-audit.sh https://example.com --max-pages 60

# Specific pages: paths or full URLs
./wp-cli/seo/schema-audit.sh https://example.com --pages /,/services/,/contact/

# With custom output directory
OUTPUT_DIR="reports/seo" ./wp-cli/seo/schema-audit.sh https://example.com
./wp-cli/seo/schema-audit.sh https://example.com --output reports/seo
```

### Output Files
Expand Down
Loading
Loading