Reports & Plugins
Ox has a plugin system for extending analysis and training plan generation. Plugins come in two types:
- Reports — query the SQLite database and return tabular results
- Generators — accept parameters and return
.oxformatted text for planning
Built-in Reports
These reports are included with ox and available immediately.
volume
Volume over time for a single movement.
CLI usage:
Parameters:
- -m / --movement (required) — exercise name to filter by
- -b / --bin (default: weekly) — time bin: daily, weekly, weekly-num, or monthly
Examples:
Output columns: period, total_volume, total_reps, avg_weight_per_rep
matrix
Session count per movement per time period. Rows are time periods; columns are movement names sorted by frequency.
CLI usage:
Parameters:
- -b / --bin (default: weekly) — time bin: daily, weekly, weekly-num, or monthly
Examples:
Output columns: period, then one column per movement name
e1rm
Estimated one-rep max (1RM) progression for a movement over time.
Only considers sets where the movement note contains ^rm — this is the convention for marking max-effort sets.
CLI usage:
Parameters:
- -m / --movement (required) — exercise name
- -f / --formula (default: brzycki) — formula to use: brzycki or epley
Examples:
Output columns: date, estimated_1rm, weight, reps, unit
Marking max-effort sets in your log:
Add ^rm anywhere in the movement note:
Example Generator
An example generator plugin for Wendler 5/3/1 cycles is included in examples/plugins/wendler531.py. It is not installed by default — see Installing Plugins below.
wendler531
Generates a 4-week Wendler 5/3/1 cycle as .ox planned sessions.
CLI usage (after installing):
Parameters:
- -m / --movement (required) — movement name (e.g., squat)
- -t / --training_max (required) — training max weight (numeric)
- -u / --unit (default: lbs) — weight unit: lbs or kg
- -d / --start_date (default: today) — start date as YYYY-MM-DD
Example:
ox> generate wendler531 -m squat -t 315
ox> generate wendler531 -m bench-press -t 200 -u lbs -d 2026-03-01
Output: Valid .ox text with ! (planned) sessions that you can paste into your log:
# Wendler 5/3/1 — squat (TM: 315lbs)
@session
2026-03-01 ! 5s Week
squat: 205lbs 1x5
squat: 235lbs 1x5
squat: 270lbs 1x5
@end
@session
2026-03-08 ! 3s Week
...
Installing Plugins
Personal scripts (~/.ox/plugins/)
Place any .py file in ~/.ox/plugins/. It will be loaded automatically on startup.
To install the bundled Wendler 5/3/1 example:
Installable packages (entry points)
Plugins can also be distributed as Python packages that register via the ox.plugins entry point group. In your pyproject.toml:
The referenced module must export a register() function (see below).
Writing a Plugin
A plugin is a Python module that exports a register() function returning a list of plugin descriptors.
Report plugin
import sqlite3
def my_report(conn: sqlite3.Connection, movement: str) -> tuple[list[str], list[tuple]]:
rows = conn.execute(
"SELECT date, SUM(reps) FROM training WHERE movement_name = ? GROUP BY date",
(movement,),
).fetchall()
return ["date", "total_reps"], rows
def register():
return [
{
"type": "report",
"name": "my-report",
"fn": my_report,
"description": "Total reps per day for a movement",
"params": [
{"name": "movement", "type": str, "required": True, "short": "m"},
],
}
]
Generator plugin
def my_generator(movement: str, sets: int = 5) -> str:
from datetime import date
today = date.today().strftime("%Y-%m-%d")
lines = ["@session", f"{today} ! Generated Session"]
for _ in range(sets):
lines.append(f"{movement}: BW 1x10")
lines.append("@end")
return "\n".join(lines)
def register():
return [
{
"type": "generator",
"name": "my-generator",
"fn": my_generator,
"description": "Generate a bodyweight session",
"params": [
{"name": "movement", "type": str, "required": True, "short": "m"},
{"name": "sets", "type": int, "required": False, "default": 5, "short": "s"},
],
}
]
Plugin descriptor fields
| Field | Required | Description |
|---|---|---|
type |
yes | "report" or "generator" |
name |
yes | Name used in the CLI (report NAME or generate NAME) |
fn |
yes | Callable that implements the plugin |
description |
yes | Short description shown in listings |
params |
yes | List of parameter descriptors (see below) |
needs_db |
no | If True, generator receives conn as first arg |
Parameter descriptor fields
| Field | Required | Description |
|---|---|---|
name |
yes | Parameter name (used as --name flag) |
type |
yes | Python type to cast the value to (e.g., str, int) |
required |
yes | Whether the parameter is required |
default |
no | Default value if not provided |
short |
no | Single-character short flag (e.g., "m" → -m) |