Skip to content

Enable plugins to extend core GraphQL API entry points #22592

Description

@jeremystretch

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 fieldsPluginConfig.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):

  1. 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]
  2. 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).

  3. 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.

  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    complexity: highExpected to require a large amont of time and effort to implement relative to other tasksnetboxstatus: backlogAwaiting selection for worktopic: GraphQLtopic: pluginsRelates to the plugins frameworktype: featureIntroduction of new functionality to the application

    Fields

    No fields configured for Feature.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions