Skip to main content

Type reference

Every type exported by @oaf/spec, and re-exported by @oaf/core. Optional properties are marked ?.

For what these mean rather than what they are, see the OAF format.

Package​

interface OAFPackage {
metadata: OAFMetadata;
course: OAFCourse;
scenes: OAFScene[];
slides: OAFSlide[];
quizzes: OAFQuiz[];
media: OAFMediaRef[];
}

Metadata​

interface OAFMetadata {
oafVersion: '1.0';
title: string;
language: string; // ISO 639-1
created: string; // ISO 8601
modified: string; // ISO 8601
description?: string;
author?: string;
organization?: string;
version?: string;
duration?: number; // minutes
sourceFormat?: string; // e.g. "articulate-storyline-360"
sourceVersion?: string;
custom?: Record<string, unknown>;
}

oafVersion is a literal type, not a string — a package claiming any other version will not type-check.

Course​

interface OAFCourse {
id: string;
scenes: OAFSceneRef[];
navigation: OAFNavigation;
variables?: OAFVariable[];
settings?: OAFSettings;
}

interface OAFSceneRef {
id: string;
path: string; // must match ^scenes/.+\.json$
}

interface OAFNavigation {
mode: 'linear' | 'free' | 'restricted';
showMenu?: boolean;
allowSeeking?: boolean;
allowSkipping?: boolean;
}

interface OAFVariable {
name: string;
type: 'string' | 'number' | 'boolean';
defaultValue: string | number | boolean;
description?: string;
}

interface OAFSettings {
slideSize: { width: number; height: number };
defaultDuration?: number; // seconds
theme?: string;
}

Scenes and slides​

interface OAFScene {
id: string;
title: string;
slides: OAFSlideRef[];
description?: string;
}

interface OAFSlideRef {
id: string;
path: string; // must match ^slides/.+\.json$
}

interface OAFSlide {
id: string;
title: string;
layers: OAFLayer[];
notes?: string;
duration?: number; // seconds
audio?: OAFMediaRef;
background?: OAFMediaRef;
triggers?: OAFTrigger[];
}

interface OAFLayer {
id: string;
name: string;
visible: boolean;
objects: OAFObject[];
}

Objects​

type OAFObjectType =
| 'text' | 'image' | 'video' | 'audio'
| 'shape' | 'button' | 'hotspot' | 'quiz';

interface OAFObject {
type: OAFObjectType;
id: string;
position: OAFPosition;
name?: string;
content?: OAFTextContent;
media?: OAFMediaRef;
quiz?: OAFQuizRef;
interactions?: OAFInteraction[];
styles?: OAFStyles;
altText?: string;
}

interface OAFPosition {
x: number;
y: number;
width: number;
height: number;
rotation?: number; // degrees
zIndex?: number;
}

content, media, and quiz are all optional because which one applies depends on type. The type system does not enforce that pairing — a text object carrying media type-checks. The schemas are where that gets caught.

Text​

interface OAFTextContent {
blocks: OAFTextBlock[];
}

interface OAFTextBlock {
type: 'heading' | 'paragraph' | 'list';
spans: OAFTextSpan[];
level?: number; // heading level 1-6, or list indent
listType?: 'bullet' | 'numbered';
}

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

Media​

interface OAFMediaRef {
type: 'image' | 'video' | 'audio';
path: string;
filename?: string;
mimeType?: string;
width?: number;
height?: number;
duration?: number; // seconds
alt?: string;
}

Quizzes​

type OAFQuizType =
| 'multiple-choice' | 'true-false' | 'fill-blank'
| 'matching' | 'drag-drop' | 'sequence' | 'essay';

interface OAFQuizRef {
id: string;
path: string;
}

interface OAFQuiz {
id: string;
type: OAFQuizType;
question: OAFTextContent;
correctAnswer: string | string[];
options?: OAFQuizOption[];
points?: number;
feedback?: {
correct?: OAFTextContent;
incorrect?: OAFTextContent;
};
attempts?: number;
shuffle?: boolean;
required?: boolean;
}

interface OAFQuizOption {
id: string;
content: OAFTextContent;
isCorrect?: boolean;
feedback?: OAFTextContent;
}

correctAnswer is a string or an array of strings — single-answer types use the former, matching and sequence the latter.

Interactions and triggers​

interface OAFInteraction {
event: 'click' | 'hover' | 'focus' | 'dragstart' | 'drop';
action: OAFAction;
}

interface OAFTrigger {
when: 'slideStart' | 'slideEnd' | 'timelineStart'
| 'timelineEnd' | 'variableChange';
action: OAFAction;
condition?: OAFCondition;
}

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

interface OAFAction {
type: OAFActionType;
target: string; // slide id, layer id, variable name, or URL
params?: Record<string, unknown>;
}

interface OAFCondition {
variable: string;
operator: 'equals' | 'notEquals' | 'greaterThan' | 'lessThan' | 'contains';
value: string | number | boolean;
}

OAFAction.target is a bare string whose meaning depends on type — a slide id for jumpToSlide, a URL for openUrl, a variable name for setVariable.

Styles​

interface OAFStyles {
backgroundColor?: string; // hex
border?: {
color: string;
width: number;
style: 'solid' | 'dashed' | 'dotted';
};
borderRadius?: number; // pixels
opacity?: number; // 0-1
shadow?: {
color: string;
offsetX: number;
offsetY: number;
blur: number;
};
}

Package internals​

interface OAFRelationship {
type: 'media' | 'slide' | 'scene' | 'quiz' | 'theme';
target: string;
id: string;
}

interface OAFRelationships {
relationships: OAFRelationship[];
}

interface OAFContentTypes {
defaults: Record<string, string>; // by extension
overrides: Record<string, string>; // by path
}

These mirror the Open Packaging Conventions, the same relationship-and-content-type scheme .docx and .story are built on.

Helpers​

function textContentToPlain(content: OAFTextContent): string;
function createEmptyPackage(): OAFPackage;

textContentToPlain joins spans within a block and blocks with newlines, discarding all formatting.