Authoring Scripts
Write Python classes to automate tasks in NetBox. This page covers the plugin's authoring API and how Projects discover and publish Scripts. See Uploading to add source and Execution to run it.
A minimal Script
Import the authoring API from netbox_scripts and subclass Script:
from netbox_scripts import Script, StringVar
class PreviewName(Script):
class Meta:
name = 'Preview a name'
description = 'Shows a proposed device name without changing anything'
proposed_name = StringVar(max_length=64)
def run(self, data, commit):
self.log_info(f'Proposed name: {data["proposed_name"]}')
return data['proposed_name']
Define run(self, data, commit) to perform the work. For UI, REST and
runcustomscript runs, data contains cleaned form values keyed by variable
name. Event Rules pass their action payload without Script form validation.
See Event Rules.
commit indicates whether database changes should persist. False means a dry
run. The value returned from run() becomes the output.
Use BaseScript for shared helper classes that should not appear as runnable
Scripts.
Variables
Variables become form fields. For form-based runs, their cleaned values reach
run() through data. All types accept label, description, default,
required and widget. Variables are required by default unless noted below.
| Class | Purpose | Type-specific arguments |
|---|---|---|
StringVar |
Character string. | min_length, max_length, regex |
TextVar |
Multi-line text. | |
IntegerVar |
Integer. | min_value, max_value |
DecimalVar |
Decimal number. | min_value, max_value, max_digits, decimal_places |
BooleanVar |
Checkbox, never required. | |
ChoiceVar |
One of several static choices. | choices |
MultiChoiceVar |
Several static choices. | choices |
ObjectVar |
One NetBox object. | model, query_params, context, null_option, selector, quick_add |
MultiObjectVar |
Multiple NetBox objects. | Same as ObjectVar. |
FileVar |
Uploaded file. | |
IPAddressVar |
IPv4 or IPv6 address without a mask. | |
IPAddressWithMaskVar |
IPv4 or IPv6 address with a mask. | |
IPNetworkVar |
IPv4 or IPv6 prefix. | min_prefix_length, max_prefix_length |
DateVar |
Date. | |
DateTimeVar |
Date and time. |
Subclass ScriptVariable to create a custom variable type.
Queued runs support only uploads that Django keeps in memory. The run form
and REST endpoint reject disk-backed uploads before creating a Job. Django chooses
storage using the whole request size and FILE_UPLOAD_MAX_MEMORY_SIZE, so several
files or large fields can put even a small file on disk.
Recurring runs cannot carry uploads. Later occurrences would receive a file
an earlier occurrence may have read or closed. The request is rejected before a
Job is created. Use a one-shot run instead. An optional FileVar left empty does
not prevent recurrence.
Meta attributes
Use the inner Meta class for presentation and execution defaults.
| Attribute | Default | Purpose |
|---|---|---|
name |
Class name | Display name, up to 255 characters. |
description |
Empty | Description shown in the UI, with no length limit. |
field_order |
None | Places listed variables first. Others keep their declaration order. |
fieldsets |
None | Groups variables into named form sections instead of one default group. |
commit_default |
True | Default state of Commit changes. |
scheduling_enabled |
True | Whether the form offers scheduling. Set False when the Script should not run unattended. |
notifications_default |
'always' |
Default notification policy: 'always', 'on_failure' or 'never'. Other values are rejected during validation. |
job_timeout |
None | Worker timeout as positive seconds or an RQ duration string such as '5m'. Uses the system setting when unset. |
Operators can override commit_default, notifications_default and job_timeout.
Treat them as recommended defaults rather than guarantees. scheduling_enabled
remains the author's decision and cannot be overridden. See
Override execution defaults.
Logging
Use the five logging methods below. Each accepts an optional message and related object.
def run(self, data, commit):
self.log_debug('Verbose detail')
self.log_info('Something noteworthy')
self.log_success('Something worked', obj=device)
self.log_warning('Something looks off')
self.log_failure('Something broke')
Entries include a timestamp, level, message and link to the related object when
provided. They are also sent to the NetBox system log under
netbox.plugins.netbox_scripts.scripts.
log_failure() records a failure-level message and sets self.failed. It does
not stop execution, fail the Job or roll back changes. See
Aborting a script to stop a run with a failure.
Do not log credentials, tokens or unreviewed response bodies. Logs are stored
on Jobs and read through NetBox's Job-view permission, core.view_job.
The plugin removes its runtime identities, storage keys, digests and cache paths from log messages and string output. It does not detect secrets or recursively sanitize structured output. The Job retains its revision digest to identify the source that ran.
Run context
The following attributes are set before run() is called. Check for None
before using context that may not be available.
| Attribute | Set when | Holds |
|---|---|---|
self.request |
UI, REST or runcustomscript runs, or an Event Rule carrying a request. |
The request used to attribute changes to its user. Event Rules pass a stripped copy without uploaded files. |
self.event |
An Event Rule started the run. | JSON-safe event context, including the type, object and rule. See Event Rules. |
A manually requested run has no event context:
def run(self, data, commit):
if self.event is None:
self.log_info('Started by hand.')
else:
self.log_info(f'Started by the rule {self.event["event_rule"]}.')
Aborting a script
Raise AbortScript to stop execution with an explanatory message:
from netbox_scripts import AbortScript
def run(self, data, commit):
if invalid_precondition:
raise AbortScript('Explain why the script stopped')
Publishing scripts from a project
Projects discover Scripts through declared Script Files.
Validation imports those files and discovers the Script subclasses defined
in them, in alphabetical order.
my-project/
├── tools/
│ ├── deploy.py <- declared script file, its Script classes publish
│ └── naming.py <- helper, importable but never published
└── audit.py <- declared script file
Helpers need no declaration. Import them with relative imports, such as
from . import naming or from .tools import naming. A root __init__.py
serves as the Project's package initializer. See
Runtime and Loading for import behavior.
To set presentation order or publish a class from a helper module, list the
classes in script_order at the top of the Script File:
from .helpers import SharedAudit
script_order = [SharedAudit, PreviewName]
Each entry must be a Script subclass defined in the Project and listed once.
Classes from installed packages do not publish. BaseScript helpers remain
unpublished unless they also subclass Script.
Distinct published classes cannot share a module path and class name, within one Script File or across the revision. A class re-exported by several Script Files publishes once.
Module-level code runs during validation imports, not just execution. Keep module
bodies to imports and definitions, and put work in run(). See
Runtime and Loading for validation and loading details.
Discovered classes become Scripts when the revision is
activated. Identity follows the defining module, even when script_order
re-exports the class elsewhere.
Class validation
Validation builds the run form so configuration errors are found before a user tries to run the Script. It rejects:
- Variable arguments Django cannot use to build a field, such as
max_lengthon a field type that does not accept it. - Variable names reserved by the run form, such as
_commit. Meta.fieldsetsentries that do not name declared variables.- A
Meta.namelonger than 255 characters.
Differences from NetBox's built-in scripts
The plugin reimplements the built-in authoring API, with these differences:
- Zero-valued bounds are honored.
IntegerVar(min_value=0),IntegerVar(max_value=0)andDecimalVar(decimal_places=0)do not lose their constraints as they do in the built-in implementation. DateVarandDateTimeVarattach picker widgets to their variables instead of changing Django's field classes process-wide.- Legacy Report behavior is not supported. Scripts need
run(). The Report methodstest_*,pre_run(),post_run()andrun_tests()are not part of the API. - Recurrences resolve the active revision for each occurrence. This follows source updates, like the built-in scheduler using a module's current source, rather than pinning the whole schedule to one revision.
- Recurring uploads are rejected. The built-in implementation accepts them but reuses file objects that an earlier occurrence may have read or closed.
TextVarhonors an explicitly supplied widget rather than always using a textarea.- A custom
ScriptVariablecan declarefield_attrsat class level. Each instance receives its own copy, preventing labels or defaults from leaking between declarations. ScriptVariable,AbortScriptandLogLevelChoicesare public imports from the plugin's API, rather than being spread across unrelated modules.- Storage-coupled members
filename,source,findsource()andget_module_and_scriptare absent. Discovery uses Project, logical module path and class identity instead. self.storageis not available. Revision source is immutable and identified by its digest. Changing it would fail later verification. Persist data in a NetBox model or a separate backend provided by the deployment instead.- The Report harness and
self._current_testare absent. Report-style classes are rejected rather than emulated. - Discovery publishes classes defined by the Script File or explicitly selected
in
script_order, not every imported Script subclass bound in that module. - System logging uses
netbox.plugins.netbox_scripts.scripts.<project key>.<module>.<Class>instead ofnetbox.scripts. Update handlers and filters that depend on the old logger name.
Legacy scripts
Existing extras.scripts imports continue to resolve through the compatibility
layer. Source is stored unchanged. Check the
differences above when migrating
Scripts that use APIs outside the supported surface.
These import forms are supported, including aliases and wildcards:
from extras.scripts import Script, StringVar # named, aliased, or a wildcard
import extras.scripts # dotted, with or without "as"
from extras import scripts # the submodule from the package
import extras # then extras.scripts.Script
Imports inside functions and methods work the same way. Dynamic imports do not use the compatibility layer:
importlib.import_module('extras.scripts') # reaches NetBox, not this plugin
A class based on that dynamically imported module uses NetBox's Script base,
not the plugin's. It does not publish and validation marks the revision invalid,
naming the class and base. If NetBox no longer provides that module, the import
fails instead and validation leaves no verdict for that attempt. Use an import
statement to reach the compatibility layer.
Other extras imports are unchanged. For example,
from extras.models import Tag still imports NetBox's model.
Legacy Reports are refused, not emulated
A class with test_* methods and no run() makes the revision invalid, with an
error naming the class. Convert the Report to a Script by defining
run(self, data, commit) and moving its work there.
The compatibility layer is transitional
Legacy imports work while NetBox still provides extras.scripts, which the
authoring and migration guides expect to end at NetBox v5.0. Once the module is
removed, legacy imports fail with a message pointing to the plugin's API.
No configuration is needed for the compatibility layer while it is available.
Use netbox_scripts.scripts for new Scripts. Update existing authoring imports
before that upgrade:
from netbox_scripts.scripts import Script, StringVar
Compatibility scope
The stability guarantee covers the authoring API documented here. Scripts can also import other modules available in the NetBox environment, but those APIs are outside the plugin's guarantee. Undocumented NetBox imports may break on a NetBox upgrade even when this plugin has not changed.