> ## Documentation Index
> Fetch the complete documentation index at: https://bloodhound.specterops.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Package an extension bundle

> Package an OpenGraph extension schema and supported assets into an extension bundle.

![Applies to BloodHound Enterprise and Community Edition](https://mintcdn.com/specterops/tTIczgde9H07oLXf/assets/enterprise-AND-community-edition-pill-tag.svg?fit=max\&auto=format\&n=tTIczgde9H07oLXf\&q=85\&s=ad49a576589f4d2a8081df77d07fdf56)

Package your OpenGraph extension definition schema, Privilege Zone rules, and saved queries in an **extension bundle** to install them together. An extension bundle is a ZIP archive supported in BloodHound v9.9.0 and later.

This page is intended primarily for extension developers. Unless otherwise noted, the guidance below covers developing and packaging a bundle; users and administrators should see [Manage extensions](https://bloodhound.specterops.io/opengraph/extensions/manage.md) for installation and management workflows.

## Bundle composition

A bundle supports three components:

* **Extension definition schema**: Defines the extension's graph structure, with findings and remediation guidance (if included).
* **Privilege Zone rules**: Identify principals that belong to a zone. Bundles support only the built-in Tier Zero zone.
* **Saved queries**: Help you explore and understand the extension's data with contextually relevant queries.

| Component | Requirement | Installation behavior | Alternative without a bundle |
| - | - | - | - |
| `schema.json` | Required in both BloodHound Enterprise and BloodHound Community Edition. | Provides the same functionality as a schema-only JSON installation. | Upload the schema as a JSON file. |
| `pz_rules.json` | Required if `schema.json` contains `relationship_findings`, which defines findings for BloodHound Enterprise. Otherwise optional. | Installs Privilege Zone rules together, without a separate OpenHound workflow. | Install Privilege Zone rules from OpenHound, if available. Otherwise, developers should provide rules for users to recreate in their environment. |
| `saved_queries.json` | Optional in both BloodHound Enterprise and BloodHound Community Edition. | Installs read-only queries organized by `platform` and `category`. | Install saved queries from OpenHound, if available. Otherwise, developers should provide queries for bulk import. Saved queries will be editable and either public (OpenHound) or private (non-OpenHound) by default. Neither method organizes queries by `platform` or `category`. |

To migrate an existing extension, update your publishing workflow to package its components in a bundle. See [Migrate an extension](https://bloodhound.specterops.io/opengraph/developer/extension-bundles.md#migrate-an-extension).

## Installation behavior

BloodHound Enterprise and BloodHound Community Edition accept ZIP bundles through [`PUT /api/v2/extensions`](https://bloodhound.specterops.io/reference/opengraph-experimental/upserts-the-opengraph-extension). See the [OpenGraph API index](https://bloodhound.specterops.io/opengraph/developer/api.md) for related operations and how general saved-query endpoints apply to extension-managed queries.

BloodHound installs all bundle components together. If any component fails to install, BloodHound rolls back all changes and returns error messages. No partial installation remains.

> [!NOTE]
>
> Test the complete bundle at least once, even if you test individual components during development. An error in any component causes the entire installation to fail.

## ZIP archive structure

Place each component at the root of the ZIP archive. Use the exact file names shown here:

**\<extension-name>-v\<version>.zip**

**schema.json**

**pz\_rules.json**

**saved\_queries.json**

Follow these archive requirements:

* Place each component at the archive root, using the exact file names shown above. BloodHound rejects nested files, documentation, checksums, and other extra files.
* Include `schema.json` in every bundle. See [Bundle composition](https://bloodhound.specterops.io/opengraph/developer/extension-bundles.md#bundle-composition) for the requirements for `pz_rules.json` and `saved_queries.json`.
* Include each component at most once. BloodHound rejects duplicate component names.
* Keep each component at or below the 10 MB decompressed size limit.
* Keep optional components separate from `schema.json`. BloodHound rejects a bundled `schema.json` that embeds `pz_rules` or `queries`.

If you omit a component when updating a bundle, BloodHound removes any existing extension-managed assets of that type.

Use the existing extension definition schema format for `schema.json`. A schema-only bundle is valid if the file contains the required `schema`, `node_kinds`, and `relationship_kinds` fields and does not define `relationship_findings`.

> [!NOTE]
>
> If `schema.json` defines relationship findings, include `pz_rules.json` with at least one valid rule.

## Privilege Zone rules

In `pz_rules.json`, define a JSON object with a top-level `pz_rules` array containing one or more Privilege Zone rules.

For each rule:

* Provide a stable `key`, a display name, and exactly one Cypher seed: an object that specifies the query used to identify principals.
* Set the seed's `type` to `2` and provide the Cypher query in its `value` field. Bundles support only Cypher-based rules.
* Optionally, set `enabled` and `allow_disable`. Both default to `true`.

BloodHound associates each rule with its extension and assigns the rule to Tier Zero. Bundles do not support other zones.

When you update the bundle, BloodHound uses each rule's `key` to match existing rules. It updates matching rules with the values from the bundle and removes rules you omit. Uninstalling the extension also removes its rules.

```json title="pz_rules.json" theme={null}
{
  "pz_rules": [
    {
      "key": "GH_tier_zero_principals",
      "name": "GitHub: Tier Zero principals",
      "description": "Identifies GitHub principals assigned to Tier Zero.",
      "enabled": true,
      "allow_disable": true,
      "seeds": [
        {
          "type": 2,
          "value": "MATCH (n:GH_User) RETURN n"
        }
      ]
    }
  ]
}
```

**pz\_rules** `array` *required*

Array of Privilege Zone rules. Include at least one valid rule when `schema.json` defines relationship findings.

**pz\_rules\[].key** `string` *required*

Stable identifier that BloodHound uses to match the rule during extension updates. Use a nonempty value that is unique within the component and contains the extension's namespace prefix, such as `GH_`.

**pz\_rules\[].name** `string` *required*

Unique display name for the Privilege Zone rule. Provide a value that is not empty or whitespace-only.

**pz\_rules\[].description** `string`

Optional description of the rule's purpose or the principals it identifies.

**pz\_rules\[].enabled** `boolean`

Whether the rule is enabled. Defaults to `true`. When enabled, the rule assigns matching principals to Tier Zero.

**pz\_rules\[].allow\_disable** `boolean`

Whether users can disable the rule. Defaults to `true`.

**pz\_rules\[].seeds** `array` *required*

Array containing exactly one Cypher seed object that identifies the principals for the rule.

**pz\_rules\[].seeds\[].type** `integer` *required*

Seed type. Use `2` for a Cypher-based rule, the only supported type.

**pz\_rules\[].seeds\[].value** `string` *required*

Cypher query that identifies principals for zone assignment. Provide a nonempty, valid Cypher query.

## Saved queries

> [!TIP]
>
> **Best practice**
>
> As an extension developer, you should plan to consolidate your queries under as few categories as possible and clearly determine the category nomenclature. Providing too many categories can make it difficult for users to find what they are looking for in BloodHound.
>
> The `category` field is a generic string, so you can specify any value. You should be careful not to make duplicative categories with spelling variations.

In `saved_queries.json`, define a JSON object with a top-level `queries` array containing one or more saved queries. Use `queries` as the array key.

For each query:

* Provide a stable `query_key`, a display name, and a Cypher query.
* Optionally, provide a `description` and `category`.

BloodHound associates each query with its extension and makes it public and read-only. In the [Saved Queries section of the Explore page](https://bloodhound.specterops.io/analyze-data/explore/cypher-search.md#search-and-filter), the extension name determines the option shown in the **Platforms** filter, and each query's `category` value determines an option shown in the **Categories** filter.

When you update the bundle, BloodHound uses each query's `query_key` to match existing queries. It updates matching queries with the values from the bundle and removes queries you omit. Omitting `saved_queries.json` removes all existing saved queries associated with the extension. Uninstalling the extension also removes its queries.

> [!NOTE]
>
> To modify a query, publish a bundle update with the changes. To remove it, omit it from the bundle.

```json title="saved_queries.json" theme={null}
{
  "queries": [
    {
      "query_key": "GH_github_critical_repositories",
      "name": "GitHub: Critical repositories",
      "query": "MATCH (n:GH_Repository) WHERE n.critical = true RETURN n",
      "description": "Lists repositories marked as critical.",
      "category": "Critical Assets"
    }
  ]
}
```

**queries** `array` *required*

Array of saved queries. Include at least one query when you include `saved_queries.json`.

**queries\[].query\_key** `string` *required*

Stable identifier that BloodHound uses to match the query during extension updates. Use a nonempty value that is unique within the component and contains the extension's namespace prefix, such as `GH_`.

**queries\[].name** `string` *required*

Display name for the saved query. Provide a nonempty value that is unique within the component.

**queries\[].query** `string` *required*

Cypher query to save. Provide a nonempty, valid Cypher query.

**queries\[].description** `string`

Optional description of the saved query.

**queries\[].category** `string`

Optional category used to organize the saved query.

## Migrate an extension

BloodHound continues to support schema-only extensions. To include the supported assets with your schema, package them in an extension bundle.

Increment the extension version when you migrate.

**1. Prepare the components**

Update each component to meet the requirements on this page.

**2. Create the ZIP archive**

Create a ZIP archive with the [required components](https://bloodhound.specterops.io/opengraph/developer/extension-bundles.md#zip-archive-structure).

**3. Test the complete bundle**

Test installation with all components present. Confirm that the extension and its assets install successfully.

**4. Make the updated version available**

Commit the updated extension bundle or make it available through your distribution method.
