Schema Markup Generator
Fill a typed form and get a valid JSON-LD script block for eight schema.org types, with required fields checked — entirely on this device.
The title of the piece, as it reads on the page.
ISO 8601 — 2026-08-12, or 2026-08-12T09:30:00+02:00.
Absolute https URLs, one per line. One value per line.
ISO 8601, same format as the publish date.
One or two sentences. Plain text, no markup.
The canonical URL of the page carrying this block.
The name as it should appear in the block.
Absolute https URLs, one per line. One value per line.
One or two sentences. Plain text, no markup.
Your own stock-keeping unit, free text.
The name as it should appear in the block.
A phone number in the form you publish it.
An absolute http or https URL.
A short indicator such as $$ or €10–€25.
One line per rule, in schema.org form: Mo-Fr 09:00-17:00. One value per line.
Absolute https URLs, one per line. One value per line.
The name as it should appear in the block.
An absolute http or https URL.
An absolute https URL to the logo image.
Absolute https profile URLs, one per line. One value per line.
The name as it should appear in the block.
An absolute http or https URL.
The role, as the person publishes it.
An address you publish. It is emitted exactly as typed.
Absolute https profile URLs, one per line. One value per line.
Absolute https URLs, one per line.
Required here means this tool needs it for a coherent document — it is not a schema.org rule.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Northwind Coffee",
"url": "https://example.com",
"logo": "https://example.com/logo.png",
"sameAs": "https://example.com/about",
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+31 20 555 0100",
"contactType": "customer service"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "1 Example Street",
"addressLocality": "Amsterdam",
"addressRegion": "North Holland",
"postalCode": "1011 AB",
"addressCountry": "NL"
}
}
</script>Ready — Organization block built from the fields above.605 B
Paste this block into the <head> or <body> of the page it describes.
A state file saved with Download state opens here too.
Every field, the parse and the emit run on this device. Nothing is uploaded.
Press ? for shortcuts
Do it offline:$ python3 -c "import json,sys;d=json.load(open(sys.argv[1]));print(d['@type'])" schema.json
How to use it
- Pick the type you are describing — Article, BlogPosting, Product, FAQPage, BreadcrumbList, LocalBusiness, Organization or Person.
- Fill the fields; required ones carry a badge, and repeatable rows add questions, crumbs or offers one at a time.
- Read the output card: the block updates as you type, with anything missing or rejected named beside it.
- Copy the block, copy the JSON alone, download it, or share the form as a link.
Choose one of eight schema.org types, fill the labelled fields, and the matching JSON-LD script block assembles as you type — nested authors, offers and questions included. Required fields are checked before anything is emitted, and every value is escaped so the block cannot break the page it lands in. Paste an existing block back in to edit it.
How it works
JSON-LD is metadata, not markup you can see: the block sits in a <script type="application/ld+json"> element and renders nothing. @context names the vocabulary — always https://schema.org here — and @type names the thing described, with nested objects carrying their own @type. Key order is fixed, so the same form emits the same bytes.
Required here means required by this tool for a coherent document, not a schema.org obligation and not a promise about any search feature. The checks are structural and local: absolute URLs, ISO 8601 dates, decimal prices, enumeration members. Search engines read the block; what they show for it is their call.
Examples
Organization — the default document<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Northwind Coffee",
"url": "https://example.com",
"logo": "https://example.com/logo.png",
"sameAs": "https://example.com/about",
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+31 20 555 0100",
"contactType": "customer service"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "1 Example Street",
"addressLocality": "Amsterdam",
"addressRegion": "North Holland",
"postalCode": "1011 AB",
"addressCountry": "NL"
}
}
</script>What the page shows before you type: every field filled, so the block carries no complaints. ContactPoint and PostalAddress nest with their own @type, and a single sameAs value emits as a string, not a one-item list.
Product with an offer and rating<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Ridgeline Pour-Over Kettle",
"image": "https://ridgeline.example/kettle.jpg",
"sku": "RL-KTL-900",
"brand": {
"@type": "Brand",
"name": "Ridgeline"
},
"offers": {
"@type": "Offer",
"price": "49.99",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"url": "https://ridgeline.example/kettle",
"priceValidUntil": "2026-12-31"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6",
"reviewCount": "218",
"bestRating": "5",
"worstRating": "1"
}
}
</script>Offer and AggregateRating nest inside the product, each with its own @type. The currency was typed lower case and the change is declared: “offers.priceCurrency was written "usd" and is emitted as "USD" — ISO 4217 codes are upper case.” availability took the member name InStock and emits the full enumeration URL.
FAQPage with a </script> in an answer<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Can an answer contain HTML?",
"acceptedAnswer": {
"@type": "Answer",
"text": "It can. A phrase like <\/script> is escaped on emit, so it cannot close the block early."
}
}
]
}
</script>The answer text holds a literal closing script tag, and the emitter writes its slash escaped: the browser never sees a closing tag inside the value, while the parsed characters come back unchanged.
BreadcrumbList — three crumbs, hand-typed positions<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://ridgeline.example/"
},
{
"@type": "ListItem",
"position": 2,
"name": "Brewing gear",
"item": "https://ridgeline.example/gear"
},
{
"@type": "ListItem",
"position": 3,
"name": "Pour-over kettle"
}
]
}
</script>Rows two and three arrived numbered 3 and 2. Row order wins, and the correction is stated: “2 rows carried a position that did not match its place in the list; positions are always emitted 1 to 3 in row order.” The last crumb keeps no item.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Angle brackets in a value cannot break the block
Both </ and <! are escaped on emit — the only two sequences that can move an HTML parser out of a script body. A comment opener plus a later script tag would otherwise leave the block unterminated.
Breadcrumb positions belong to the list
position is filled from row order, 1 to n, with no field to type it in. Reorder a row and the numbers follow.
availability is an enumeration, not a phrase
“In stock” is refused; InStock is accepted and emitted as https://schema.org/InStock. All twelve members are listed in the rejection.
A date-time without an offset is ambiguous
2026-08-12 is fine alone. Add a time and an offset becomes mandatory — 2026-08-12T09:30:00+02:00 — because the same wall clock names a different moment in every zone.
Paste-back loads one node
Give it an array or a @graph and the first supported node is loaded, with a note counting what was left behind.
Required is this tool's word
schema.org marks almost nothing mandatory. The badges mark what makes a document worth emitting; everything else is listed, never invented — “image is empty. It is not required by this tool, but schema.org documents it for this type and filling it makes the block more useful.”
Common errors
headline is required by this tool and is empty.- Nothing is emitted while a badged field is blank, and the message names the property rather than the input box.
url "/about" is not an absolute URL. Use an absolute http:// or https:// URL — schema.org values are resolved as-is, not against the page they sit on.- A site-root path resolves against whatever page the consumer is reading, which is rarely yours. javascript: and data: are refused on the same field.
datePublished "2026-08-12T09:30:00" has a time but no UTC offset, so the moment it names is ambiguous. Write a calendar date as YYYY-MM-DD (2026-08-12) or a full date-time with a UTC offset (2026-08-12T09:30:00+02:00).- The check walks the calendar, so 2026-02-30 and 2026-13-45 fail as dates, and 03/04/2026 fails because nobody can tell March from April.
offers.price "$49.99" is not a decimal string. Write digits only, with a dot for the fraction and no currency symbol, no thousands separator and no comma decimal: 49.99, not $49.99 or 49,99.- The currency lives in priceCurrency, so a symbol here is duplicate information consumers have to strip.
Input is larger than the 256 KB limit for this tool. Nothing was truncated. For a document this size, read its type on the command line instead: python3 -c "import json,sys;d=json.load(open(sys.argv[1]));print(d['@type'])" schema.json- A half-read document emits a block that looks finished and is not, so the tool stops instead of trimming. A paste that large is usually a whole page source.
FAQ
Which schema.org types can this tool build?
Eight: Article, BlogPosting, Product with Offer and AggregateRating, FAQPage, BreadcrumbList, LocalBusiness, Organization and Person. schema.org defines hundreds more, and anything outside those eight is refused by name rather than half-built.
Does adding this block get my page a rich result?
No page can promise that, and this one does not. Search engines decide what to show, and eligibility rules live with them, not in the vocabulary. Google's Rich Results Test is the tool that answers it — a hosted service we cite rather than imitate.
Where should the script block go in my page?
Anywhere inside <head> or <body>; position carries no meaning because the block is not rendered. Give each entity its own block, and keep the values equal to what a visitor can actually read on the page.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Re-emit the block from the current fields.
- ⌘/Ctrl + S
- Download the document as .json.
- ⌘/Ctrl + C
- Copy the script block while nothing is selected.
- ⌘/Ctrl + Shift + C
- Copy the script block even over a selection.
- t
- Cycle the color theme.
- ? or ⌘/Ctrl + /
- Open the shortcut help dialog.
- Esc
- Close the topmost dialog.