Skip to content

About

Open JSON Schema for workout plans + zero-dependency JS validator, volume/duration stats and weekly progression expander

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

workout-plan-schema

An open, plain-JSON format for strength and bodyweight workout plans, with a tiny dependency-free JavaScript library to go with it:

  • schema/workout-plan.schema.json: a JSON Schema (draft 2020-12) for plans. It covers sessions, exercises, sets, reps, rep ranges, timed holds, per-side work, load, rest, supersets, RPE, tempo, progression rules and a multi-week schedule.
  • validatePlan(plan): checks a plan and returns every problem with a JSON path. No Ajv needed.
  • sessionStats(session) / planStats(plan): sets, reps, volume (reps × load, also in kg) and an estimated duration including rest.
  • expandPlan(plan): turns the template plus its progression rules into the concrete sessions for every week ("week 3, day 2: squat 5×5 @ 52.5 kg").

It runs in Node 18+ and modern browsers, ships TypeScript types, and has four example plans in plans/.

Why a schema? Workout plans usually live in notes, spreadsheets and screenshots, and every app has its own format for them. A small shared format makes plans easy to write by hand, generate, diff, review and move between tools.

Install

Not published to npm. Clone it, or copy src/ and schema/ into your project:

git clone https://github.com/jackships/workout-plan-schema.git
cd workout-plan-schema
npm test
node examples/summary.js plans/five-by-five.json

A plan in 20 lines

{
  "$schema": "https://raw.githubusercontent.com/jackships/workout-plan-schema/main/schema/workout-plan.schema.json",
  "schemaVersion": "1.0",
  "name": "Barbell 5x5",
  "unit": "kg",
  "defaultRestSeconds": 180,
  "sessions": [
    { "id": "A", "name": "Workout A", "exercises": [
      { "name": "Back squat", "sets": 5, "reps": 5, "load": 40, "progression": { "type": "linear", "increment": 2.5 } },
      { "name": "Bench press", "sets": 5, "reps": 5, "load": 30, "progression": { "type": "linear", "increment": 2.5 } }
    ] },
    { "id": "B", "name": "Workout B", "exercises": [
      { "name": "Back squat", "sets": 5, "reps": 5, "load": 40, "progression": { "type": "linear", "increment": 2.5 } },
      { "name": "Deadlift", "sets": 1, "reps": 5, "load": 50, "progression": { "type": "linear", "increment": 5 } }
    ] }
  ],
  "schedule": { "weeks": 8, "daysPerWeek": 3, "rotation": ["A", "B"], "deloadWeeks": [6] }
}

Most editors (VS Code, JetBrains) pick up $schema and give you autocomplete and inline errors.

Usage

import { readFileSync } from "node:fs";
import { validatePlan, sessionStats, expandPlan, planStats } from "./src/index.js";

const plan = JSON.parse(readFileSync("plans/beginner-full-body.json", "utf8"));

validatePlan(plan);
// { valid: true, errors: [] }

validatePlan({ schemaVersion: "1.0", name: "x", sessions: [{ id: "A", name: "A", exercises: [{ name: "Row", sets: 3, reps: 10, progression: { type: "linear", increment: 2.5 } }] }] });
// { valid: false, errors: [{ path: "$.sessions[0].exercises[0].progression", message: "linear progression needs a load" }] }

sessionStats(plan.sessions[0], { defaultRestSeconds: plan.defaultRestSeconds });
// { exercises: 4, sets: 11, reps: 90, timedSeconds: 40, volume: 0, volumeKg: 0, unit: "kg",
//   workSeconds: 310, restSeconds: 585, durationSeconds: 895, durationMinutes: 15 }

expandPlan(plan)[3].exercises[0];
// { name: "Reverse lunge", sets: 3, reps: 9, perSide: true, step: 1 }   (week 2, day 1)

const { perTemplate, sessions, program, weekly } = planStats(plan);

node examples/summary.js plans/push-pull-legs.json 1 prints the templates, the expanded sessions and the weekly totals:

Push / pull / legs (dumbbells + cable)

  Push: 5 exercises, 16 sets, ~26 min, volume 6860 lb
  Pull: 5 exercises, 16 sets, ~27 min, volume 11550 lb
  Legs: 5 exercises, 15 sets, ~30 min, volume 8640 lb

Week 1 day 1 - Push: Dumbbell bench press 4x6 @ 50lb, Seated dumbbell shoulder press 3x8 @ 35lb, ...
Week 1 day 4 - Push: Dumbbell bench press 4x8 @ 50lb, Seated dumbbell shoulder press 3x10 @ 35lb, ...

The format

Level Field Meaning
plan schemaVersion Always "1.0" for now.
plan name, description, tags Free text.
plan unit "kg" (default) or "lb". Applies to every load and load increment.
plan defaultRestSeconds Rest after each set when an exercise doesn't say (default 60).
plan sessions[] Template sessions: { id, name, note?, exercises[] }. Ids are unique.
plan schedule Optional: { weeks, daysPerWeek, rotation[], deloadWeeks?, deloadFactor? }.
exercise name, sets Required.
exercise reps / repRange / seconds Exactly one, e.g. 5, [8, 12] or 30.
exercise amrap As many as possible. Then the target above is optional and acts as a minimum.
exercise perSide The target is for each side. Reps and time are doubled in the stats.
exercise load In the plan unit. Leave it out for bodyweight.
exercise restSeconds, group, rpe, tempo, note Rest after each set, superset label, effort 1–10, "3-1-1" style tempo, free text.
exercise progression See below.

Schedule. Sessions are numbered across the whole program and the rotation keeps cycling, so rotation: ["A", "B"] at 3 days a week gives A B A, then B A B. Without a schedule, a plan is one pass through its sessions in order.

Progression rules ({ type, increment, every?, repStep?, max? }):

type What moves Needs
linear load goes up by increment load
reps reps go up by increment reps
time seconds go up by increment seconds
double reps climb from the bottom to the top of repRange in repStep steps; then load goes up by increment and reps go back to the bottom repRange and load

every: "session" (default) steps on every exposure of the exercise. Exposures are counted by exercise name across the whole program, so a squat that appears in both workout A and B steps up every time you squat. every: "week" steps once per week. max caps the moving value. In deloadWeeks, loads are multiplied by deloadFactor (default 0.9) and progression pauses, so the week after a deload picks up where the week before it left off.

How the numbers are estimated

  • Volume = reps × load, summed over sets (per-side reps count twice). Bodyweight and timed sets add nothing. volumeKg converts pounds with 1 lb = 0.45359237 kg.
  • Duration = work + rest. A reps set takes reps × secondsPerRep (default 3 s), with a 15 s minimum for setup. A timed set takes its seconds. An AMRAP set without a target takes 45 s. Rest is counted after every set except the last one of the session. All of these are options you can change, as is range ("low", "mid" or "high", default "high") for which end of a rep range to count.
  • Supersets are not special-cased. Write the rest you actually take: restSeconds: 0 on the first exercise of the pair, rest on the second.

What this is not

expandPlan describes the planned path, assuming every target is met. Real training needs feedback: repeat a weight when you miss reps, back off after a bad week. That logic belongs in the app that logs your sets. The example plans are reasonable general templates, not individual coaching or medical advice.

Example plans

File What it is
beginner-full-body.json Bodyweight, 2 alternating days, 3×/week for 4 weeks, weekly rep and hold steps
five-by-five.json Classic novice barbell 5×5, A/B, +2.5 kg per session (+5 kg deadlift), deload in week 6
push-pull-legs.json 6 days/week PPL in pounds, double progression, arm supersets
kettlebell-conditioning.json One kettlebell, 2×/week, carries and an AMRAP finisher

Tests

npm test   # node:test, no dependencies

The tests check that every example plan validates, that the validator and the JSON Schema allow exactly the same properties, and the stats and expansion maths.

License

MIT © 2026 Jack Ships


Made by Jack, who also builds Movo, an iOS app that turns a pasted workout plan into a set-by-set session.

About

Open JSON Schema for workout plans + zero-dependency JS validator, volume/duration stats and weekly progression expander

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages