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
Studio
Work with units, lessons and activities from one interface. When finished, download or save the file .p2lrn.
Toolkit
Build the same type of package from a workspace using API or CLI.
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
├─ META-INF/manifest.json
├─ content/graph.json
├─ assets/...
├─ packages/...
└─ multipart/...Identifies the package, declares profiles and entrypoints, and contains integrity information.
Describes the course structure, units, activities, resources and relationships.
They contain the resources that travel within the package when applicable.
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.
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.
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.
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.
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.
p2lrn-validate course.p2lrn
p2lrn-inspect course.p2lrn
p2lrn-reader course.p2lrnvalidate 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.