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.