Skip to main content

The OAF format

The Open Authoring Format is the representation everything converts through. A reader's job is to produce a valid OAF package; a writer's job is to consume one. Neither needs to know the other exists.

The current specification version is 1.0, carried on every package as metadata.oafVersion.

Package shape​

An OAF package is six parts:

interface OAFPackage {
metadata: OAFMetadata; // title, author, language, dates, source format
course: OAFCourse; // scene refs, navigation, variables, settings
scenes: OAFScene[]; // each holds slide refs
slides: OAFSlide[]; // the actual content
quizzes: OAFQuiz[]; // referenced from slide objects
media: OAFMediaRef[]; // manifest of every asset
}

Scenes and slides are stored flat and referenced by path, not nested. A OAFSceneRef points at scenes/scene-001.json; a OAFSlideRef points at slides/slide-001.json. The schema enforces those path shapes.

Storing them flat means a single slide can be read without parsing the whole course.

Content model​

The nesting that matters is inside a slide:

Slide
└── Layer (a content group, with its own visibility)
└── Object (text, image, video, audio, shape, button, hotspot, quiz)
├── position (x, y, width, height, rotation, zIndex)
├── content (for text objects)
├── media (for image/video/audio objects)
├── quiz (for quiz objects)
└── styles

Layers carry a visible flag, which is how source tools represent content that appears in response to something the learner does.

Text​

Text is structured, not a string. A OAFTextContent holds blocks; a block is a heading, paragraph, or list; each block holds spans, and formatting lives on the span:

interface OAFTextSpan {
text: string;
bold?: boolean;
italic?: boolean;
underline?: boolean;
strikethrough?: boolean;
link?: string;
color?: string;
fontSize?: number;
fontFamily?: string;
}

This is what lets a Markdown writer emit ## Heading and **bold** rather than guessing from font sizes.

textContentToPlain() flattens the structure when a writer only needs the words.

Behavior​

Three constructs carry interactivity, and they are the ones flat text outputs cannot represent.

Interactions hang off an object and fire on click, hover, focus, dragstart, or drop.

Triggers hang off a slide and fire on slideStart, slideEnd, timelineStart, timelineEnd, or variableChange. A trigger may carry an OAFCondition that gates it.

Both resolve to an action:

type OAFActionType =
| 'navigate' | 'showLayer' | 'hideLayer'
| 'playMedia' | 'pauseMedia'
| 'setVariable' | 'jumpToSlide' | 'openUrl';

Variables are declared at course level with a name, a type (string | number | boolean), and a default. Triggers read them through conditions and write them through setVariable.

Quizzes​

Quizzes are stored separately and referenced from a slide object by OAFQuizRef, because the same question can appear in more than one place.

Seven types are defined: multiple-choice, true-false, fill-blank, matching, drag-drop, sequence, and essay.

A quiz carries its question as OAFTextContent, its options, its correct answer (a string or an array), and optional points, per-answer feedback, attempt limit, shuffle flag, and required flag.

OAFCourse.navigation.mode is one of:

ModeMeaning
linearSlides are taken in order
freeThe learner moves anywhere
restrictedMovement is gated, typically on completion

Alongside it sit showMenu, allowSeeking, and allowSkipping.

Schema validation​

Five JSON Schemas ship with @oaf/spec, under src/schemas/:

SchemaCovers
metadata.schema.jsonPackage metadata
course.schema.jsonCourse structure and navigation
scene.schema.jsonScenes and their slide references
slide.schema.jsonSlides, layers, and objects
quiz.schema.jsonQuiz definitions

They are drafted against JSON Schema 2020-12 and set additionalProperties: false throughout, so an unrecognized field is an error rather than something silently carried along. A converter emitting a field the schema does not know about will fail validation, which is the intent.