Quiz Object Standard
Important: Frozen at v1.0. Existing fields, required fields, constraints, and correctness semantics will not change. Version 1 may only grow by adding optional fields; any breaking change ships as
schemaVersion: 2. A Quiz that validates today keeps validating.
This document is the normative reference for authors, AI generators, and Renderer authors. The Zod schema in src/shared/lib/quiz/schema.ts is the executable source of truth, and the published JSON Schema at /schema/quiz.v1.json is the generated companion artifact for tools. The import page and Zod validator are the final authority when JSON Schema cannot express a rule.
Versioning and strictness
schemaVersionis the integer1. Strings such as"1"or"1.0"are invalid.- Unknown fields are invalid at every level: Quiz, Question, Option, and validation objects.
- The Standard contains content and correctness only. It never contains presentation fields such as page size, Option labels, shuffle settings, themes, or layout hints.
- Adding an optional field inside version 1 is allowed. Removing a field, changing required fields, tightening existing constraints, or changing correctness semantics requires
schemaVersion: 2.
Quiz
A Quiz is one JSON object with metadata and an ordered list of Questions.
| Field | Required | Semantics |
|---|---|---|
schemaVersion |
yes | Must be the integer 1. |
id |
yes | Stable Quiz identifier. Use kebab-case with lowercase latin letters, digits, and single hyphens: react-hooks-basics. |
title |
yes | Short Markdown field naming the Quiz. Must be a non-empty string after trimming. |
description |
no | Long Markdown field describing the Quiz. Must be non-empty when present. |
language |
no | BCP-47-shaped content language tag such as en or en-US. |
tags |
no | Array of Tags. Defaults to [] when omitted. Each Tag uses the same kebab-case charset as id. |
author |
no | Free-form author string. Not an account id and not structured metadata. Must be non-empty when present. |
questions |
yes | Ordered array of one or more Questions. Question ids must be unique within this Quiz. |
Quiz id is the Library primary key for private Quizzes. A private Quiz may use the same id as a Catalog Quiz because the Library and Catalog are separate namespaces.
The Public catalog profile is stricter than the Standard and is enforced only in CI for repository content. Public catalog Quizzes require description, language, at least one Tag, and repo-wide Quiz id uniqueness.
Questions
Every Question has common fields plus a type-specific body.
| Field | Required | Semantics |
|---|---|---|
id |
yes | Stable Question identifier, unique within the Quiz. Use the same kebab-case charset as Quiz id. |
title |
yes | Short Markdown prompt carrying the question itself — the actual ask, usually ending in ?. Must be a non-empty string after trimming. |
description |
no | Long Markdown supporting context for the prompt: a scenario, code snippet, or constraints — never the question itself. Must be non-empty when present. |
explanation |
yes | Long Markdown teaching text shown after submission regardless of correctness. Must be non-empty. |
references |
no | Long Markdown content for links, citations, or further reading, shown after the Explanation. Must be non-empty when present. |
type |
yes | One of single-choice, multiple-choice, or input. |
Question id is part of Progress identity. Changing it creates a new Question from the Renderer and storage perspective.
Renderers display the title prominently and the description as smaller secondary text. Put the question itself in title and use description only for supporting context; the Question's ask must still be clear with the description hidden.
Choice Questions
Choice Questions use an options array. Each Option is a bare object:
{
"text": "React state updates are scheduled",
"isCorrect": true
}
| Field | Required | Semantics |
|---|---|---|
text |
yes | Short Markdown Option text. Must be a non-empty string after trimming. |
isCorrect |
yes | Boolean correctness marker. |
Rules:
optionsmust contain at least two Options.- Options have no ids and no labels. Identity is the Option's index in the original JSON order.
single-choicemust have exactly one Option withisCorrect: true.multiple-choicemust have at least one Option withisCorrect: true; all Options may be correct.- Choice correctness is binary. The submitted original-order Option indexes must exactly match the correct original-order Option indexes.
- Multiple-choice is all-or-nothing. There is no partial credit in version 1.
Input Questions
Input Questions use a validation object. Version 1 supports text and numeric modes.
Text validation
{
"mode": "text",
"acceptedAnswers": ["first conditional"],
"caseSensitive": false
}
| Field | Required | Semantics |
|---|---|---|
mode |
yes | Must be "text". |
acceptedAnswers |
yes | One or more non-empty strings. |
caseSensitive |
no | Defaults to false. When true, case must match after trimming, whitespace collapse, and NFC normalization. |
Text matching rules:
- Compare the submitted value against every Accepted answer. Any match is correct.
- Trim leading and trailing whitespace from both sides.
- Collapse each internal run of whitespace to one ASCII space.
- Normalize both sides to Unicode NFC.
- Unless
caseSensitiveistrue, compare case-insensitively after the previous normalization steps.
Numeric validation
{
"mode": "numeric",
"acceptedAnswers": [3.14],
"tolerance": 0.01
}
| Field | Required | Semantics |
|---|---|---|
mode |
yes | Must be "numeric". |
acceptedAnswers |
yes | One or more finite JSON numbers. Numeric strings are invalid. |
tolerance |
no | Finite number greater than or equal to 0. Defaults to 0. |
Numeric matching rules:
- Trim the submitted string before parsing.
- Accept
.or,as the decimal separator. A value may not use both separators. - Thousands separators are not supported.
- The parsed value must be finite.
- A submitted value is correct when
abs(submitted - acceptedAnswer) <= tolerancefor any Accepted answer.
Correctness model
Every Question evaluates to exactly one binary result: correct or incorrect. Quizbun does not support points, weighting, partial credit, scoring configuration, or attempt history in version 1. A Summary reports X of Y correct.
A Renderer may prevent empty submissions for usability. The Standard itself defines correctness by comparison: because every choice Question has at least one correct Option, an empty set of selected Options cannot be correct.
Renderer rules
Renderer behavior must preserve the Standard's content identity rules.
- Saved choice answers reference Option indexes in the original JSON order, never the displayed order. If a Renderer shuffles Options, it must translate displayed positions back to original indexes before saving or checking answers.
- Progress is keyed by Quiz
idand Questionid, and each saved Question answer is invalidated by a Content hash. If a Question's content changes during re-import, the saved answer for that Question is discarded while unchanged Questions may keep Progress.
Shuffling, Option labels, pagination, Page size, keyboard controls, and layout are Renderer behavior. They must not be written into the Quiz object. If a Renderer shuffles Options during a Run, it should persist that Run's shuffle order so reloads remain stable.
Markdown
Every text field is Markdown, rendered safely. Raw HTML is not supported and is stripped from rendered output.
The Renderer uses two tiers:
- Short fields are inline-only: Quiz
title, Questiontitle, and Optiontext. - Long fields allow full Markdown: Quiz
description, Questiondescription, Explanation, and References.
Inline-only is a rendering rule, not an import-time validation rule. If a short field contains block Markdown, the Renderer flattens or degrades it instead of rejecting the Quiz. In contexts that cannot render Markdown, such as the browser tab title or compact lists, the Renderer strips formatting to plain text.
Code blocks
Long fields may use fenced code blocks. The Renderer applies syntax highlighting for a fixed set of languages, selected by the fence info string:
| Language | Fence info string |
|---|---|
| JavaScript | js, javascript |
| TypeScript | ts, typescript |
| JSX | jsx |
| TSX | tsx |
| JSON | json |
| HTML | html |
| CSS | css |
| Python | py, python |
| Bash / Shell | bash, sh, shell |
| SQL | sql |
Any other info string, or none at all, renders as a plain un-highlighted code block; it is never an error. Highlighting is Renderer presentation, not Quiz content: the info string is ordinary Markdown and the highlighter adds no fields to the Standard.
Resolved micro-decisions for version 1
- Choice Questions require at least two Options.
multiple-choicerequires at least one correct Option; all Options correct is valid.- Version 1 has no hard length caps for titles, Option text, Tag count, or Question count beyond non-empty strings and required non-empty arrays. Tightening caps later would be breaking.
- Numeric
acceptedAnswersare JSON numbers only. Numeric strings are invalid.
These decisions are part of frozen version 1. Changing any of them is a breaking change that requires schemaVersion: 2; the schema, JSON Schema artifact, examples, and this document always change together.
Related resources
- Published JSON Schema (
quiz.v1.json)— the generated artifact for editors, validators, and AI tools. - Canonical example quizzes — one valid quiz per question shape, ready to copy.
- AI generation prompt — the schema and an example packaged as a single copyable prompt.