Concepts

OctoPrint’s plugins are Python Packages which in their top-level module define a bunch of control properties defining metadata (like name, version etc of the plugin) as well as information on how to initialize the plugin and into what parts of the system the plugin will actually plug in to perform its job.

There are three types of ways a plugin might attach itself to the system, through so called mixin implementations, by attaching itself to specified hook or by offering helper functionality to be used by other plugins.

Plugin mixin implementations will get a bunch of properties injected by OctoPrint plugin system to help them work.

Control Properties

As already mentioned above, plugins are Python packages which provide certain pieces of metadata to tell OctoPrint’s plugin subsystem about themselves. These are simple package attributes defined in the top most package file, e.g.:

import octoprint.plugin

# ...

__plugin_name__ = "My Plugin"
def __plugin_load__():
    # whatever you need to do to load your plugin, if anything at all
    pass

The following properties are recognized:

__plugin_name__
Name of your plugin, optional, overrides the name specified in setup.py if provided. If neither this property nor a name from setup.py is available to the plugin subsystem, the plugin’s identifier (= package name) will be used instead.
__plugin_version__
Version of your plugin, optional, overrides the version specified in setup.py if provided.
__plugin_description__
Description of your plugin, optional, overrides the description specified in setup.py if provided.
__plugin_author__
Author of your plugin, optional, overrides the author specified in setup.py if provided.
__plugin_url__
URL of the webpage of your plugin, e.g. the Github repository, optional, overrides the URL specified in setup.py if provided.
__plugin_license__
License of your plugin, optional, overrides the license specified in setup.py if provided.
__plugin_implementation__
Instance of an implementation of one or more plugin mixins.
__plugin_hooks__
Handlers for one or more of the various plugin hooks.
__plugin_check__
Method called upon discovery of the plugin by the plugin subsystem, should return True if the plugin can be instantiated later on, False if there are reasons why not, e.g. if dependencies are missing.
__plugin_load__
Method called upon loading of the plugin by the plugin subsystem, can be used to instantiate plugin implementations, connecting them to hooks etc.
__plugin_unload__
Method called upon unloading of the plugin by the plugin subsystem, can be used to do any final clean ups.
__plugin_enable__
Method called upon enabling of the plugin by the plugin subsystem. Also see on_plugin_enabled`().
__plugin_disable__
Method called upon disabling of the plugin by the plugin subsystem. Also see on_plugin_disabled`().

Mixins

Plugin mixins are the heart of OctoPrint’s plugin system. They are special base classes which are to be subclassed and extended to add functionality to OctoPrint. Plugins declare their instances that implement one or multiple mixins using the __plugin_implementation__ control property. OctoPrint’s plugin core collects those from the plugins and offers methods to access them based on the mixin type, which get used at multiple locations within OctoPrint.

Using mixins always follows the pattern of retrieving the matching implementations from the plugin subsystem, then calling the specific mixin’s methods as defined and necessary.

The following snippet taken from OctoPrint’s code for example shows how all AssetPlugin implementations are collected and then all assets they return via their get_assets methods are retrieved and merged into one big asset map (differing between javascripts and stylesheets of various types) for use during rendition of the UI.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
asset_plugins = pluginManager.get_implementations(octoprint.plugin.AssetPlugin)
for name, implementation in asset_plugins.items():
    all_assets = implementation.get_assets()

    if "js" in all_assets:
        for asset in all_assets["js"]:
            assets["js"].append(url_for('plugin_assets', name=name, filename=asset))

    if preferred_stylesheet in all_assets:
        for asset in all_assets[preferred_stylesheet]:
            assets["stylesheets"].append((preferred_stylesheet, url_for('plugin_assets', name=name, filename=asset)))
    else:
        for stylesheet in supported_stylesheets:
            if not stylesheet in all_assets:
                continue

            for asset in all_assets[stylesheet]:
                assets["stylesheets"].append((stylesheet, url_for('plugin_assets', name=name, filename=asset)))
            break

See also

Available Mixins
An overview of all mixin types available for extending OctoPrint.
The Getting Started Guide
Tutorial on how to write a simple OctoPrint module utilizing mixins for various types of extension.

Hooks

Hooks are the smaller siblings of mixins, allowing to extend functionality or data processing where a custom mixin type would be too much overhead. Where mixins are based on classes, hooks are based on methods. Like with the mixin implementations, plugins inform OctoPrint about hook handlers using a control property, __plugin_hooks__.

Each hook defines a contract detailing the call parameters for the hook handler method and the expected return type. OctoPrint will call the hook with the define parameters and process the result depending on the hook.

An example for a hook within OctoPrint is octoprint.comm.protocol.scripts, which allows adding additional lines to OctoPrint’s GCODE scripts, either as prefix (before the existing lines) or as postfix (after the existing lines).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
self._gcode_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.scripts")

# ...

for hook in self._gcodescript_hooks:
    try:
        retval = self._gcodescript_hooks[hook](self, "gcode", scriptName)
    except:
        self._logger.exception("Error while processing gcodescript hook %s" % hook)
    else:
        if retval is None:
            continue
        if not isinstance(retval, (list, tuple)) or not len(retval) == 2:
            continue

        def to_list(data):
            if isinstance(data, str):
                data = map(str.strip, data.split("\n"))
            elif isinstance(data, unicode):
                data = map(unicode.strip, data.split("\n"))

            if isinstance(data, (list, tuple)):
                return list(data)
            else:
                return None

        prefix, suffix = map(to_list, retval)
        if prefix:
            scriptLines = list(prefix) + scriptLines
        if suffix:
            scriptLines += list(suffix)

As you can see, the hook’s method signature is defined to take the current self (as in, the current comm layer instance), the general type of script for which to look for additions (“gcode”) and the script name for which to look (e.g. beforePrintStarted for the GCODE script executed before the beginning of a print job). The hook is expected to return a 2-tuple of prefix and postfix if has something for either of those, otherwise None. OctoPrint will then take care to add prefix and suffix as necessary after a small round of preprocessing.

Note

At the moment there exists no way to determine the execution order of various hook handlers within OctoPrint, or to prevent the execution of further handlers down the chain.

This is planned for the very near future though.

Plugins can easily add their own hooks too. For example, the Software Update Plugin declares a custom hook “octoprint.plugin.softwareupdate.check_config” which other plugins can add handlers for in order to register themselves with the Software Update Plugin by returning their own update check configuration.

If you want your hook handler to be an instance method of a mixin implementation of your plugin (for example since you need access to instance variables handed to your implementation via mixin invocations), you can get this work by using a small trick. Instead of defining it directly via __plugin_hooks__ utilize the __plugin_load__ property instead, manually instantiate your implementation instance and then add its hook handler method to the __plugin_hooks__ property and itself to the __plugin_implementation__ property. See the following example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# coding=utf-8

import octoprint.plugin

class CustomActionCommandPlugin(octoprint.plugin.OctoPrintPlugin):

    def custom_action_handler(self, comm, line, action, *args, **kwargs):
        if not action == "custom":
            return

        self._logger.info("Received \"custom\" action from printer")

__plugin_name__ = "Custom action command"

def __plugin_load__():
    plugin = CustomActionCommandPlugin()

    global __plugin_implementation__
    __plugin_implementation__ = plugin

    global __plugin_hooks__
    __plugin_hooks__ = {"octoprint.comm.protocol.action": plugin.custom_action_handler}

See also

Available Hooks
An overview of all hooks defined in OctoPrint itself.

Helpers

Helpers are methods that plugin can exposed to other plugins in order to make common functionality available on the system. They are registered with the OctoPrint plugin system through the use of the control property __plugin_helpers__.

An example for providing a couple of helper functions to the system can be found in the Discovery Plugin, which provides it’s SSDP browsing and Zeroconf browsing and publishing functions as helper methods.

Listing 7 Excerpt from the Discovery Plugin showing the declaration of its exported helpers.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def __plugin_load__():
    if not pybonjour:
        # no pybonjour available, we can't use that
        logging.getLogger("octoprint.plugins." + __name__).info("pybonjour is not installed, Zeroconf Discovery won't be available")

    plugin = DiscoveryPlugin()

    global __plugin_implementation__
    __plugin_implementation__ = plugin

    global __plugin_helpers__
    __plugin_helpers__ = dict(
        ssdp_browse=plugin.ssdp_browse
    )
    if pybonjour:
        __plugin_helpers__.update(dict(
            zeroconf_browse=plugin.zeroconf_browse,
            zeroconf_register=plugin.zeroconf_register,
            zeroconf_unregister=plugin.zeroconf_unregister
        ))

An example of how to use helpers can be found in the Growl Plugin. Using get_helpers() plugins can retrieve exported helper methods and call them as (hopefully) documented.

Listing 8 Excerpt from the Growl Plugin showing utilization of the helpers published by the Discovery Plugin.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def on_after_startup(self):
    host = self._settings.get(["hostname"])
    port = self._settings.getInt(["port"])
    password = self._settings.get(["password"])

    helpers = self._plugin_manager.get_helpers("discovery", "zeroconf_browse")
    if helpers and "zeroconf_browse" in helpers:
        self.zeroconf_browse = helpers["zeroconf_browse"]

    self.growl, _ = self._register_growl(host, port, password=password)

# ...

def on_api_get(self, request):
    if not self.zeroconf_browse:
        return flask.jsonify(dict(
            browsing_enabled=False
        ))

    browse_results = self.zeroconf_browse("_gntp._tcp", block=True)
    growl_instances = [dict(name=v["name"], host=v["host"], port=v["port"]) for v in browse_results]

    return flask.jsonify(dict(
        browsing_enabled=True,
        growl_instances=growl_instances
    ))

Injected Properties

OctoPrint’s plugin subsystem will inject a bunch of properties into each mixin implementation. An overview of these properties follows.

self._identifier
The plugin’s identifier.
self._plugin_name
The plugin’s name, as taken from either the __plugin_name__ control property or the package info.
self._plugin_version
The plugin’s version, as taken from either the __plugin_version__ control property or the package info.
self._plugin_info
The octoprint.plugin.core.PluginInfo object associated with the plugin.
self._basefolder
The plugin’s base folder where it’s installed. Can be used to refer to files relative to the plugin’s installation location, e.g. included scripts, templates or assets.
self._datafolder
The plugin’s additional data folder path. Can be used to store additional files needed for the plugin’s operation (cache, data files etc). Plugins should not access this property directly but instead utilize get_plugin_data_folder() which will make sure the path actually does exist and if not create it before returning it.
self._logger
A python logger instance logging to the log target octoprint.plugin.<plugin identifier>.
self._settings
The plugin’s personalized settings manager, injected only into plugins that include the SettingsPlugin mixin. An instance of octoprint.plugin.PluginSettings.
self._plugin_manager
OctoPrint’s plugin manager object, an instance of octoprint.plugin.core.PluginManager.
self._printer_profile_manager
OctoPrint’s printer profile manager, an instance of octoprint.printer.profile.PrinterProfileManager.
self._event_bus
OctoPrint’s event bus, an instance of octoprint.events.EventManager.
self._analysis_queue
OctoPrint’s analysis queue for analyzing GCODEs or other files, an instance of octoprint.filemanager.analysis.AnalysisQueue.
self._slicing_manager
OctoPrint’s slicing manager, an instance of octoprint.slicing.SlicingManager.
self._file_manager
OctoPrint’s file manager, an instance of octoprint.filemanager.FileManager.
self._printer
OctoPrint’s printer management object, an instance of octoprint.printer.PrinterInterface.
self._app_session_manager
OctoPrint’s application session manager, an instance of octoprint.server.util.flask.AppSessionManager.

See also

Plugin and OctoPrintPlugin
Class documentation also containing the properties shared among all mixing implementations.
Available Mixins
Some mixin types trigger the injection of additional properties.