Schema Jsonld¶
"Missing" vs "Basic" — Don't Conflate Them¶
From legacy section: Schema Auditing & Deployment
Pattern: Schema audit labeled inner pages as "Missing" schema when Schema Pro was actually outputting basic Person/Service/LocalBusiness blocks — just without @id cross-referencing, hasCredential, areaServed, or provider linking. Jim caught it: "confirm if the schema is missing or vague." Rule: When Schema Pro is active, default status for inner pages should be "Partial" (basic schema present, missing advanced properties), not "Missing." Only use "Missing" when literally zero schema exists. The strategic audit's earlier note — "inner pages have it, homepage doesn't" — should have carried through. Date: 2026-04-16
Attorney Data Pattern — WebSearch Directory Profiles¶
From legacy section: Schema Auditing & Deployment
Pattern: When site returns 403, couldn't fetch attorney bio pages directly. But WebSearch queries like
"[Name] attorney [Firm]"returned rich data from Avvo, Justia, Martindale, SuperLawyers, LinkedIn — including education, bar admissions, practice areas. Rule: For professional services schema, don't rely on the client site alone. WebSearch across directory profiles gives you credentials, education, sameAs URLs, and practice areas in parallel. Always confirm with Jim before deploying (he verifies images, job titles, and org memberships). Date: 2026-04-16
Deployment Scripts: Auto-Resolve URLs + Auto-Disable Schema Pro¶
See also sops/schema-deploy-runbook.md — this is incorporated as a key rule in the deploy runbook.
Pattern: Initial deployment script required manual post ID lookup per page. Also required Jim to manually disable Schema Pro's overlapping types on each page before deploying. Rule: Every schema deployment script should: (1) use
get_page_by_path()to auto-resolve URLs to post IDs — no manual lookup, (2) auto-disable Schema Pro types viaupdate_post_meta($id, '_aiosrs_schema_tab_<type>', 'disabled')before deploying overlapping custom schema. One-command deployment, no manual WordPress admin work required. Date: 2026-04-16
Schema Pro "All Singulars" Excludes Front Page¶
From legacy section: Elementor
Pattern: Schema Pro's "All Singulars" targeting does NOT include "Front Page" — they're separate options. Enable On only supports AND rules. Rule: When Schema Pro needs to target the front page, create a separate schema with "Front Page" targeting or use manual injection. Date: 2026-03-05
Schema Pro DB Options Persist After Deactivation¶
From legacy section: Schema Deployment
Pattern: Deactivated Schema Pro but its
wp-schema-pro-general-settings,wp-schema-pro-global-schemas, andwp-schema-pro-corporate-contactoptions stayed in the DB. If any other plugin reads those options, the old schema could resurface. Rule: After deactivating Schema Pro, verify no ghost schema output by checking the live page source (curl -s URL | grep 'LocalBusiness\|Corporation'). If ghosts appear, checkwp option list --search='*schema-pro*'and clean up if needed. Date: 2026-04-16
JetTabs Outputs FAQPage Schema from Accordion Widgets¶
From legacy section: Schema Deployment
Pattern: Deployed our own FAQPage schema to the FAQ page, then discovered JetTabs was auto-generating FAQPage from visible accordion content — doubling all 24 Q&As. Rule: Before deploying FAQPage schema to any page, check if JetTabs (or any accordion widget) is already outputting FAQ schema:
curl -s URL | grep -c FAQPage. If so, skip custom FAQPage and let the widget handle it (matches visible content structure). Deploy other types (BreadcrumbList, Service, etc.) but not FAQPage. Date: 2026-04-16
Cloud Claude Code Sessions Can't Execute Schema Deploy¶
From legacy section: Schema Deployment
Pattern: Ran
/schema-deployin a cloud Claude Code session (claude.ai/code). Session has nossh/scp/wpbinaries and no WordPress MCP connection to the target site. The deploy script and runbook are fine, but actual execution (SSH to Hostinger, upload mu-plugin,wp eval-file) is physically impossible from the sandbox. Rule: Cloud/web Claude Code sessions are file-generation and validation only. When/schema-deployruns in a cloud session: (1) confirm the package is build-ready (JSON valid, PHP syntax OK, VERIFY markers resolved), (2) produce the laptop command list, (3) explicitly tell Jim the deploy must run from his laptop. Never pretend a deploy happened. Post-deploy validation (WebFetch the live URLs, grep forapplication/ld+json) IS possible from cloud. Date: 2026-04-20
Source-File Documentation Keys Must Be Stripped Before Deploy¶
From legacy section: Schema Deployment
Pattern: Schema source JSON files carried
_VERIFY_BEFORE_DEPLOYand_employee_notekeys as author comments (helpful in the repo). First version of_build-deploy-php.pyonly stripped the top-level_VERIFY_BEFORE_DEPLOY— the nested_employee_noteinside Organization would have leaked into production JSON-LD. Rule: In any schema-to-PHP build script, recursively strip ALL keys starting with_before embedding in the deploy output. JSON-LD processors ignore non-schema.org keys, but leaking internal notes into production is sloppy. Pattern:strip_underscore_keys()helper that walks dicts/lists recursively. Applied inclients/_active/northway-title/schema/_build-deploy-php.py. Date: 2026-04-20
Generate-Then-Build Pattern for Programmatic Schema Packages¶
From legacy section: Schema Deployment
Pattern: Northway Title needed 42 pages of schema — hand-writing 36 county/service JSON files would have been tedious and error-prone. Built a two-step pipeline:
_generate-county-schemas.pyreadsnta_pages_data.jsonand emits 36 county JSON files (pulling FAQs, H1s, intros from the source data verbatim so schema matches visible content);_build-deploy-php.pywalks all 42 JSON files and emits the WP-CLI deploy script. Rule: For any schema package with >10 similar pages (county pages, service variations, location pages), write a Python generator that reads the source content data file and emits the schema JSON. Never hand-write more than a handful of near-identical schema files. The generator is also the single source of truth when the source data changes — regenerate, re-validate, redeploy. Date: 2026-04-20
Wix Schema Paste-Target Trap (Mid Ohio MBS)¶
From legacy section: Schema Deployment
Pattern: Schema deploy guide for midohiomindbodyandsoul.com (commit 52ed7ea, 2026-04-16) instructed pasting JSON-LD into Page Settings → SEO (Google) → Advanced SEO → Additional Markup. For /services-8 it landed in a Rich Text widget on the page body instead, rendering the full JSON as visible text overlapping content. Head schema was correct in parallel — issue stayed invisible until 9 days later when Jim spotted it in browser. Confirmed via
curl + python(only /services-8 had"@context"in non-<script>HTML; component IDcomp-mo1jxmypwas the rich-text widget). Rule: Wix schema deploy guides MUST include a verification step that runscurl -sL URL | python3 -c "..."to assert that"@context"appears ONLY inside<script type="application/ld+json">and NOT in body content. Run this immediately after the Wix paste-and-publish on every page in the deploy. The Rich Text element is two clicks away in the Wix Editor; Advanced SEO Additional Markup is 6+ clicks deep — paste targets get confused. Also: never instruct "paste here" without providing a screenshot of the exact field. Date: 2026-04-25
Wix FAQ Widget Auto-Generates FAQPage (Mid Ohio MBS)¶
From legacy section: Schema Deployment
Pattern: /faqs page on midohiomindbodyandsoul.com showed
Rich Results: FAIL — FAQ × 2 unnamed itemsin GSC URL inspection (2026-04-24). Static HTML had only ONE<script type="application/ld+json">containing one FAQPage. The duplicate was injected client-side at runtime by Wix's FAQ widget — Wix auto-emits FAQPage schema whenever its FAQ accordion widget is on the page. Custom FAQPage in Advanced SEO Additional Markup + Wix widget = duplicate. Service pages with custom FAQs but NO Wix FAQ widget showedPASS — 1 FAQ(red-light-therapy confirmed) — same pattern as JetTabs on WordPress. Rule: Before pasting custom FAQPage into Wix Advanced SEO on any page, check if the page uses Wix's FAQ widget. If yes, skip custom FAQPage — let the Wix widget's auto-schema do the job and edit Q&As in the widget itself. Deploy other types (BreadcrumbList, Service, WebPage, etc.) normally. Same pattern asJetTabs Outputs FAQPage Schema from Accordion Widgets(2026-04-16) — generalize: any page builder/widget that renders accordion FAQ content auto-generates FAQPage schema. Date: 2026-04-25
evolvebusiness.com Blocks Automated Fetching¶
From legacy section: Schema Audit
Pattern: WebFetch returns 403 on all evolvebusiness.com pages (homepage, services, about, contact, sitemap.xml, robots.txt). Cloudflare or similar bot protection blocks all automated requests. Confirmed again on protocolsvc.com (2026-04-16) during
/schema-audit— same 403 pattern. Rule: For bot-protected client sites (evolvebusiness.com, protocolsvc.com confirmed), either: (1) use SSH + WP-CLI to inspect schema directly from the server, (2) use WordPress MCP if available, (3) have Jim manually run Rich Results Test and paste results, or (4) fall back to the latest prior walkthrough audit inclients/_active/<client>/audits/— and ALWAYS flag data freshness in the deliverable so it gets re-verified before deployment. Don't waste time retrying WebFetch — it won't work. Date: 2026-04-15
Legacy Brand Names Leak Into Schema via alternateName¶
From legacy section: Schema Audit
Pattern: Putnam Place was rebranded from "Putnam Den" years ago. Rich Results Test still showed
"alternateName": "Putnam Den"in the homepage Organization block (emitted by Schema Pro's Organization config). An earlier audit had logged it as "Club Putnam" — different name but same root issue: legacy pre-rebrand names baked into the SP config. Schema Pro emits whatever is in the field; nobody cleared it at rebrand time. This directly contradicts Tier 4 #50 ("Clean 'Putnam Den' legacy citations") on the same client's master priority list — you can't clean legacy names from third-party directories while your own schema is re-emitting them to Google. Rule: For any client with a known rebrand history (tagged in client-profile.md as "Formerly Known As"), always check rendered JSON-LD foralternateNameon Organization and LocalBusiness blocks. Use Google Rich Results Test or Chrome MCP — Schema Pro's admin UI can look clean while output is wrong. When replacing Schema Pro via mu-plugin, explicitly OMITalternateNameunless the client confirms a current live alternate brand. Add a pre-publish check to /schema-audit and /schema-generate: "If client has rebrand history, verify alternateName is empty or current-brand-only." Date: 2026-04-16
Schema Pro Has Known Output Bugs That Can't Be Fixed Via UI¶
From legacy section: Schema Audit
Pattern: Schema Pro (v2.11.0) on putnamplace.com was outputting: lowercase
@type: "organization"instead of "Organization",ContactPoint.url: "18"(raw page ID instead of resolved URL), duplicated sameAs entries (every URL twice), and falsecontactOption: ["HearingImpairedSupported", "TollFree"]defaults. None of these were fixable through the Schema Pro admin UI — the form values were correct, but the output rendering was broken. Rule: When auditing a site with Schema Pro, check the actual rendered JSON-LD on the live page (not the Schema Pro settings UI). If output has structural bugs, don't try to fix Schema Pro — draft the broken schemas and replace with custom JSON-LD via the evolve-schema mu-plugin. Schema Pro is acceptable for breadcrumbs, SiteNavigationElement, and basic WebSite — but replace anything requiring @id, specific LocalBusiness subtypes, or complex nested entities. Date: 2026-04-16
Save Audit Findings to Client Folder¶
From legacy section: Schema Audit
Pattern: Jim said "always save" when asked whether to save the schema audit findings. Conversational output that takes significant work to produce should always be persisted. Rule: After any substantive audit, analysis, or deliverable — even if delivered conversationally — save to the client folder (e.g.,
clients/_active/[client]/schema-audit.md). Don't ask, just do it. The next session needs this context. Date: 2026-04-15
Include Blog Title Context in Image Prompts¶
From legacy section: Schema Audit
Pattern: Generated featured images with generic concept descriptions. Jim wanted the images to visually reference the blog post title/topic directly (e.g., Yellow Pages book for "Map Pack Is the New Yellow Pages"). Rule: Always include the blog post title in Gemini image generation prompts so the image directly connects to the article's hook/metaphor. Date: 2026-04-07
NYS Uses DWI/DWAI — Not DUI¶
From legacy section: Schema Audit
Pattern: Listed charges as "DWI/DWAI offenses" on the criminal defense page. Jim asked for the proper distinction. New York State does not use "DUI" as a legal term — the proper charges are DWI (BAC 0.08+), Aggravated DWI (BAC 0.18+), DWAI-Alcohol (BAC 0.05-0.07), DWAI-Drugs, and DWAI-Combined. Rule: Use the correct NYS terminology (DWI, DWAI) in all legal/penalty content. Use "DWI/DUI" in page titles, headings, and navigation for SEO (people search "DUI lawyer"), but body content should use the proper NYS terms. Label the page/service as "DWI/DUI Defense" — not "DWI/DWAI offenses." Date: 2026-04-15
Prioritize High-ROI Practice Areas in Lists¶
From legacy section: Schema Audit
Pattern: DWI/DUI was listed 10th out of 20 charges on the criminal defense page and 5th out of 6 practice areas. Jim flagged that DWI and PI are the highest ROI practice areas and should be listed first. Rule: When building charge lists or practice area sections for law firm sites, order by revenue potential — not alphabetically. DWI and PI go at the top. The order clients see = the order of business priority. Date: 2026-04-15
Squarespace Code Injection schema deploy — CodeMirror v5 setValue is the cleanest path; per-page settings dialog is iframed and inaccessible to Chrome MCP¶
From legacy section: SEO NEO / Workbook
Squarespace's site-wide Code Injection panel at
/config/pages/code-injectionuses CodeMirror v5. The clean deploy pattern via Chrome MCP:javascript_tool→document.querySelectorAll('.CodeMirror')[0].CodeMirror.setValue(decoded)for the Header field, thenfindthe Save button via accessibility tree → click. Dirty state detection works because CM5 fires input events on setValue. Verified 2026-05-14 on meatandcompanynisky.com — schema deployed correctly. Per-page settings dialog (the gear icon on a page row) opens a modal in an iframe withchrome-extension://origin — Chrome MCP can't access it (returns "Cannot access a chrome-extension:// URL of different extension" on screenshots and JS). Workaround: append per-page schema to the site-wide injection — Google's rich result parser doesn't care if a Menu/FAQPage/Event entity appears on every page as long as theurlproperty anchors it to the right URL. Why: Deployed schema for Meat & Company (Squarespace BBQ restaurant) on 2026-05-14. Site-wide injection (Restaurant + BarbecueRestaurant + Org + WebSite + AggregateRating + Menu) all went in via the CodeMirror setValue pattern in one save. Menu schema was originally planned for the /menu page header-only injection, but Squarespace's per-page advanced dialog kept throwing "Cannot access a chrome-extension:// URL" errors. Pivoted to appending Menu schema to the site-wide injection — verified live on the homepage withBarbecueRestaurant,aggregateRating 4.8/294, andhasMenuSectionall rendering. No SEO downside; SERP star eligibility is now active. How to apply: 1. Squarespace schema = site-wide Header Code Injection — accept that all schema fires on every page; use@idandurlproperties to scope entities to their canonical URLs. 2. Deploy pattern: navigate to internal admin subdomain (e.g.kumquat-kazoo-9ktx.squarespace.com/config/pages/code-injection) — direct domain/configpaths sometimes 503; the internal subdomain is more stable. CodeMirror v5.setValue()writes content; triggerinput/changeevents; click Save button viafind+left_click. 3. If the chrome-extension iframe error appears, close the current tab and start fresh — sometimes a stuck overlay propagates. Don't fight the per-page dialog; route through site-wide. 4. For base64-decoding large schema blobs in the JS injection, usedecodeURIComponent(escape(atob(b64)))to handle UTF-8 characters (em-dashes, smart quotes) correctly. Date: 2026-05-18