Plugin Development
Expected Workflow for Running Plugins
- Read this skill document for context and instructions.
- Find the plugin in
server/plugins/<code_name>/. - Read the plugin's
config.jsonandscript.pyto understand its functionality and settings. - Formulate and run the command:
python3 server/plugins/<code_name>/script.py. - Retrieve the result from the plugin log folder (
/tmp/log/plugins/last_result.<PREF>.log) quickly, as the backend may delete it after processing.
Run a Plugin Manually
python3 server/plugins/<code_name>/script.py
Ensure sys.path includes /app/server/plugins and /app/server (as in the template).
Plugin Structure
server/plugins/<code_name>/
├── config.json # Manifest with settings
├── script.py # Main script
└── ...
Manifest Location
server/plugins/<code_name>/config.json
code_name== folder nameunique_prefixdrives settings and filenames (e.g.,ARPSCAN)
Settings Pattern
<PREF>_RUN: execution phase<PREF>_RUN_SCHD: cron-like schedule<PREF>_CMD: script path<PREF>_RUN_TIMEOUT: timeout in seconds — this is enforced by the core plugin runner as the whole script's kill-timeout (server/plugin.pypasses it straight tosubprocess(..., timeout=...)). It is not a safe per-HTTP-call timeout — don't reuse it for individual network calls in a loop, or one slow call can burn the whole budget and get the process killed before it writes its result file.<PREF>_WATCH: columns to watch for changes<PREF>_IMPORT_ON: optional — gates whether this run's rows get promoted intoCurrentScan(only relevant ifmapped_to_table: "CurrentScan"). Seedocs/PLUGINS_IMPORT_BEHAVIOR.mdfor the related per-rowscanCreatesDevice/scanNotificationMode/scanPresencecolumns.dataTypeanddefault_valuemust agree.dataType: "array"/"object"needs a real JSON literal fordefault_value('["default"]'), not a bare string ("default").setting_value_to_python_type()(server/helper.py)json.loads()s the default at runtime; a bare string fails silently — logged, and[]is returned instead of your default (e.g.devParentRelType,UI_theme,UI_TOPOLOGY_ORDER). IfelementOptionsalready setsmultiple/orderable: "false", the setting is scalar — usedataType: "string"instead.
Data Contract
Scripts write to /tmp/log/plugins/last_result.<PREF>.log
Important: The backend will almost immediately process this result file and delete it after ingestion. If you need to inspect the output, run the plugin and immediately retrieve the result file before the backend processes it.
Use server/plugins/plugin_helper.py:
from plugin_helper import Plugin_Objects
plugin_objects = Plugin_Objects()
plugin_objects.add_object(...) # During processing
plugin_objects.write_result_file() # Exactly once at end
Execution Phases
once: runs once at startupschedule: runs on cron schedulealways_after_scan: runs after every scanbefore_name_updates: runs before name resolutionon_new_device: runs when new device detectedon_notification: runs when notification triggered
Plugin Formats
| Format | Purpose | Runs |
|---|---|---|
| publisher | Send notifications | on_notification |
| dev scanner | Create/manage devices | schedule |
| name discovery | Discover device names | before_name_updates |
| importer | Import from services | schedule |
| system | Core functionality | schedule |
Before Opening a PR
Check the plugin against the Conventions Checklist in docs/PLUGINS_DEV.md — RUN default, schedule precedent, RUN_TIMEOUT semantics, reusing core settings, description length, and the multi-instance settings pattern. Most plugin PR review comments trace back to one of these.
Starting Point
Copy from server/plugins/__template and customize.