diff --git a/README.rst b/README.rst
index 6357e55302..33e1fd9762 100644
--- a/README.rst
+++ b/README.rst
@@ -100,6 +100,12 @@ The Learning MFE is similar to all the other Open edX MFEs. Read the Open
edX Developer Guide's section on
`MFE applications `_.
+Plugins
+=======
+This MFE can be customized using `Frontend Plugin Framework `_.
+
+The parts of this MFE that can be customized in that manner are documented `here `_.
+
Environment Variables
======================
diff --git a/src/courseware/course/sequence/Sequence.jsx b/src/courseware/course/sequence/Sequence.jsx
index 949a77f668..f2e7fc84fd 100644
--- a/src/courseware/course/sequence/Sequence.jsx
+++ b/src/courseware/course/sequence/Sequence.jsx
@@ -13,7 +13,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { useSelector } from 'react-redux';
import SequenceExamWrapper from '@edx/frontend-lib-special-exams';
import { breakpoints, useWindowSize } from '@openedx/paragon';
-import { PluginSlot } from '@openedx/frontend-plugin-framework';
+import SequenceContainerSlot from '../../../plugin-slots/SequenceContainerSlot';
import PageLoading from '../../../generic/PageLoading';
import { useModel } from '../../../generic/model-store';
@@ -200,13 +200,7 @@ const Sequence = ({
{isNewDiscussionSidebarViewEnabled ? : }
-
+
>
);
diff --git a/src/courseware/course/sequence/Unit/__snapshots__/index.test.jsx.snap b/src/courseware/course/sequence/Unit/__snapshots__/index.test.jsx.snap
index 2419acb694..2249c87122 100644
--- a/src/courseware/course/sequence/Unit/__snapshots__/index.test.jsx.snap
+++ b/src/courseware/course/sequence/Unit/__snapshots__/index.test.jsx.snap
@@ -28,14 +28,9 @@ exports[`Unit component output snapshot: not bookmarked, do not show content 1`]
>
unit-title
-
{formatMessage(messages.headerPlaceholder)}
{
-
+
>
);
}
@@ -51,7 +51,7 @@ const CourseAccessErrorPage = ({ intl }) => {
}}
/>
-
+
>
);
};
diff --git a/src/plugin-slots/FooterSlot/README.md b/src/plugin-slots/FooterSlot/README.md
new file mode 100644
index 0000000000..b5cf099f54
--- /dev/null
+++ b/src/plugin-slots/FooterSlot/README.md
@@ -0,0 +1,44 @@
+# Footer Slot
+
+### Slot ID: `footer_slot`
+
+## Example
+
+The following `env.config.jsx` will replace the default footer
+
+![Screenshot of Default Footer](./images/default_footer.png)
+
+with a simple custom footer
+
+![Screenshot of Custom Footer](./images/custom_footer.png)
+
+```js
+import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
+
+const config = {
+ pluginSlots: {
+ footer_slot: {
+ plugins: [
+ {
+ // Hide the default footer
+ op: PLUGIN_OPERATIONS.Hide,
+ widgetId: 'default_contents',
+ },
+ {
+ // Insert a custom footer
+ op: PLUGIN_OPERATIONS.Insert,
+ widget: {
+ id: 'custom_footer',
+ type: DIRECT_PLUGIN,
+ RenderWidget: () => (
+ 🦶
+ ),
+ },
+ },
+ ]
+ }
+ },
+}
+
+export default config;
+```
diff --git a/src/plugin-slots/FooterSlot/images/custom_footer.png b/src/plugin-slots/FooterSlot/images/custom_footer.png
new file mode 100644
index 0000000000..9db1093b48
Binary files /dev/null and b/src/plugin-slots/FooterSlot/images/custom_footer.png differ
diff --git a/src/plugin-slots/FooterSlot/images/default_footer.png b/src/plugin-slots/FooterSlot/images/default_footer.png
new file mode 100644
index 0000000000..e6ee956c10
Binary files /dev/null and b/src/plugin-slots/FooterSlot/images/default_footer.png differ
diff --git a/src/plugin-slots/FooterSlot/index.jsx b/src/plugin-slots/FooterSlot/index.jsx
new file mode 100644
index 0000000000..b0d7f9a773
--- /dev/null
+++ b/src/plugin-slots/FooterSlot/index.jsx
@@ -0,0 +1,10 @@
+import { PluginSlot } from '@openedx/frontend-plugin-framework';
+import Footer from '@edx/frontend-component-footer';
+
+const FooterSlot = () => (
+
+);
+
+export default FooterSlot;
diff --git a/src/plugin-slots/README.md b/src/plugin-slots/README.md
new file mode 100644
index 0000000000..f7c775d200
--- /dev/null
+++ b/src/plugin-slots/README.md
@@ -0,0 +1,5 @@
+# `frontend-app-learning` Plugin Slots
+
+* [`footer_slot`](./FooterSlot/)
+* [`sequence_container_slot`](./SequenceContainerSlot/)
+* [`unit_title_slot`](./UnitTitleSlot/)
diff --git a/src/plugin-slots/SequenceContainerSlot/README.md b/src/plugin-slots/SequenceContainerSlot/README.md
new file mode 100644
index 0000000000..593f8dfddc
--- /dev/null
+++ b/src/plugin-slots/SequenceContainerSlot/README.md
@@ -0,0 +1,41 @@
+# Sequence Container Slot
+
+### Slot ID: `sequence_container_slot`
+### Props:
+* `courseId`
+* `unitId`
+
+## Example
+
+The following `env.config.jsx` will add content after the sequence container
+
+![Screenshot of Content added after the Sequence Container](./images/post_sequence_container.png)
+
+```js
+import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
+
+const config = {
+ pluginSlots: {
+ sequence_container_slot: {
+ plugins: [
+ {
+ // Insert custom content after sequence content
+ op: PLUGIN_OPERATIONS.Insert,
+ widget: {
+ id: 'custom_sequence_container_content',
+ type: DIRECT_PLUGIN,
+ RenderWidget: ({courseId, unitId}) => (
+
+
📚: {courseId}
+
📙: {unitId}
+
+ ),
+ },
+ },
+ ]
+ }
+ },
+}
+
+export default config;
+```
diff --git a/src/plugin-slots/SequenceContainerSlot/images/post_sequence_container.png b/src/plugin-slots/SequenceContainerSlot/images/post_sequence_container.png
new file mode 100644
index 0000000000..8b723b50c4
Binary files /dev/null and b/src/plugin-slots/SequenceContainerSlot/images/post_sequence_container.png differ
diff --git a/src/plugin-slots/SequenceContainerSlot/index.jsx b/src/plugin-slots/SequenceContainerSlot/index.jsx
new file mode 100644
index 0000000000..37b789d977
--- /dev/null
+++ b/src/plugin-slots/SequenceContainerSlot/index.jsx
@@ -0,0 +1,23 @@
+import PropTypes from 'prop-types';
+import { PluginSlot } from '@openedx/frontend-plugin-framework';
+
+const SequenceContainerSlot = ({ courseId, unitId }) => (
+
+);
+
+SequenceContainerSlot.propTypes = {
+ courseId: PropTypes.string.isRequired,
+ unitId: PropTypes.string,
+};
+
+SequenceContainerSlot.defaultProps = {
+ unitId: null,
+};
+
+export default SequenceContainerSlot;
diff --git a/src/plugin-slots/UnitTitleSlot/README.md b/src/plugin-slots/UnitTitleSlot/README.md
new file mode 100644
index 0000000000..8419cd3585
--- /dev/null
+++ b/src/plugin-slots/UnitTitleSlot/README.md
@@ -0,0 +1,41 @@
+# Unit Title Slot
+
+### Slot ID: `unit_title_slot`
+### Props:
+* `courseId`
+* `unitId`
+
+## Example
+
+The following `env.config.jsx` will add content after the unit title
+
+![Screenshot of Content added after the Unit Title](./images/post_unit_title.png)
+
+```js
+import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
+
+const config = {
+ pluginSlots: {
+ unit_title_slot: {
+ plugins: [
+ {
+ // Insert custom content after unit title
+ op: PLUGIN_OPERATIONS.Insert,
+ widget: {
+ id: 'custom_unit_title_content',
+ type: DIRECT_PLUGIN,
+ RenderWidget: ({courseId, unitId}) => (
+ <>
+ 📚: {courseId}
+ 📙: {unitId}
+ >
+ ),
+ },
+ },
+ ]
+ }
+ },
+}
+
+export default config;
+```
diff --git a/src/plugin-slots/UnitTitleSlot/images/post_unit_title.png b/src/plugin-slots/UnitTitleSlot/images/post_unit_title.png
new file mode 100644
index 0000000000..b0adc94c20
Binary files /dev/null and b/src/plugin-slots/UnitTitleSlot/images/post_unit_title.png differ
diff --git a/src/plugin-slots/UnitTitleSlot/index.jsx b/src/plugin-slots/UnitTitleSlot/index.jsx
new file mode 100644
index 0000000000..40f449cf97
--- /dev/null
+++ b/src/plugin-slots/UnitTitleSlot/index.jsx
@@ -0,0 +1,19 @@
+import PropTypes from 'prop-types';
+import { PluginSlot } from '@openedx/frontend-plugin-framework';
+
+const UnitTitleSlot = ({ courseId, unitId }) => (
+
+);
+
+UnitTitleSlot.propTypes = {
+ courseId: PropTypes.string.isRequired,
+ unitId: PropTypes.string.isRequired,
+};
+
+export default UnitTitleSlot;
diff --git a/src/tab-page/TabPage.jsx b/src/tab-page/TabPage.jsx
index c3d2ec3afa..3dbac5d35e 100644
--- a/src/tab-page/TabPage.jsx
+++ b/src/tab-page/TabPage.jsx
@@ -4,9 +4,9 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useDispatch, useSelector } from 'react-redux';
import { Navigate } from 'react-router-dom';
-import Footer from '@edx/frontend-component-footer';
import { Toast } from '@openedx/paragon';
import { LearningHeader as Header } from '@edx/frontend-component-header';
+import FooterSlot from '../plugin-slots/FooterSlot';
import PageLoading from '../generic/PageLoading';
import { getAccessDeniedRedirectUrl } from '../shared/access';
import { useModel } from '../generic/model-store';
@@ -80,7 +80,7 @@ const TabPage = ({ intl, ...props }) => {
{intl.formatMessage(messages.failure)}
)}
-
+
>
);
};