netbox-plugin-development について
NetBox のカスタムモデル、ビュー、API、UI を構築するための開発スキルです。NetBox 4.5~4.7 を対象とし、リリースごとに変わる API を最新の資料で確認する方針を示します。
主な機能
- 01プラグイン構成と登録設定
- 02モデル、ビュー、REST API のパターン
- 03フォーム、テーブル、フィルターの指針
- 04ナビゲーションとテンプレート拡張
- 05テストとパッケージ化のガイド
活用シーン
- 01NetBox プラグインの初期構成
- 02NetBox の版に合わせたプラグイン更新
- 03プラグインへのカスタム API と UI の追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ netboxlabs/ skills --skill netbox-plugin-developmentインストール後、Claude Code で/netbox-plugin-developmentを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/forms-tables-filtersets.mdmodel-patterns.mdnetbox-4.7-migration.mdpackaging.mdtesting-guide.mdviews-and-api.md詳細
NetBox プラグイン開発
NetBox プラグイン API に関する知識は古くなっている可能性があります。 ベースクラス、ミックスイン、テンプレートタグ、登録パターンは NetBox のリリースごとに変わります。事前学習済みの知識より、情報の取得を優先してください。
情報取得元
| 情報源 | URL / 方法 | 用途 |
|---|---|---|
| プラグイン開発ドキュメント | https://netboxlabs.com/docs/netbox/plugins/development/ | プラグインフレームワークのリファレンス |
| NetBox リポジトリ | https://github.com/netbox-community/netbox | ベースクラス、ミックスイン、現行 API |
| NetBox リリースノート | https://netboxlabs.com/docs/netbox/release-notes/ | 破壊的変更、新しいプラグイン機能 |
| プラグインの例 | https://github.com/netbox-community | コミュニティプラグインのパターン |
カスタムモデル、ビュー、API、UI 要素を追加して NetBox を拡張するプラグインを構築します。 対象:NetBox 4.5–4.7 / Python 3.12–3.14。Django の分割に注意してください。NetBox 4.5 は Django 5.2、NetBox 4.6 は Django 6.0、NetBox 4.7 は Django 6.1 で動作します。4.6 以降を対象とするコード、マイグレーション、サードパーティー依存関係は Django 6.0 互換、4.7 以降を対象とする場合は Django 6.1 互換である必要があります。
REST API クライアントのパターン(ページネーション、フィルタリング、トークン)については、 netbox-api-integration を参照してください。
クイックリファレンス — 主なインポート
# Models
from netbox.models import NetBoxModel # base for all plugin models
from netbox.models import PrimaryModel # adds description + comments (4.5+)
from netbox.models import OrganizationalModel # name/slug/description/comments (4.5+)
# Views
from netbox.views.generic import (ObjectListView, ObjectView, ObjectEditView,
ObjectDeleteView, BulkImportView, BulkEditView, BulkDeleteView)
from utilities.views import register_model_view, ViewTab
# Forms
from netbox.forms import NetBoxModelForm, NetBoxModelBulkEditForm
from netbox.forms import NetBoxModelFilterSetForm, NetBoxModelImportForm
# Tables
from netbox.tables import NetBoxTable, columns
# FilterSets
from netbox.filtersets import NetBoxModelFilterSet
from utilities.filters import register_filterset # 4.5+: UI lookup modifiers
# REST API
from netbox.api.serializers import NetBoxModelSerializer
from netbox.api.viewsets import NetBoxModelViewSet
from netbox.api.routers import NetBoxRouter
# Navigation
from netbox.plugins import PluginMenu, PluginMenuItem, PluginMenuButton
1. プラグインの構造とセットアップ
パッケージ構成
netbox_myplugin/
├── __init__.py # PluginConfig + config variable
├── version.py # __version__ = '1.0.0'
├── models.py # Django models (NetBoxModel subclasses)
├── views.py # UI views
├── tables.py # django-tables2 table classes
├── forms.py # Model, bulk edit, filter, import forms
├── filtersets.py # FilterSet classes
├── navigation.py # Menu items (auto-discovered)
├── search.py # Search indexes (auto-discovered)
├── graphql/ # Strawberry GraphQL schema
│ ├── schema.py # Query class (auto-discovered)
│ ├── types.py
│ └── filters.py
├── api/
│ ├── urls.py # NetBoxRouter config
│ ├── views.py # API ViewSets
│ └── serializers.py
├── templates/netbox_myplugin/
│ └── myplugin_model.html # Detail view templates
├── template_content.py # Template extensions (auto-discovered)
├── event_rules.py # EventRuleAction subclasses (4.7+, auto-discovered)
├── jinja_env.py # Config-template Jinja filters (4.7+, auto-discovered)
├── graphql_extensions.py # Fields/filters on core GraphQL types (4.7+, auto-discovered)
└── tests/
├── test_models.py
├── test_views.py
├── test_api.py
└── test_forms.py
PluginConfig(__init__.py)
from netbox.plugins import PluginConfig
from .version import __version__
class MyPluginConfig(PluginConfig):
name = 'netbox_myplugin'
verbose_name = 'My Plugin'
description = 'Adds custom functionality to NetBox'
version = __version__
author = 'Your Name'
author_email = '[email protected]'
base_url = 'myplugin' # URL prefix under /plugins/
min_version = '4.5.0'
max_version = '4.7.99' # span 4.5–4.7; use .99 to allow patch releases
default_settings = {'feature_x': True}
required_settings = []
def ready(self):
super().ready()
from . import signals # deferred imports to avoid circular deps
config = MyPluginConfig # MUST be module-level variable named 'config'
自動検出: NetBox は navigation.menu、navigation.menu_items、
template_content.template_extensions、search.indexes、graphql.schema、および (4.7+)
event_rules.event_rule_actions、jinja_env.filters、graphql_extensions.type_extensions/.filter_extensions を自動的に検出します。
2. モデル
すべての機能(タグ、カスタムフィールド、変更ログ、
ブックマーク、ジャーナリング、エクスポートテンプレート、イベントルール、通知)をサポートするには、NetBoxModel をサブクラス化します。
from django.db import models
from django.urls import reverse
from netbox.models import NetBoxModel
class AccessList(NetBoxModel):
name = models.CharField(max_length=100)
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='%(app_label)s_access_lists', # avoid FK collisions
)
type = models.CharField(max_length=50, choices=AccessListTypeChoices)
class Meta:
ordering = ['name']
unique_together = ('device', 'name')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('plugins:netbox_myplugin:accesslist', args=[self.pk])
4.5+:
PrimaryModel(説明とコメント)およびOrganizationalModel(名前/スラッグ/説明/コメント。名前とスラッグの一意性を強制)は、プラグイン API の一部になりました。 モデルがこれらのパターンに当てはまる場合に使用してください。
主な注意点:
- プラグイン間の FK の衝突を避けるため、
related_nameに%(app_label)s_プレフィックスを使用します - ForeignKey フィールドでは、コアモデルを文字列(
'dcim.Device')で参照します - 必ず
get_absolute_url()を定義します —plugins:<plugin_name>:<model_name>パターンに従います - 権限の形式:
netbox_myplugin.view_accesslist、.add_accesslistなど
ベースクラスの階層、ミックスイン、選択肢のパターン、マイグレーションのヒントについては、references/model-patterns.md を参照してください。
3. ビュー
標準的な CRUD ビューには @register_model_view デコレーターを使用します。
from netbox.views.generic import (
ObjectListView, ObjectView, ObjectEditView, ObjectDeleteView,
BulkImportView, BulkEditView, BulkDeleteView
)
from utilities.views import register_model_view, ViewTab
from .models import AccessList
from .tables import AccessListTable
from .forms import AccessListForm, AccessListFilterForm, AccessListBulkEditForm
from .filtersets import AccessListFilterSet
@register_model_view(AccessList, 'list')
class AccessListListView(ObjectListView):
queryset = AccessList.objects.all()
table = AccessListTable
filterset = AccessListFilterSet
filterset_form = AccessListFilterForm
@register_model_view(AccessList)
class AccessListView(ObjectView):
queryset = AccessList.objects.all()
@register_model_view(AccessList, 'edit')
class AccessListEditView(ObjectEditView):
queryset = AccessList.objects.all()
form = AccessListForm
@register_model_view(AccessList, 'delete')
class AccessListDeleteView(ObjectDeleteView):
queryset = AccessList.objects.all()
URL 設定(urls.py)
from netbox.views.generic import get_model_urls
from . import models
urlpatterns = get_model_urls('netbox_myplugin', models)
すべてのビュークラス、URL パターン、ViewTab(コアオブジェクトのビューにタブを追加)、宣言型レイアウトとパンくずリスト (4.7)、GraphQL コア型の拡張 (4.7)、カスタムイベントルールアクション (4.7) については、references/views-and-api.md を参照してください。
4. REST API
シリアライザー
from netbox.api.serializers import NetBoxModelSerializer
from ..models import AccessList
class AccessListSerializer(NetBoxModelSerializer):
class Meta:
model = AccessList
fields = ('id', 'url', 'display', 'name', 'device', 'type',
'tags', 'custom_fields', 'created', 'last_updated')
brief_fields = ('id', 'url', 'display', 'name') # for nested representation
ViewSet
from netbox.api.viewsets import NetBoxModelViewSet
from ..models import AccessList
from ..filtersets import AccessListFilterSet
from .serializers import AccessListSerializer
class AccessListViewSet(NetBoxModelViewSet):
queryset = AccessList.objects.all()
serializer_class = AccessListSerializer
filterset_class = AccessListFilterSet
ルーター(api/urls.py)
from netbox.api.routers import NetBoxRouter
from . import views
router = NetBoxRouter()
router.register('access-lists', views.AccessListViewSet)
urlpatterns = router.urls
API URL の命名: plugins-api:netbox_myplugin-api:accesslist-detail
(これはシリアライザーの HyperlinkedIdentityField で重要です)。
GraphQL、ネストされたシリアライザー、高度な API パターンについては、references/views-and-api.md を参照してください。
5. フォーム、テーブル、FilterSet
モデルフォーム
from netbox.forms import NetBoxModelForm
from utilities.forms.fields import DynamicModelChoiceField
from utilities.forms.rendering import FieldSet
from dcim.models import Device
from .models import AccessList
class AccessListForm(NetBoxModelForm):
device = DynamicModelChoiceField(queryset=Device.objects.all())
fieldsets = (
FieldSet('name', 'device', 'type'),
FieldSet('tags', name='Tags'),
)
class Meta:
model = AccessList
fields = ('name', 'device', 'type', 'tags')
テーブル
import django_tables2 as tables
from netbox.tables import NetBoxTable, columns
class AccessListTable(NetBoxTable):
name = tables.Column(linkify=True) # no LinkColumn in netbox.tables.columns
device = tables.Column(linkify=True)
type = columns.ChoiceFieldColumn()
tags = columns.TagColumn()
class Meta(NetBoxTable.Meta):
model = AccessList
fields = ('pk', 'name', 'device', 'type', 'tags')
default_columns = ('name', 'device', 'type')
FilterSet
from netbox.filtersets import NetBoxModelFilterSet
from utilities.filters import register_filterset
from .models import AccessList
@register_filterset # 4.5+: enables lookup modifiers in UI
class AccessListFilterSet(NetBoxModelFilterSet):
class Meta:
model = AccessList
fields = ('name', 'device', 'type')
def search(self, queryset, name, value):
"""Required for ?q= parameter to work."""
return queryset.filter(name__icontains=value)
4.5+:検索修飾子(例:
name__ic、device_id__n)を UI のフィルターフォームに表示するには、@register_filtersetデコレーターが必要です。
一括編集/インポート/フィルターフォーム、GenericObjectChoiceField (4.7)、django-tables2 v3 の注意事項 (4.7) については、references/forms-tables-filtersets.md を参照してください。
6. ナビゲーション
# navigation.py — auto-discovered
from netbox.plugins import PluginMenu, PluginMenuItem, PluginMenuButton
menu = PluginMenu(
label='My Plugin',
icon_class='mdi mdi-shield-lock',
groups=(
('Access Control', (
PluginMenuItem(
link='plugins:netbox_myplugin:accesslist_list',
link_text='Access Lists',
permissions=['netbox_myplugin.view_accesslist'],
buttons=(
PluginMenuButton(
link='plugins:netbox_myplugin:accesslist_add',
title='Add',
icon_class='mdi mdi-plus-thick',
permissions=['netbox_myplugin.add_accesslist'],
),
),
),
)),
),
)
7. テンプレート拡張
2つの方法があります。タブベースの拡張には ViewTab(モダン)を使用するか、 既存のページにコンテンツを挿入するには PluginTemplateExtension を使用します:
# template_content.py
from netbox.plugins import PluginTemplateExtension
class DeviceAccessInfo(PluginTemplateExtension):
models = ['dcim.device'] # list of strings, not model classes; None = global
def right_page(self):
return self.render('netbox_myplugin/inc/device_access.html', extra_context={
'access_lists': self.context['object'].accesslists.all()
})
template_extensions = [DeviceAccessInfo]
利用可能なメソッド:head()、navbar()、list_buttons()、buttons()、alerts()、
left_page()、right_page()、full_width_page()。
8. テスト
from django.test import TestCase
from utilities.testing import APITestCase
from .models import AccessList
class AccessListAPITest(APITestCase):
model = AccessList
def setUp(self):
self.device = Device.objects.create(name='test-device', ...)
self.access_list = AccessList.objects.create(
name='test-acl', device=self.device, type='standard'
)
def test_list(self):
url = reverse('plugins-api:netbox_myplugin-api:accesslist-list')
response = self.client.get(url, **self.header)
self.assertEqual(response.status_code, 200)
フィクスチャ、ビューのテスト、フォームのテスト、CI のセットアップについては、 references/testing-guide.md を参照してください。
9. パッケージ化
pyproject.toml(モダン)または setup.py を使用します:
[project]
name = "netbox-myplugin"
version = "1.0.0"
dependencies = [] # never list "netbox" here — see note below
[project.entry-points."netbox.plugins"]
netbox_myplugin = "netbox_myplugin:config"
注: エントリーポイントのキーは
PluginConfig.nameと一致し、モジュールレベルのconfig変数を指している必要があります。dependenciesにnetboxを絶対に記載しないでください。NetBox は 4.7 以降 PyPI で公開されているため、pip が実際の NetBox と並べて venv に2つ目の NetBox をインストールしてしまいます。 代わりに、PluginConfigのmin_version/max_versionで互換性を宣言してください。
バージョン管理戦略、PyPI への公開、バージョン互換性マトリクスについては、 references/packaging.md を参照してください。
10. よくある落とし穴
configは__init__.pyのモジュールレベルに置く必要があります。関数内に置いてはいけません- FK の
related_nameが衝突する場合 — 複数のプラグインが同じコアモデルを対象とする場合は、%(app_label)s_をプレフィックスとして付けます - URL 名前空間 — ビュー:
plugins:<plugin>:<view>、API:plugins-api:<plugin>-api:<basename>-detail - filterset に
search()がない — これがないと?q=は機能しません - TemplateExtension の
models— クラスではなく'app.model'文字列のリストである必要があります。Noneはグローバルを意味します __init__.pyでの重いインポート — シグナルと遅延インポートにはready()を使用します(4.5.2 で修正されましたが、現在もベストプラクティスです)max_versionが厳しすぎる — パッチを許可するには'4.5.0'ではなく'4.7.99'(または使用する最上位マイナーバージョンの.99)を使用します- GraphQL は 4.0 以降 Strawberry を使用します — Graphene のパターンは機能しません
@register_filterset(4.5 以降)— これがないと、ルックアップ修飾子が UI に表示されません- 権限 — 形式は
<plugin_name>.view_<model>、<plugin_name>.add_<model>などです。カスタムアクション(4.6.0 以降): モデルのMeta.permissionsで追加アクションを宣言します。NetBox はそれらを ObjectPermission フォームで選択可能なアクションとして自動登録します(その場しのぎの権限チェックより推奨)。
バージョンに関する注記
NetBox 4.7 (2026-09-02)
- PostgreSQL 15 以降(14 では
manage.py test実行時も含め、システムチェックが失敗します)および Django 6.1 が必要です。NetBox は PyPI で配布されています(pyproject.tomlでnetboxに依存しないでください)。4.7.3 以降を推奨します。4.7.0/4.7.1 では、バックグラウンドの一括リクエストで作成された API トークンがジョブの結果に平文で記録されていました。4.7.3 では、ブランチに安全なバックグラウンド書き込みと、config-context の omit 最適化が追加されました。 - 削除:
DEFAULT_ACTION_PERMISSIONS、レガシービューアクション、registry['models'](ObjectType.objects.public()を使用)およびregistry['denormalized_fields']、OptionalLimitOffsetPagination(→NetBoxPagination)、ExpandableIPAddressField/expand_ipaddress_pattern()(→*IPNetwork*)、populate_custom_field_defaults()、OwnerMixinの逆リレーション(site_set)、Django のEMAIL_*設定、NetBox のquerystringタグ(Django のタグを使用し、requestは不要です:{% querystring page=1 %})。完全なチェックリストは netbox-4.7-migration を参照してください。 - 変更:
custom_fields/get_for_model()はリストを返すようになりました。django-tables2 v3(querystring_replace、RelatedLinkColumnはありません)。filterset テストミックスインの名前を*TestMixinに変更しました。検索インデックスの更新は on-commit まで延期されます(テストではcaptureOnCommitCallbacks(execute=True)を使用)。プラグインのビュースタに?background=trueとオブジェクト単位の一括エラーが適用されます。 - コアのツリー構造を MPTT から ltree に移行(Region、Location、Platform、…)。
levelはフィルタリングや並べ替えに使用できません。get_root()/get_family()/is_leaf_node()/move_to()/insert_at()は削除されました。NestedGroupModelは非推奨です。→NestedLtreeGroupModel。4.7.0 で ltree トリガーをインストールしたプラグインでは、ReinstallLtreeTriggersマイグレーションが必要です。model-patterns を参照してください。 - 新しいプラグイン拡張ポイント(すべて
min_version='4.7.0'が必要です):コアの GraphQL 型/フィルター拡張(graphql_extensions.py)、カスタムイベントルールアクション(EventRuleAction)、Jinja フィルターと config-template コンテキスト(jinja_env.py、get_jinja_context())、レイアウトのパンくずリスト、GenericObjectChoiceField、選択肢の説明
NetBox 4.6 (2026)
- Django 6.0(4.5 の 5.2 から変更)。コード、マイグレーション、依存関係が Django 6.0 に対応していることを確認してください。以下の 4.6 専用 API を使用するプラグインでは、
min_version='4.6.0'を設定してください。 - 宣言的なビューレイアウト/再利用可能な UI コンポーネント —
from netbox.ui import layout(Layout/Row/Column)。手書きの詳細テンプレートに代わる最新の方法です(NetBox ≥4.6.0 が必要です)。 Meta.permissionsによるカスタムモデルアクション/権限 — モデルのMetaに宣言すると、NetBox が ObjectPermission フォームで選択可能なアクションとして自動登録します。get_serializer_for_model()用のカスタムシリアライザーリゾルバー(4.6.2)— シリアライザーの検索を制御するリゾルバーを登録できます。- セキュリティ: ExportTemplate/ConfigTemplate の
environment_paramsにおける RCE(CVE-2026-29514)は 4.6.1 で修正されました。許可リストによりextensions/finalize/loader/bytecode_cacheがブロックされます。 - 避けるべき非推奨機能:
DEFAULT_ACTION_PERMISSIONS、レガシービューアクション、内部レジストリのmodelsキー、カスタムquerystringテンプレートタグ、OptionalLimitOffsetPagination
NetBox 4.5 (2026-01-06)
- Python 3.12 以降が必須(3.10/3.11 はサポート対象外)。Django 5.2
PrimaryModel、OrganizationalModel、NestedGroupModelがプラグイン API で利用可能になりました。@register_filtersetデコレーターにより、UI のルックアップ修飾子を使用できます。- オブジェクトの所有権に
OwnerMixinを利用できます。 - 互換性を破る変更: GraphQL のフィルター構文で、ID/列挙型にルックアップ修飾子が必要になりました。
NetBox 4.4 (2025-09-02)
- バックグラウンドジョブのログ記録:ジョブメソッド内で
self.logger.info()を使用 - ビュー上の個別/一括カスタム操作用の
ObjectAction - プラグインがカスタムモデル機能を登録するための
register_model_feature() - テーブルで
ArrayColumnをサポート