A worked example of every major Open edX plugin interface, built around a small "course archiving" feature you can run end-to-end. Use this repo as a reference when building your own Open edX plugin.
This is a monorepo of four sub-packages, each demonstrating one extension point:
| Sub-package | Plugin type | What it does |
|---|---|---|
backend-plugin-sample/ |
Django app plugin | Adds a CourseArchiveStatus model with a REST API, an Open edX Events handler, and an Open edX Filters pipeline step |
frontend-plugin-sample/ |
MFE plugin slot widget | Replaces the learner-dashboard course list with one that lets learners archive courses |
brand-sample/ |
Paragon brand package | An autumn-inspired color palette |
tutor-contrib-sample/ |
Tutor plugin | Installs and wires up the three above for a Tutor-based deployment |
Requires Tutor >= 20 with tutor-mfe, and an Open edX environment that supports design tokens (Paragon >= 23, "Teak" release or later).
The tutor-contrib-sample plugin in this repo installs the published backend, frontend, and brand packages and wires them into Tutor:
pip install -e ./tutor-contrib-sample
tutor plugins enable sample
tutor dev launchThis is enough to see everything working: visit the learner dashboard and you should see the customized course list rendered with the brand applied. See tutor-contrib-sample/README.md for what each piece of the plugin does.
To edit code in this repo and have your changes apply inside Tutor:
-
Backend —
tutor-contrib-sampleregistersbackend-plugin-sampleas a mounted directory, so a single command before launch is enough:tutor mounts add "$PWD/backend-plugin-sample" tutor dev launch -
Frontend — bind-mount a local MFE checkout into
tutor-mfe, then point its webpack at your localfrontend-plugin-samplecheckout. Seefrontend-plugin-sample/README.md. -
Brand — use tutor-contrib-paragon to recompile and serve the brand from disk. See
brand-sample/README.md.
This path assumes you already have edx-platform running locally (bare-metal or devstack-style venv) and at least one MFE checked out.
-
Backend — install editable into the edx-platform Python environment and migrate:
pip install -e ./backend-plugin-sample python manage.py lms migrate openedx_plugin_sample python manage.py cms migrate openedx_plugin_sample
-
Frontend — in your MFE checkout, add the
module.config.jsandenv.config.jsxshown infrontend-plugin-sample/README.md, thennpm ci && npm start. -
Brand — set
PARAGON_THEME_URLS.variants.light.urls.brandOverridein your MFE'senv.config.js[x](ortheme.variants.light.urlin a frontend-basesite.config.tsx) tohttps://cdn.jsdelivr.net/gh/openedx/sample-plugin@main/brand-sample/dist/light.min.css. Seebrand-sample/README.mdfor the full snippet.TODO: a fully local brand-development flow without Tutor (recompile + serve from disk) is not yet documented.
- Create a
release/<name>branch from the release tag, e.g.git switch -c release/ulmo v3.8.7, and merge the fix into it as afix:commit. Only branches matchingrelease/<name>can be released. - Go to Actions → Semantic Release → Run workflow and select that branch.
- Optionally set
forcetopatch,minorormajor. The defaultautoderives the bump from the commits.
The version comes from the tags on that branch only, so a fix: on release/ulmo releases 3.8.8 even if main is on 3.9.x.
Backports should land as fix: commits, so the line gets a patch and not the next minor that main will want. For the same reason, make main's next release after cutting the branch at least a minor bump (a feat: commit, or force: minor).
The GitHub Release is not marked as latest, and the npm package is published under the <name> dist-tag instead of latest.
This works only for branches cut from a release that includes this workflow. Older release branches have to be released manually.
See Multibranch Releases for more details.
- Open edX community Slack and discussion forums
- Issues with this sample specifically: openedx/sample-plugin issues