Docs

GraphQL API

The NetBox Custom Objects plugin exposes custom objects through NetBox's GraphQL API at the standard /graphql/ endpoint, alongside NetBox's built-in models. For each Custom Object Type you have defined, two root query fields are generated:

  • <name> - fetch a single custom object by id.
  • <name>_list - fetch a list of custom objects (paginated).

The <name> is custom_objects_<slug>, where <slug> is the Custom Object Type's slug with any characters that are not valid in a GraphQL name replaced by underscores (for example a type with slug dhcp-scope becomes custom_objects_dhcp_scope and custom_objects_dhcp_scope_list).

The custom_objects_ prefix namespaces these fields so they can never collide with NetBox's own (or another plugin's) root query fields - every plugin's GraphQL query is merged into NetBox's single global Query, so a bare, slug-derived name like site or group would otherwise be shadowed by a core model's field.

Authentication

GraphQL requests use the same authentication as the REST API. Pass a v2 token in the Authorization header:

curl -H "Authorization: Bearer nbt_<key>.<token>" \
     -H "Content-Type: application/json" \
     https://netbox/graphql/ \
     --data '{"query": "{ custom_objects_dhcp_scope_list { id display } }"}'

Legacy v1 tokens use Authorization: Token <token> instead.

Object-level permissions are enforced. The top-level <name> / <name>_list queries only return custom objects the authenticated user has permission to view, and objects reached through a relationship field are likewise filtered to those the user may view.

The schema itself includes only the Custom Object Types the user may see: those they have permission to view, either as a type or through view permission on its objects. Other types have no queries and don't appear in introspection, and a relationship field pointing at one returns the generic CustomObjectRelatedObjectType (id, object_type, display, url) instead of that type's fields.

Using the GraphiQL explorer

NetBox's built-in GraphiQL explorer (at /graphql/) loads the GraphQL schema once when the page opens and caches it for the life of the page. Because custom object types and their fields can change at runtime, the explorer's autocomplete suggestions and Documentation panel can go stale.

If you add, remove, or change a custom object type's fields, or add a new custom object type, while GraphiQL is open, those changes will not appear in the field autocomplete or the Docs panel until GraphiQL re-reads the schema. (The change is already live on the server - a query using the new field works immediately - it is only GraphiQL's cached copy of the schema that lags.)

To refresh it, click the Re-fetch GraphQL schema button - the circular-arrow icon in the GraphiQL toolbar (lower left). This re-reads the schema without reloading the page, so your current query is preserved:

The Re-fetch GraphQL schema button in the GraphiQL toolbar

Reloading the whole browser page has the same effect, but discards whatever you have typed in the editor.

This only affects the interactive explorer. Programmatic API clients fetch the schema via introspection whenever they choose, and always query against the current server-side schema.

Querying a list

The fields you can select depend on how the Custom Object Type is defined. Every type exposes id and display; the examples below also assume the type defines custom fields named name and description.

query {
  custom_objects_dhcp_scope_list {
    id
    display
    name
    description
  }
}

display is the only human-readable field guaranteed to exist on every type. It is the object's display name: the type's display expression, else its primary field's value, else the type's name and the object's ID. name exists only if the type defines a custom field literally named name.

Filtering a list

<name>_list takes a filters: argument, the same way NetBox's own list queries do, so filtering happens on the server:

query {
  custom_objects_server_list(
    filters: {
      name: {i_starts_with: "web"}
      cpu_count: {gte: 4}
      primary_site: {slug: {exact: "dc1"}}
    }
  ) {
    id
    name
  }
}

Filters can be combined with AND, OR and NOT, and DISTINCT: true removes duplicate rows (useful when filtering on a multi-object field). Every type can be filtered on id, tags, journal_entries, created and last_updated (plus local_context_data when config context is enabled). Each custom field adds a filter based on its type:

Custom field typeFilterExample
text, long text, URL, select, color, MAC addressString lookupsname: {i_contains: "web"}
integer, decimalComparison lookupscpu_count: {range: {start: 2, end: 8}}
booleanexact / is_nullactive: {exact: true}
date, datetimeComparison and date-part lookupspurchased: {year: {exact: 2026}}
JSONPath lookupdata: {path: "env", lookup: {string_lookup: {exact: "prod"}}}
multi-selectArray lookupsroles: {overlap: ["edge", "core"]}
objectThe target's own filter, plus <field>_idprimary_site_id: 7
multi-objectThe target's own filterinterfaces: {name: {exact: "eth0"}}
polymorphic objectPer allowed type: that type's filter, plus _idtarget_dcim_device_id: 7
polymorphic multi-objectPer allowed type: that type's filtertargets_dcim_site: {slug: {exact: "dc1"}}
coordinates<field>_latitude and <field>_longitude comparison lookupslocation_latitude: {gte: "40"}

Object and multi-object fields nest the target type's own filter, whether the target is a NetBox model (primary_site: {region: {slug: {exact: "emea"}}}) or another Custom Object Type. A non-polymorphic multi-object field also takes filters: and pagination: on the field itself, to filter and page the related objects returned, as long as its target has a GraphQL type (see Fallback for targets without a GraphQL type).

Polymorphic fields get one filter per type they can point at, named <field>_<app_label>_<model> as in the REST API, e.g. target_dcim_site and target_dcim_device for a field target allowing Sites and Devices. Each one only matches objects pointing at that type.

Nested filters are unavailable for relationships that close a reference cycle. For single-object relationships, use <field>_id (or, for a polymorphic field, <field>_<app_label>_<model>_id) instead. Array fields can't be filtered in GraphQL yet, and are returned as lists (of lists, for tuples).

A field whose Filter logic is set to Disabled has no filter, as in the REST API.

Lists are paginated with a pagination: argument, as in NetBox's own list queries, e.g. custom_objects_server_list(pagination: {offset: 0, limit: 50}). See NetBox's GraphQL pagination documentation for the options.

Querying a single object

query {
  custom_objects_dhcp_scope(id: 42) {
    id
    display
  }
}

Available fields

Each generated type exposes:

FieldDescription
idThe object's primary key.
displayThe object's display name (see the tip above).
class_typeThe name of the object's model class, e.g. Table9Model.
created / last_updatedChange-logging timestamps.
ownerThe object's owner. A custom field named owner takes its place.
tagsThe object's tags.
journal_entriesThe object's journal entries.
changelogThe object's change records.
local_context_data / config_contextWith config context support enabled: the local context data, and the rendered context (read-only). A custom field named config_context takes its place.
One field per custom fieldNamed exactly as the field's name. Coordinates fields, and the link title of a URL field, aren't available in GraphQL.

Each Custom Object Type's GraphQL type is named Table<id>ModelType, where <id> is the Custom Object Type's ID, e.g. Table9ModelType.

Scalar field types

Scalar custom fields map to their natural GraphQL types:

Custom field typeGraphQL type
text, long text, URL, color, MAC addressString
integerInt
decimalDecimal
booleanBoolean
dateDate
datetimeDateTime
JSONJSON
selectCustomObjectChoiceType
multi-select[CustomObjectChoiceType]

CustomObjectChoiceType has a value (the stored choice value) and a label, as in the REST API. Filters on select fields still match the stored value.

Relationship field types

Object and multi-object fields resolve to the native GraphQL type of their target, so a related object is fully traversable exactly as it would be when queried directly. A field pointing at a Site resolves to NetBox's SiteType:

query {
  custom_objects_server_list {
    name
    primary_site {          # an object (single) field → SiteType
      id
      name
      region { name }       # traverse into the related object's own fields
    }
    interfaces {            # a multi-object (list) field → [InterfaceType]
      id
      name
    }
  }
}

Polymorphic fields

A polymorphic relationship field may point at several different model types. It resolves to a GraphQL union of those types; select fields per type with inline fragments:

query {
  custom_objects_binding_list {
    name
    target {                # polymorphic object field
      ... on SiteType { id site_name: name }
      ... on DeviceType { id device_name: name }
      ... on Table9ModelType { id display }   # a Custom Object Type
    }
  }
}

A field selected in more than one fragment must have the same GraphQL type in each, or be given a different alias in each. Here a site's name is required (String!) while a device's can be empty (String), so each fragment aliases it.

Fallback for targets without a GraphQL type

A small number of NetBox models are not exposed in GraphQL. When a relationship field's target has no native GraphQL type, that field falls back to a uniform CustomObjectRelatedObjectType so it is never silently dropped:

FieldDescription
idThe referenced object's primary key.
object_typeThe referenced object's type, as <app_label>.<model>.
displayThe referenced object's display string.
urlThe referenced object's absolute URL, if resolvable.

On this page