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.

Before you begin

Before you start writing your plugin, you need to understand the following core concepts:

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.

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

LISTENING_CREATED = 'listening_created'

Called when a track is being listened

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.

# funkwhale_startup.py
# ...
from config import plugins

@plugins.register_filter(plugins.PLUGINS_DEPENDENCIES, PLUGIN)
def dependencies(dependencies, **kwargs):
    return dependencies + ["django_prometheus"]

Available filters

PLUGINS_DEPENDENCIES = 'plugins_dependencies'

Called with an empty list, use this filter to append pip dependencies to the list for installation.

PLUGINS_APPS = 'plugins_apps'

Called with an empty list, use this filter to append apps to INSTALLED_APPS

MIDDLEWARES_BEFORE = 'middlewares_before'

Called with an empty list, use this filter to prepend middlewares to MIDDLEWARE

MIDDLEWARES_AFTER = 'middlewares_after'

Called with an empty list, use this filter to append middlewares to MIDDLEWARE

URLS = 'urls'

Called with an empty list, use this filter to register new urls and views

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:

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 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.

# 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

# 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”