Over the course of this little tutorial we’ll build a full fledged, installable OctoPrint plugin that displays “Hello World!”
at some locations throughout OctoPrint and also offers some other basic functionality to give you an idea of what
you can achieve with OctoPrint’s plugin system.

First of all let use make sure that you have OctoPrint checked out and set up for development on your local
development environment:

This tutorial assumes you are running OctoPrint 1.3.0 and up. Please make sure your version of
OctoPrint is up to date before proceeding. If you did a fresh checkout, that should already
be the case but if not you might have to update first. You can check your version of OctoPrint
by running octoprint--version or by taking a look into the lower left corner in OctoPrint’s
web interface.

We’ll start at the most basic form a plugin can take - just a few simple lines of Python code:

OctoPrint found that plugin in the folder and took a look into it. The name and the version it displays in that log
entry it got from the __plugin_name__ and __plugin_version__ lines. It also read the description from
__plugin_description__ and stored it in an internal data structure, but we’ll just ignore this for now.

Neat, isn’t it? We added a custom class that subclasses one of OctoPrint’s plugin mixins
with StartupPlugin and another control property, __plugin_implementation__, that instantiates
our plugin class and tells OctoPrint about it. Taking a look at the documentation of StartupPlugin we see that
this mixin offers two methods that get called by OctoPrint during startup of the server, on_startup() and
on_after_startup(). We decided to add our logging output by overriding on_after_startup(), but we could also have
used on_startup() instead, in which case our logging statement would be executed before the server was done starting
up and ready to serve requests.

You’ll also note that we are using self._logger for logging. Where did that one come from? OctoPrint’s plugin system
injects a some useful objects into our plugin implementation classes,
one of those being a fully instantiated python logger ready to be
used by your plugin. As you can see in the log output above, that logger uses the namespace octoprint.plugins.helloworld
for our little plugin here, or more generally octoprint.plugins.<pluginidentifier>.

If you now want to distribute this plugin to other OctoPrint users (since it is so awesome to be greeted upon server
startup), let’s take a look at how you’d go about that now before our plugin gets more complicated.

You basically have two options to distribute your plugin. One would be about the exact same way we are using it now,
as a simple python file following the naming convention <pluginidentifier>.py that your users add to their
~/.octoprint/plugins folder. You already know how that works. But let’s say you have more than just a simple plugin
that can be done in one file. Distributing multiple files and getting your users to install them in the right way
so that OctoPrint will be able to actually find and load them is certainly not impossible, but we want to do it in the
best way possible, meaning we want to make our plugin a fully installable Python module that your users will be able to
install directly via OctoPrint’s built-in Plugin Manager
or alternatively manually utilizing Python’s standard package manager pip directly.

So let’s begin. We’ll use the cookiecutter template for OctoPrint plugins
here. This should already be installed if you used the plugins extra while installing OctoPrint. However,
you may install it with:

(venv) $ pip install "cookiecutter>=1.4,<1.7"

Then we can use the octoprintdevplugin:new command [1] to generate a new OctoPrint plugin skeleton for us:

While we’ll need some of those folders later on, we’ll now delete everything that we don’t need right now first, that
will make it easier to understand what folder does what later on. Delete the following folders and anything in them:

Now all that’s left to do is to move our helloworld.py into the octoprint_helloworld folder and renaming it to
__init__.py. Make sure to delete the copy under ~/.octoprint/plugins in the process, including the .pyc file!

The plugin is now ready to be installed via pythonsetup.pyinstall. However, since we are still
working on our plugin, it makes more sense to use pythonsetup.pydevelop for now – this way the plugin becomes
discoverable by OctoPrint, however we don’t have to reinstall it after any changes we will still do. We can have the
octoprintdevplugin:install command do everything for us here, it will ensure to use the python binary belonging
to your OctoPrint installation:

The nice thing about our plugin now being a proper Python package is that OctoPrint can and will access the metadata defined
within setup.py! So, we don’t really need to define all this data twice. Remove __plugin_name__, __plugin_version__
and __plugin_description__ from __init__.py:

Our “Hello World” Plugin still gets detected fine, but it’s now listed under the same name it’s installed under,
“OctoPrint-HelloWorld”. That’s a bit redundant and squashed, so we’ll override that bit via __plugin_name__ again:

Outputting a log line upon server startup is all nice and well, but we want to greet not only the administrator of
our OctoPrint instance but actually everyone that opens OctoPrint in their browser. Therefore, we need to modify
OctoPrint’s web interface itself.

We can do this using the TemplatePlugin mixin. For now, let’s start with a little “Hello World!” in OctoPrint’s
navigation bar right at the top that links to the Wikipedia node about “Hello World” programs. For this we’ll first
add the TemplatePlugin to our HelloWorldPlugin class:

Remember that Wikipedia link we added to our little link in the navigation bar? It links to the english Wikipedia. But
what if we want to allow our users to adjust that according to their wishes, e.g. to link to the german language node
about “Hello World” programs instead?

To allow your users to customized the behaviour of your plugin you’ll need to implement the SettingsPlugin
mixin and override it’s get_settings_defaults() method. We’ll save the URL to
inject into the link under the key url in our plugin’s settings and set it to the old value by default. We’ll therefore
return just a single key in our default settings dictionary. To be able to quickly see if we’ve done that right we’ll
extend our little startup message to also log the current setting to the console. We can access that via self._settings,
which is a little settings manager OctoPrint conveniently injects into our Plugin when we include the SettingsPlugin
mixin.

Let’s take a look at how all that would look in our plugin’s __init__.py:

So far so good. But how do we now get that value into our template? We have two options, the
static one using so called template variables and a dynamic one which retrieves that data from the backend and binds it
into the template using Knockout data bindings. First let’s
take a look at the static version using template variables. We already have the TemplatePlugin
mixin included in our plugin, we just need to override its method get_template_vars()
to add our URL as a template variable.

OctoPrint injects the template variables that your plugin defines prefixed with plugin_<pluginidentifier>_ into
the template renderer, so your url got turned into plugin_helloworld_url which you can now use as a simple
Jinja2 Variable in your plugin’s template.

Restart OctoPrint and shift-reload the page in your browser (to make sure you really get a fresh copy). The link should
still work and point to the URL we defined as default.

Let’s change the URL! Open up your OctoPrint instance’s config.yaml file and add the following to it (if a plugins
section doesn’t yet exist in the file, create it):

Restart OctoPrint. Not only should the URL displayed in the log file have changed, but also the link should now (after
a proper shift-reload) point to the german Wikipedia node about “Hello World” programs:

Nice! But not very user friendly. We don’t have any way yet to edit the URL from within OctoPrint and have to restart
the server and reload the page every time we want a value change to take effect. Let’s try adding a little settings dialog
for our plugin in which we can edit the URL and take any changes take immediate effect.

First of all, we’ll create the settings dialog. You might already have guessed that we’ll need another template for that.
So in your plugin’s templates folder create a new file helloworld_settings.jinja2 and put the following content
into it:

Note how we access our plugin’s property via settings.plugins.helloworld.url. The settings observable is made
available in the SettingsViewModel and holds the exact data structure returned from the server for all of
OctoPrint’s settings. Accessing plugin settings hence works by following the path under which they are stored in
OctoPrint’s internal settings data model (made public via the config.yaml), plugins.<pluginidentifier>.<configurationkey>.
We’ll bind our own settings dialog to the existing SettingsViewModel, so this will be the way we’ll access our
property.

Now adjust your templates/helloworld_navbar.jinja2 file to use a data-bind attribute to set the value from the
settings view model into the href attribute of the link tag:

You might have noticed the quite ugly way to access our plugin’s url property here: settings.settings.plugins.helloworld.url.
The reason for this is that we’ll make our plugin use the existing NavigationViewModel which holds the
SettingsViewModel as a property called settings. So to get to the settings property of the SettingsViewModel
from the NavigationViewModel, we’ll need to first “switch” to the SettingsViewModel using its property name. Hence
the ugly access string.

If you were now to restart OctoPrint and reload the web interface, you’ll get the settings dialog placed just fine
in OctoPrint’s settings, and the link would also still show up in the navigation bar, but both the input field of the
settings dialog as well as the link’s href attribute would not show our link. The reason for this is that OctoPrint
by default assumes that you’ll want to bind your own view models to your templates and hence “unbinds” the included
templates from the templates that are in place at the injected location already. In order to tell OctoPrint to please
don’t do this here (since we do want to use both NavigationViewModel and SettingsViewModel), we’ll need to
override the default template configuration using the get_template_configs method. We’ll tell OctoPrint to use no custom bindings
for both our navbar and our settings plugin. We’ll also remove the override of octoprint.plugin.TemplatePlugin.get_template_vars()
again since we don’t use that anymore:

Restart OctoPrint and shift-reload your browser. Your link in the navigation bar should still point to the URL we
defined in config.yaml earlier. Open the “Settings” and click on the new “Hello World” entry that shows up under
“Plugins”.

Nice! Edit the value, then click “Save”. Your link in the navigation bar should now have been updated as well.

Note

The way we’ve done our data binding and how OctoPrint currently works, your link’s target will update immediately
when you update the value in the settings dialog. Even if you click Cancel instead of Save, the change will still
be reflected in the UI but will be overwritten again by the stored data upon a reload. This is caused by OctoPrint
not storing a copy of the settings data while it is being edited, which might be changed in the future to
prevent this unexpected behaviour from occurring.

In the previous section we set that custom_bindings parameter to False since we wanted OctoPrint to bind the
SettingsViewModel to our settings dialog and the NavigationViewModel to our entry in the nav bar.

But what if we want to define our own, with more functionality that is already available? Let’s take a look. We’ll now
add an additional UI component to our OctoPrint interface, a custom tab. It will act as a little internal web browser,
showing the website behind the URL from the settings in an IFrame but also allowing the user to load a different URL
without having to change the settings.

First let us create the Jinja2 template for our tab. In your plugin’s templates folder create a new file
helloworld_tab.jinja2 like so:

Then we create a new folder in your plugin’s root called static and within that folder another folder by the name of
js. Finally, within that folder create a file helloworld.js. Our plugin’s folder structure should now
look like this:

Note how we did not add another entry to the return value of get_template_configs().
Remember how we only added those since we wanted OctoPrint to use existing bindings on our navigation bar and settings
menu entries? We don’t want this this time, and we named our tab template such that OctoPrint will pick it up automatically
so we don’t have to do anything here.

$(function(){functionHelloWorldViewModel(parameters){varself=this;self.settings=parameters[0];// this will hold the URL currently displayed by the iframeself.currentUrl=ko.observable();// this will hold the URL entered in the text fieldself.newUrl=ko.observable();// this will be called when the user clicks the "Go" button and set the iframe's URL to// the entered URLself.goToUrl=function(){self.currentUrl(self.newUrl());};// This will get called before the HelloWorldViewModel gets bound to the DOM, but after its// dependencies have already been initialized. It is especially guaranteed that this method// gets called _after_ the settings have been retrieved from the OctoPrint backend and thus// the SettingsViewModel been properly populated.self.onBeforeBinding=function(){self.newUrl(self.settings.settings.plugins.helloworld.url());self.goToUrl();}}// This is how our plugin registers itself with the application, by adding some configuration// information to the global variable OCTOPRINT_VIEWMODELSOCTOPRINT_VIEWMODELS.push([// This is the constructor to call for instantiating the pluginHelloWorldViewModel,// This is a list of dependencies to inject into the plugin, the order which you request// here is the order in which the dependencies will be injected into your view model upon// instantiation via the parameters argument["settingsViewModel"],// Finally, this is the list of selectors for all elements we want this view model to be bound to.["#tab_plugin_helloworld"]]);});

Take a close look at lines 31 to 42. This is how our plugin tells OctoPrint about our new view model, how to
instantiate it, which dependencies to inject and to which elements in the final page to bind. Since we want to access
the URL from the settings of our plugin, we’ll have OctoPrint inject the SettingsViewModel into our own view model,
which is registered within OctoPrint under the name settingsViewModel. We’ll only bind to our custom tab
for now, which OctoPrint will make available in a container with the id tab_plugin_helloworld (unless otherwise
configured).

Our view model defines two observables: newUrl, which we bound to the input field in our template, and currentUrl
which we bound to the src attribute of the “browser iframe” in our template. There’s also a function goToUrl
which we bound to the click event of the “Go” button in our template.

Restart OctoPrint and shift-reload the browser. You should see a shiny new “Hello World” tab right at the end of the
tab bar. Click on it!

The desktop version of that article looks a bit squished in there, so let’s enter https://de.m.wikipedia.org/wiki/Hallo-Welt-Programm
into the input field and click the “Go” button. The page inside the iframe should be replaced with the mobile version
of the same article.

We hardcoded some style on our iframe in line 6, to make it look a bit better. It would be nicer if that was actually
located inside a stylesheet instead of directly inside our HTML template. Of course that’s no problem, we’ll just
add a CSS file to our plugin’s provided static assets.

First we’ll create a new folder within our plugin’s static folder called css and within that folders a file
helloworld.css. Our plugin’s file structure should now look like this:

OctoPrint by default bundles all CSS, JavaScript and LESS files to reduce the amount of requests necessary to fully
load the page. But in order to fully be able to see how what we just did changes how our plugin interacts with OctoPrint
we want to disable that behaviour for now. Open up OctoPrint’s config.yaml and disable bundling of the webassets:

# [...]devel:webassets:bundle:false# [...]

Restart OctoPrint, shift-reload your browser and take a look. Everything should still look like before, but now
OctoPrint included our stylesheet and the style information for the iframe is taken from that instead of
hardcoded in our template. Way better!

Now, if you had something more complicated than just the couple of line of CSS we used here, you might want to use
something like LESS for generating your CSS from. If you use LESS, which is what OctoPrint
uses for that purpose, you can even put OctoPrint into a mode where it directly uses your LESS files instead of the
generated CSS files (and compiles them on the fly in your browser using lessjs),
which makes development so much easier. Let’s try that, so you know how it works for future bigger projects.

Add another folder to our static folder called less and within that create a file helloworld.less. Put
into that the same content as into our CSS file. Compile that LESS file to CSS [2], overwriting our old helloworld.css
in the process. The folder structure of our plugin should now look like this:

and enable LESS mode by adjusting one of OctoPrint’s devel flags via the config.yaml file:

# [...]devel:stylesheet:lesswebassets:bundle:false# [...]

Restart OctoPrint and shift-reload. Your “Hello World” tab should still look like before. Take a look at the site’s
source code. In the head section of the page you’ll see that instead of your helloworld.css OctoPrint now
embedded the helloworld.less file instead:

Now the CSS file is linked and no trace of the LESS links is left in the source. This should help to speed up your development
tremendously when you have to work with complex stylesheets, just don’t forgot to check the generated CSS file in with
the rest of your plugin or people will miss it when trying to run your plugin!

Remember when I mentioned that OctoPrint by default bundles all our assets for us? We adjusted our config.yaml to
stop it from doing that at the start of this section, we should switch this back now:

# [...]devel:stylesheet:css# [...]

Just out of curiosity, restart, shift-reload and take a final look at the head:

If your plugin only provides CSS files, OctoPrint will detect this when switched to LESS mode and include your
CSS files instead of any non-existing LESS files. So you don’t really have to use LESS if you don’t want, but
as soon as you need it just switch over.

The same thing works the other way around too by the way. If your plugin only provides LESS files, OctoPrint will link to
those, lessjs will take care of the compilation. Please keep in mind though that also providing CSS files is the
cleaner way.

For some insight on how to create plugins that react to various events within OctoPrint,
the Growl Plugin might be a good example to learn from. For how to
add support for a slicer, OctoPrint’s own bundled CuraEngine plugin
might give some hints. For extending OctoPrint’s interface, the NavbarTemp plugin
might show what’s possible with a few lines of code already. Finally, just take a look at the
official Plugin Repository if you are looking for examples.

Jinja’s Template Designer Documentation describes the syntax and semantics of the template language used
by OctoPrint’s frontend. Linked here are the docs for Jinja 2.8.1, which OctoPrint still
relies on for backwards compatibility reasons [3].

Instead of the octoprintdevplugin:new you could also have manually called cookiecutter with the
template’s repository URL shortcut: cookiecuttergh:OctoPrint/cookiecutter-octoprint-plugin. The
devel:newplugin command already does this for you, makes sure cookiecutter always uses a fresh
checkout without prompting you for it and also allows to pre-specify a bunch of settings (like the
plugin’s identifier) directly from the command line. Take a look at octoprintdevplugin:new--help
for the usage details.

Refer to the LESS documentation on how to do that. If you are developing
your plugin under Windows you might also want to give WinLESS a look which will run
in the background and keep your CSS files up to date with your various project’s LESS files automatically.

Please always consult the Jinja documentation at jinja.octoprint.org instead of
the current stable documentation available at Jinja’s project page. The reason for that is that for backwards
compatibility reasons OctoPrint currently sadly has to rely on an older version of Jinja. The documentation
available at jinja.octoprint.org matches that older version.