A typical Plugin is structured like this:
├── assets/ # Assets such as images and other resources
├── defaults/ # Plain-text configs and templates (not required)
├── main.py # Backend Python code
├── plugin.json # Metadata file
├── package.json # Metadata file for pnpm
├── README.md # Project Readme
├── LICENSE # License
├── src/ # Frontend TypeScript code
├── index.tsx
The easiest way to get this structure and get started with development is by visiting the Decky Plugin Template Repository and clicking on Use this Template
.
Afterwards you can clone your newly created repository and get started.
The plugin.json file is used by decky and the store to determine how things are being displayed and how the plugin behaves. It's required for every plugin.
The following fields can be specified:
"name"
: The name of the Plugin as it will be displayed in the Decky menu."author"
: The author of the Plugin. That's you!"flags"
: An array of flags that get used to configure various behaviour for your plugin
"debug"
: Enable various debug capabilities such as auto reload for your plugin"root"
: Tells decky to run your plugin as root. ONLY DO THIS WHEN ACTUALLY REQUIRED"publish"
: A object containing various information used by the Decky Store
"tags"
: A list of tags that describe your plugin"description"
: A short description that will be displayed with your Plugin on the store"image"
: A link to an image (must be a PNG!) that will be embedded in the store page.The package.json file is used by decky and the store to determine how things are being displayed and how the plugin behaves. It's required for every plugin.
The following fields can be specified:
"name"
: The name of the Plugin as the CI/CLI understands it, make sure this is all lowercase with dashes rather than spaces between words that would appear seperated visually.
Donkey Farm
becomes donkey-farm
etc."version"
: The version of the plugin as the CI/CLI understands it. Make sure to bump it before submitting update PRs etc."remote_binary"
: This field is used to ensure plugins with large binary dependencies have their large binary downloaded seperately to prevent issues the developers experienced in the past with Loader choking on large plugin zips. This field has 3 sub fields
"name"
: Name of the file (including suffix) that the file should be named when downloaded."url"
: Direct URL to file for download. Please note this must be from a project that is entirely FOSS or source publicly available so that the plugin can be audited. If the binary is not provided by a FOSS project, if the file is provided by a large/trusted hardware manufacturer etc the Loader team will have to approve the file."sha256hash"
: SHA256 Hash to meet download requirements. This will be audited by the team if the "remote_binary" field is used.The frontend is a collection of TypeScript files where index.tsx
is the main entry point.
In there you can find a definePlugin
method that returns an object containing the plugin name, content and icon for your plugin.
In the Content
object, the interface of the Plugin and various init code can be defined.
To talk to the backend, simply use the ServerAPI
object passed in with the Content
variable.
The following code with call a method called my_backend_function
in your backend and passes two parameters parameter_a
and parameter_b
with the values "Hello"
and "World"
to it.
serverAPI!.callPluginMethod("my_backend_function", { "parameter_a": "Hello", "parameter_b": "World" });
In a python backend, all your plugin functions can be defined like this:
class Plugin:
# The backend function that we called above
async def my_backend_function(self, parameter_a, parameter_b):
print(f"{parameter_a} {parameter_b}")
# Function that can contain long-running code that will stay alive for the entire duration of your plugin
async def _main(self):
pass
# Function used to clean up a plugin when it's told to unload by Decky-Loader
async def _unload(self):
pass
Decky exposes a python class called SettingsManager from the settings.py
in Decky Loader to save settings/configurations etc in local json files as opposed to the usage of localStorage directly within React.
Here's a snippet from decky-autosuspend
's main.py
which utilizes SettingsManager to commit settings to local json file:
# THIS CODE IS LICENSED UNDER THE BSD 3-CLAUSE LICENSE
# PLEASE SEE THE RELEVANT REPOSITORY LINKED ABOVE FOR FURTHER DETAILS
# Initialize decky-loader settings manager
from settings import SettingsManager
# Get environment variable
settingsDir = os.environ["DECKY_PLUGIN_SETTINGS_DIR"]
import os
logger.info('Settings path: {}'.format(os.path.join(settingsDir, 'settings.json'))
settings = SettingsManager(name="settings", settings_directory=settingsDir)
settings.read()
class Plugin:
async def settings_read(self):
logger.info('Reading settings')
return settings.read()
async def settings_commit(self):
logger.info('Saving settings')
return settings.commit()
async def settings_getSetting(self, key: str, defaults):
logger.info('Get {}'.format(key))
return settings.getSetting(key, defaults)
async def settings_setSetting(self, key: str, value):
logger.info('Set {}: {}'.format(key, value))
return settings.setSetting(key, value)
If you would like to better understand how SettingsManager works and how to utilize it in your own plugin, you can reference it's source code here.
Please note that there is logic in SettingsManager that runs when an instance of SettingsManager is intialized. This logic accomodates older plugins who effected by important adjustments to SettingsManager. The wrong_dir
variable, and some other sections commented along the same lines are your relevant lines. Unless you are using a custom directory to save these files, this logic shouldn't effect you.
Originally developed as a method for plugins to include default configuration files and other related items, this is now the defacto method for including files outside of those generated by the backend/frontend build processes or usage of remote binaries. The defaults folder is usually utilized for including python libraries that a plugin is dependent upon etc.
This is extremely hacky but it is the "official" method that is needed for including items that are not a part of the scope of a plugin by default (enforced by CI).
Say you need to include python code that you seperated out into other files in a folder called py
etc. This folder would be moved into the defaults folder and symlinked into the root of the directory for local code testing if that is needed etc.
We appreciate feedback on this topic, and a suggestion for a plugin's plugin.json including a whitelist of needed files is likely a future solution that will be adopted.
Frontend dependencies are managed by pnpm. The CI for builds requires pnpm-lock.yaml to be on version 6.0. To ensure you're on the right version update your pnpm with pnpm add -g pnpm
and run pnpm i
. To verify, check that your pnpm-lock.yaml contains lockfileVersion: '6.0'
at the top.