NetBox is an extensible open-source network source-of-truth application powering network automation. It manages network infrastructure data including data center infrastructure (DCIM), IP address management (IPAM), circuits, virtualization, wireless, VPNs, and more. It supports a plugin ecosystem and exposes both a REST API and GraphQL API.
NetBox is the core product maintained by NetBox Labs. The current version is 4.6 (Python 3.12+, Django 6.x).
- Python 3.12+ / Django 6.x / Django REST Framework 3.x
- PostgreSQL (required), Redis (required for caching/queuing)
- GraphQL via Strawberry, background jobs via django-rq
- django-tables2 for list views, django-filter for filtering
- drf-spectacular for OpenAPI/Swagger schema generation
- Docs: MkDocs with mkdocs-material theme (in
docs/) - Ruff for lint (config in
pyproject.toml)
.
├── netbox/ — Django project root (run manage.py from here)
│ ├── manage.py
│ ├── netbox/ — Core settings, URLs, WSGI, plugin infrastructure
│ │ ├── settings.py — Main Django settings
│ │ ├── configuration.py — Instance configuration (gitignored)
│ │ ├── configuration_example.py — Configuration template
│ │ ├── configuration_testing.py — Test configuration
│ │ ├── urls.py — Root URL routing
│ │ ├── wsgi.py — WSGI entrypoint
│ │ ├── api/ — Core REST API infrastructure
│ │ ├── graphql/ — Core GraphQL schema
│ │ ├── models/ — Core model infrastructure (features, mixins)
│ │ ├── navigation/ — Navigation menu system
│ │ ├── plugins/ — Plugin system infrastructure
│ │ ├── registry.py — Object registry
│ │ ├── search/ — Full-text search implementation
│ │ ├── ui/ — UI utilities
│ │ └── tests/ — Core framework tests
│ ├── account/ — User account management
│ ├── circuits/ — Circuit and provider management
│ ├── core/ — Core data management (data sources, jobs)
│ ├── dcim/ — Data center infrastructure (devices, racks, cables, etc.)
│ ├── extras/ — Cross-cutting features (custom fields, tags, webhooks, scripts)
│ ├── ipam/ — IP address management (prefixes, addresses, VLANs, etc.)
│ ├── tenancy/ — Tenancy and organization
│ ├── users/ — User management and tokens
│ ├── utilities/ — Shared utilities (no models)
│ ├── virtualization/ — Virtual machines and clusters
│ ├── vpn/ — VPN tunnels and configurations
│ ├── wireless/ — Wireless LANs and links
│ ├── templates/ — Django templates (per-app subdirectories)
│ ├── static/ — Compiled static assets
│ ├── project-static/ — Source static assets
│ ├── media/ — User-uploaded media
│ └── translations/ — i18n translation files
├── docs/ — MkDocs documentation source
│ ├── administration/
│ ├── configuration/
│ ├── customization/
│ ├── development/ — Contributing guide, code style
│ ├── features/
│ ├── getting-started/
│ ├── installation/
│ ├── integrations/
│ ├── models/ — Per-model documentation (by app)
│ ├── plugins/
│ ├── reference/
│ └── release-notes/
├── scripts/ — Database management and verification scripts
├── contrib/ — Example configs (systemd, nginx, generated schemas)
├── pyproject.toml — Project metadata, ruff config
├── requirements.txt — Python dependencies
└── mkdocs.yml — Docs site configuration
Each Django app (account, circuits, core, dcim, extras, ipam, tenancy, users, virtualization, vpn, wireless) follows a standard layout:
<app>/
├── __init__.py
├── models/ — Database models (or models.py for smaller apps)
├── migrations/ — Database migrations
├── api/
│ ├── serializers.py
│ ├── views.py — DRF viewsets
│ └── urls.py — NetBoxRouter registrations
├── forms/ — Django forms (model forms, filter forms, bulk edit, etc.)
├── tables/ — django-tables2 table definitions
├── graphql/
│ └── types.py — Strawberry GraphQL types
├── filtersets.py — django-filter FilterSets
├── choices.py — ChoiceSet subclasses
├── views.py — UI views (registered with register_model_view())
├── urls.py — URL routing
├── search.py — SearchIndex registrations
├── signals.py — Django signal definitions (where applicable)
└── tests/
├── test_api.py
├── test_filtersets.py
├── test_models.py
├── test_views.py
└── test_forms.py
Use register_model_view() to register model views by action (e.g. "add", "list", etc.). List views typically don't need to add select_related() or prefetch_related() on their querysets — prefetching is handled dynamically by the table class so that only relevant fields are prefetched.
DRF serializers live in <app>/api/serializers.py; viewsets in <app>/api/views.py; URLs auto-registered in <app>/api/urls.py. NetBoxModelSerializer provides standard fields including url, display, tags, and custom_fields. drf-spectacular generates the OpenAPI schema automatically. REST API views typically don't need to add select_related() or prefetch_related() — prefetching is handled dynamically by the serializer.
Strawberry types live in <app>/graphql/types.py. The core GraphQL schema is assembled in netbox/netbox/graphql/. Use Strawberry's @strawberry.type and auto field resolution, following the patterns in existing apps.
django-rq drives background task processing. Job classes live in core/jobs.py and app-specific jobs.py files. Use JobRunner subclasses (from netbox.jobs) for all background work. The core app exposes job status in the UI.
Plugin infrastructure lives in netbox/netbox/plugins/. Plugins are Django apps registered in PLUGINS (configuration.py). The plugin API exposes stable extension points: custom models, views, navigation, template extensions, search indexes, object actions, and event rules. Internal NetBox APIs are subject to change without notice.
FilterSets live in <app>/filtersets.py, using NetBoxModelFilterSet as the base. Used for both UI filtering and API ?field= params. FK filters must declare an explicit <field>_id = ModelMultipleChoiceFilter(field_name='<field>', ...) — don't rely on Meta.fields to auto-generate _id variants.
extras is a catch-all for cross-cutting features: custom fields, custom links, tags, webhooks/event rules, export templates, config contexts, saved filters, bookmarks, notifications, scripts, and reports. New cross-cutting features belong here. Use FeatureQuery for generic relations (config contexts, custom fields, tags, etc.).
All commands run from the netbox/ subdirectory with the venv active. There is no Makefile or Justfile; use raw commands.
| Command | What it does |
|---|---|
python manage.py runserver |
Start development server |
python manage.py test |
Run full test suite (set NETBOX_CONFIGURATION first — see Testing) |
python manage.py test --keepdb --parallel 4 |
Faster test run (no DB rebuild, parallel) |
python manage.py test dcim.tests.test_api |
Run a single test module |
python manage.py makemigrations |
Generate migrations after model changes |
python manage.py migrate |
Apply migrations |
python manage.py nbshell |
NetBox-enhanced interactive shell |
python manage.py collectstatic |
Collect static assets |
ruff check |
Lint (run from repo root) |
mkdocs serve |
Preview documentation |
mkdocs build |
Build static docs site |
python -m venv ~/.venv/netbox
source ~/.venv/netbox/bin/activate
pip install -r requirements.txt
# Copy and configure
cp netbox/netbox/configuration.example.py netbox/netbox/configuration.py
# Edit configuration.py: set DATABASE, REDIS, SECRET_KEY, ALLOWED_HOSTS
cd netbox/
python manage.py migrate
python manage.py runserverRequires PostgreSQL and Redis on localhost at their default ports.
Tests use django.test.TestCase (not pytest). Test modules mirror the app structure in <app>/tests/. Always set the NETBOX_CONFIGURATION environment variable before running tests:
export NETBOX_CONFIGURATION=netbox.configuration_testing
python manage.py test
# Faster runs
python manage.py test --keepdb --parallel 4
# Single module
python manage.py test dcim.tests.test_apiStandard test modules per app:
| Module | Coverage area |
|---|---|
test_api.py |
REST API endpoints (CRUD, filtering, bulk operations) |
test_filtersets.py |
FilterSet fields and query behavior |
test_models.py |
Model methods, validation, constraints |
test_views.py |
UI views (list, create, edit, delete, bulk actions) |
test_forms.py |
Form validation |
test_tables.py |
Table column rendering |
Additional specialized test modules exist in some apps (e.g., test_cablepaths.py in dcim, test_lookups.py in ipam).
GitHub Actions workflows in .github/workflows/:
ci.yml— Main CI pipeline: runs on every PR. Executes linting (ruff) and the full test suite across the supported Python version matrix.codeql.yml— CodeQL security scanning.claude.yml— Claude Code automation hook; triggers on issue/PR comments mentioning@claude.claude-issue-triage.yml— Automated issue triage via Claude AI.close-stale-issues.yml/close-incomplete-issues.yml— Issue hygiene automation.lock-threads.yml— Locks closed issue/PR threads after a period.update-translation-strings.yml— Extracts and updates i18n translation strings.
- Add the model to the appropriate app's
models/directory (or create a new module imported frommodels/__init__.py). Inherit fromNetBoxModelfor full feature support (custom fields, tags, etc.). - Prompt the user to run
python manage.py makemigrations— never write migrations manually. - Wire up the full surface area: filterset (
filtersets.py), forms (forms/), table (tables/), serializer (api/serializers.py), viewset (api/views.py), URL routes (api/urls.py,urls.py), UI views (views.py), navigation, and a template undertemplates/<app>/. - Register a
SearchIndexinsearch.pyif the model should appear in global search. - Add tests covering model logic, API, filtersets, forms, and views.
- Add the serializer to
api/serializers.pyusingNetBoxModelSerializerforNetBoxModel-based models. Include aurlfield. - Add the viewset to
api/views.py. For custom actions use@action(detail=True, methods=['post']). - Register the route in
api/urls.pyviaNetBoxRouter. - Ensure a corresponding
FilterSetexists infiltersets.py; add explicit<field>_id = ModelMultipleChoiceFilter(field_name='<field>', ...)for FK filters. - Add an integration test in
tests/test_api.py.
- Add a Strawberry type to
<app>/graphql/types.py, inheriting from the appropriate base (see existing types for examples). - Register any new query fields in the app's GraphQL module and ensure it is included in the root schema.
- Follow the patterns in existing apps — use
autofields and lazy-resolve relations.
- Add the field to
<app>/filtersets.py. UseNetBoxModelFilterSetas the base. - For FK relations, add both
<field>(name/slug lookup) and<field>_id(ID lookup) as explicitModelMultipleChoiceFilterentries. - Update the filter form in
forms/filtersets.pyto expose the field in the UI. - Add a test in
tests/test_filtersets.py.
- Bump
versioninpyproject.toml. - Update
docs/release-notes/. - Tag and publish a GitHub release.
- Apps: Each app owns its models, views, serializers, filtersets, forms, and tests. Don't reach across app boundaries except via FK relations and public APIs.
- Views: Use
register_model_view(). List views don't need manualselect_related()/prefetch_related()— the table handles it. - REST API: Serializers don't need manual
select_related()/prefetch_related()— handled dynamically. - New models: Inherit from
NetBoxModel; includecreatedandlast_updatedfields. - Every UI model: Needs model, serializer, filterset, form, table, views, URL route, and tests.
- API serializers: Must include a
urlfield (absolute URL of the object). - Generic relations: Use
FeatureQueryfor config contexts, custom fields, tags, etc. - FK filters: Always add explicit
<field>_idvariants in FilterSets; don't rely onMeta.fields. - No new dependencies without strong justification.
- No manual migrations: Prompt the user to run
manage.py makemigrations. - No
ruff formaton existing files — tends to introduce unnecessary style changes. - Linting: Ruff config in
pyproject.toml. Line length 120, single quotes. Enabled rules: E/W/F/I/RET/UP/RUF022. Ignored: F403, F405, RET504, UP032. - Extras: Cross-cutting features (custom fields, tags, webhooks, scripts) belong in the
extrasapp. - Plugin API: Only documented public APIs are stable. Internal code may change without notice.
- Branch naming:
<issue-number>-short-description(e.g.,1234-device-typerror) - Use the
mainbranch for patch releases;featuretracks work for the upcoming minor/major release. - Every PR must reference an approved GitHub issue.
- PRs must include tests for new functionality.
Do not open a PR unless all the following conditions are met:
- Issue reference required — The PR body must include a
Closes: #<number>line identifying the associated GitHub issue. PRs without this line must not be submitted. - Issue must be open — Before opening a PR, verify via
gh issue view <number>that the referenced issue is currently open. Do not submit a PR against a closed issue. - Issue must be assigned to you — Verify that the referenced issue is assigned to the submitting user. Do not open a PR for an issue that is unassigned or assigned to someone else.
- No exceptions without maintainer status — These three requirements are waived only for project maintainers (members of the
netboxlabsGitHub organization). All other contributors must satisfy all three checks before a PR is opened.
Pre-submission checklist for AI agents:
# Confirm the issue is open and assigned before opening a PR
gh issue view <number> --json state,assigneesReject the PR submission and report the problem if the issue is closed, unassigned, or assigned to a different user.
Do not include an entry in the release notes for the PR unless explicitly instructed to do so. (Release notes are typically generated in aggregate as part of the release process to avoid merge conflicts.)
- Wrong directory for
manage.py—manage.pylives innetbox/, not the repo root. Alwayscd netbox/first or use the full path. - Wrong configuration loaded — Set
NETBOX_CONFIGURATION=netbox.configuration_testingfor tests. configuration.pynot found — Copyconfiguration.example.pytoconfiguration.pyand fill in DATABASE, REDIS, SECRET_KEY, ALLOWED_HOSTS. This file is gitignored and must never be committed.- Migration errors — Never write migrations manually. Run
python manage.py makemigrationsand let Django generate them. - Plugin issues — Only documented public APIs are stable. Internal NetBox code may change without notice.
configuration.pyis gitignored — never commit it.manage.pylives innetbox/, NOT the repo root. Running from the wrong directory is a common mistake.NETBOX_CONFIGURATIONenv var controls which settings module loads; set tonetbox.configuration_testingfor tests.- The
extrasapp is a catch-all for cross-cutting features (custom fields, tags, webhooks, scripts). - Plugins API: only documented public APIs are stable. Internal NetBox code is subject to change without notice.
- See
docs/development/for the full contributing guide and code style details.
- Documentation:
docs/ - Contributing guide:
docs/development/ - Release notes:
docs/release-notes/ - Plugin development:
docs/plugins/ - NetBox Labs: https://netboxlabs.com