diff --git a/docs/env.md b/docs/env.md
index 0b3f9b3bf6..781ad2f853 100644
--- a/docs/env.md
+++ b/docs/env.md
@@ -99,6 +99,8 @@ These are the environment variables you can set for the `impress-backend` contai
| STORAGES_STATICFILES_BACKEND | | whitenoise.storage.CompressedManifestStaticFilesStorage |
| THEME_CUSTOMIZATION_CACHE_TIMEOUT | Cache duration for the customization settings | 86400 |
| THEME_CUSTOMIZATION_FILE_PATH | Full path to the file customizing the theme. An example is provided in src/backend/impress/configuration/theme/default.json | BASE_DIR/impress/configuration/theme/default.json |
+| PLUGINS_CONFIG_FILE_PATH | Full path to the JSON file containing the plugins configuration loaded by the backend. Example: src/backend/impress/configuration/plugins/default.json | BASE_DIR/impress/configuration/plugins/default.json |
+| PLUGINS_CONFIG_CACHE_TIMEOUT | Time in seconds the plugins configuration file is cached by the backend before being reloaded. Default is 2 hours (7200 seconds). | 7200 |
| TRASHBIN_CUTOFF_DAYS | Trashbin cutoff | 30 |
| USER_OIDC_ESSENTIAL_CLAIMS | Essential claims in OIDC token | [] |
| Y_PROVIDER_API_BASE_URL | Y Provider url | |
diff --git a/docs/frontend-plugins.md b/docs/frontend-plugins.md
new file mode 100644
index 0000000000..1e098efe94
--- /dev/null
+++ b/docs/frontend-plugins.md
@@ -0,0 +1,557 @@
+# Frontend Plugin System
+
+## Table of Contents
+- [Overview](#overview "Go to Overview section")
+- [Getting Started: Building Your First Plugin](#getting-started-building-your-first-plugin "Go to the Getting Started guide")
+ - [1. Prepare the Host Environment](#1-prepare-the-host-environment "Step 1: Prepare the host")
+ - [2. Scaffolding a New Plugin Project](#2-scaffolding-a-new-plugin-project "Step 2: Scaffold the plugin")
+ - [3. Creating a Plugin Component](#3-creating-a-plugin-component "Step 3: Create the React component")
+ - [4. Federation Configuration](#4-federation-configuration "Step 4: Configure module federation")
+ - [5. Enabling Type-Sharing for Intellisense](#5-enabling-type-sharing-for-intellisense "Step 5: Enable type-sharing")
+ - [6. Running and Configuring Your Plugin](#6-running-and-configuring-your-plugin "Step 6: Run and configure")
+- [Host-Plugin Interaction](#host-plugin-interaction "How the host and plugin interact")
+ - [Host Exports](#host-exports "What the host exports")
+ - [Choosing Shared Dependencies](#choosing-shared-dependencies "Learn about shared dependencies")
+- [Development Workflow](#development-workflow "Go to Development Workflow section")
+ - [Test and Debug](#test-and-debug "How to test and debug")
+ - [Best Practices](#best-practices "View best practices")
+- [Plugin Configuration File Reference](#plugin-configuration-file-reference "Go to the Configuration File reference")
+ - [Configuration Structure](#configuration-structure "See the config file structure")
+ - [Injection Position Examples](#injection-position-examples "See examples of injection positions")
+- [Releasing a Plugin](#releasing-a-plugin "How to release a plugin")
+- [Deploying Docs with Plugins](#deploying-docs-with-plugins "How to deploy plugins in production")
+
+## Overview
+
+The plugin system allows developers to extend the application's functionality and appearance without modifying the core.
+It's ideal for teams or third parties to add custom features.
+
+
+
+### Glossary
+- **Remote**: An application exposing components via module federation.
+- **Host**: The main entry point application. This is Docs itself ("impress").
+- **Plugin**: A remote module integrated into the host to provide UI components.
+- **Module Federation**: The technology that enables runtime module sharing between separate applications.
+
+
+
+### Features and Limitations
+**Features:**
+- Add new UI components.
+- Reuse host UI components.
+- Dynamically inject components via CSS selectors and a [configuration file](#plugin-configuration-file-reference "See the configuration file reference").
+- Integrate without rebuilding or redeploying the host application.
+- Build and version plugins independently.
+
+
+
+**Limitations:**
+- Focused on DOM/UI customisations; you cannot add Next.js routes or other server-side features.
+- Runs client-side without direct host state access.
+ Shared caches (e.g., React Query) only work if the dependency is also [shared as a singleton](#choosing-shared-dependencies "Learn about shared dependencies").
+- Host upgrades may require tweaking CSS selectors
+ and matching versions for shared libraries.
+
+
+
+## Getting Started: Building Your First Plugin
+
+A plugin is a standalone React application bundled with Webpack
+that exposes one or more components via [Module Federation](#4-federation-configuration "See the federation configuration").
+This guide walks you through creating your first plugin.
+
+
+
+### 1. Prepare the Host Environment
+
+Developing a plugin requires running the host application (Docs) in parallel.
+This live integration is essential for rendering your plugin, enabling hot-reloading, sharing types for Intellisense,
+and discovering the exact versions of [shared dependencies](#choosing-shared-dependencies "Learn about shared dependencies").
+
+
+
+1. **Clone the repository locally**:
+ If you haven't already, clone the Docs repository to your local machine and follow the initial setup instructions.
+2. **Set the development flag**:
+ In the host application's `.env.development` file, set `NEXT_PUBLIC_DEVELOP_PLUGINS=true`.
+3. **Stop conflicting services**:
+ If you are using the project's Docker setup, make sure
+ the frontend service is stopped (`docker compose stop frontend-development`), as we will run the Docs frontend locally.
+4. **Run the host**:
+ Navigate to `src/frontend/apps/impress`, run `yarn install`, and then `yarn dev`.
+5. **Check the logs**:
+ On startup, the Next.js dev server will print the versions of all shared singleton libraries (e.g., React, styled-components).
+ You will need these exact versions for your plugin's `package.json`.
+
+
+
+### 2. Scaffolding a New Plugin Project
+
+Create a new, simple React project.
+Your project should have a [`webpack.config.js`](#4-federation-configuration "See the federation configuration") and include dependencies for React, Webpack, and TypeScript.
+
+
+
+A minimal `package.json` would look like this:
+
+```json
+{
+ "name": "my-plugin",
+ "version": "1.0.0",
+ "scripts": {
+ "dev": "webpack serve --mode=development",
+ "build": "webpack --mode=production"
+ },
+ "dependencies": {
+ "react": "",
+ "react-dom": "",
+ "styled-components": "",
+ "@openfun/cunningham-react": "",
+ "@tanstack/react-query": ""
+ },
+ "devDependencies": {
+ "webpack": "^5.0.0",
+ "webpack-cli": "^5.0.0",
+ "webpack-dev-server": "^4.0.0",
+ "ts-loader": "^9.0.0",
+ "typescript": "^5.0.0",
+ "@types/react": "^18.0.0",
+ "@module-federation/native-federation-typescript": "^0.2.1"
+ }
+}
+```
+
+> Replace `` with the versions found in the [host's dev startup log](#1-prepare-the-host-environment "See how to prepare the host").
+
+
+
+### 3\. Creating a Plugin Component
+
+This is a React component that your `webpack.config.js` file exposes.
+This minimal example shows how to accept `props`, which can be passed from the [plugin configuration file](#plugin-configuration-file-reference "See the configuration file reference").
+
+
+
+```typescript
+// src/MyCustomComponent.tsx
+import React from 'react';
+
+// A simple component with inline prop types
+const MyCustomComponent = ({ message }: { message?: string }) => {
+ return (
+
+ This is the plugin component.
+ {message &&
Message from props: {message}
}
+
+ );
+};
+
+export default MyCustomComponent;
+```
+
+
+
+### 4\. Federation Configuration
+
+The core of the plugin is its Webpack configuration.
+All plugins should use this sample `webpack.config.js` as a base.
+
+
+
+```javascript
+const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
+const { NativeFederationTypeScriptHost } = require('@module-federation/native-federation-typescript/webpack');
+
+module.exports = (env, argv) => {
+ const dev = argv.mode !== 'production';
+
+ const moduleFederationConfig = {
+ name: 'my_plugin', // A unique name for your plugin
+ filename: 'remoteEntry.js',
+ exposes: {
+ // Maps a public name to a component file
+ './MyCustomComponent': './src/MyCustomComponent.tsx',
+ },
+ remotes: {
+ // Allows importing from the host application. The URL is switched automatically.
+ impress: dev
+ ? 'impress@http://localhost:3000/_next/static/chunks/remoteEntry.js' // Development
+ : 'impress@/_next/static/chunks/remoteEntry.js', // Production
+ },
+ shared: {
+ // Defines shared libraries to avoid duplication
+ react: { singleton: true },
+ 'react-dom': { singleton: true },
+ 'styled-components': { singleton: true },
+ '@openfun/cunningham-react': { singleton: true },
+ '@tanstack/react-query': { singleton: true },
+ },
+ };
+
+ return {
+ devServer: {
+ // The port should match the one in your plugin's configuration file
+ port: 8080,
+ },
+ entry: './src/index.tsx', // Your plugin's entry point; can be an empty file as modules are exposed directly.
+ plugins: [
+ new ModuleFederationPlugin(moduleFederationConfig),
+ // This plugin enables type-sharing for intellisense
+ ...(dev ? [NativeFederationTypeScriptHost({ moduleFederationConfig })] : []),
+ ],
+ // ... other webpack config (output, module rules, etc.)
+ };
+};
+```
+
+> Don't change `remotes.impress` if you want your [released plugin](#releasing-a-plugin "Learn how to release a plugin") to be [deployable by others](#deploying-docs-with-plugins "Learn about deployment").
+
+
+
+### 5\. Enabling Type-Sharing for Intellisense
+
+To get autocompletion for components and hooks exposed by the host,
+configure your plugin's `tsconfig.json` to find the host's types.
+
+
+
+In your plugin's `tsconfig.json`:
+
+```json
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "*": ["./@mf-types/*"]
+ }
+ }
+}
+```
+
+
+
+When you run the host application with `NEXT_PUBLIC_DEVELOP_PLUGINS=true`, it generates a `@mf-types.zip` file.
+The `NativeFederationTypeScriptHost` plugin in your webpack config will automatically download and unpack it,
+making the host's types available to your plugin and IDE.
+
+
+
+### 6\. Running and Configuring Your Plugin
+
+With the host application already running (from step 1),
+you can now start your plugin's development server and configure the host to load it.
+
+
+
+1. **Start the plugin**:
+ In your plugin's project directory, run `yarn dev`.
+2. **Configure the host**:
+ Tell the host to load your plugin by editing its configuration file.
+ When running Docs locally, this file is located at `src/backend/impress/configuration/plugins/default.json`.
+ Update it to point to your local plugin's `remoteEntry.js`.
+
+
+
+```json
+{
+ "id": "my-custom-component",
+ "remote": {
+ "url": "http://localhost:8080/remoteEntry.js",
+ "name": "my_plugin",
+ "module": "./MyCustomComponent"
+ },
+ "injection": {
+ "target": "#some-element-in-the-host"
+ },
+ "props": {
+ "message": "Hello from the configuration!"
+ }
+}
+```
+
+
+
+After changing the `target` to a valid CSS selector in the host's DOM, save the file.
+The host application will automatically detect the change and inject your component, passing the `props` object along.
+
+Your component should appear in the running host application after a reload.
+
+
+
+## Host-Plugin Interaction
+
+### Host Exports
+
+The host automatically exposes many of its components and hooks.
+You can import them in the plugin as if they were local modules, thanks to the [`remotes` configuration](#4-federation-configuration "See the remotes config in Webpack") in the `webpack.config.js`.
+
+
+
+```typescript
+// In the plugin's code
+import { Icon } from 'impress/components';
+import { useAuthQuery } from 'impress/features/auth/api';
+```
+
+
+
+### Choosing Shared Dependencies
+
+Sharing dependencies is critical for performance and stability.
+
+
+
+ - **Minimal Shared Libraries**:
+ Always share **`react`**, **`react-dom`**, **`styled-components`**, and **`@openfun/cunningham-react`** to use the same instances as the host.
+ - **Sharing State**:
+ Libraries that rely on a global context (like `@tanstack/react-query`) **must** be shared to access the host's state and cache.
+ - **Discovering More Shared Libraries**: With `NEXT_PUBLIC_DEVELOP_PLUGINS=true`,
+ [the host prints its shared dependency map to the Next.js dev server logs on startup](#1-prepare-the-host-environment "See how to prepare the host").
+ You can use this to align versions and add more shared libraries to your plugin.
+
+
+
+> **Important**: Both the host and the plugin must declare a dependency in [`moduleFederationConfig.shared`](#4-federation-configuration "See the federation configuration") for it to become a true singleton.
+> If a shared dependency is omitted from the plugin's config, Webpack will bundle a separate copy, breaking the singleton pattern.
+
+
+
+## Development Workflow
+
+### Test and Debug
+
+ - Use the `[PluginSystem]` logs in the browser console to see if the plugin is loading correctly.
+ - Errors in the plugin are caught by an `ErrorBoundary` and will not crash the host.
+
+
+
+Common Errors:
+| Issue | Cause/Fix |
+| :--- | :--- |
+| Unreachable `remoteEntry.js` | Check the `url` in the [plugin configuration](#6-running-and-configuring-your-plugin "See how to configure the plugin"). |
+| Library version conflicts | Ensure `shared` library versions in `package.json` match the [host's versions](#1-prepare-the-host-environment "See how to check host versions"). |
+| Invalid CSS selectors | Validate the `target` selector against the host's DOM. |
+
+
+
+### Best Practices
+
+ - Build modular components with well-typed props.
+ - Prefer using the host's exposed types and components over implementing new ones.
+ - Keep shared dependency versions aligned with the host
+ and re-test after host upgrades.
+ - Treat plugin bundles as untrusted: vet dependencies and avoid unsafe scripts.
+
+
+
+## Plugin Configuration File Reference
+
+This section provides a detailed reference for all fields in the plugin configuration JSON.
+For deployment details, see [Deploying Docs with Plugins](#deploying-docs-with-plugins "Learn about production deployment").
+
+
+
+### Configuration Structure
+
+| Field | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| `id` | String | Yes | Unique component identifier (e.g., "my-widget"). |
+| `remote` | Object | Yes | Remote module details. |
+| - `url` | String | Yes | Path to `remoteEntry.js` (absolute/relative). |
+| - `name` | String | Yes | Federation remote name (e.g., "myPlugin"). |
+| - `module` | String | Yes | Exposed module (e.g., "./Widget"). |
+| `injection`| Object | Yes | Integration control. |
+| - `target` | String | Yes | CSS selector for insertion point. |
+| - `position` | String | No (default: "append") | Insertion position (`before`, `after`, `replace`, `prepend`, `append`). See [examples](#injection-position-examples "See injection examples"). |
+| - `observerRoots` | String/Boolean | No | DOM observation: CSS selector, `true` (observe whole document), or `false` (default; disable observers). |
+| `props` | Object | No | Props passed to the [plugin component](#3-creating-a-plugin-component "See how to create a component with props"). |
+| `visibility` | Object | No | Visibility controls. |
+| - `routes` | Array | No | Path globs (e.g., `["/docs/*", "!/docs/secret*"]`); supports `*` and `?` wildcards plus negation (`!`). |
+
+
+
+### Injection Position Examples
+
+The `injection.position` property controls how the plugin is inserted relative to the `target` element.
+
+
+View injection examples
+
+
+
+**before**
+
+```json
+{
+ "id": "my-custom-component-0",
+ "injection": {
+ "target": "#item2",
+ "position": "before"
+ }
+}
+```
+
+```html
+