AUTHORSHIP / PACKAGES

From content to .p2lrn package

A course can be created visually with Studio or programmatically with Toolkit. This guide focuses on the common outcome: how the package is organized, how it is built, and what to check before handing it over to the Player.

1. Two paths, one result

It is not necessary to repeat both routes. Choose the form of authorship that applies to you and use the following sections to understand the resulting package.

2. What a package contains

course.p2lrn
course.p2lrn
├─ META-INF/manifest.json
├─ content/graph.json
├─ assets/...
├─ packages/...
└─ multipart/...
Manifest

Identifies the package, declares profiles and entrypoints, and contains integrity information.

Graph

Describes the course structure, units, activities, resources and relationships.

assets / packages

They contain the resources that travel within the package when applicable.

multipart

Describes large resources distributed across verified external parties.

3. Authoring Workspace

When you create by code, the workspace is the editable project and the .p2lrn It is the artifact you publish. Keep the workspace if you want to rebuild or modify the course later.

TypeScript
import {P2LRNAuthoringWorkspace} from '@p2lrn/toolkit/authoring';

const workspace = P2LRNAuthoringWorkspace.create('./workspace', {
  title: 'Basic digital safety course',
  language: 'en',
});

Authorship and rights

If the course must identify author, date, license or rights holder, use the portable metadata of the course itself. Studio, Toolkit, Player and Reader share the same contract.

TypeScript
workspace.setCourseRights({
  author: 'Author name',
  createdAt: '2026-10-26',
  license: 'CC BY 4.0',
  copyrightHolder: 'Rights holder'
});

All fields are optional and only those you have defined are saved. For the scheme and its limits consult P2LRN format.

4. Add structure and resources

Units organize the course and resources provide the content used by the activities. The keys returned by the authoring functions are used to later relate progression or other rules to the actual entities.

TypeScript
workspace.addUnit({
  key: 'unit-1',
  title: 'Unit 1 · Fundamentals',
});

const intro = workspace.addFile({
  source: './material/intro.html',
  key: 'intro',
  title: 'Introduction',
  parent: 'unit-1',
  mediaType: 'text/html',
});

For documents that must first be analyzed, see Reader documental.

5. Add optional rules

If the course requires mandatory, blocking or prerequisites, define those rules before the build. The guide Progress explains the difference between completing an activity and blocking further navigation.

6. Build the package

When the structure and resources are ready, build the portable file that the Player will consume.

TypeScript
workspace.build('./course.p2lrn', {
  strictProfiles: true,
});

The build converts the editable project into the portable package that the Player must consume.

7. Validate and inspect before publishing

Before delivering the course, validate the package and review its structure. Do not publish a package that fails validation.

Terminal
p2lrn-validate course.p2lrn
p2lrn-inspect course.p2lrn
p2lrn-reader course.p2lrn

validate Please check the package contract. inspect shows its structure. reader allows you to review the content from the Toolkit tools. Correct any validation errors before delivering the package.

8. Large resources

If a large resource should not travel within the main file, use Multipart. The descriptor maintains the hashes and allows the Player to verify the parts it needs.

9. Next step

Continue with P2LRN format to understand in more detail manifest, graph, profiles and integrity. Then you can add Progress and test the result with Player.