|
1 | 1 | # Open edX Sample Plugin |
2 | 2 |
|
3 | | -A comprehensive example demonstrating all major plugin interfaces available in the Open edX platform. This repository shows how to extend Open edX functionality without modifying core platform code. |
| 3 | +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. |
4 | 4 |
|
5 | | -## Table of Contents |
| 5 | +This is a monorepo of four sub-packages, each demonstrating one extension point: |
6 | 6 |
|
7 | | -- [What This Repository Demonstrates](#what-this-repository-demonstrates) |
8 | | -- [Plugin Types & Official Documentation](#plugin-types--official-documentation) |
9 | | -- [Quick Start Guide](#quick-start-guide) |
10 | | -- [Learning Path for New Plugin Developers](#learning-path-for-new-plugin-developers) |
11 | | -- [Repository Structure](#repository-structure) |
12 | | -- [Development Workflows](#development-workflows) |
13 | | -- [Integration Examples](#integration-examples) |
14 | | -- [Troubleshooting](#troubleshooting) |
15 | | -- [Additional Resources](#additional-resources) |
| 7 | +| Sub-package | Plugin type | What it does | |
| 8 | +|---|---|---| |
| 9 | +| [`backend-plugin-sample/`](./backend-plugin-sample/) | [Django app plugin](https://docs.openedx.org/projects/edx-django-utils/en/latest/plugins/how_tos/how_to_create_a_plugin_app.html) | Adds a `CourseArchiveStatus` model with a REST API, an [Open edX Events](https://docs.openedx.org/projects/openedx-events/en/latest/) handler, and an [Open edX Filters](https://docs.openedx.org/projects/openedx-filters/en/latest/) pipeline step | |
| 10 | +| [`frontend-plugin-sample/`](./frontend-plugin-sample/) | [MFE plugin slot widget](https://docs.openedx.org/en/latest/site_ops/how-tos/use-frontend-plugin-slots.html) | Replaces the learner-dashboard course list with one that lets learners archive courses | |
| 11 | +| [`brand-sample/`](./brand-sample/) | [Paragon brand package](https://github.com/openedx/paragon) | An autumn-inspired color palette | |
| 12 | +| [`tutor-contrib-sample/`](./tutor-contrib-sample/) | [Tutor plugin](https://docs.tutor.edly.io/) | Installs and wires up the three above for a Tutor-based deployment | |
16 | 13 |
|
17 | | -## What This Repository Demonstrates |
| 14 | +## Development with Tutor |
18 | 15 |
|
19 | | -This sample plugin showcases the **Open edX Hooks Extension Framework**, which allows you to extend the platform in a stable and maintainable way. The framework provides two main types of hooks: |
| 16 | +Requires [Tutor](https://docs.tutor.edly.io/install.html) >= 20 with [tutor-mfe](https://github.com/overhangio/tutor-mfe), and an Open edX environment that supports design tokens (Paragon >= 23, "Teak" release or later). |
20 | 17 |
|
21 | | -- **Events**: React to things happening in the platform (e.g., when a course is published) |
22 | | -- **Filters**: Modify platform behavior (e.g., change where course about pages redirect) |
| 18 | +### Running the demo as-is |
23 | 19 |
|
24 | | -**Key Concept**: All extensions are implemented as standard Django plugins that integrate seamlessly with edx-platform. |
25 | | - |
26 | | -**Official Documentation**: [Hooks Extension Framework Overview](https://docs.openedx.org/en/latest/developers/concepts/hooks_extension_framework.html) |
27 | | - |
28 | | -## Plugin Types & Official Documentation |
29 | | - |
30 | | -| Plugin Type | What It Does | Official Documentation | Sample Code | When To Use | |
31 | | -|-------------|--------------|------------------------|-------------|-------------| |
32 | | -| **Django App Plugin** | Add models, APIs, views, and business logic | [How to create a plugin app](https://docs.openedx.org/projects/edx-django-utils/en/latest/plugins/how_tos/how_to_create_a_plugin_app.html) | [`backend-plugin-sample/`](./backend-plugin-sample/) | Adding new functionality, APIs, or data models | |
33 | | -| **Events (Signals)** | React to platform events | [Open edX Events Guide](https://docs.openedx.org/projects/openedx-events/en/latest/) | [`backend-plugin-sample/openedx_plugin_sample/signals.py`](./backend-plugin-sample/openedx_plugin_sample/signals.py) | Integrating with external systems, audit logging | |
34 | | -| **Filters** | Modify platform behavior | [Using Open edX Filters](https://docs.openedx.org/projects/openedx-filters/en/latest/how-tos/using-filters.html) | [`backend-plugin-sample/openedx_plugin_sample/pipeline.py`](./backend-plugin-sample/openedx_plugin_sample/pipeline.py) | Customizing business logic, URL redirects | |
35 | | -| **Frontend Slots** | Customize MFE interfaces | [Frontend Plugin Slots](https://docs.openedx.org/en/latest/site_ops/how-tos/use-frontend-plugin-slots.html) | [`frontend-plugin-sample/`](./frontend-plugin-sample/) | UI customization, adding new components | |
36 | | -| **Brand Packages** | Customize theming | [Open edX Brand Package Interface](https://github.com/openedx/brand-openedx) | [`brand-sample/`](./brand-sample/) | UI theming | |
37 | | -| **Tutor Plugin** | Deploy plugins easily | [Tutor Plugin Development](https://docs.tutor.edly.io/) | [`tutor-contrib-sample/`](./tutor-contrib-sample/) | Simplified deployment and configuration | |
38 | | - |
39 | | -## Quick Start Guide |
40 | | - |
41 | | -### Prerequisites |
42 | | -1. **Platform Setup**: Follow the [Open edX Development Setup](https://docs.openedx.org/en/latest/developers/how-tos/get-ready-for-python-dev.html) |
43 | | -2. **Understanding**: Read the [Platform Overview](https://docs.openedx.org/en/latest/developers/concepts/platform_overview.html) |
44 | | - |
45 | | -### Option 1: Development with Tutor (Recommended) |
| 20 | +The `tutor-contrib-sample` plugin in this repo installs the published backend, frontend, and brand packages and wires them into Tutor: |
46 | 21 |
|
47 | 22 | ```bash |
48 | | -# Bind-mount backend source into Tutor image and containers. |
49 | | -tutor mounts add "$PWD/backend-plugin-sample" |
50 | | - |
51 | | -# Rebuild image, run migrations, reboot containers: |
52 | | -tutor dev launch |
53 | | - |
54 | | -# Frontend Plugin Setup (for learner-dashboard MFE development) |
55 | | -# Add env.config.jsx and module.config.js (see frontend-plugin-sample/README.md) |
56 | | -# Then, install and run. |
57 | | -cd path/to/frontend-app-learner-dashboard && npm ci && npm run dev |
58 | | -``` |
59 | | - |
60 | | -### Option 2: Development without Tutor |
61 | | - |
62 | | -```bash |
63 | | -# In your edx-platform directory |
64 | | -pip install -e /path/to/sample-plugin/backend-plugin-sample |
65 | | - |
66 | | -# Enable Learner Dashboard MFE |
67 | | -# Go to http://localhost:18000/admin/waffle/flag/ |
68 | | -# Create flag: learner_home_mfe.enabled = Yes |
69 | | - |
70 | | -# Run migrations |
71 | | -python manage.py lms migrate |
72 | | -``` |
73 | | - |
74 | | -### Verification |
75 | | - |
76 | | -1. **Backend**: Visit `http://localhost:18000/sample-plugin/api/v1/course-archive-status/` |
77 | | -2. **Frontend**: Check learner dashboard for archive/unarchive functionality |
78 | | -3. **Events**: Check logs for course catalog change events |
79 | | -4. **Filters**: Course about page URLs should redirect to example.com |
80 | | - |
81 | | -## Learning Path for New Plugin Developers |
82 | | - |
83 | | -### 1. Understand the Architecture |
84 | | -- **Start here**: [Hooks Extension Framework](https://docs.openedx.org/en/latest/developers/concepts/hooks_extension_framework.html) |
85 | | -- **Deep dive**: [OEP-50: Hooks Extension Framework](https://docs.openedx.org/projects/openedx-proposals/en/latest/architectural-decisions/oep-0050-hooks-extension-framework.html) |
86 | | - |
87 | | -### 2. Choose Your Plugin Type |
88 | | -Use the table above to identify which type of plugin matches your needs. You can combine multiple types in one plugin. |
89 | | - |
90 | | -### 3. Study the Sample Code |
91 | | -- **Backend**: Start with [`backend-plugin-sample/openedx_plugin_sample/apps.py`](./backend-plugin-sample/openedx_plugin_sample/apps.py) to understand plugin registration |
92 | | -- **Events**: Examine [`backend-plugin-sample/openedx_plugin_sample/signals.py`](./backend-plugin-sample/openedx_plugin_sample/signals.py) for event handling patterns |
93 | | -- **Filters**: Review [`backend-plugin-sample/openedx_plugin_sample/pipeline.py`](./backend-plugin-sample/openedx_plugin_sample/pipeline.py) for behavior modification |
94 | | -- **Frontend**: Explore [`frontend-plugin-sample/src/plugin.jsx`](./frontend-plugin-sample/src/plugin.jsx) for UI customization |
95 | | - |
96 | | -### 4. Run This Sample |
97 | | -Follow the [Quick Start Guide](#quick-start-guide) to see everything working together. |
98 | | - |
99 | | -### 5. Adapt for Your Use Case |
100 | | -Each directory contains detailed README.md files with adaptation guidance. |
101 | | - |
102 | | -## Repository Structure |
103 | | - |
104 | | -``` |
105 | | -sample-plugin/ |
106 | | -├── README.md # This file - overview and quick start |
107 | | -├── backend-plugin-sample/ |
108 | | -│ ├── README.md # Backend plugin detailed guide |
109 | | -│ ├── openedx_plugin_sample/ |
110 | | -│ │ ├── apps.py # Django plugin configuration |
111 | | -│ │ ├── models.py # Database models example |
112 | | -│ │ ├── views.py # REST API endpoints |
113 | | -│ │ ├── signals.py # Event handlers (Open edX Events) |
114 | | -│ │ ├── pipeline.py # Filter implementations (Open edX Filters) |
115 | | -│ │ ├── settings/ # Plugin settings configuration |
116 | | -│ │ └── urls.py # URL routing |
117 | | -│ └── tests/ # Comprehensive test examples |
118 | | -├── frontend-plugin-sample/ |
119 | | -│ ├── README.md # Frontend plugin detailed guide |
120 | | -│ ├── src/ |
121 | | -│ │ ├── plugin.jsx # React component for MFE slot |
122 | | -│ │ └── index.jsx # Export configuration |
123 | | -│ └── package.json # NPM package configuration |
124 | | -└── tutor-contrib-sample/ |
125 | | - ├── README.md # Tutor deployment guide |
126 | | - └── sample.py # Tutor plugin configuration |
| 23 | +pip install -e ./tutor-contrib-sample |
| 24 | +tutor plugins enable sample |
| 25 | +tutor dev launch |
127 | 26 | ``` |
128 | 27 |
|
129 | | -## Development Workflows |
130 | | - |
131 | | -### Backend Plugin Development |
132 | | - |
133 | | -1. **Setup**: Follow backend setup in [Quick Start](#quick-start-guide) |
134 | | -2. **Development**: |
135 | | - - Modify models in `models.py` |
136 | | - - Add API endpoints in `views.py` |
137 | | - - Implement event handlers in `signals.py` |
138 | | - - Create filters in `pipeline.py` |
139 | | -3. **Testing**: `cd backend-plugin-sample && make test` |
140 | | -4. **Quality**: `cd backend-plugin-sample && make quality` |
141 | | - |
142 | | -**Detailed Guide**: See [`backend-plugin-sample/README.md`](./backend-plugin-sample/README.md) |
143 | | - |
144 | | -### Frontend Plugin Development |
145 | | - |
146 | | -1. **Setup**: Follow frontend setup in [Quick Start](#quick-start-guide) |
147 | | -2. **Development**: |
148 | | - - Modify React components in `frontend-plugin-sample/src/` |
149 | | - - Test with local MFE development server |
150 | | -3. **Testing**: Integration testing with MFE |
151 | | - |
152 | | -**Detailed Guide**: See [`frontend-plugin-sample/README.md`](./frontend-plugin-sample/README.md) |
| 28 | +This 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`](./tutor-contrib-sample/README.md) for what each piece of the plugin does. |
153 | 29 |
|
154 | | -### Full-Stack Plugin Development |
| 30 | +### Hacking on the source |
155 | 31 |
|
156 | | -This sample shows how backend and frontend plugins work together: |
| 32 | +To edit code in this repo and have your changes apply inside Tutor: |
157 | 33 |
|
158 | | -- **Backend** provides API endpoints for course archive status |
159 | | -- **Frontend** consumes these APIs to show archive/unarchive UI |
160 | | -- **Events** log when course information changes |
161 | | -- **Filters** modify course about page URLs |
162 | | - |
163 | | -## Integration Examples |
164 | | - |
165 | | -### Backend + Frontend Integration |
166 | | - |
167 | | -```python |
168 | | -# backend-plugin-sample/openedx_plugin_sample/views.py - Provides API |
169 | | -class CourseArchiveStatusViewSet(viewsets.ModelViewSet): |
170 | | - # API implementation |
171 | | -``` |
172 | | - |
173 | | -```jsx |
174 | | -// frontend-plugin-sample/src/plugin.jsx - Consumes API |
175 | | -const response = await client.get( |
176 | | - `${lmsBaseUrl}/sample-plugin/api/v1/course-archive-status/` |
177 | | -); |
178 | | -``` |
179 | | - |
180 | | -### Events + Filters Working Together |
181 | | - |
182 | | -```python |
183 | | -# Events: Log course changes |
184 | | -@receiver(COURSE_CATALOG_INFO_CHANGED) |
185 | | -def log_course_info_changed(signal, sender, catalog_info, **kwargs): |
186 | | - logging.info(f"{catalog_info.course_key} has been updated!") |
187 | | - |
188 | | -# Filters: Modify course about URLs |
189 | | -class ChangeCourseAboutPageUrl(PipelineStep): |
190 | | - def run_filter(self, url, org, **kwargs): |
191 | | - # Custom URL logic |
192 | | -``` |
| 34 | +- **Backend** — `tutor-contrib-sample` registers `backend-plugin-sample` as a mounted directory, so a single command before launch is enough: |
193 | 35 |
|
194 | | -## Troubleshooting |
| 36 | + ```bash |
| 37 | + tutor mounts add "$PWD/backend-plugin-sample" |
| 38 | + tutor dev launch |
| 39 | + ``` |
195 | 40 |
|
196 | | -### Common Issues |
| 41 | +- **Frontend** — bind-mount a local MFE checkout into `tutor-mfe`, then point its webpack at your local `frontend-plugin-sample` checkout. See [`frontend-plugin-sample/README.md`](./frontend-plugin-sample/README.md). |
197 | 42 |
|
198 | | -**Plugin not loading:** |
199 | | -- Verify `pyproject.toml` entry points are correct |
200 | | -- Check that plugin app is in INSTALLED_APPS (should be automatic) |
201 | | -- Review Django app plugin configuration in `apps.py` |
| 43 | +- **Brand** — use [tutor-contrib-paragon](https://github.com/openedx/openedx-tutor-plugins/tree/main/plugins/tutor-contrib-paragon) to recompile and serve the brand from disk. See [`brand-sample/README.md`](./brand-sample/README.md). |
202 | 44 |
|
203 | | -**Events not firing:** |
204 | | -- Confirm signal receivers are imported in `apps.py` ready() method |
205 | | -- Check event is being sent by platform (some events only fire in specific contexts) |
206 | | -- Verify event data structure matches your handler signature |
| 45 | +## Development without Tutor |
207 | 46 |
|
208 | | -**Filters not working:** |
209 | | -- Ensure filter is registered in Django settings |
210 | | -- Check that filter step class inherits from `PipelineStep` |
211 | | -- Verify `run_filter` method returns correct dictionary format |
| 47 | +Assumes you already have edx-platform running locally (bare-metal or devstack-style venv) and at least one MFE checked out. |
212 | 48 |
|
213 | | -**Frontend plugin not appearing:** |
214 | | -- Check MFE slot configuration in `env.config.jsx` |
215 | | -- Verify plugin is installed (`npm install`) |
216 | | -- Ensure slot exists in target MFE (check MFE documentation) |
| 49 | +- **Backend** — install editable into the edx-platform Python environment and migrate: |
217 | 50 |
|
218 | | -### Getting Help |
| 51 | + ```bash |
| 52 | + pip install -e ./backend-plugin-sample |
| 53 | + python manage.py lms migrate openedx_plugin_sample |
| 54 | + python manage.py cms migrate openedx_plugin_sample |
| 55 | + ``` |
219 | 56 |
|
220 | | -1. **Documentation**: Start with official docs linked in the [Plugin Types table](#plugin-types--official-documentation) |
221 | | -2. **Community**: [Open edX Community Slack](https://openedx.org/slack) |
222 | | -3. **Forums**: [Open edX Discuss Forums](https://discuss.openedx.org) |
223 | | -4. **Issues**: Create issues in this repository for sample-specific problems |
| 57 | +- **Frontend** — in your MFE checkout, add the `module.config.js` and `env.config.jsx` shown in [`frontend-plugin-sample/README.md`](./frontend-plugin-sample/README.md), then `npm ci && npm start`. |
224 | 58 |
|
225 | | -## Additional Resources |
| 59 | +- **Brand** — set `PARAGON_THEME_URLS.variants.light.urls.brandOverride` in your MFE's `env.config.js[x]` (or `theme.variants.light.url` in a frontend-base `site.config.tsx`) to `https://cdn.jsdelivr.net/gh/openedx/sample-plugin@main/brand-sample/dist/light.min.css`. See [`brand-sample/README.md`](./brand-sample/README.md) for the full snippet. |
226 | 60 |
|
227 | | -### Official Documentation |
228 | | -- **Platform**: [Open edX Developer Documentation](https://docs.openedx.org/en/latest/developers/) |
229 | | -- **Architecture**: [OEP-49: Django App Patterns](https://docs.openedx.org/projects/openedx-proposals/en/latest/best-practices/oep-0049-django-app-patterns.html) |
230 | | -- **Events**: [Open edX Events Reference](https://docs.openedx.org/projects/openedx-events/en/latest/reference/events.html) |
231 | | -- **Filters**: [Open edX Filters Reference](https://docs.openedx.org/projects/openedx-filters/en/latest/reference/filters.html) |
232 | | -- **Frontend**: [Available Frontend Plugin Slots](https://docs.openedx.org/en/latest/site_ops/references/frontend-plugin-slots.html) |
| 61 | + > TODO: a fully local brand-development flow without Tutor (recompile + serve from disk) is not yet documented. |
233 | 62 |
|
234 | | -### Community Resources |
235 | | -- **Cookiecutter**: [Django App Template](https://github.com/openedx/cookiecutter-django-app) for creating new plugins |
236 | | -- **Examples**: Other Open edX plugins in the [openedx organization](https://github.com/openedx) |
237 | | -- **Best Practices**: [OEP Index](https://docs.openedx.org/projects/openedx-proposals/en/latest/) for architectural guidance |
| 63 | +## Getting help |
238 | 64 |
|
239 | | -### What This Sample Provides That Official Docs Don't |
240 | | -- **Working Integration**: Complete example showing all plugin types working together |
241 | | -- **Real Business Logic**: Realistic course archiving functionality vs. hello-world examples |
242 | | -- **Development Workflow**: End-to-end development and testing process |
243 | | -- **Troubleshooting**: Common plugin development issues and solutions |
| 65 | +- Open edX [community Slack](https://openedx.org/slack) and [discussion forums](https://discuss.openedx.org) |
| 66 | +- Issues with this sample specifically: [openedx/sample-plugin issues](https://github.com/openedx/sample-plugin/issues) |
0 commit comments