diff --git a/changes/changelog.d/1910.doc b/changes/changelog.d/1910.doc
new file mode 100644
index 000000000..a1f331caa
--- /dev/null
+++ b/changes/changelog.d/1910.doc
@@ -0,0 +1 @@
+Rewrote the plugins documentation (#1910)
diff --git a/docs/developer_documentation/plugins/create.md b/docs/developer_documentation/plugins/create.md
new file mode 100644
index 000000000..f65cb2632
--- /dev/null
+++ b/docs/developer_documentation/plugins/create.md
@@ -0,0 +1,200 @@
+# Write a plugin
+
+You can write plugins to extend the features of your Funkwhale pod. Follow the instructions in this guide to get started with your first plugin.
+
+```{contents}
+:local:
+:depth: 2
+```
+
+## Before you begin
+
+Before you start writing your plugin, you need to understand the following core concepts:
+
+```{contents}
+:local:
+:depth: 1
+```
+
+We'll explain each of these concepts in the next few sections
+
+### Scopes
+
+Plugins fall into two different **scopes**:
+
+1. User-level plugins that are configured by end-users for their own use
+2. Pod-level plugins that are configured by pod admins and are not connected to a particular user
+
+User-level plugins can also be used to import files from a third-party service, such as cloud storage or FTP.
+
+### Hooks
+
+**Hooks** are entrypoints that allow your plugin to listen to changes. You can create hooks to react to different events that occur in the Funkwhale application.
+
+An example of this can be seen in our Scrobbler plugin. We register a `LISTENING_CREATED` hook to notify any registered callback function when a listening is recorded. When a user listens to a track, the `notfy_lastfm` function fires.
+
+```{code-block} python
+from config import plugins
+from .funkwhale_startup import PLUGIN
+
+@plugins.register_hook(plugins.LISTENING_CREATED, PLUGIN)
+def notify_lastfm(listening, conf, **kwargs):
+ # do something
+```
+
+#### Available hooks
+
+```{eval-rst}
+.. autodata:: config.plugins.LISTENING_CREATED
+```
+
+### Filters
+
+**Filters** are entrypoints that allow you to modify or add information. When you use the `register_filter` decorator, your function should return a value to be used by the server.
+
+In this example, the `PLUGINS_DEPENDENCIES` filter is used to install additional dependencies required by your plugin. The `dependencies` function returns the additional dependency `django_prometheus` to request the dependency be installed by the server.
+
+```{code-block} python
+# funkwhale_startup.py
+# ...
+from config import plugins
+
+@plugins.register_filter(plugins.PLUGINS_DEPENDENCIES, PLUGIN)
+def dependencies(dependencies, **kwargs):
+ return dependencies + ["django_prometheus"]
+
+```
+
+#### Available filters
+
+```{eval-rst}
+.. autodata:: config.plugins.PLUGINS_DEPENDENCIES
+.. autodata:: config.plugins.PLUGINS_APPS
+.. autodata:: config.plugins.MIDDLEWARES_BEFORE
+.. autodata:: config.plugins.MIDDLEWARES_AFTER
+.. autodata:: config.plugins.URLS
+```
+
+## Write your plugin
+
+Once you know what type of plugin you want to write and what entrypoint you want to use, you can start writing your plugin.
+
+Plugins are made up of the following 3 files:
+
+- `__init__.py` - indicates that the directory is a Python package
+- `funkwhale_startup.py` - the file that loads during Funkwhale initialization
+- `funkwhale_ready.py` - the file that loads when Funkwhale is configured and ready
+
+### Declare your plugin
+
+You need to declare your plugin and its configuration options so that Funkwhale knows how to load the plugin. To do this, you must declare a new `plugins` instance in your `funkwhale_startup.py` file.
+
+Your `plugins` should include the following information:
+
+```{list-table}
+:header-rows: 1
+
+ * - Parameter
+ - Data type
+ - Description
+ * - `name`
+ - String
+ - The name of your plugin, used in the `.env` file
+ * - `label`
+ - String
+ - The readable label that appears in the Funkwhale frontend
+ * - `description`
+ - String
+ - A meaningful description of your plugin and what it does
+ * - `version`
+ - String
+ - The version number of your plugin
+ * - `user`
+ - Boolean
+ - Whether the plugin is a **user-level** plugin or a **pod-level** plugin. See [scopes](#scopes) for more information
+ * - `conf`
+ - Array of Objects
+ - A list of configuration options
+
+```
+
+In this example, we declare a new **user-level** plugin called "My Plugin". The user can configure a `greeting` in the plugin configuration.
+
+```{code-block} python
+# funkwhale_startup.py
+from config import plugins
+
+PLUGIN = plugins.get_plugin_config(
+ name="myplugin",
+ label="My Plugin",
+ description="An example plugin that greets you",
+ version="0.1",
+ user=True,
+ conf=[
+ # This configuration option is editable by each user
+ {"name": "greeting", "type": "text", "label": "Greeting", "default": "Hello"},
+ ],
+)
+```
+
+### Write your plugin logic
+
+Once you've declared your plugin, you can write the plugin code in your `funkwhale_ready.py` file.
+
+```{note}
+You must import your plugin declaration from your `funkwhale_startup.py` file.
+```
+
+In this example, we create a simple API endpoint that returns a greeting to the user. To do this:
+
+1. We create a new APIView class that accepts a `GET` request
+2. We read the greeting value from the plugin `conf`
+3. We return the greeting value with the user's username
+4. We register this view at the endpoint `/greeting`
+
+```{code-block} python
+# funkwhale_ready.py
+from django.urls import path
+from rest_framework import response
+from rest_framework import views
+
+from config import plugins
+
+# Import the plugin declaration from funkwhale_startup
+from .funkwhale_startup import PLUGIN
+
+# Create a new APIView class
+class GreetingView(views.APIView):
+ permission_classes = []
+ # Register a GET response
+ def get(self, request, *args, **kwargs):
+ # Check the conf value of the plugin for the user
+ conf = plugins.get_conf(PLUGIN["name"], request.user)
+ if not conf["enabled"]:
+ # Return an error code if the user hasn't enabled the plugin
+ return response.Response(status=405)
+ # Set the greeting value to the user's configured greeting
+ greeting = conf["conf"]["greeting"]
+ data = {
+ # Append the user's username to the greeting
+ "greeting": "{} {}!".format(greeting, request.user.username)
+ }
+ # Return the greeting
+ return response.Response(data)
+
+# Register the new APIView at the /greeting endpoint
+@plugins.register_filter(plugins.URLS, PLUGIN)
+def register_view(urls, **kwargs):
+ return urls + [
+ path('greeting', GreetingView.as_view())
+ ]
+```
+
+### Result
+
+Here is an example of how the above plugin works:
+
+1. User "Harry" enables the plugin
+2. "Harry" changes the greeting to "You're a wizard"
+3. "Harry" visits the `/greeting` endpoint in their browser
+4. The browser returns the message "You're a wizard Harry"
diff --git a/docs/developer_documentation/plugins/index.md b/docs/developer_documentation/plugins/index.md
new file mode 100644
index 000000000..6bce1a96c
--- /dev/null
+++ b/docs/developer_documentation/plugins/index.md
@@ -0,0 +1,16 @@
+# Funkwhale plugins
+
+Plugins can be used to extend Funkwhale's featureset without needing to touch the underlying code. Plugins can extend existing features, add support for third-party services, or introduce cosmetic changes to the Funkwhale webapp.
+
+Plugins have been supported since Funkwhale 1.0. Some core plugins, such as the standard Scrobbler plugin, are maintained by the Funkwhale team.
+
+```{toctree}
+---
+caption: Resources
+maxdepth: 1
+---
+
+create
+install
+
+```
diff --git a/docs/developer_documentation/plugins/install.md b/docs/developer_documentation/plugins/install.md
new file mode 100644
index 000000000..4f50c50ca
--- /dev/null
+++ b/docs/developer_documentation/plugins/install.md
@@ -0,0 +1,50 @@
+# Install a plugin
+
+Once you have [created your plugin](create.md), you can install it on your Funkwhale pod.
+
+## Install a local plugin
+
+To install a plugin located on your server:
+
+1. Add the plugin directory to the `FUNKWHALE_PLUGINS_PATH` variable in your `.env` file
+2. Add the plugin name to the `FUNKWHALE_PLUGINS` variable in your `.env` file
+
+ ```{code-block} text
+ FUNKWHALE_PLUGINS=myplugin,anotherplugin
+ ```
+
+3. Restart Funkwhale to pick up the changes
+
+## Install a third-party plugin
+
+You can install third-party plugins using the `manage.py` script. To do this:
+
+1. Add the plugin name to the `FUNKWHALE_PLUGINS` variable in your `.env` file
+
+ ```{code-block} text
+ FUNKWHALE_PLUGINS=myplugin,anotherplugin
+ ```
+
+2. Call the `manage.py` script with the location of the plugin archive
+
+ :::: {tab-set}
+
+ :::{tab-item} Debian
+
+ ```{code-block} shell
+ python manage.py fw plugins install https://plugin_url.zip
+ ```
+
+ :::
+
+ :::{tab-item} Docker
+
+ ```{code-block} shell
+ docker-compose run --rm api python manage.py fw plugins install https://plugin_url.zip
+ ```
+
+ :::
+
+ ::::
+
+3. Restart Funkwhale to pick up the changes
diff --git a/docs/developers/authentication.rst b/docs/developers/authentication.rst
deleted file mode 100644
index 6c8f7d6de..000000000
--- a/docs/developers/authentication.rst
+++ /dev/null
@@ -1,95 +0,0 @@
-API Authentication
-==================
-
-Each Funkwhale API endpoint supports access from:
-
-- Anonymous users (if the endpoint is configured to do so, for exemple via the ``API Authentication Required`` setting)
-- Logged-in users
-- Third-party apps (via OAuth2)
-
-To seamlessly support this range of access modes, we internally use oauth scopes
-to describes what permissions are required to perform any given operation.
-
-OAuth
------
-
-Create an app
-:::::::::::::
-
-To connect to Funkwhale API via OAuth, you need to create an application. There are
-two ways to do that:
-
-1. By visiting ``/settings/applications/new`` when logged in on your Funkwhale instance.
-2. By sending a ``POST`` request to ``/api/v1/oauth/apps/``, as described in `our API documentation `_.
-
-Both method will give you a client ID and secret.
-
-Getting an access token
-:::::::::::::::::::::::
-
-Once you have a client ID and secret, you can request access tokens
-using the `authorization code grant flow `_.
-
-We support the ``urn:ietf:wg:oauth:2.0:oob`` redirect URI for non-web applications, as well
-as traditionnal redirection-based flow.
-
-Our authorization endpoint is located at ``/authorize``, and our token endpoint at ``/api/v1/oauth/token/``.
-
-Refreshing tokens
-:::::::::::::::::
-
-When your access token is expired, you can `request a new one as described in the OAuth specification `_.
-
-Security considerations
-:::::::::::::::::::::::
-
-- Grant codes are valid for a 5 minutes after authorization request is approved by the end user.
-- Access codes are valid for 10 hours. When expired, you will need to request a new one using your refresh token.
-- We return a new refresh token everytime an access token is requested, and invalidate the old one. Ensure you store the new refresh token in your app.
-
-
-Scopes
-::::::
-
-Scopes are defined in :file:`funkwhale_api/users/oauth/scopes.py:BASE_SCOPES`, and generally are mapped to a business-logic resources (follows, favorites, etc.). All those base scopes come in two flawours:
-
-- `read:`: get read-only access to the resource
-- `write:`: get write-only access to the ressource
-
-For example, ``playlists`` is a base scope, and ``write:playlists`` is the actual scope needed to perform write
-operations on playlists (via a ``POST``, ``PATCH``, ``PUT`` or ``DELETE``. ``read:playlists`` is used
-to perform read operations on playlists such as fetching a given playlist via ``GET``.
-
-Having the generic ``read`` or ``write`` scope give you the corresponding access on *all* resources.
-
-This is the list of OAuth scopes that third-party applications can request:
-
-.. list-table:: Oauth scopes
- :header-rows: 1
-
- * - Scope
- - Description
- * - ``read``
- - Read-only access to all data (equivalent to all ``read:*`` scopes).
- * - ``write``
- - Read-only access to all data (equivalent to all ``write:*`` scopes).
- * - ``:profile``
- - Access to profile data (e-mail address, username, etc.)
- * - ``:libraries``
- - Access to library data (uploads, libraries, tracks, albums, artists…)
- * - ``:favorites``
- - Access to favorites
- * - ``:listenings``
- - Access to history
- * - ``:follows``
- - Access to followers
- * - ``:playlists``
- - Access to playlists
- * - ``:radios``
- - Access to radios
- * - ``:filters``
- - Access to content filters
- * - ``:notifications``
- - Access to notifications
- * - ``:edits``
- - Access to metadata edits
diff --git a/docs/developers/index.rst b/docs/developers/index.rst
deleted file mode 100644
index e8201d501..000000000
--- a/docs/developers/index.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-Developer documentation
-=========================
-
-This documentation is targeted primarily at developers who want to understand
-how Funkwhale works and how to build apps that integrate with Funkwhale's ecosystem.
-
-.. toctree::
- :maxdepth: 2
-
- ../api
- ./authentication
- ./plugins
- ../federation/index
- subsonic
diff --git a/docs/developers/plugins.rst b/docs/developers/plugins.rst
deleted file mode 100644
index abfd58dc1..000000000
--- a/docs/developers/plugins.rst
+++ /dev/null
@@ -1,165 +0,0 @@
-Funkwhale plugins
-=================
-
-Starting with Funkwhale 1.0, it is now possible to implement new features
-via plugins.
-
-Some plugins are maintained by the Funkwhale team (e.g. this is the case of the ``scrobbler`` plugin),
-or by third-parties.
-
-Installing a plugin
--------------------
-
-To install a plugin, ensure its directory is present in the ``FUNKWHALE_PLUGINS_PATH`` directory.
-
-Then, add its name to the ``FUNKWHALE_PLUGINS`` environment variable, like this::
-
- FUNKWHALE_PLUGINS=myplugin,anotherplugin
-
-We provide a command to make it easy to install third-party plugins::
-
- python manage.py fw plugins install https://pluginurl.zip
-
-.. note::
-
- If you use the command, you will still need to append the plugin name to ``FUNKWHALE_PLUGINS``
-
-
-Types of plugins
-----------------
-
-There are two types of plugins:
-
-1. Plugins that are accessible to end-users, a.k.a. user-level plugins. This is the case of our Scrobbler plugin
-2. Pod-level plugins that are configured by pod admins and are not tied to a particular user
-
-Additionally, user-level plugins can be regular plugins or source plugins. A source plugin provides
-a way to import files from a third-party service, e.g via webdav, FTP or something similar.
-
-Hooks and filters
------------------
-
-Funkwhale includes two kind of entrypoints for plugins to use: hooks and filters. B
-
-Hooks should be used when you want to react to some change. For instance, the ``LISTENING_CREATED`` hook
-notify each registered callback that a listening was created. Our ``scrobbler`` plugin has a callback
-registered to this hook, so that it can notify Last.fm properly:
-
-.. code-block:: python
-
- from config import plugins
- from .funkwhale_startup import PLUGIN
-
- @plugins.register_hook(plugins.LISTENING_CREATED, PLUGIN)
- def notify_lastfm(listening, conf, **kwargs):
- # do something
-
-Filters work slightly differently, and expect callbacks to return a value that will be used by Funkwhale.
-
-For instance, the ``PLUGINS_DEPENDENCIES`` filter can be used as a way to install additional dependencies needed by your plugin:
-
-
-.. code-block:: python
-
- # funkwhale_startup.py
- # ...
- from config import plugins
-
- @plugins.register_filter(plugins.PLUGINS_DEPENDENCIES, PLUGIN)
- def dependencies(dependencies, **kwargs):
- return dependencies + ["django_prometheus"]
-
-To sum it up, hooks are used when you need to react to something, and filters when you need to alter something.
-
-Writing a plugin
-----------------
-
-Regardless of the type of plugin you want to write, lots of concepts are similar.
-
-First, a plugin need three files:
-
-- a ``__init__.py`` file, since it's a Python package
-- a ``funkwhale_startup.py`` file, that is loaded during Funkwhale initialization
-- a ``funkwhale_ready.py`` file, that is loaded when Funkwhale is configured and ready
-
-So your plugin directory should look like this::
-
- myplugin
- ├── funkwhale_ready.py
- ├── funkwhale_startup.py
- └── __init__.py
-
-Now, let's write our plugin!
-
-``funkwhale_startup.py`` is where you declare your plugin and it's configuration options:
-
-.. code-block:: python
-
- # funkwhale_startup.py
- from config import plugins
-
- PLUGIN = plugins.get_plugin_config(
- name="myplugin",
- label="My Plugin",
- description="An example plugin that greets you",
- version="0.1",
- # here, we write a user-level plugin
- user=True,
- conf=[
- # this configuration options are editable by each user
- {"name": "greeting", "type": "text", "label": "Greeting", "default": "Hello"},
- ],
- )
-
-Now that our plugin is declared and configured, let's implement actual functionality in ``funkwhale_ready.py``:
-
-.. code-block:: python
-
- # funkwhale_ready.py
- from django.urls import path
- from rest_framework import response
- from rest_framework import views
-
- from config import plugins
-
- from .funkwhale_startup import PLUGIN
-
- # Our greeting view, where the magic happens
- class GreetingView(views.APIView):
- permission_classes = []
- def get(self, request, *args, **kwargs):
- # retrieve plugin configuration for the current user
- conf = plugins.get_conf(PLUGIN["name"], request.user)
- if not conf["enabled"]:
- # plugin is disabled for this user
- return response.Response(status=405)
- greeting = conf["conf"]["greeting"]
- data = {
- "greeting": "{} {}!".format(greeting, request.user.username)
- }
- return response.Response(data)
-
- # Ensure our view is known by Django and available at /greeting
- @plugins.register_filter(plugins.URLS, PLUGIN)
- def register_view(urls, **kwargs):
- return urls + [
- path('greeting', GreetingView.as_view())
- ]
-
-And that's pretty much it. Now, login, visit https://yourpod.domain/settings/plugins, set a value in the ``greeting`` field and enable the plugin.
-
-After that, you should be greeted properly if you go to https://yourpod.domain/greeting.
-
-Hooks reference
----------------
-
-.. autodata:: config.plugins.LISTENING_CREATED
-
-Filters reference
------------------
-
-.. autodata:: config.plugins.PLUGINS_DEPENDENCIES
-.. autodata:: config.plugins.PLUGINS_APPS
-.. autodata:: config.plugins.MIDDLEWARES_BEFORE
-.. autodata:: config.plugins.MIDDLEWARES_AFTER
-.. autodata:: config.plugins.URLS
diff --git a/docs/index.md b/docs/index.md
index 68e44872e..aab103d87 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -76,6 +76,7 @@ developer_documentation/setup/index
developer_documentation/contribute/index
developer_documentation/workflows/index
developer_documentation/api/index
+developer_documentation/plugins/index
```