NetBox plugin that adds a Custom Objects tab to object detail pages
Project description
netbox-custom-objects-tab
A NetBox 4.5.x / 4.6.x plugin that adds Custom Objects tabs to object detail pages,
showing Custom Object instances from the netbox_custom_objects plugin that reference
those objects via OBJECT or MULTIOBJECT fields. Works on standard NetBox models (Device,
Site, Rack, …), third-party plugin models, and Custom Object detail pages themselves
(CO→CO relationships).
Two tab modes are available:
- Combined tab — a single tab showing all Custom Object Types in one table, with pagination, text search, column sorting, type/tag filtering, and HTMX partial updates.
- Typed tabs — each Custom Object Type gets its own tab with a full-featured list view (type-specific columns, filterset sidebar, bulk actions, configure table) matching the native Custom Objects list page.
Screenshot
Requirements
- NetBox 4.5.2 – 4.6.99
netbox_custom_objectsplugin ≥ 0.5.1 installed and configured (0.5.0 had an upstream Delete bug fixed in 0.5.1; see Known Issues)
Compatibility
| Plugin version | NetBox version | netbox_custom_objects version |
|---|---|---|
| 2.4.x | 4.5.2+ / 4.6.x | ≥ 0.5.1 required |
| 2.3.x | 4.5.4+ / 4.6.x | ≥ 0.4.6 (≥ 0.5.0 on 4.6) |
| 2.2.x | 4.5.4+ / 4.6.x | ≥ 0.4.6 (≥ 0.5.0 on 4.6) |
| 2.1.x | 4.5.4+ | ≥ 0.4.6 |
| 2.0.x | 4.5.x | ≥ 0.4.6 |
| 1.0.x | 4.5.x | ≥ 0.4.4 |
Plugin 2.4.x enforces the 0.5.1 minimum at startup: PluginConfig.ready()
probes for the upstream is_polymorphic model field (introduced in 0.5.0)
and raises ImproperlyConfigured with an upgrade message pointing at
>=0.5.1 if the installed upstream is older. The check is behaviour-based
(looks for the field, not a version string) so it stays correct across forks
and pre-release tags; the message advances to 0.5.1 because 2.4.x assumes
the bug fixes shipped in that release.
Installation
source /opt/netbox/venv/bin/activate
pip install netbox-custom-objects-tab
Add to NetBox configuration.py:
PLUGINS = [
'netbox_custom_objects',
'netbox_custom_objects_tab',
]
# Optional — defaults shown below
PLUGINS_CONFIG = {
'netbox_custom_objects_tab': {
'combined_models': ['dcim.*', 'ipam.*', 'virtualization.*', 'tenancy.*'],
'combined_label': 'Custom Objects',
'combined_weight': 2000,
'typed_models': [], # opt-in: e.g. ['dcim.*']
'typed_weight': 2100,
}
}
Restart NetBox. No database migrations required.
Configuration
| Setting | Default | Description |
|---|---|---|
combined_models |
['dcim.*', 'ipam.*', 'virtualization.*', 'tenancy.*'] |
Models that get the combined "Custom Objects" tab. Accepts app_label.model_name or app_label.* wildcards. |
combined_label |
'Custom Objects' |
Text displayed on the combined tab. |
combined_weight |
2000 |
Tab position for the combined tab; lower = further left. |
typed_models |
[] |
Models that get per-type tabs (opt-in, empty by default). Same format as combined_models. |
typed_weight |
2100 |
Tab position for all typed tabs. |
A model can appear in both combined_models and typed_models to get both tab styles.
Examples
# Combined tab only (default)
'combined_models': ['dcim.*', 'ipam.*', 'virtualization.*', 'tenancy.*']
# Per-type tabs for dcim models
'typed_models': ['dcim.*']
# Both modes for dcim, combined only for others
'combined_models': ['dcim.*', 'ipam.*', 'virtualization.*', 'tenancy.*'],
'typed_models': ['dcim.*'],
# Only specific models
'combined_models': ['dcim.device', 'dcim.site', 'ipam.prefix']
# Third-party plugin models work identically
'combined_models': ['dcim.*', 'ipam.*', 'inventory_monitor.*']
# Tabs on Custom Object detail pages (CO → CO relationships)
'typed_models': ['netbox_custom_objects.*']
# Combined tab on Custom Object pages + typed tabs on Device pages
'combined_models': ['dcim.*', 'netbox_custom_objects.*'],
'typed_models': ['dcim.*', 'netbox_custom_objects.*'],
Third-party plugin models are fully supported — Django treats plugin apps and built-in apps the same way in the app registry. Add the plugin's app label and restart NetBox once.
Tabs on Custom Object detail pages
Setting netbox_custom_objects.* in combined_models or typed_models enables tabs on
Custom Object detail pages themselves. This is useful when one Custom Object Type has a
field referencing another Custom Object Type — the referenced object will show a tab listing
all objects that link to it.
Because Custom Object model classes are generated dynamically (one per type, on-demand), a NetBox restart is required whenever a new Custom Object Type is added — the same requirement that applies to all typed tabs.
The tab is hidden automatically (hide_if_empty=True) when no custom objects reference
the object being viewed, so it only appears when relevant.
Features
Pagination
Results are paginated using NetBox's standard EnhancedPaginator. The page size respects
the user's personal NetBox preference and can be overridden with ?per_page=N in the URL.
Page controls appear at the top and bottom of the table.
Text search
A search box in the card header filters results by:
- Custom Object instance display name
- Custom Object Type name
- Field label
Filtering uses the ?q= query parameter and is applied before pagination.
Type filter
A dropdown (shown when 2 or more Custom Object Types are present) lets you narrow
results to a single type. Uses the ?type=<slug> query parameter. The dropdown
auto-submits on selection and is populated from the types actually present in the
current result set.
Tag filter
A dropdown (shown when at least one linked Custom Object has a tag) lets you narrow
results to objects with a specific tag. Uses the ?tag=<slug> query parameter. The
dropdown auto-submits on selection and is populated from the tags present across the
full result set. Tag data is pre-fetched in bulk so there is no N+1 query cost.
Column sorting
Clicking the Type, Object, or Field column header sorts the table in-memory. A second click on the same header reverses the direction. The active column shows an up/down arrow icon. Sort state is preserved when the search form is submitted.
HTMX / Partial updates
Pagination clicks, column sort clicks, search form submissions, type-dropdown changes,
and tag-dropdown changes all update the table zone in-place using HTMX — no full page
reload. The URL is updated via pushState so links stay shareable and the browser back
button returns to the previous filter/page state.
Value column
Each row includes a Value column showing the actual field value on the Custom Object instance:
- Object fields: a link to the related object.
- Multi-Object fields: comma-separated links to the related objects, truncated at 3 with an ellipsis when more are present.
Configure Table
A Configure Table button in the card header opens a NetBox modal that lets
authenticated users show, hide, and reorder the table columns (Type, Object, Value,
Field, Tags). Preferences are stored per-user in UserConfig and respected on every
subsequent page load, including HTMX partial updates. The Actions column is always
visible and cannot be hidden.
Action buttons
Each row has right-aligned action buttons, shown only when the user has the relevant permission:
- Edit (pencil icon) — links to the Custom Object instance's edit page. Shown when the user has
changepermission on the object. - Delete (trash icon) — links to the Custom Object instance's delete confirmation page. Shown when the user has
deletepermission on the object.
Users without either permission see no action buttons in the row. After completing either action, NetBox redirects back to the Custom Objects tab on the same parent object.
Efficient badge counts
The tab badge (shown in the tab bar on every detail page) is computed with a
COUNT(*) query per field — no object rows are fetched. Full object rows are only
loaded when the tab itself is opened. This keeps detail page loads fast even when
thousands of custom objects reference an object.
How It Works
When a Custom Object Type has a field of type Object or Multi-Object pointing to a NetBox model (e.g. Device), any Custom Object instances with that field set will appear in the "Custom Objects" tab on the referenced object's detail page.
The tab displays:
| Column | Content |
|---|---|
| Type | Custom Object Type name (sortable); links to the type detail page when the user has view permission |
| Object | Link to the Custom Object instance (sortable) |
| Value | The value stored in the linking field — a link for Object fields, comma-separated links for Multi-Object fields |
| Field | The field that holds the reference (sortable) |
| Tags | Colored tag badges assigned to the Custom Object instance; — when none |
| (actions) | Edit and Delete buttons, each shown only when the user has the corresponding permission |
Known Issues
Upstream Delete bug on netbox-custom-objects == 0.5.0 (fixed in 0.5.1)
Affected versions: netbox-custom-objects == 0.5.0 only.
Fixed in: netbox-custom-objects main (PR
#501,
merged 2026-05-11) and the forthcoming 0.5.1 release.
Not affected: 0.4.x (no polymorphic through-models) and any build
that contains PR #501.
Deleting a Custom Object instance through the NetBox UI on a 0.5.0
install can raise a ValueError inside
netbox_custom_objects.CustomObjectDeleteView:
ValueError: Cannot query "<row title>": Must be "Table<N>Model" instance.
(at netbox_custom_objects/views.py:977, inside _get_dependent_objects,
called by Django's Collector.collect()). The same crash also occurs from
the bulk-delete view (CustomObjectBulkDeleteView) because NetBox's
generic BulkDeleteView.post() iterates the queryset and calls obj.delete()
per row — the same code path. Bulk Delete is NOT a workaround
(earlier versions of this README claimed it was; that was incorrect).
Recommended fix — upgrade upstream
The cleanest resolution is to upgrade netbox-custom-objects to a
build that contains PR #501. As of writing (2026-05-13) no 0.5.1
release tag exists yet, so the options are:
# Option A: install from upstream main (contains PR #501)
pip install --upgrade --force-reinstall \
git+https://github.com/netboxlabs/netbox-custom-objects.git@main
# Option B: wait for the 0.5.1 release tag and pin to it
pip install --upgrade 'netbox-custom-objects>=0.5.1'
Then restart NetBox. The entire delete-bug class disappears regardless of this plugin's state — no plugin-side change required.
Several adjacent fixes also landed in upstream main post-0.5.0 and
will ship with 0.5.1: PR #504 (cross-COT FK fields after restart),
PR #505 (stale through-model FK path_infos on COT regeneration), and
PR #510 (self-referential FK isinstance check). Upgrading once closes
the whole family.
Workarounds if you cannot upgrade yet
manage.py shelldirect delete (recommended for one-off rows). A freshly-spawned shell process initialises the model cache exactly once, so the class identity is consistent throughout the session and the collector's identity-check succeeds:/opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py shell <<'PY' from netbox_custom_objects.models import CustomObjectType cot = CustomObjectType.objects.get(slug="<your-slug>") cot.get_model().objects.filter(pk=<row-pk>).delete() PY
- Refresh the typed-tab list page between Create and per-row Delete. This worked reliably for non-polymorphic fields on earlier versions and still often works on 0.5.0, but it is no longer guaranteed — polymorphic-MultiObject rows can drift the model cache mid-flow.
- Restart NetBox. Clears
_model_cacheoutright. Reliable but heavyweight; use when shell access isn't available.
Why polymorphic fields amplify the bug on 0.5.0
netbox-custom-objects 0.5.0 introduced is_polymorphic=True Object /
MultiObject fields. Each polymorphic Object field adds a
GenericForeignKey descriptor and each polymorphic MultiObject field
adds a per-field through model. Django's collector traverses every
related model when collecting deletion dependencies, so each extra
related-model is another opportunity to hit a stale class generation in
CustomObjectType._model_cache. Plugin 2.4.0's discovery code walks
those same descriptors to find inbound links (the original goal of
2.4.0), which warms the cache enough that the upstream drift becomes
deterministic rather than intermittent.
Root cause (for the curious)
Each Custom Object Type backs a dynamically-generated Django model
(Table<N>Model), and the class registry can rebuild between requests
(or during a request that touches get_model(no_cache=True)). Django's
Collector then sees the queryset's model class on one side and a
related-field descriptor's .to pointing at a different copy of the
same class name on the other — its identity check raises ValueError.
PR #501 fixes the symptom by overriding
CustomObjectDeleteView._get_dependent_objects to filter through-table
entries out of the collector's dependency walk before the identity check
runs. This plugin does not override delete or model caching and cannot
patch the bug from its own code.
Cosmetic follow-up on patched builds
On builds that already contain PR #501, the delete-success toast for
some dynamic models renders as "Deleted <Type> <Type> None" — the
patched view reads str(obj) after the row's deletion, so the
dynamic model's primary field returns None. Models whose __str__
captures the display value before delete are unaffected. This is a
cosmetic, post-fix upstream issue; it does not affect the delete
itself.
Support
- Open an issue on GitHub
Contributing
Pull requests are welcome. For significant changes, please open an issue first.
License
Apache-2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file netbox_custom_objects_tab-2.4.1.tar.gz.
File metadata
- Download URL: netbox_custom_objects_tab-2.4.1.tar.gz
- Upload date:
- Size: 44.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
352e525f965fddfe28da3af2676818799a1004213b84b1125988dd9bdec87300
|
|
| MD5 |
5c9fc42a72649442977f90617fa158e6
|
|
| BLAKE2b-256 |
a76184ac3e1c429c19e0f5cf04e22336879ae711cd97265cfba6df6a21518eed
|
Provenance
The following attestation bundles were made for netbox_custom_objects_tab-2.4.1.tar.gz:
Publisher:
release.yml on CESNET/netbox-custom-objects-tab
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
netbox_custom_objects_tab-2.4.1.tar.gz -
Subject digest:
352e525f965fddfe28da3af2676818799a1004213b84b1125988dd9bdec87300 - Sigstore transparency entry: 1629488512
- Sigstore integration time:
-
Permalink:
CESNET/netbox-custom-objects-tab@ed9c02d5b3663bb7f622a659332098e9d3c6e551 -
Branch / Tag:
refs/tags/v2.4.1 - Owner: https://github.com/CESNET
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ed9c02d5b3663bb7f622a659332098e9d3c6e551 -
Trigger Event:
release
-
Statement type:
File details
Details for the file netbox_custom_objects_tab-2.4.1-py3-none-any.whl.
File metadata
- Download URL: netbox_custom_objects_tab-2.4.1-py3-none-any.whl
- Upload date:
- Size: 36.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d803a22cb6acf991c21b5226bf477d347f098d6146bb7d6a34d1cea463578c32
|
|
| MD5 |
4b56448d5301fefc5f40fdd0f734d6b0
|
|
| BLAKE2b-256 |
fc2f71b6066804c9a98c66272fd9efd9b4ec6190f40c4c153405fa931b3b795c
|
Provenance
The following attestation bundles were made for netbox_custom_objects_tab-2.4.1-py3-none-any.whl:
Publisher:
release.yml on CESNET/netbox-custom-objects-tab
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
netbox_custom_objects_tab-2.4.1-py3-none-any.whl -
Subject digest:
d803a22cb6acf991c21b5226bf477d347f098d6146bb7d6a34d1cea463578c32 - Sigstore transparency entry: 1629488525
- Sigstore integration time:
-
Permalink:
CESNET/netbox-custom-objects-tab@ed9c02d5b3663bb7f622a659332098e9d3c6e551 -
Branch / Tag:
refs/tags/v2.4.1 - Owner: https://github.com/CESNET
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ed9c02d5b3663bb7f622a659332098e9d3c6e551 -
Trigger Event:
release
-
Statement type: