How to Create a Course
A complete guide to building an adaptive learning course on Graspful. Every section explains the concept first, then shows a worked example using a JavaScript Fundamentals course as illustration. The principles apply to any subject — firefighting, real estate, AWS, or anything else that can be broken into learnable concepts.
1. Choose Your Subject
Graspful works best for skill-based subjects that can be broken into discrete, testable concepts. Good candidates have a natural prerequisite structure — some topics must be understood before others make sense.
What makes a good Graspful course
- Skill-based: Students need to apply knowledge, not just recall it. Certification exams, professional skills, and technical subjects are ideal.
- Decomposable: The subject can be split into 20-200 concepts, each independently testable. If you can write 3+ practice problems for an idea, it is probably a concept.
- Prerequisite-rich: Some topics depend on others. "Closures" requires "functions" which requires "variables." This structure is what makes adaptive learning powerful — without it, you just have a quiz bank.
Single course vs. academy
Use a single course when the subject has one clear learning path (e.g., "JavaScript Fundamentals"). Use an academy when you have multiple related courses that share foundational concepts (e.g., a "Web Development Academy" containing JavaScript, TypeScript, and React courses). Academies let prerequisites span course boundaries.
2. Design the Knowledge Graph
The knowledge graph is the skeleton of your course. Get this right and everything else follows. Get it wrong and no amount of great content will save the learning experience.
Start with the tree, not the prose
Do not start by writing lessons. Start by listing every concept in your subject and drawing the prerequisite arrows between them. Think of it as a dependency graph: what does a student need to know before they can understand this idea? Write the graph structure first, then fill in the content.
Identify atomic concepts
A concept is one teachable idea that can be tested independently. The test: can you write 3 distinct practice problems for this concept that do not require knowledge from any other concept? If yes, it is a good concept. If you need to explain two unrelated things to write the problems, the concept is too broad — split it. If you cannot write 3 distinct problems, it is too narrow — merge it into a parent concept as a knowledge point.
Map prerequisites
For each concept, ask: "What must the student already know?" List only direct prerequisites — max 3-4 per concept. Transitive prerequisites are inferred automatically. If A requires B and B requires C, do not list C as a prerequisite of A. The prerequisite graph must be a DAG (no cycles).
Add encompassing edges
Encompassing edges answer: "When a student practices this advanced concept, which foundational concepts get exercised?" For example, every closure problem exercises the student's understanding of functions and variables. Adding encompassing edges with appropriate weights (0.2-1.0) lets the spaced repetition engine grant implicit review credit, reducing the total review burden.
Example: JavaScript course graph skeleton
This is the structure only — no content yet. Notice how the prerequisite arrows form a DAG, and encompassing edges capture implicit practice relationships.
course:
id: js-fundamentals
name: JavaScript Fundamentals
description: Core JavaScript for web developers
estimatedHours: 25
version: "2026.1"
sourceDocument: "MDN Web Docs"
sections:
- id: basics
name: Language Basics
description: Variables, types, and operators
- id: functions
name: Functions
description: Declarations, scope, and closures
- id: async
name: Asynchronous JavaScript
description: Callbacks, promises, and the event loop
concepts:
# --- Basics ---
- id: variables
name: Variables and Declarations
section: basics
difficulty: 2
estimatedMinutes: 15
prerequisites: []
knowledgePoints: [] # stub — fill later
- id: data-types
name: Primitive Data Types
section: basics
difficulty: 2
estimatedMinutes: 20
prerequisites: [variables]
knowledgePoints: []
- id: operators
name: Operators and Expressions
section: basics
difficulty: 3
estimatedMinutes: 20
prerequisites: [variables, data-types]
knowledgePoints: []
# --- Functions ---
- id: function-declarations
name: Function Declarations and Expressions
section: functions
difficulty: 3
estimatedMinutes: 25
prerequisites: [variables, operators]
knowledgePoints: []
- id: scope
name: Scope and Hoisting
section: functions
difficulty: 4
estimatedMinutes: 25
prerequisites: [function-declarations]
encompassing:
- concept: variables
weight: 0.5
knowledgePoints: []
- id: closures
name: Closures
section: functions
difficulty: 6
estimatedMinutes: 30
prerequisites: [scope]
encompassing:
- concept: function-declarations
weight: 0.7
- concept: scope
weight: 0.8
- concept: variables
weight: 0.4
knowledgePoints: []
# --- Async ---
- id: callbacks
name: Callbacks
section: async
difficulty: 5
estimatedMinutes: 20
prerequisites: [function-declarations]
encompassing:
- concept: function-declarations
weight: 0.6
knowledgePoints: []
- id: promises
name: Promises
section: async
difficulty: 6
estimatedMinutes: 30
prerequisites: [callbacks]
encompassing:
- concept: callbacks
weight: 0.5
knowledgePoints: []
- id: event-loop
name: The Event Loop
section: async
difficulty: 7
estimatedMinutes: 35
prerequisites: [promises, closures]
encompassing:
- concept: promises
weight: 0.6
- concept: callbacks
weight: 0.4
- concept: closures
weight: 0.3
knowledgePoints: []3. Author Knowledge Points
Knowledge points (KPs) are the progressive steps within each concept. They form the learning staircase — each step builds on the last, and students climb one step at a time.
The staircase: recognition, guided application, transfer
- KP1 — Recognition: Can the student identify the concept? Define terms, recognize examples, distinguish it from similar ideas.
- KP2 — Guided application: Can the student use it with support? Apply a formula, follow a procedure, work a standard problem.
- KP3 — Transfer: Can the student apply it to a novel scenario? Debug unfamiliar code, choose the right approach for a new situation, combine with other concepts.
Not every concept needs exactly 3 KPs. Simple concepts (difficulty 1-3) might have 2. Complex concepts (difficulty 7+) might have 4. The key is that each KP teaches one distinct thing and the difficulty increases monotonically.
Writing instruction text
Instruction text is what the student sees before practicing. Write it for audio — Graspful generates TTS from instruction text. This means: short sentences, no walls of text, no markdown tables (they do not work in audio). If instruction exceeds 100 words, you must add structured instructionContent blocks (images, callouts) to break it up. The review gate enforces this.
Writing worked examples
A worked example shows the concept applied step by step. It bridges instruction and practice — the student sees how to think through the problem before attempting one themselves. At least 50% of authored concepts should have at least one KP with a worked example. Focus worked examples on the higher-difficulty KPs where students are most likely to get stuck.
Example: closures concept with 3 KPs
- id: closures
name: Closures
section: functions
difficulty: 6
estimatedMinutes: 30
prerequisites: [scope]
encompassing:
- concept: function-declarations
weight: 0.7
- concept: scope
weight: 0.8
- concept: variables
weight: 0.4
knowledgePoints:
- id: closures-kp1
instruction: |
A closure is a function that remembers the variables from the
scope where it was created, even after that scope has closed.
Every function in JavaScript is a closure. When you write a
function inside another function, the inner function can access
the outer function's variables — even after the outer function
has returned.
problems: [] # filled in step 4
- id: closures-kp2
instruction: |
Closures are commonly used to create private state. You write a
function that declares a variable, then returns an inner function
that reads or modifies that variable. The variable is invisible
to the outside world — only the returned function can access it.
workedExample: |
Problem: Create a counter that starts at 0 and increments by 1
each time it is called.
Step 1: Write an outer function that declares a count variable.
function makeCounter() { let count = 0; }
Step 2: Return an inner function that increments and returns count.
function makeCounter() {
let count = 0;
return function() { count += 1; return count; };
}
Step 3: Call the outer function to get a counter.
const counter = makeCounter();
counter(); // 1
counter(); // 2
The inner function closes over count. Each call to makeCounter
creates a new, independent count variable.
problems: []
- id: closures-kp3
instruction: |
A common closure bug happens in loops. When you create functions
inside a loop using var, all functions share the same variable
and see its final value. The fix: use let (which creates a new
binding per iteration) or wrap the body in an IIFE.
workedExample: |
Bug: This code logs "3" three times instead of "0, 1, 2":
for (var i = 0; i < 3; i++) {
setTimeout(function() { console.log(i); }, 100);
}
Why: All three functions close over the same i, which is 3
when the timeouts fire.
Fix: Change var to let:
for (let i = 0; i < 3; i++) {
setTimeout(function() { console.log(i); }, 100);
}
Now each iteration creates a new i binding, so each function
closes over its own copy.
problems: []4. Write Problems
Problems are how the engine tests and tracks mastery. The quality of your problems directly determines the quality of the adaptive experience.
Problem types
multiple_choice4 options, 1 correct. The workhorse. Use for most problems.
true_falseBinary choice. Good for testing precise definitions and common misconceptions.
fill_blankFree-text answer. The system normalizes input. Good for recall.
orderingArrange 4-6 steps in sequence. Good for procedures and algorithms.
matchingMatch items from two columns. Good for terminology and associations.
scenarioRich context followed by a question. Best for complex application.
The deduplication rule
No two problems should test the same fact at the same cognitive level. The review gate checks for near-duplicate questions via normalized text hashing. To create distinct variants, change the scenario, vary the distractors, adjust the difficulty, or test the concept from a different angle.
Variant depth: 3-4 minimum per KP
Every knowledge point needs at least 3 problems. This gives the adaptive engine enough material to (a) verify mastery with two consecutive correct answers and (b) retry with a different problem if the student fails. Four or more is better. The review gate blocks publishing if any KP has fewer than 3.
Writing explanations that diagnose misconceptions
An explanation should not just reveal the answer. It should name the likely misconception that led to the wrong answer. Instead of "The answer is B," write "If you chose A, you may be confusing closures with callbacks. The key difference is..." Good explanations turn wrong answers into learning moments.
Example: closures problems at three difficulty levels
problems:
# Difficulty 1 — recognition
- id: closures-kp1-p1
type: multiple_choice
question: "What is a closure in JavaScript?"
options:
- "A function that remembers variables from its creation scope"
- "A function that runs immediately when defined"
- "A function that cannot access outer variables"
- "A function that is only callable once"
correct: 0
explanation: >
A closure is a function bundled with references to its surrounding
scope. If you chose B, you may be thinking of an IIFE (Immediately
Invoked Function Expression), which is a separate concept.
difficulty: 1
# Difficulty 2 — recognition with nuance
- id: closures-kp1-p2
type: true_false
question: >
True or false: Every function in JavaScript is technically a closure.
options: ["True", "False"]
correct: 0
explanation: >
True. In JavaScript, every function closes over the scope in which
it was defined. We typically use the term "closure" for cases where
this behavior is deliberate and observable — but technically, all
functions are closures.
difficulty: 2
# Difficulty 3 — identify a closure in code
- id: closures-kp1-p3
type: multiple_choice
question: >
Which line creates a closure?
function greet(name) {
return function() { console.log("Hi " + name); };
}
options:
- "Line 1: function greet(name)"
- "Line 2: return function()"
- "Line 2: the returned function closes over 'name'"
- "No closure is created in this code"
correct: 2
explanation: >
The anonymous function returned on line 2 closes over the 'name'
parameter from greet's scope. When greet finishes executing, the
returned function still has access to 'name'. If you chose A, note
that greet itself does not close over anything unusual — it is the
inner function that forms the closure.
difficulty: 3
# Difficulty 4 — predict output
- id: closures-kp1-p4
type: multiple_choice
question: >
What does this code output?
function outer() {
let x = 10;
function inner() { console.log(x); }
x = 20;
return inner;
}
outer()();
options:
- "10"
- "20"
- "undefined"
- "ReferenceError"
correct: 1
explanation: >
The inner function closes over the variable x, not its value at
the time of creation. When inner executes, x has been reassigned
to 20. If you chose A, remember: closures capture variables by
reference, not by value.
difficulty: 45. Add Section Exams
Section exams are optional cumulative assessments at the end of a section. They gate progression: a student must pass the exam before moving to concepts in the next section. Use section exams when your course has natural groupings where you want to verify broad understanding before advancing.
When to use sections vs. flat concept lists
Use sections when your course has 15+ concepts and natural groupings. A flat concept list (no sections) works fine for smaller courses where the prerequisite graph alone provides enough structure. You do not need to add a section exam to every section — only add them where cumulative verification adds value.
Blueprint design
The exam blueprint specifies minimum questions per concept, ensuring broad coverage. The total questionCount must be greater than or equal to the sum of all minQuestions in the blueprint. The engine fills remaining slots by sampling from all section concepts. Design for unaided reasoning — no hints, no instruction text.
Passing score and time limits
The default passing score is 75%. You can adjust it per section. Time limits are optional — if set, the exam auto-submits when time runs out. A good heuristic: allow 1-2 minutes per question.
Example: Functions section exam
sections:
- id: functions
name: Functions
description: Declarations, scope, and closures
sectionExam:
enabled: true
passingScore: 0.75
timeLimitMinutes: 20
questionCount: 12
blueprint:
- conceptId: function-declarations
minQuestions: 3
- conceptId: scope
minQuestions: 3
- conceptId: closures
minQuestions: 4
instructions: >
This exam covers all concepts in the Functions section.
You have 20 minutes to complete 12 questions. No notes
or references are allowed. You need 75% to pass.6. Set Difficulty and Metadata
Each concept has metadata that calibrates the adaptive engine and helps with course organization.
Difficulty scale (1-10)
Difficulty is a concept-level rating that tells the engine how hard this idea is relative to other concepts in the course. The scale:
| Level | Description | Example |
|---|---|---|
| 1-2 | Recognition and basic recall | Variables, primitive data types |
| 3-4 | Comprehension and guided application | Operators, function declarations |
| 5-6 | Application and analysis | Closures, callbacks, promises |
| 7-8 | Complex analysis and synthesis | Event loop, async patterns |
| 9-10 | Multi-step transfer, novel problem solving | Designing async architectures |
Estimated minutes
How long you expect a typical student to spend mastering this concept, including instruction, worked examples, and practice. This calibrates course-level time estimates and XP awards. A good heuristic: difficulty 1-3 takes 10-20 minutes, difficulty 4-6 takes 20-35 minutes, difficulty 7+ takes 30-45 minutes.
Tags
Tags are free-form labels for cross-cutting concerns. Use them for filtering and analytics — for example, foundational, calculation, hands-on. Tags have no effect on the adaptive engine.
Source references
The sourceRef field traces a concept back to the authoritative source material. For certification courses, this is the exam guide section. For academic courses, the textbook chapter. Source references help with auditing and content updates.
7. Validate and Import
Before publishing, your course must pass schema validation and the 10-check review gate.
Schema validation
Run graspful validate to check that your YAML conforms to the schema — correct field types, required fields present, no cycles in the prerequisite graph.
graspful validate js-fundamentals.yamlReview gate
The review gate runs 10 mechanical quality checks: YAML parsing, unique problem IDs, valid prerequisites, question deduplication, difficulty staircase, cross-concept coverage, variant depth, instruction formatting, worked example coverage, and import dry run. A score of 10/10 is required to publish.
# Run all 10 checks
graspful review js-fundamentals.yaml
# JSON output for CI pipelines
graspful review js-fundamentals.yaml --format jsonSee Review Gate for details on each check and how to fix failures.
Import and publish
Once you pass 10/10, import the course. Use --publish to go live immediately (the server re-runs the review gate before publishing).
# Import as draft
graspful import js-fundamentals.yaml --org my-org
# Import and publish in one step
graspful import js-fundamentals.yaml --org my-org --publish8. Create Your Brand
A brand YAML configures the white-label product that students see: theme, landing page, pricing, SEO, and domain. Students never see Graspful — they see your brand.
Brand YAML
The brand YAML defines the product identity, theme preset, landing page copy, FAQ, pricing, and SEO metadata. See the Brand Schema for the full reference.
brand:
id: js-mastery
name: JS Mastery
domain: js-mastery.graspful.com
tagline: "Master JavaScript from zero to async."
orgSlug: my-org
theme:
preset: amber
radius: "0.5rem"
landing:
hero:
headline: "Learn JavaScript the Smart Way"
subheadline: "Adaptive learning that focuses on what you don't know yet."
ctaText: "Start Learning"
features:
heading: "Why JS Mastery?"
items:
- title: "Adaptive Engine"
description: "Skips what you know. Focuses on your gaps."
icon: Brain
- title: "Spaced Repetition"
description: "Never forget what you've learned."
icon: Clock
- title: "Real Problems"
description: "Practice with code, not flashcards."
icon: Code
howItWorks:
heading: "How It Works"
items:
- title: "Take a diagnostic"
description: "20 questions to map what you already know."
- title: "Learn adaptively"
description: "The engine builds your personal learning path."
- title: "Master JavaScript"
description: "Prove mastery through progressive challenges."
faq:
- question: "How long does it take?"
answer: "Most learners finish in 4-6 weeks studying 30 min/day."
- question: "Do I need prior experience?"
answer: "No. The course starts from variables and builds up."
bottomCta:
headline: "Ready to master JavaScript?"
seo:
title: "JS Mastery — Adaptive JavaScript Learning"
description: "Learn JavaScript with adaptive diagnostics and spaced repetition."
keywords: [javascript, learn javascript, javascript course]
pricing:
monthly: 19
yearly: 149
currency: usd
trialDays: 7
contentScope:
courseIds: [js-fundamentals]Custom domain setup
By default, brands are served at your-brand.graspful.com. For a custom domain (e.g., learn.yourdomain.com), add a CNAME record pointing to brands.graspful.com and update the domain field in your brand YAML. SSL is provisioned automatically.
Launch checklist
- Course: Imported and published (review gate 10/10)
- Brand: Imported with landing page copy, theme, and pricing configured
- Domain: CNAME record set (if using custom domain)
- Stripe: Connect account linked for paid courses (see Billing)
- Test: Go through the diagnostic as a student to verify the experience
# Import the brand
graspful import js-mastery-brand.yaml
# Verify everything is live
graspful describe --org my-org