Plugins extend Shelly CLI functionality. They are standalone executables that integrate seamlessly with the CLI.
Terminology: “Plugin” is the primary term. “Extension” is an alias for compatibility.
Quick Start 1
2
3
4
5
6
7
8
9
10
11
# Create a new plugin scaffold
shelly plugin create myext --lang bash
# Test locally
./shelly-myext/shelly-myext --help
# Install
shelly plugin install ./shelly-myext/shelly-myext
# Run
shelly myext --help
How Plugins Work Plugins are executable programs named shelly-<name> that:
Discovered automatically from ~/.config/shelly/plugins/ and $PATHInvoked transparently - shelly myext runs shelly-myextReceive context via environment variables (devices, config, theme)Output handled - stdout/stderr passed through to user1
2
User runs: shelly myext --flag arg
CLI executes: ~/.config/shelly/plugins/shelly-myext --flag arg
Plugin Commands Command Aliases Description shelly plugin listls, lList installed plugins shelly plugin install <source>addInstall from file, URL, or GitHub shelly plugin remove <name>rm, uninstallRemove a plugin shelly plugin upgrade [name]updateUpgrade plugin(s) shelly plugin create <name>new, initCreate plugin scaffold shelly plugin exec <name>runExecute plugin explicitly
Environment Variables Plugins receive these environment variables:
Variable Description Example SHELLY_CONFIG_PATHPath to config file ~/.config/shelly/config.yamlSHELLY_DEVICES_JSONJSON of registered devices {"kitchen": {"ip": "192.168.1.100"}}SHELLY_OUTPUT_FORMATCurrent output format table, json, yamlSHELLY_NO_COLORColor disabled 1 if disabledSHELLY_VERBOSEVerbose mode 1 if enabledSHELLY_QUIETQuiet mode 1 if enabledSHELLY_API_MODEAPI mode local, cloud, autoSHELLY_THEMECurrent theme name dracula
Installation Sources Local File 1
2
shelly plugin install ./shelly-myext
shelly plugin install /path/to/shelly-myext
GitHub Repository 1
2
shelly plugin install gh:user/shelly-myext
shelly plugin install github:user/shelly-myext
Downloads the latest release binary for your platform (linux/darwin, amd64/arm64).
HTTP URL 1
shelly plugin install https://example.com/shelly-myext
Creating Plugins Using the Scaffold Command 1
2
3
4
5
6
7
8
9
10
11
# Bash plugin (default)
shelly plugin create myext
# Go plugin
shelly plugin create myext --lang go
# Python plugin
shelly plugin create myext --lang python
# Custom output directory
shelly plugin create myext --output ~/projects
Plugin Requirements Naming : Must be named shelly-<name> (e.g., shelly-notify)Executable : Must have executable permissionsVersion flag : Should support --version for version detectionHelp flag : Should support --help for usage infoBash Plugin Template 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
32
33
34
35
36
37
38
39
#!/usr/bin/env bash
# shelly-myext - Shelly CLI Plugin
set -euo pipefail
VERSION = "0.1.0"
show_help() {
cat << EOF
shelly-myext - Description of what this plugin does
Usage: shelly myext [command] [options]
Commands:
help Show this help message
version Show version information
Options:
-h, --help Show help
-v, --version Show version
Environment:
SHELLY_DEVICES_JSON - JSON of registered devices
SHELLY_CONFIG_PATH - Path to config file
EOF
}
main() {
case " ${ 1 :- help } " in
-h|--help|help ) show_help ;;
-v|--version|version) echo "shelly-myext version $VERSION " ;;
*)
echo "Unknown command: $1 " >&2
exit 1
;;
esac
}
main " $@ "
Go Plugin Template 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main
import (
"encoding/json"
"fmt"
"os"
)
const version = "0.1.0"
func main () {
if len (os.Args) < 2 {
showHelp ()
return
}
switch os.Args[1 ] {
case "-h" , "--help" , "help" :
showHelp ()
case "-v" , "--version" , "version" :
fmt.Printf ("shelly-myext version %s\n" , version)
case "devices" :
listDevices ()
default :
fmt.Fprintf (os.Stderr, "Unknown command: %s\n" , os.Args[1 ])
os.Exit (1 )
}
}
func showHelp () {
fmt.Println (`shelly-myext - Description
Usage: shelly myext [command]
Commands:
help Show help
version Show version
devices List devices from environment` )
}
func listDevices () {
devicesJSON := os.Getenv ("SHELLY_DEVICES_JSON" )
if devicesJSON == "" {
fmt.Println ("No devices (SHELLY_DEVICES_JSON not set)" )
return
}
var devices map [string ]any
if err := json.Unmarshal ([]byte (devicesJSON), & devices); err != nil {
fmt.Fprintf (os.Stderr, "Failed to parse devices: %v\n" , err)
os.Exit (1 )
}
fmt.Printf ("Found %d device(s):\n" , len (devices))
for name := range devices {
fmt.Printf (" - %s\n" , name)
}
}
Python Plugin Template 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/usr/bin/env python3
"""shelly-myext - Shelly CLI Plugin"""
import json
import os
import sys
VERSION = "0.1.0"
def show_help ():
print ("""shelly-myext - Description
Usage: shelly myext [command]
Commands:
help Show help
version Show version
devices List devices""" )
def list_devices ():
devices_json = os. environ. get("SHELLY_DEVICES_JSON" , " {} " )
try :
devices = json. loads(devices_json)
except json. JSONDecodeError:
print ("Failed to parse devices" , file= sys. stderr)
sys. exit(1 )
print (f "Found { len (devices)} device(s):" )
for name in devices:
print (f " - { name} " )
def main ():
args = sys. argv[1 :]
cmd = args[0 ] if args else "help"
if cmd in ("-h" , "--help" , "help" ):
show_help()
elif cmd in ("-v" , "--version" , "version" ):
print (f "shelly-myext version { VERSION} " )
elif cmd == "devices" :
list_devices()
else :
print (f "Unknown command: { cmd} " , file= sys. stderr)
sys. exit(1 )
if __name__ == "__main__" :
main()
Included Example: shelly-notify The repository includes a complete example plugin at examples/plugins/shelly-notify/. This plugin sends desktop notifications for Shelly device events.
Features Send custom notifications Check device status and notify Monitor device online/offline state Report power consumption Cross-platform (Linux notify-send, macOS osascript) Usage 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Install the example plugin
shelly plugin install examples/plugins/shelly-notify/shelly-notify
# Send a custom notification
shelly notify send "Kitchen" "Light turned on"
# Check device status
shelly notify device kitchen
# Check if device is online
shelly notify online kitchen
# Get power consumption notification
shelly notify power kitchen
# Test notification system
shelly notify test
See examples/plugins/shelly-notify/README.md for full documentation.
Plugin Discovery Plugins are discovered in this order (first match wins):
~/.config/shelly/plugins/ - User plugin directoryCustom paths from config (plugins.path in config.yaml) $PATH directories - System-wide plugins1
2
3
4
5
# config.yaml
plugins :
path :
- /opt/shelly-plugins
- ~/custom-plugins
Best Practices 1. Handle Missing Environment 1
2
# Don't assume SHELLY_DEVICES_JSON exists
devices = " ${ SHELLY_DEVICES_JSON :- {} }"
1
2
3
4
5
if [[ " $SHELLY_OUTPUT_FORMAT " == "json" ]] ; then
echo '{"status": "ok"}'
else
echo "Status: OK"
fi
3. Respect Color Settings 1
2
3
4
5
6
7
8
if [[ -z " $SHELLY_NO_COLOR " ]] ; then
GREEN = '\033[0;32m'
NC = '\033[0m'
else
GREEN = ''
NC = ''
fi
echo -e " ${ GREEN } Success ${ NC } "
4. Exit Codes Code Meaning 0 Success 1 General error 2 Invalid arguments
5. Version Output Keep version output simple for parsing:
1
shelly-myext version 0.1.0
Publishing Plugins GitHub Releases Create a GitHub repository named shelly-<name> Build binaries for each platform Create a release with assets:shelly-myext-linux-amd64.tar.gzshelly-myext-linux-arm64.tar.gzshelly-myext-darwin-amd64.tar.gzshelly-myext-darwin-arm64.tar.gz Users can then install with:
1
shelly plugin install gh:youruser/shelly-myext
GoReleaser Example 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# .goreleaser.yaml
project_name : shelly-myext
builds :
- env :
- CGO_ENABLED=0
goos :
- linux
- darwin
goarch :
- amd64
- arm64
archives :
- format : tar.gz
name_template : "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}"
Plugin Manifest System Plugins are stored with metadata that enables automatic upgrades and source tracking.
Directory Structure 1
2
3
4
5
6
7
8
~/.config/shelly/plugins/
├── shelly-myext/
│ ├── shelly-myext # Binary executable
│ └── manifest.json # Metadata file
├── shelly-another/
│ ├── shelly-another
│ └── manifest.json
└── .migrated # Migration marker
Manifest Contents Each plugin has a manifest.json tracking:
Field Description schema_versionManifest format version namePlugin name (without shelly- prefix) versionSemantic version installed_atInstallation timestamp updated_atLast upgrade timestamp source.typeInstallation source: github, url, local, unknown source.urlSource URL (GitHub or HTTP) source.refGit tag/commit for GitHub sources binary.checksumSHA256 checksum for integrity binary.platformPlatform (e.g., linux-amd64)
Example Manifest 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"schema_version" : "1" ,
"name" : "notify" ,
"version" : "1.2.0" ,
"installed_at" : "2024-12-15T10:30:00Z" ,
"updated_at" : "2024-12-15T10:30:00Z" ,
"source" : {
"type" : "github" ,
"url" : "https://github.com/user/shelly-notify" ,
"ref" : "v1.2.0" ,
"asset" : "shelly-notify-linux-amd64.tar.gz"
},
"binary" : {
"name" : "shelly-notify" ,
"checksum" : "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ,
"platform" : "linux-amd64" ,
"size" : 5242880
}
}
Upgrade Behavior by Source Type Source Upgrade Behavior githubChecks GitHub releases, downloads newer version if available urlRe-downloads from URL, replaces if checksum differs localCannot auto-upgrade - reinstall manually unknownMigrated plugin - reinstall to enable auto-upgrade
Plugins installed before the manifest system are automatically migrated on first CLI run. Migrated plugins have source.type: "unknown" - reinstall them to enable auto-upgrade:
1
2
3
4
5
6
# Check which plugins need reinstallation
shelly plugin list
# Reinstall from GitHub to enable upgrades
shelly plugin remove myext
shelly plugin install gh:user/shelly-myext
Enhanced Plugin Capabilities Plugins can declare capabilities and hooks to integrate deeply with shelly-cli. This enables features like device detection during discovery, device control via unified commands, and firmware updates.
Capabilities The capabilities field in the manifest declares what a plugin can do:
1
2
3
4
5
6
7
8
9
10
11
12
{
"capabilities" : {
"device_detection" : true ,
"platform" : "tasmota" ,
"components" : ["switch" , "light" , "sensor" , "energy" ],
"firmware_updates" : true ,
"hints" : {
"scene" : "Tasmota uses Rules for automation. See: https://tasmota.github.io/docs/Rules/" ,
"script" : "Tasmota uses Berry scripting on ESP32. See: https://tasmota.github.io/docs/Berry/"
}
}
}
Field Type Description device_detectionboolean Plugin can detect devices during shelly discover platformstring Platform name (e.g., “tasmota”, “esphome”) componentsarray Controllable component types: “switch”, “light”, “cover”, “sensor”, “energy” firmware_updatesboolean Plugin supports firmware update operations hintsobject Helpful messages for unsupported commands (key=command, value=hint)
Hooks The hooks field defines executable entry points that shelly-cli calls:
1
2
3
4
5
6
7
8
9
{
"hooks" : {
"detect" : "./shelly-myext detect" ,
"status" : "./shelly-myext status" ,
"control" : "./shelly-myext control" ,
"check_updates" : "./shelly-myext check-updates" ,
"apply_update" : "./shelly-myext apply-update"
}
}
Detect Hook Called during shelly discover to probe if an address belongs to this platform.
Input:
1
./shelly-myext detect --address= 192.168.1.100 [ --auth-user= <user> --auth-pass= <pass>]
Output: JSON DeviceDetectionResult:
1
2
3
4
5
6
7
8
9
10
11
12
{
"detected" : true ,
"platform" : "tasmota" ,
"device_id" : "sonoff-basic-1234" ,
"device_name" : "Garage Light" ,
"model" : "Sonoff Basic R3" ,
"firmware" : "14.3.0" ,
"mac" : "AA:BB:CC:DD:EE:FF" ,
"components" : [
{"type" : "switch" , "id" : 0 , "name" : "Relay 1" }
]
}
Exit codes: 0 = detected, 1 = not this platform
Status Hook Called to get device status.
Input:
1
./shelly-myext status --address= 192.168.1.100 [ --auth-user= <user> --auth-pass= <pass>]
Output: JSON DeviceStatusResult:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"online" : true ,
"components" : {
"switch:0" : {"output" : true }
},
"sensors" : {
"wifi_rssi" : -52
},
"energy" : {
"power" : 45.3 ,
"voltage" : 121.5 ,
"current" : 0.372 ,
"total" : 123.456
}
}
Control Hook Called to execute device control commands.
Input:
1
./shelly-myext control --address= 192.168.1.100 --action= <on|off|toggle> --component= <switch|light|cover> --id= <n> [ --auth-user= <user> --auth-pass= <pass>]
Output: JSON ControlResult:
1
2
3
4
{
"success" : true ,
"state" : "on"
}
Check Updates Hook Called to check for firmware updates.
Input:
1
./shelly-myext check-updates --address= 192.168.1.100 [ --auth-user= <user> --auth-pass= <pass>]
Output: JSON FirmwareUpdateInfo:
1
2
3
4
5
6
7
8
9
10
{
"current_version" : "14.3.0" ,
"latest_stable" : "15.2.0" ,
"latest_beta" : "15.3.0b1" ,
"has_update" : true ,
"has_beta_update" : true ,
"ota_url_stable" : "http://ota.tasmota.com/tasmota/release/tasmota.bin.gz" ,
"ota_url_beta" : "http://ota.tasmota.com/tasmota/tasmota.bin.gz" ,
"chip_type" : "ESP8266"
}
Apply Update Hook Called to apply a firmware update.
Input:
1
./shelly-myext apply-update --address= 192.168.1.100 --stage= <stable|beta> [ --url= <custom_ota_url>] [ --auth-user= <user> --auth-pass= <pass>]
Output: JSON UpdateResult:
1
2
3
4
5
{
"success" : true ,
"message" : "Update initiated" ,
"rebooting" : true
}
Device Lifecycle When a plugin declares device_detection: true, it participates in the discovery flow:
Discovery : shelly discover scans the networkDetection : For addresses that aren’t Shelly devices, each detection-capable plugin’s detect hook is calledRegistration : Detected devices are registered with platform: "<plugin-platform>"Command Routing : When running commands on plugin-managed devices, the CLI routes to the appropriate plugin hookEnvironment Variables for Hooks Hooks receive the standard plugin environment variables plus:
Variable Description SHELLY_PLUGIN_DIRDirectory where the plugin is installed SHELLY_CLI_VERSIONCLI version for compatibility checks
Complete Manifest Example 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
32
33
34
35
36
{
"schema_version" : "1" ,
"name" : "tasmota" ,
"version" : "1.0.0" ,
"description" : "Tasmota device support for shelly-cli" ,
"installed_at" : "2024-12-25T10:30:00Z" ,
"source" : {
"type" : "github" ,
"url" : "https://github.com/user/shelly-tasmota" ,
"ref" : "v1.0.0"
},
"binary" : {
"name" : "shelly-tasmota" ,
"checksum" : "sha256:abc123..." ,
"platform" : "linux-amd64"
},
"minimum_shelly_version" : "1.0.0" ,
"capabilities" : {
"device_detection" : true ,
"platform" : "tasmota" ,
"components" : ["switch" , "light" , "sensor" , "energy" ],
"firmware_updates" : true ,
"hints" : {
"scene" : "Tasmota uses Rules for automation" ,
"script" : "Tasmota uses Berry scripting on ESP32" ,
"schedule" : "Tasmota uses Timers for scheduling"
}
},
"hooks" : {
"detect" : "./shelly-tasmota detect" ,
"status" : "./shelly-tasmota status" ,
"control" : "./shelly-tasmota control" ,
"check_updates" : "./shelly-tasmota check-updates" ,
"apply_update" : "./shelly-tasmota apply-update"
}
}
Troubleshooting Plugin not found 1
2
3
4
5
6
7
8
# Check if plugin is installed
shelly plugin list
# Check plugin path
ls -la ~/.config/shelly/plugins/
# Verify executable permission
chmod +x ~/.config/shelly/plugins/shelly-myext
Plugin crashes on load 1
2
3
4
5
6
# Run plugin directly to see errors
~/.config/shelly/plugins/shelly-myext --help
# Check for missing dependencies
ldd ~/.config/shelly/plugins/shelly-myext # Linux
otool -L ~/.config/shelly/plugins/shelly-myext # macOS
Environment variables not set 1
2
# Debug environment
shelly plugin exec myext env | grep SHELLY_