Schema Injector
geo schema generates and injects JSON-LD structured data into HTML pages. It supports multiple schema types, framework-specific output, and in-place file injection.
What is JSON-LD Schema?
JSON-LD (JavaScript Object Notation for Linked Data) is a format for embedding machine-readable metadata in HTML pages. You add it inside a <script type="application/ld+json"> tag in your <head>.
AI search engines read JSON-LD schema to understand:
- What type of page this is (tool, article, FAQ, organization)
- What the page is about, without parsing the full body text
- How to extract structured answers (especially from FAQPage)
Without schema, AI engines have to guess your contentβs meaning. With schema, you tell them directly.
The 3 Most Important Schema Types for GEO
| Schema | GEO Priority | Where to use |
|---|---|---|
| FAQPage | π΄ Highest | Any page with Q&A, how-to, explainer content |
| WebApplication | π High | Every tool, calculator, or interactive page |
| WebSite | π‘ Baseline | Global layout β all pages |
FAQPage has the highest GEO impact because AI engines directly extract Q&A pairs to answer user questions β and cite your page as the source.
Usage
# Analyze an HTML file β see what schema is present and what's missing
geo schema --file index.html --analyze
# Generate WebSite schema (print to stdout)
geo schema --type website --name "MySite" --url https://yoursite.com
# Generate FAQPage schema from a JSON file
geo schema --type faq --faq-file faqs.json
# Inject schema directly into an HTML file (creates .bak backup first)
geo schema --file page.html --type website --name "MySite" --url https://yoursite.com --inject
# Generate Astro BaseLayout snippet
geo schema --type website --name "MySite" --url https://yoursite.com --astro
All Flags
| Flag | Description |
|---|---|
--file |
Path to the HTML file to analyze or inject into |
--analyze |
Analyze --file and print what schema is present / missing |
--type |
Schema type to generate: website, webapp, faq, article, organization, breadcrumb |
--name |
Site or page name |
--url |
Canonical URL |
--description |
Short description |
--inject |
Inject generated schema directly into --file (modifies in place) |
--faq-file |
Path to a JSON file with Q&A pairs (for --type faq) |
--astro |
Output an Astro component snippet instead of raw HTML |
Flag: βanalyze
Analyzes an existing HTML file and reports which schema types are present and which are missing.
geo schema --file index.html --analyze
Example output:
Analyzing: index.html
βββββββββββββββββββββββββββββββββ
β
WebSite schema found
β FAQPage schema missing β high GEO impact
β WebApplication schema missing
β Article schema missing
Suggestion: Add FAQPage schema β highest impact for AI citation visibility.
Run: geo schema --file index.html --type faq --faq-file faqs.json --inject
Flag: βtype
Generates a specific schema type. Prints JSON-LD to stdout by default; combine with --inject to write directly to the file.
# WebSite
geo schema --type website --name "MySite" --url https://yoursite.com --description "Free calculators"
# WebApplication
geo schema --type webapp --name "Mortgage Calculator" --url https://yoursite.com/mortgage --description "Calculate monthly payments"
# FAQPage (from --faq-file)
geo schema --type faq --faq-file faqs.json
# Organization
geo schema --type organization --name "MySite" --url https://yoursite.com
# Article
geo schema --type article --name "What is GEO?" --url https://yoursite.com/blog/geo --description "Introduction to GEO"
# BreadcrumbList
geo schema --type breadcrumb --url https://yoursite.com/finance/mortgage
Flag: βastro
Generates a ready-to-paste snippet for Astro BaseLayout.astro. Supports conditional rendering based on props.
geo schema --type website --name "MySite" --url https://yoursite.com --astro
Output β complete Astro layout snippet:
---
// Types
interface FAQItem {
question: string;
answer: string;
}
interface Props {
title: string;
description: string;
url?: string;
isCalculator?: boolean;
faqItems?: FAQItem[];
}
const {
title,
description,
url = Astro.url.href,
isCalculator = false,
faqItems = [],
} = Astro.props;
const SITE_NAME = "MySite";
const SITE_URL = "https://yoursite.com";
---
<head>
<meta charset="UTF-8" />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={url} />
<!-- WebSite schema (all pages) -->
<script type="application/ld+json" set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "WebSite",
"name": SITE_NAME,
"url": SITE_URL,
"description": description,
})} />
<!-- WebApplication schema (calculator pages only) -->
{isCalculator && (
<script type="application/ld+json" set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "WebApplication",
"name": title,
"url": url,
"description": description,
"applicationCategory": "UtilityApplication",
"operatingSystem": "Web",
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
})} />
)}
<!-- FAQPage schema (pages with FAQ content) -->
{faqItems.length > 0 && (
<script type="application/ld+json" set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": faqItems.map(item => ({
"@type": "Question",
"name": item.question,
"acceptedAnswer": { "@type": "Answer", "text": item.answer },
})),
})} />
)}
</head>
Use it in a page:
---
import BaseLayout from '../layouts/BaseLayout.astro';
const faqItems = [
{
question: "How is the monthly mortgage payment calculated?",
answer: "The formula is M = P Γ (r(1+r)^n) / ((1+r)^n - 1), where P is the principal, r is the monthly rate, and n is the total number of payments.",
},
{
question: "What is the difference between fixed and adjustable rates?",
answer: "A fixed rate stays constant for the life of the loan. An adjustable rate is tied to a market index and can change periodically, usually after an initial fixed period.",
},
];
---
<BaseLayout
title="Mortgage Calculator"
description="Calculate your monthly mortgage payment instantly."
isCalculator={true}
faqItems={faqItems}
/>
Flag: βinject
Injects the generated schema directly into the HTML file, inserting it before </head>. A backup (.bak) is created automatically.
geo schema \
--file index.html \
--type faq \
--faq-file faqs.json \
--inject
Output:
β
Backup created: index.html.bak
β
FAQPage schema injected into index.html (before </head>)
If the file has no </head> tag, injection fails gracefully with an error. See Troubleshooting for the fix.
Flag: βfaq-file
Reads Q&A pairs from a JSON file to generate FAQPage schema.
Format of faqs.json:
[
{
"question": "How is the monthly mortgage payment calculated?",
"answer": "The standard formula is M = P Γ (r(1+r)^n) / ((1+r)^n - 1). For a $200,000 loan at 6% over 30 years, the monthly payment is $1,199."
},
{
"question": "What is LTV (Loan-to-Value) ratio?",
"answer": "LTV measures how much of the property value you are financing. An LTV of 80% means you are borrowing 80% and providing 20% as a down payment."
},
{
"question": "Should I choose a fixed or variable rate?",
"answer": "Fixed rates offer payment stability for the full term. Variable rates may start lower but change with market benchmarks like SOFR. Choose fixed if you expect rates to rise or prefer predictability."
}
]
geo schema --type faq --faq-file faqs.json
Framework Examples
Astro β Full Integration
See the --astro section above. Pass isCalculator={true} for tool pages and faqItems={[...]} for pages with Q&A.
Next.js (App Router)
In app/layout.tsx (WebSite schema on all pages):
export default function RootLayout({ children }: { children: React.ReactNode }) {
const websiteSchema = {
"@context": "https://schema.org",
"@type": "WebSite",
"name": "MySite",
"url": "https://yoursite.com",
"description": "Free online tools and calculators",
};
return (
<html lang="en">
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML=
/>
</head>
<body>{children}</body>
</html>
);
}
In a tool page (WebApplication + FAQPage):
export default function MortgagePage() {
const appSchema = {
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "Mortgage Calculator",
"url": "https://yoursite.com/mortgage",
"description": "Calculate monthly mortgage payments for fixed and variable rates.",
"applicationCategory": "UtilityApplication",
"operatingSystem": "Web",
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
};
const faqSchema = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "How is a mortgage payment calculated?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Using the formula M = P Γ (r(1+r)^n) / ((1+r)^n - 1), where P is the loan amount, r the monthly rate, and n the number of payments.",
},
},
],
};
return (
<>
<script type="application/ld+json" dangerouslySetInnerHTML= />
<script type="application/ld+json" dangerouslySetInnerHTML= />
{/* page content */}
</>
);
}
WordPress β functions.php
Add WebSite schema to all pages and FAQPage to pages with an FAQ section:
function add_geo_schema() {
$schema = [
'@context' => 'https://schema.org',
'@type' => 'WebSite',
'name' => get_bloginfo('name'),
'url' => home_url('/'),
'description' => get_bloginfo('description'),
];
echo '<script type="application/ld+json">' . wp_json_encode($schema) . '</script>' . "\n";
}
add_action('wp_head', 'add_geo_schema');
For FAQPage on specific pages, use a custom field or ACF repeater to store Q&A pairs and output them similarly.
HTML Static β Manual
Add directly in the <head> of your HTML file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Page</title>
<!-- WebSite schema -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "MySite",
"url": "https://yoursite.com",
"description": "Free online calculators"
}
</script>
<!-- FAQPage schema -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Your question here?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Your answer here, with specific details and numbers."
}
}
]
}
</script>
</head>
FAQPage Best Practices
How many questions?
- Minimum: 3 questions per page
- Optimal: 5β8 questions
- Maximum: 15 (more than 15 dilutes individual question weight)
How to write questions for maximum GEO impact:
- Write them as real user queries: βHow is X calculated?β not βX calculationβ
- Be specific: βWhat is the LTV ratio and how does it affect my mortgage?β not βWhat is LTV?β
- Include numbers in answers: βA typical LTV is 80%, meaning you borrow 80% of the property valueβ
- Keep answers 2β5 sentences. Long answers reduce extraction clarity.
- Include at least one statistic or concrete example in each answer
What makes an answer extractable by AI:
- Starts with a direct answer to the question (not a preamble)
- Includes a specific number, formula, or comparison
- Self-contained β can be understood without reading the rest of the page
Validate your schema:
# Google's validator
open https://validator.schema.org
# Paste your JSON-LD or enter your URL