> For the complete documentation index, see [llms.txt](https://docs.durohub.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.durohub.com/advanced-topics/component-grouping.md).

# Component Grouping

Group components by category, lifecycle status, type, modified-by user, or label — server-side, in a single GraphQL call. Each group returns a capped preview of its components with cursor-based pagination for loading more.

## Quick start

```graphql
query GroupByCategory {
  component {
    componentGroups(
      groupBy: CATEGORY
      pagination: { first: 10 }
    ) {
      edges {
        node {
          key
          label
          totalCount
          components {
            edges {
              node {
                id
                name
                identifier { displayValue }
              }
            }
            pageInfo {
              hasNextPage
            }
          }
        }
      }
      totalCount
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}
```

This returns up to 10 groups, each containing a preview of up to 50 components sorted by the default order (`CREATED_AT DESC`). Groups with more than 50 components signal overflow via `components.pageInfo.hasNextPage`, and the remaining items can be fetched with the [`componentsInGroup`](#loading-more-components-in-a-group) query.

***

## When to use grouping

Use `componentGroups` when your integration needs to:

* **Render a grouped UI** — category buckets, status lanes, label-based views
* **Count items per dimension** — each group carries a `totalCount` without a separate aggregation query
* **Combine grouping with filtering** — pass `filter` or `advancedFilter` alongside `groupBy` to scope results before grouping

For flat, ungrouped lists, continue using `component.findAll` or `component.filter`.

***

## Grouping dimensions

The `ComponentGroupField` enum defines the five available dimensions:

| Value              | Groups by               | Group key                         | Group label                                |
| ------------------ | ----------------------- | --------------------------------- | ------------------------------------------ |
| `CATEGORY`         | Component category      | Category UUID                     | Category name (e.g., "Capacitor")          |
| `LIFECYCLE_STATUS` | Custom lifecycle status | Status UUID                       | Status name (e.g., "Design", "Production") |
| `TYPE`             | Category type           | `ASSEMBLY`, `PART`, or `DOCUMENT` | Same as key, title-cased                   |
| `MODIFIED_BY`      | Last-modified user      | User UUID                         | User display name                          |
| `LABEL`            | Attached labels         | Label UUID                        | Label display name                         |

### Sentinel groups for null values

Components that lack a value for the grouping dimension are collected into synthetic "null-bucket" groups with sentinel keys:

| Dimension          | Sentinel key         | Label          |
| ------------------ | -------------------- | -------------- |
| `CATEGORY`         | `__uncategorized__`  | Uncategorized  |
| `LIFECYCLE_STATUS` | `__unknown-status__` | Unknown status |
| `TYPE`             | `__unknown-type__`   | Unknown type   |
| `MODIFIED_BY`      | `__unknown-user__`   | Unknown user   |
| `LABEL`            | `__no-label__`       | No label       |

Sentinel groups always sort last in the response. Your integration should handle these keys gracefully — they are stable strings, not UUIDs.

{% hint style="info" %}
**Labels are many-to-many.** A component with multiple labels appears in every label group it belongs to. The component's `totalCount` across label groups may exceed the library's total component count.
{% endhint %}

***

## Pagination across groups

The outer `componentGroups` connection uses offset-based cursor pagination to page through groups:

```graphql
query NextGroupPage {
  component {
    componentGroups(
      groupBy: LIFECYCLE_STATUS
      pagination: {
        first: 5
        after: "eyJvZmZzZXQiOjV9"  # endCursor from previous page
      }
    ) {
      edges {
        node { key label totalCount }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
      totalCount   # true total across all pages
    }
  }
}
```

`totalCount` on the outer connection is the total number of groups matching the active filters, not the number returned on this page. Use it for "Showing 1-5 of 12 groups" labels.

***

## Loading more components in a group

Each group's `components` connection is capped at 50 items. When `components.pageInfo.hasNextPage` is `true`, use `componentsInGroup` to fetch the next batch:

```graphql
query LoadMoreInGroup {
  component {
    componentsInGroup(
      groupBy: CATEGORY
      groupKey: "550e8400-e29b-41d4-a716-446655440000"
      pagination: {
        first: 50
        after: "cursor-from-last-edge"
        orderBy: { field: NAME, direction: ASC }
      }
    ) {
      edges {
        cursor
        node {
          id
          name
          identifier { displayValue }
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
      totalCount
    }
  }
}
```

The `groupKey` matches the `key` field from the parent group. For sentinel groups, use the sentinel string directly (e.g., `"__uncategorized__"`).

`componentsInGroup` uses keyset cursor pagination — pass the last edge's `cursor` as `after` to advance. The cursor encodes the sort-field value and the component ID, so it's tied to the `orderBy` you specify. Changing the sort field or direction invalidates previous cursors.

***

## Filtering + grouping

Both `filter` (simple) and `advancedFilter` (AND/OR logic) work with `componentGroups`. When both are provided, `advancedFilter` takes priority and the simple filter is ignored.

### Simple filter

```graphql
query GroupReleasedParts {
  component {
    componentGroups(
      groupBy: CATEGORY
      filter: { categoryType: PART, isArchived: false }
    ) {
      edges {
        node {
          key
          label
          totalCount
        }
      }
    }
  }
}
```

### Advanced filter

```graphql
query GroupByStatusFilteredByLabel {
  component {
    componentGroups(
      groupBy: LIFECYCLE_STATUS
      advancedFilter: {
        and: [
          { labels: { name: { eq: "High Priority" } } }
          { category: { type: { eq: "PART" } } }
        ]
      }
    ) {
      edges {
        node {
          key
          label
          totalCount
        }
      }
    }
  }
}
```

See [Searching and Filtering](/advanced-topics/searching-and-filtering.md) for the full `AdvancedComponentFilterInput` reference.

***

## Sorting within groups

Control how components are ordered inside each group using `pagination.orderBy`:

```graphql
query GroupSortedByName {
  component {
    componentGroups(
      groupBy: LABEL
      pagination: {
        first: 20
        orderBy: { field: NAME, direction: ASC }
      }
    ) {
      edges {
        node {
          key
          label
          components {
            edges {
              node { id name }
            }
          }
        }
      }
    }
  }
}
```

**Available sort fields:**

| Field        | Description                                      |
| ------------ | ------------------------------------------------ |
| `NAME`       | Component name (alphabetical)                    |
| `CPN`        | Component Part Number (identifier display value) |
| `CREATED_AT` | Creation timestamp                               |
| `UPDATED_AT` | Last-modified timestamp                          |

**Directions:** `ASC`, `DESC`

**Default:** When `orderBy` is omitted, components are sorted by `CREATED_AT DESC` (most recently created first). This matches the default behavior of `component.findAll` and `component.filter`.

{% hint style="warning" %}
The `orderBy` applies to both the initial per-group preview AND subsequent `componentsInGroup` calls. If you change the sort when loading more, the cursor from the previous page becomes invalid — start a fresh pagination sequence.
{% endhint %}

***

## Complete API reference

### ComponentGroupField

```graphql
"""Fields that can be used to group components via the componentGroups query."""
enum ComponentGroupField {
  CATEGORY
  LIFECYCLE_STATUS
  TYPE
  MODIFIED_BY
  LABEL
}
```

### componentGroups field

```graphql
type ComponentQuery {
  componentGroups(
    groupBy: ComponentGroupField!
    filter: ComponentFilterInput
    advancedFilter: AdvancedComponentFilterInput
    pagination: PaginationInput
  ): ComponentGroupConnection!
}
```

### ComponentGroupConnection

```graphql
type ComponentGroupConnection {
  edges: [ComponentGroupEdge!]!
  pageInfo: PageInfo!
  """Total number of distinct groups matching the active filters, across all pages."""
  totalCount: Int!
}

type ComponentGroupEdge {
  node: ComponentGroup!
  cursor: String!
}
```

### ComponentGroup

```graphql
type ComponentGroup {
  """Stable server-defined group identifier (entity UUID or sentinel key)."""
  key: ID!

  """Human-readable group name."""
  label: String!

  """Total components in this group matching the active filters."""
  totalCount: Int!

  """Paginated component preview, capped at 50 items per group."""
  components: ComponentConnection!
}
```

### componentsInGroup field

```graphql
type ComponentQuery {
  componentsInGroup(
    groupBy: ComponentGroupField!
    groupKey: String!
    filter: ComponentFilterInput
    advancedFilter: AdvancedComponentFilterInput
    pagination: PaginationInput
  ): ComponentConnection!
}
```

### PaginationInput

```graphql
input PaginationInput {
  """Number of items to return (max 100)."""
  first: Int

  """Cursor from a previous page's endCursor."""
  after: String

  """Sort order for results."""
  orderBy: GenericOrderInput
}

input GenericOrderInput {
  field: String!
  direction: OrderDirection!
}

enum OrderDirection {
  ASC
  DESC
}
```

***

## Next steps

* Learn about [Searching and Filtering](/advanced-topics/searching-and-filtering.md) for the full filter reference
* Explore [Components](/core-concepts/components.md) for create, update, and assembly operations
* Set up [Webhooks](/advanced-topics/webhooks.md) to react to component changes in real-time


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.durohub.com/advanced-topics/component-grouping.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
