NetBox version
v4.6.4
Feature type
New functionality
Proposed functionality
Provide a supported plugin extension point for adding fields and filters to existing core GraphQL types (e.g. the Device type), so a plugin's related data can be traversed within a single query rooted at a core object rather than requiring a separate top-level query.
Today, plugins can only contribute new top-level query fields — PluginConfig.graphql_schema classes are spread into the root Query via multiple inheritance in netbox/netbox/graphql/schema.py. There is no way for a plugin to add a field/relation to an already-defined core type. A plugin model with an FK/GFK to dcim.Device therefore cannot surface its reverse relation on the Device GraphQL type; clients must issue a second query against the plugin's own type.
Proposed mechanism (modeled on the existing PluginTemplateExtension pattern):
-
Plugins ship output-type mixins and filter mixins, each declaring a models attribute (list of lowercased app_label.model labels), in graphql/type_extensions.py and graphql/filter_extensions.py:
# my_plugin/graphql/type_extensions.py
@strawberry.type
class DeviceGraphQLExtension:
models = ['dcim.device']
@strawberry_django.field(prefetch_related='widgets')
def widgets(self) -> list[Annotated['WidgetType', strawberry.lazy('my_plugin.graphql.types')]]:
return self.widgets.all()
type_extensions = [DeviceGraphQLExtension]
-
New PluginConfig resources (graphql_type_extensions, graphql_filter_extensions) with default paths graphql.type_extensions / graphql.filter_extensions, loaded in PluginConfig.ready() (alongside the existing graphql_schema handling in netbox/netbox/plugins/__init__.py).
-
New register_graphql_type_extensions() / register_graphql_filter_extensions() functions in netbox/netbox/plugins/registration.py (mirroring register_template_extensions) that collect classes into per-model registry buckets keyed by model label.
-
New NetBox decorators register_type() / register_filter() (in netbox/netbox/graphql/types.py / filters.py) that wrap strawberry_django.type / strawberry_django.filter_type, splice the registered mixins for that model into the class bases, then delegate. All model-bound declarations across <app>/graphql/{types,filters}.py are swept to use these wrappers (~40+ types across all apps). Fully transparent — with no plugins registered the wrappers are exact pass-throughs, so schema output is unchanged.
UI changes: none.
Load-order requirement: the registry must be populated (during ready()) before the GraphQL modules import (via ROOT_URLCONF). This holds today provided nothing imports the GraphQL schema during app ready(); a regression test should assert an extension appears in the built schema.
Out of scope (possible follow-ups): mutations/input types, and order_by on injected fields (same injection pattern could later apply to order_type).
Use case
Plugin authors routinely add models that relate to core objects (a plugin storing per-device metrics, compliance state, licensing, custom attachments, etc.). Consumers of NetBox's GraphQL API expect to fetch a device and its related plugin data in one round trip:
query {
device_list(filters: { my_plugin_widget_count: { gt: 0 } }) {
name
my_plugin_widgets { id name }
}
}
Currently this is impossible — the client must query the core device and the plugin's top-level list separately and stitch results together client-side, losing the graph-traversal benefit that GraphQL exists to provide. A first-class extension point brings GraphQL to parity with the existing UI extension mechanism (PluginTemplateExtension), which already lets plugins inject content into core object pages.
Database changes
None. This is purely a change to the GraphQL API assembly and plugin registration framework; no models or fields are added or altered.
External dependencies
None. The implementation relies on strawberry / strawberry-django, which are already core dependencies.
NetBox version
v4.6.4
Feature type
New functionality
Proposed functionality
Provide a supported plugin extension point for adding fields and filters to existing core GraphQL types (e.g. the
Devicetype), so a plugin's related data can be traversed within a single query rooted at a core object rather than requiring a separate top-level query.Today, plugins can only contribute new top-level query fields —
PluginConfig.graphql_schemaclasses are spread into the rootQueryvia multiple inheritance innetbox/netbox/graphql/schema.py. There is no way for a plugin to add a field/relation to an already-defined core type. A plugin model with an FK/GFK todcim.Devicetherefore cannot surface its reverse relation on theDeviceGraphQL type; clients must issue a second query against the plugin's own type.Proposed mechanism (modeled on the existing
PluginTemplateExtensionpattern):Plugins ship output-type mixins and filter mixins, each declaring a
modelsattribute (list of lowercasedapp_label.modellabels), ingraphql/type_extensions.pyandgraphql/filter_extensions.py:New
PluginConfigresources (graphql_type_extensions,graphql_filter_extensions) with default pathsgraphql.type_extensions/graphql.filter_extensions, loaded inPluginConfig.ready()(alongside the existinggraphql_schemahandling innetbox/netbox/plugins/__init__.py).New
register_graphql_type_extensions()/register_graphql_filter_extensions()functions innetbox/netbox/plugins/registration.py(mirroringregister_template_extensions) that collect classes into per-model registry buckets keyed by model label.New NetBox decorators
register_type()/register_filter()(innetbox/netbox/graphql/types.py/filters.py) that wrapstrawberry_django.type/strawberry_django.filter_type, splice the registered mixins for that model into the class bases, then delegate. All model-bound declarations across<app>/graphql/{types,filters}.pyare swept to use these wrappers (~40+ types across all apps). Fully transparent — with no plugins registered the wrappers are exact pass-throughs, so schema output is unchanged.UI changes: none.
Load-order requirement: the registry must be populated (during
ready()) before the GraphQL modules import (viaROOT_URLCONF). This holds today provided nothing imports the GraphQL schema during appready(); a regression test should assert an extension appears in the built schema.Out of scope (possible follow-ups): mutations/input types, and
order_byon injected fields (same injection pattern could later apply toorder_type).Use case
Plugin authors routinely add models that relate to core objects (a plugin storing per-device metrics, compliance state, licensing, custom attachments, etc.). Consumers of NetBox's GraphQL API expect to fetch a device and its related plugin data in one round trip:
Currently this is impossible — the client must query the core
deviceand the plugin's top-level list separately and stitch results together client-side, losing the graph-traversal benefit that GraphQL exists to provide. A first-class extension point brings GraphQL to parity with the existing UI extension mechanism (PluginTemplateExtension), which already lets plugins inject content into core object pages.Database changes
None. This is purely a change to the GraphQL API assembly and plugin registration framework; no models or fields are added or altered.
External dependencies
None. The implementation relies on
strawberry/strawberry-django, which are already core dependencies.