Skip to main content

GraphQL

The GraphQL interface is the main interface to interact with Infrahub. The GraphQL schema is automatically generated based on the core models and the user-defined schema models.

The endpoint to interact with the main branch is accessible at https://<host>/graphql. To interact with a branch the URL must include the name of the branch, such as https://<host>/graphql/<branch_name>. If you need to extract the current GraphQL schema in your environment you can issue an HTTP get request to:

  • https://<host>//schema.graphql
  • https://<host>//schema.graphql?branch=some-other-branch

Introduction to GraphQL videos

This short demo shows how to use the GraphQL query interface to explore and read data from Infrahub. It walks through how to open the built-in GraphQL interface and run your first queries.

This video demonstrates how to use filters and relationships in GraphQL to find specific information, such as IP addresses for a particular device, using the Infrahub query interface.

Query & mutations

In GraphQL, a query is used to fetch data and mutations are use to create/update or delete data. In Infrahub, a GraphQL query and 4 mutations will be generated for each model you define in the schema. The name of the query or mutation is based on the namespace and name of the model.

For example, for the model CoreRepository the following query and mutations have been generated:

  • Query : CoreRepository to fetch CoreRepository nodes from Infrahub
  • Mutation : CoreRepositoryCreate to create a CoreRepository node
  • Mutation : CoreRepositoryUpdate to update an existing CoreRepository node
  • Mutation : CoreRepositoryUpsert to create or update a CoreRepository node
  • Mutation : CoreRepositoryDelete to delete a CoreRepository node

Query format

The top level query for each model will always return a list of objects and the query will have the following format CoreRepository > edges > node > display_label

query {
CoreRepository { # PaginatedCoreRepository object
count
edges { # EdgedCoreRepository object
node { # CoreRepository object
id
hfid
display_label
__typename
}
}
}
}
info

All list of objects will be nested under edges & node to make it possible to control the pagination and access the attribute count.

ID, hfid and display_label

For all nodes, the attribute id, hfid and display_label are automatically available.

The value used to generate the display_label can be defined for each model in the schema. If no value has been provided a generic display label with the kind and the ID of the Node will be generated.

The value used to generate the hfid can be defined for each model in the schema. If no value has been provided and the model has a single uniqueness constraint defined, then the hfid will be automatically generated from the uniqueness constraint.

At the object level, there are mainly 3 types of resources that can be accessed, each with a different format:

  • Attribute
  • Relationship of Cardinality One
  • Relationship of Cardinality Many

Attribute

Each attribute is its own object in GraphQL to expose the value and all the metadata.

In the query below, to access the attribute name of the object the query must be CoreRepository > edges > node > name > value. At the same level all the metadata of the attribute are also available, for example: is_protected, source & owner

Example query to access the value and the properties of the attribute 'name'
query {
CoreRepository {
count
edges {
node {
name { # TextAttribute object
value
is_protected
source {
id
display_label
}
}
}
}
}
}

Relationship of Cardinality One

A relationship to another model with a cardinality of One will be represented with a NestedEdged object composed of a node and a properties objects. The node gives access to the remote node (the peer of the relationship) while properties gives access to the properties of the relationship itself.

Example query to access the peer and the properties of the relationship 'account', with a cardinality of one.
query {
CoreRepository {
count
edges {
node {
account {
properties {
is_protected
source {
id
display_label
}
}
node {
display_label
hfid
id
}
}
}
}
}
}

Relationship of Cardinality Many

A relationship with a cardinality of Many will be represented with a NestedPaginated object composed. It was the same format as the top level PaginatedObject with count and edges but the child element will expose both node and properties. The node gives access to the remote node (the peer of the relationship) while properties gives access to the properties of the relationship itself.

Example query to access the relationship 'tags', with a cardinality of Many.
query {
CoreRepository {
count
edges {
node {
tags { # NestedPaginatedBuiltinTag object
count
edges { # NestedEdgedBuiltinTag object
properties {
is_protected
source {
id
}
}
node {
display_label
hfid
id
}
}
}
}
}
}
}

Mutations format

The format of the mutation to Create, Update and Upsert an object has some similarities with the query format. The format will be slightly different for:

  • An Attribute
  • A relationship of Cardinality One
  • A relationship of Cardinality Many

Create, update and upsert

To Create, Update or Upsert an object, the mutations will have the following properties.

  • The input for the mutation must be provided inside data.
  • All mutations will return ok and object to access some information after the mutation has been executed.
  • Update mutations require you to provide an id or hfid to identify the object you want to update.
  • Upsert mutations do not require you to provide the id or the hfid, but enough information needs to be provided for the back-end to uniquely identify the node. Typically this means that all the attribute or relationship values need to be provided that make up the hfid or uniqueness_constraints of the node.
mutation {
CoreRepositoryCreate(
data: {
name: { value: "myrepop" }, # Attribute
location: { value: "myrepop" }, # Attribute
account: { hfid: ["my_account"] }, # Relationship One
tags: [ { hfid: ["my_tag"] } ]} # Relationship Many
) {
ok
object {
id
hfid
}
}
}

Delete

For a Delete mutation, we have to provide the id or the hfid of the node as part of the data argument.

mutation {
CoreRepositoryDelete(data: {hfid: ["myrepo"]}) {
ok
}
}

Branch management

In addition to the queries and the mutations automatically generated based on the schema, there are some queries and mutations to interact with the branches.

  • Query: Branch, Query a list of all branches
  • Mutation: BranchCreate, Create a new branch
  • Mutation: BranchUpdate, Update the description of a branch
  • Mutation: BranchDelete, Delete an existing branch
  • Mutation: BranchRebase, Rebase an existing branch with the main branch
  • Mutation: BranchMerge, Merge a branch into main
  • Mutation: BranchValidate, Validate if a branch has some conflicts

Stored GraphQL queries in the database

The GraphQLQuery model has been designed to store a GraphQL query in order to simplify its execution and to associate it with other internal objects like Transformation.

A GraphQLQuery object can be created via the web interface, the API or it can be imported from a Git repository.

Every time a GraphQLQuery is created or updated, the content of the query will be analyzed to:

  • Ensure the query is valid and compatible with the schema.
  • Extract some information about the query itself (see below).

Information extracted from the query

  • Type of operations present in the Query [Query, Mutation, Subscription]
  • Variables accepted by the query
  • Depth, number of nested levels in the query
  • Height, total number of fields requested in the query
  • List of Infrahub models referenced in the query

Import from a Git repository

GraphQL queries could be defined in file(s) with a .gql extension in a remote repository. Then queries must also be explicitly identified in the .infrahub.yml file under queries.

More details on the .infrahub.yml file format can be found in .infrahub.yml topic.

Executing stored GraphQL queries

Stored GraphQL queries can be executed by using the /api/query/{query_id} REST API endpoint. The {query_id} can be the name or the id of the GraphQLQuery node in the database. More information can be found in the Swagger documentation.

Single-target query pattern

When writing GraphQL queries for transformations, generators, artifacts, and computed attributes, it's critical to use a single-target query pattern to ensure proper tracking by the system.

What is a single-target query?

A single-target query is a GraphQL query that targets a unique node using a unique attribute or ID. This pattern enables Infrahub to identify exactly which objects are affected by a change, allowing it to selectively trigger the necessary action instead of everything.

Why is this important?

Without single-target queries, Infrahub cannot determine which specific actions need to be triggered when data changes. This can lead to excessive processing that significantly impacts performance.

Real-world impact: In one production scenario, a proposed change pipeline generated 600 artifact regenerations when only 5 actually required execution. Properly using single-target queries resolved this issue.

Requirements for a valid single-target query

For a query to be recognized as single-target, it must meet all of these criteria:

  1. Filter on a unique identifier: Use either id or a unique attribute like name__value
  2. Use a required variable: The filter must use a required variable, for example, $name: String!. A literal value is also valid but limits the query to a single fixed object
  3. Use exact match filters: Use singular filters, for example, name__value: $name, not list filters, for example, name__values: $name

Valid single-target query examples

Using a unique attribute with required variable:

query DeviceConfig($device_name: String!) {
InfraDevice(name__value: $device_name) {
edges {
node {
id
name {
value
}
interfaces {
edges {
node {
name {
value
}
}
}
}
}
}
}
}

Using ID with required variable:

query DeviceById($device_id: String!) {
InfraDevice(ids: [$device_id]) {
edges {
node {
id
name {
value
}
}
}
}
}

Invalid query examples (will cause excessive artifact generation)

Missing filter (queries all objects):

query AllDevices {
InfraDevice {
edges {
node {
id
name {
value
}
}
}
}
}

Using optional variable:

query DeviceConfig($device_name: String) {  # NOT required (no !)
InfraDevice(name__value: $device_name) {
edges {
node {
id
}
}
}
}

Using list filter instead of exact match:

query DeviceConfig($device_name: String!) {
InfraDevice(name__values: $device_name) { # name__values instead of name__value
edges {
node {
id
}
}
}
}

Filtering on non-unique attribute:

query DevicesByRole($role: String!) {
InfraDevice(role__value: $role) { # role is not unique
edges {
node {
id
}
}
}
}

Part of the query is not unique:

query DeviceConfig($device_name: String!) {
InfraDevice(name__value: $device_name) {
edges {
node {
id
name {
value
}
}
}
}
BuiltinTag { # This part is not unique
edges {
node {
name {
value
}
}
}
}
}

Ensuring your query is single-target

There is currently no automated way to verify that a query is single-target. The best way to verify is to review the query and ensure it meets all the criteria outlined above.

When loaded into the system, Infrahub will analyze the query and determine if it is single-target or not. If it is not single-target, Infrahub will log a warning.

It's planned to add more integrated checks in the future to streamline the development process and ensure that queries are correctly structured directly during development.

When single-target queries are required

Single-target queries are required for:

When single-target queries are NOT required

You do not need single-target queries for:

  • Ad-hoc queries via the GraphQL interface or API
  • Reporting queries that intentionally fetch multiple objects
  • Dashboard queries for UI components
  • Bulk data exports

In these cases, you can freely query multiple objects without unique filters.

Working with groups in GraphQL

Groups are first-class objects in Infrahub that can be queried and manipulated through GraphQL. Groups provide powerful ways to organize and operate on collections of infrastructure objects.

Querying groups and their members

Query a specific group and its members:

query {
CoreStandardGroup(name__value: "ProductionRouters") {
edges {
node {
name {
value
}
members {
edges {
node {
display_label
__typename
}
}
}
}
}
}
}

Finding groups for an object

Every object automatically gains relationships to find its group memberships:

query {
InfraDevice(name__value: "router01") {
edges {
node {
name {
value
}
member_of_groups {
edges {
node {
name {
value
}
}
}
}
}
}
}
}

Query groups for bulk operations

Groups enable efficient bulk queries across related objects:

query {
CoreStandardGroup(name__value: "EdgeDevices") {
edges {
node {
members {
edges {
node {
... on InfraDevice {
interfaces {
edges {
node {
name {
value
}
ip_addresses {
edges {
node {
address {
value
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}

This pattern enables powerful operations where you can process all objects in a group with a single query, making groups essential for scalable infrastructure management.

See organizing objects with groups for creating and managing groups, and understanding groups for architectural concepts.