Hi all,
I’m working on a branded deployment on Tutor 20.0.2 (Teak) and I’m trying to find the proper way to deploy a set of custom React components via the Frontend Plugin Framework / slots, using Tutor’s PLUGIN_SLOTS in a tutor local start -d setup.
I’d love feedback on whether my approach makes sense, and what the recommended pattern is for “complex” slot replacements (components that themselves import a bunch of stuff) shared across multiple MFEs.
What I have so far
1. Forked MFEs
I’ve created forks of several MFEs, all based on the tag that matches my Tutor version:
-
authn (for this one I’m not using SLOTS as they are not available)
-
learning (touching header and footer)
-
learner-dashboard (header, footer and widget)
Each fork has a custom branch. In the forks, I’ve added a folder under src/ with my own React components (for branding, header/footer, etc.), and they are exported normally from those modules.
Tutor plugin for overriding MFE repos:
from tutormfe.hooks import MFE_APPS
@MFE_APPS.add()
def _override_default_mfes(mfes):
mfes["authn"]["repository"] = "https://github.com/institutohumai/mfe-frontend-app-authn-fork.git"
mfes["authn"]["version"] = "newfront-lauti-26112025"
mfes["learning"]["repository"] = "https://github.com/institutohumai/mfe-frontend-app-learning-fork.git"
mfes["learning"]["version"] = "NewCampus-LH-0212"
mfes["learner-dashboard"]["repository"] = "https://github.com/institutohumai/mfe-frontend-app-learner-dashboard-fork.git"
mfes["learner-dashboard"]["version"] = "NewCampus-LH-0212"
return mfes
2. Custom components inside the MFE repo
In the MFE forks I have components like:
- src/humai/CustomHeader.jsx
- src/humai/CustomFooter.jsx
Each of these is a “complex” component: it imports Paragon, FontAwesome, router hooks, getAuthenticatedUser, etc. I want to use them as slot replacements for:
- Header slots in MFEs that use frontend-component-header
- Footer slots in MFEs that use frontend-component-footer
Ideally in a way that I can target several MFEs at once (using app=“all” in PLUGIN_SLOTS).
3. Tutor plugins
I currently have these Tutor plugins:
from tutormfe.hooks import PLUGIN_SLOTS
from tutor import hooks
# Try to import React components at build time into env.config.jsx
hooks.Filters.ENV_PATCHES.add_item(
("mfe-env-config-buildtime-imports", """
import CustomHeader from '@src/humai/CustomHeader';
import CustomFooter from '@src/humai/CustomFooter';
""")
)
PLUGIN_SLOTS.add_items([
(
"all",
"org.openedx.frontend.layout.header_desktop.v1",
"""
{
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'humai_custom_header',
type: DIRECT_PLUGIN,
RenderWidget: CustomHeader,
},
},
],
}
""",
),
(
"all",
"org.openedx.frontend.layout.footer.v1",
"""
{
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'humai_custom_footer',
type: DIRECT_PLUGIN,
RenderWidget: CustomFooter,
},
},
],
}
""",
),
(
"learner-dashboard",
"org.openedx.frontend.learner_dashboard.widget_sidebar.v1",
"""
{
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Hide,
widgetId: 'default_contents',
},
],
}
""",
),
])
4. What works in local MFE dev mode
If I run the MFE locally (outside Tutor) with an env.config.jsx like this, it works as expected:
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
import CustomHeader from './src/humai/CustomHeader';
import CustomFooter from './src/humai/CustomFooter';
const config = {
pluginSlots: {
'org.openedx.frontend.learner_dashboard.widget_sidebar.v1': {
plugins: [
{
op: PLUGIN_OPERATIONS.Hide,
widgetId: 'default_contents',
},
],
},
'org.openedx.frontend.layout.header_desktop.v1': {
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_desktop_header_component',
type: DIRECT_PLUGIN,
RenderWidget: () => <CustomHeader />,
},
},
],
},
'org.openedx.frontend.layout.footer.v1': {
plugins: [
{
op: PLUGIN_OPERATIONS.Hide,
widgetId: 'default_contents',
},
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_footer',
type: DIRECT_PLUGIN,
RenderWidget: () => <CustomFooter />,
},
},
],
},
},
};
export default config;
So in webpack dev mode, with a “normal” env.config.jsx living inside the repo, everything is fine. The imports work, the slots are applied, and my custom header/footer render.
The problem / confusion
In the Tutor build (tutor images build mfe + tutor local start -d), there are a few differences:
- Tutor generates its own env.config.jsx at build time, based on PLUGIN_SLOTS and ENV_PATCHES.
- That file lives outside src/ in the build context (/openedx/app/env.config.jsx), so things like import CustomHeader from ‘@src/humai/CustomHeader’ need to be wired correctly.
- The build has complained with errors like “Cannot resolve @src/humai/CustomHeader” and, depending on what I try, failure to resolve @openedx/frontend-plugin-framework inside that injected env.config.jsx.
Given that:
- I want to keep my “complex” components in the MFE repo (src/humai/*.jsx), with their own imports, not inline huge JSX blobs in slot definitions; and
- I would like to use app=“all” in PLUGIN_SLOTS.add_items to target header/footer slots across multiple MFEs that use frontend-component-header / frontend-component-footer,
…I’m not sure what the canonical / recommended pattern is in Tutor 20 / Teak.
Docs I’ve followed
I’ve read and tried to follow:
- Use A Frontend Plugin Framework Slot — Latest documentation
- GitHub - overhangio/tutor-mfe: This plugin makes it possible to easily add micro frontend (MFE) applications on top of an Open edX platform that runs with Tutor.
- GitHub - overhangio/tutor-mfe: This plugin makes it possible to easily add micro frontend (MFE) applications on top of an Open edX platform that runs with Tutor.
- Available Frontend Plugin Slots — Latest documentation
- frontend-plugin-workshop-2025/docs/operators.md at main · arbrandes/frontend-plugin-workshop-2025 · GitHub
These helped, but they mostly show inline RenderWidget examples (simple components in the slot config), whereas I’m trying to reuse more complex components declared in the MFE repo.
Questions
- What is the best practice to reference “real” React components from the MFE repo (e.g. src/humai/CustomHeader.jsx ) inside Tutor’s generated env.config.jsx ?
- Is mfe-env-config-buildtime-imports the right place to do something like:
or is there a better pattern?import CustomHeader from '@src/humai/CustomHeader'; - Is it more recommended to use mfe-env-config-runtime-definitions- with dynamic import(‘@src/…’) and stash those on window?
- Can I safely use (“all”, “org.openedx.frontend.layout.header_desktop.v1”, …) to apply a header replacement across multiple MFEs, when the components themselves live only in some forks?
- For example, is it OK that my components live only in the authn / learning / learner-dashboard forks, but not in every MFE?
- Is there a reference example somewhere of:
- Forked MFE repo with custom components in src/…,
- A Tutor plugin that uses PLUGIN_SLOTS to replace header/footer slots in multiple MFEs,
- And imports those components without inlining them inside the slot config?
Any pointers, patterns, or working snippets from people who have successfully done “complex” header/footer overrides with forked MFEs + Tutor would be super appreciated.
Thanks in advance!