SEOCode Documentation
Getting Started
Up and running
in 30 seconds.
SEOCode is a GitHub App. No CI config, no tokens, no YAML. Install it and every pull request is automatically reviewed.
Install the GitHub App
Go to github.com/apps/seoguard, click Install, and choose which repositories to grant access to. You can select all repos or specific ones.
Open a pull request
SEOCode runs automatically whenever a pull request is opened or updated. No manual trigger needed.
Review the SEO report
A comment appears on your PR listing every issue found, the exact fix to apply, and a link to the relevant spec. The PR status check shows pass or fail depending on whether any critical issues were found.
How it works
What gets scanned.
SEOCode reads every supported file in your repository at the commit SHA of the PR head and runs the full SEO and GEO ruleset. It never stores your code.
File types
.html .js .jsx .tsx .vue .svelte .astro
When it runs
On every PR open, push, and reopen. Automatically - no manual trigger.
Data handling
Nothing is stored.
Framework shells and utility files are automatically detected and skipped for rules that don't apply - no false positives on component-only files.
SEOCode is framework-aware. It reads metadata the way your framework renders it - titles and descriptions from the Next.js Metadata API / generateMetadata, Remix meta(), and Astro layouts, plus JSON-LD injected via dangerouslySetInnerHTML. So dynamic titles and structured data are never wrongly flagged as "missing". On framework files the <head> is composed across layouts and pages, so SEOCode reports only what it can prove - while plain HTML is checked in full.
PR Comment
Reading the report.
Every issue is categorised by severity. Fix critical issues before merging - warnings and info are advisory.
Critical
Issues that directly harm search indexing or ranking. Missing title tag, accidental noindex, missing H1. The PR status check is set to failure when any critical issue is found.
Warning
Issues that degrade SEO quality but don't break indexing. Missing Open Graph tags, broken heading hierarchy, title too long. Status check remains passing.
Info
Best-practice recommendations. Missing canonical URL, no structured data, images missing lazy loading. Advisory only - no status check impact.
New in this PR vs. pre-existing
SEOCode judges each pull request on what it changed. Issues on the lines your PR touched lead the report; anything already on main is folded into a separate "pre-existing - not from this PR" section for reference. A PR that introduces nothing reads as clean even when the repo carries older debt.
Crucially, the status check keys off only the issues this PR introduced - legacy debt you didn't cause never fails your merge. One-click fix suggestions likewise appear only on the lines you changed.
GitHub Permissions
Minimum access,
nothing more.
SEOCode requests only the permissions it needs to do its job. You can review and revoke these at any time from your GitHub settings.
Contents: Read
Reads repository files at the PR's commit SHA for analysis. Nothing is stored.
Pull requests: Read & Write
Allows SEOCode to post and update the SEO review comment on your pull request. The Write permission is needed only to create and edit that single comment - SEOCode does not merge, close, or modify your PRs in any other way.
Commit statuses: Read & Write
Allows SEOCode to post a pass or fail status check on the PR commit. This is what blocks merging when critical SEO issues are found, and what shows the green checkmark when everything passes.
Revoking access
Go to GitHub → Settings → Applications → Installed GitHub Apps, find SEOCode, and click Configure. You can remove individual repositories or uninstall the app entirely.
PR Status Check
What the status
check looks like.
Every reviewed PR gets a status check called seocode/review. You can require this check to pass before merging via your branch protection rules.
Failure
One or more Critical issues were found. The check blocks merging if your branch protection rules require it. The PR comment lists the exact issues and fixes.
Success
No critical issues found. Warnings and info items may still appear in the PR comment but they do not affect the status check outcome.
Enforcing the check
To block merging on SEO failures: go to your repo → Settings → Branches → Branch protection rules → enable Require status checks to pass → search for and add seocode/review.
Plans & Limits
What each plan
gets you.
Plans are per GitHub organization and managed from your dashboard. Studio switches on within about a minute of subscribing, with no activation step. If you cancel, Studio stays on until the end of the period you paid for.
| Plan | Price | Rules | Repos | Private repos | Support |
|---|---|---|---|---|---|
| Developer | $0 | Full ruleset | Unlimited public | Not in the app (CLI: any) | Community |
| Studio | $19/mo or $190/yr per org | Full ruleset | Unlimited public + private | Unlimited | Priority |
Plans apply to the GitHub App. The @qobi/seocode CLI and the MCP server are free on any code, public or private, and run on your own machine or CI.
Upgrade an organization and manage billing from your dashboard.
Uninstalling
Removing SEOCode.
You can restrict access to specific repositories or remove SEOCode entirely at any time. No data is retained after uninstall.
Remove from specific repos
Go to GitHub → Settings → Applications → Installed GitHub Apps → SEOCode → Configure. Under Repository access, remove the specific repositories you no longer want reviewed.
Uninstall entirely
On the same configuration page, scroll to the bottom and click Uninstall. This removes SEOCode from all repositories and revokes all GitHub permissions immediately.
Cancel your subscription
Uninstalling the app does not cancel billing. If the organization is on Studio, open your dashboard and click Manage billing on it; the organization stays listed there after you uninstall, so you can always cancel. Subscribed through an older checkout link? Cancel in the Polar customer portal instead.
Configuration
Per-repo config.
Drop a .seocode.json file in your repository root to disable rules, change their severity, or exclude paths from review. It's read at each pull request's head commit, so changes apply on the next review - and it's available on every plan.
{
"exclude": ["emails/**", "public/legacy/**", "**/*.stories.tsx"],
"rules": {
"image-missing-lazy-loading": "off",
"title-too-long": "info"
}
}
Exclude paths
exclude takes glob patterns - any matching file is skipped entirely. * matches within a path segment, ** matches across segments, and a trailing / excludes a whole directory (e.g. public/legacy/).
Disable or reclassify rules
Under rules, set a rule id to "off" (or false) to disable it, or to "critical", "warning", or "info" to change its severity - which also changes whether it can block a required status check.
Safe by default
Config can only narrow or reclassify what SEOCode reports - it never invents new rules. If the file is missing or malformed, SEOCode runs with its defaults, so a typo can't break your reviews.
Real-time Alerts
Alert your team
the second it matters.
When a pull request introduces a deploy-blocking SEO issue, SEOCode can send an instant alert to Slack, Email, Microsoft Teams, or Discord - before the PR merges. Configure any combination of channels in .seocode.json. Requires Studio.
{
"notifications": {
"slack": "https://hooks.slack.com/services/...",
"email": "seo-team@yourcompany.com",
"teams": "https://yourcompany.webhook.office.com/...",
"discord": "https://discord.com/api/webhooks/..."
}
}
All four channels are optional - include only the ones your team uses. Alerts fire on critical issues only (the same threshold that fails the PR status check). Warnings and info don't trigger notifications.
Slack
Set slack to an Incoming Webhook URL. In Slack, go to Your apps → Create new app → Incoming Webhooks, enable it, and copy the webhook URL for the channel you want alerts in. Alerts include the PR title, author, the file and rule that triggered, and a direct link to the PR.
Set email to any email address. Alerts are sent from alerts@seocode.io via Resend. Add this address to your allowlist if your inbox filters aggressively. You can use a team alias (e.g. seo-team@yourcompany.com) to reach multiple people at once.
Microsoft Teams
Set teams to a Teams incoming webhook URL. In Teams, go to the channel you want to post in → Connectors → Incoming Webhook → configure and copy the URL. SEOCode posts an Adaptive Card with the PR details and a link to view it on GitHub.
Discord
Set discord to a Discord webhook URL. In your server, go to the channel → Edit Channel → Integrations → Webhooks → New Webhook, and copy the webhook URL. Alerts are posted as embedded messages with the PR link, author, and issues found.
Studio only
Notification channels require the Studio plan ($19/mo per organization). On the Developer (free) plan, the notifications block is silently ignored - no error is shown, the rest of your .seocode.json still applies. Upgrade to Studio →
Command line
Run it in your terminal.
The same framework-aware engine ships as a zero-config CLI, so you can catch SEO issues locally - before you push. The command is seocode; it's free and un-gated (the full ruleset, every plan).
# run once, no install npx @qobi/seocode check # or install globally - the command is just `seocode` npm i -g @qobi/seocode seocode check
Commands
seocode check scans the repo (or given paths). seocode check --fix applies safe, mechanical fixes to your files (e.g. loading="lazy", rel="noopener noreferrer"). seocode check --staged reviews only git-staged files. seocode check --json prints machine-readable output.
Pre-commit hook
Run seocode init --hook to scaffold a .seocode.json and install a git pre-commit hook that runs seocode check --staged - so broken SEO never even reaches a pull request. It honors the same .seocode.json config as the GitHub App.
Exit codes
Exit code 1 when deploy-blocking (critical) issues are found, 0 otherwise - so it drops straight into a pre-commit hook or a CI step. Requires Node.js 20+.
Command line
In your AI editor (MCP).
The same engine runs as a local MCP server, so you can audit files from inside Cursor, Claude Desktop, or Claude Code. Ask your assistant "audit this file for SEO issues" and it runs the full ruleset locally - nothing leaves your machine.
# add to your MCP client config, then reload
{
"mcpServers": {
"seocode": {
"command": "npx",
"args": ["-y", "-p", "@qobi/seocode", "seocode-mcp"]
}
}
}
Where the config goes
Claude Desktop: claude_desktop_config.json. Cursor: Settings → MCP. Claude Code: a .mcp.json in your project. Reload the client after adding the block.
Two tools
audit_file runs the full ruleset against one file and returns issues grouped by severity with the fix for each. suggest_fix returns a deterministic 1-click replacement for a specific issue (by ruleId, optionally line). It never invents a fix.
Config & privacy
The server honors your .seocode.json (rule disables and severity overrides), resolved by walking up from the audited file. It runs entirely on your machine over stdio - free, local, and un-gated. To run a local build instead of npx, point command at node and the built dist/src/mcp/index.js.
Local preview, not the safety net
The MCP checks the file you ask about, when you ask. Install the GitHub App so the same rules run on every pull request across your team, automatically.
AI Discoverability
GEO rules.
Generative Engine Optimization (GEO) is the layer of technical hygiene that determines whether AI-powered answer engines - ChatGPT, Perplexity, Google AI Overviews - can read, cite, and summarize your content. SEOCode ships 8 GEO rules that catch AI discoverability regressions before they reach production. Like standard SEO rules, GEO findings appear in your PR comment and can block the status check when severity is critical.
geo-robots-ai-agents critical
What it checks: Whether robots.txt contains a Disallow directive targeting AI crawlers - specifically GPTBot (OpenAI), PerplexityBot, ClaudeBot (Anthropic), or Google-Extended (Google AI). A blanket block on any of these agents tells the crawler to skip your content entirely.
Why it matters: AI answer engines can only cite pages their crawlers can reach. Blocking them in robots.txt effectively opts your site out of AI-generated citations and summaries - often unintentionally, as a side-effect of a wildcard Disallow: / rule copied from a staging config.
# flagged - blocks OpenAI's crawler User-agent: GPTBot Disallow: / # fix - allow the agent (or remove the stanza entirely) User-agent: GPTBot Allow: /
schema-nested-graph critical
What it checks: JSON-LD entities of type Organization, Person, Product, or Place that are missing an @id property. Without @id, the graph is flat and anonymous - each entity cannot be linked to or referenced from other documents.
Why it matters: AI engines build a knowledge graph by resolving entity identifiers. An entity without @id cannot be merged with the same real-world entity found on other pages or in external knowledge bases, so it contributes nothing to the engine's model of your organization, product, or person.
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": "https://example.com/#org", // ← add this
"name": "Acme Corp",
"url": "https://example.com"
}
schema-entity-sameas warning
What it checks: Organization or Person schema that has no sameAs array. sameAs should list authoritative external identifiers such as a Wikidata entry, LinkedIn company page, or Crunchbase profile.
Why it matters: AI engines use sameAs to corroborate an entity's identity against their existing knowledge. Without it, your organization may be treated as an unknown or ambiguous entity, which reduces the likelihood of accurate citations and entity-level knowledge panel coverage.
{
"@type": "Organization",
"name": "Acme Corp",
"sameAs": [
"https://www.wikidata.org/wiki/Q12345",
"https://www.linkedin.com/company/acme",
"https://www.crunchbase.com/organization/acme"
]
}
geo-ssr-hydration critical
What it checks: Critical metadata - title, description, og:image, or canonical - that is set only inside a useEffect or useState initializer. These hooks are client-only: the metadata does not exist in the server-rendered HTML that crawlers fetch.
Why it matters: AI crawlers behave like traditional search bot crawlers: they read the static HTML response, not a post-hydration DOM. Metadata set via useEffect is simply absent from their perspective - meaning they see no title, no description, and no image for the page.
// flagged - title set after hydration, invisible to crawlers useEffect(() => { document.title = "Dashboard"; }, []); // fix - use the framework's server-side metadata API // Next.js App Router: export const metadata = { title: "Dashboard" }; // Remix: export function meta() { return [{ title: "Dashboard" }]; }
schema-faqpage-completeness warning
What it checks: Files that include a FAQPage JSON-LD block but have no corresponding question-and-answer structure in the visible HTML - no heading that matches an FAQ question, and no answer paragraph beneath it.
Why it matters: AI engines cross-reference structured data against the actual page content. A FAQPage schema that has no visible counterpart looks like spam or decoration. Engines that verify structured data against HTML content may ignore the schema entirely, eliminating any FAQ rich-result or citation benefit.
<!-- fix: mirror every FAQ item as visible HTML -->
<section>
<h3>What is SEOCode?</h3>
<p>SEOCode is a GitHub App that reviews pull requests for SEO
and AI discoverability issues automatically.</p>
</section>
geo-bluf-structure info
What it checks: h2 and h3 sections where the direct answer to the implied question appears more than a paragraph into the section - i.e. the section leads with background or narrative before stating the point. BLUF stands for Bottom Line Up Front.
Why it matters: AI engines extract "passages" - self-contained snippets of text - to answer user queries. When the direct answer is buried several sentences in, the relevant passage is harder to isolate and less likely to be surfaced. Leading with the answer improves passage extraction and citation quality without changing the overall content.
- → Lead each section with the direct answer or key claim in the first sentence.
- → Follow with context, evidence, or elaboration in subsequent sentences.
geo-passage-containment warning
What it checks: Heading elements (h2, h3) whose following content is not wrapped in a semantic block element - <article>, <section>, or <aside>. Content that flows directly as siblings of headings inside a generic <div> lacks clear passage boundaries.
Why it matters: AI engines use semantic HTML to determine where a passage begins and ends. Without a containing element, the engine has to infer passage boundaries from heading proximity alone, which is imprecise and makes it harder to extract the right content for a given query.
<!-- flagged: heading and content are bare siblings --> <div> <h2>Our pricing</h2> <p>Plans start at $0 per month...</p> </div> <!-- fix: wrap in a semantic container --> <section> <h2>Our pricing</h2> <p>Plans start at $0 per month...</p> </section>
geo-unanchored-stats info
What it checks: Numerical statistics - percentages, counts, monetary figures - that appear in body text without an inline citation link anchoring them to a source. The rule looks for patterns like 85%, 2.4 million, or $3B in paragraph text that have no adjacent <a> element.
Why it matters: AI engines apply a credibility signal to claims that are verifiable - a stat linked to its source is treated as asserted evidence rather than unverified copy. Unanchored statistics may be omitted from AI-generated summaries or cited with lower confidence. Linking also benefits traditional SEO through outbound trust signals.
<!-- flagged: numerical claim with no source link --> <p>Third-party studies show pages with schema rank N% higher within 30 days.</p> <!-- fix (option A): link the statistic to its source --> <p><a href="https://example.com/schema-study">According to Schema.org research</a>, pages with structured data rank measurably higher within 30 days.</p> <!-- fix (option B): use first-person phrasing --> <p>In our analysis, pages with schema consistently rank higher within 30 days.</p>
Configuring GEO rules
All 8 GEO rules respect your .seocode.json config. You can disable individual rules, reclassify their severity, or exclude paths - see .seocode.json configuration for details. The rule IDs above map directly to the keys in the rules object.
Help
Troubleshooting.
Common issues and how to fix them.
SEOCode didn't comment on my PR
Check these in order:
- The app is installed on that specific repository - go to GitHub → Settings → Applications → Installed GitHub Apps → SEOCode → Configure and confirm the repo is listed.
- The repository contains at least one supported file:
.html,.htm,.js,.jsx,.tsx,.vue, or.svelte. React components in plain.jsare scanned too. Repositories with only CSS, JSON, markdown, or backend code won't produce a review. - You're on the Developer plan, which covers public repositories only. Upgrade the organization to Studio to review private repositories.
- If none of the above apply, contact support with your repository name and PR number.
The PR status check is stuck as "pending"
This usually means the review job didn't complete in time. Push a new commit to retry. If it persists, contact support with your repository name and PR number.
SEOCode flagged something that isn't an SEO issue
SEOCode reads your framework's real metadata - it understands dynamic titles and descriptions from the Next.js Metadata API, Remix meta exports, and similar patterns, so those don't false-positive. If a rule still fires on something intentional (e.g. noindex on a staging route), let us know - your feedback directly improves rule accuracy.
I need to re-run the check on an existing PR
Push a new commit to the PR branch - SEOCode will run again automatically. If you have nothing to push, an empty commit works: git commit --allow-empty -m "retrigger".
I subscribed but my plan isn't active yet
Studio usually switches on within a minute of subscribing, and there's no activation step. Your dashboard shows "Activating…" on the organization until it does. If it still shows Free after a few minutes, refresh the page; if it's still Free, email support@seocode.io with the organization name. Subscribed through an older checkout link? Activate it at seocode.io/activate.
Subscribing didn't automatically review all my repos
Studio covers every repository in the organization, but SEOCode only reviews repositories the GitHub App can access. If the app was installed on selected repositories only, private ones may be missing. To cover everything, go to GitHub → Settings → Installed GitHub Apps → SEOCode → Configure and choose All repositories (or add the specific repo). See Plan limits for how many repos each plan covers.
SEOCode says "private repository not covered"
Public repositories are always covered on every plan. Private repositories need Studio: Developer (free) covers public repositories only. If a private repo wasn't reviewed, upgrade the organization to Studio. If your organization was already using a private repository on the free plan, it keeps being reviewed until the date shown in the PR comment. The free @qobi/seocode CLI can review any repository, public or private, in the meantime.
Only some of my files were reviewed
Very large repositories are reviewed up to a per-run file count, and the PR comment notes when this happens (e.g. "reviewed 40 of 58 files"). If you need full coverage of a large repository, contact support and we'll raise the limit for your account.
Can I switch between monthly and yearly billing?
Yes. Open your dashboard, click Manage billing on the organization, and choose monthly or yearly. The change takes effect at the end of your current billing period, so you keep everything you've already paid for.
I cancelled but I still have access / how do I manage billing
Cancelling stops the next renewal: Studio stays active until the end of the period you paid for, then the organization reverts to Free. Your dashboard shows the end date. To view invoices, update your card, switch between monthly and yearly, or cancel, open your dashboard and click Manage billing on the organization. Only the person who subscribed sees that button. Subscribed through an older checkout link? Use the Polar billing portal and sign in with the email you paid with.
Help
Support.
Need help? We respond to every message.
For billing questions, account issues, or anything that needs a private conversation:
support@seocode.ioGitHub
For bug reports, feature requests, or rule feedback - open an issue on the app page:
github.com/apps/seoguardStudio subscribers receive priority email & developer support with a 24-hour response guarantee. Developer (free) enquiries are answered on a best-effort basis.