Infrahub Schema Creator
Overview
Expert guidance for designing and building Infrahub
schemas. Schemas are YAML files defining nodes (concrete
types), generics (abstract base types), attributes,
relationships, and extensions.
Project Context
Existing schemas in this project:
!find . -name "*.yml" -path "*/schemas/*" -o -name "*schema*" -name "*.yml" 2>/dev/null | head -20
Infrahub config (if present):
!cat .infrahub.yml 2>/dev/null || echo "No .infrahub.yml found"
If invoked with arguments (e.g., /infrahub:managing-schemas Ipam Vlan VlanGroup),
use the first argument as the namespace and remaining arguments as node names.
When to Use
- Designing new data models or schema nodes
- Adding attributes or relationships to existing schemas
- Setting up hierarchical location trees or component/parent patterns
- Configuring display properties (human_friendly_id, display_label)
- Migrating or refactoring existing schemas
- Debugging schema validation errors
Rule Categories
| Priority |
Category |
Prefix |
Description |
| CRITICAL |
Branch-First Changes |
workflow- |
Load schema onto a branch, not the default branch |
| CRITICAL |
Naming |
naming- |
Namespace, node, attribute naming |
| CRITICAL |
Relationships |
relationship- |
IDs, peers, component/parent, on_delete |
| HIGH |
Attributes |
attribute- |
Defaults, dropdowns, computed Jinja2, branch-agnostic, deprecated |
| HIGH |
Hierarchy |
hierarchy- |
Hierarchical generics, parent/children |
| HIGH |
Display |
display- |
human_friendly_id, order_weight, menu placement |
| MEDIUM |
Extensions |
extension- |
Cross-file via extensions block, artifact targets |
| MEDIUM |
Uniqueness |
uniqueness- |
Constraint format, __value suffix |
| MEDIUM |
Migration |
migration- |
Add/remove attributes, state: absent |
| MEDIUM |
File Formatting |
format- |
Canonical key order; infrahubctl schema format (offline) |
| HIGH |
Validation |
validation- |
Load-time string-length caps (description / label / identifier), common error messages, pre-check checklist |
Schema File Basics
---
# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
version: "1.0"
generics: # Abstract base definitions (shared attributes/relationships)
- ...
nodes: # Concrete object types
- ...
extensions: # Add attributes/relationships to existing nodes from other files
nodes:
- ...
Always include the $schema comment for IDE validation.
Only version is required at the top level.
Designing for Downstream Consumers
A schema node rarely lives alone. Before finalizing it,
walk through how it will be used by other parts of the
project and add the inheritance / configuration that
those features require:
This audit is the difference between a schema that
"validates" and one that "actually works in the broader
project." Skipping it forces a schema migration once the
downstream feature is wired up — at which point the data
is already loaded.
When the task spans multiple skills (schemas + transforms,
schemas + menus, etc.), load both skills' rules together
rather than treating the boundaries as exclusive.
Design for the cheaper layer
A schema choice can remove the need for Python or
denormalized data downstream. The schema is the cheapest
place to get this right — fixing it later means a
migration on already-loaded data. Before adding a field or
node, check whether a built-in or structural feature
already covers it:
| Signal |
Cheaper layer |
See rule |
| Building any domain from scratch (the marketplace publishes far more than DCIM / location / org — routing, security, compute, and many more) |
Search the whole marketplace and reuse a published schema: infrahubctl marketplace get <ns>/<name> then inherit_from |
yagni-reuse-existing-marketplace-schema |
Copying a value onto a node that's reachable by traversing a relationship (region_code when device.location.region.code exists) |
An indirect relationship traversal; let consumers follow the link |
yagni-denormalized-vs-indirect-relationship |
| Several sibling nodes repeating the same attributes and relationships |
Extract a generic and inherit_from it |
yagni-duplicate-shape-not-extracted-to-generic |
| Defining custom IP address / prefix / VLAN nodes |
inherit_from the built-in primitive (BuiltinIPAddress, BuiltinIPPrefix, IpamVLAN) |
yagni-custom-domain-primitives-instead-of-builtin |
An Attribute + cardinality: one relationship with no inverse on the peer |
Declare the matching inverse so consumers filter in the query, not in Python |
yagni-missing-inverse-forces-python-filter |
| A Profile carrying a single value that never varies across objects |
An attribute default_value — a Profile only earns its cost when values vary or are re-tuned centrally |
yagni-profile-over-default |
| Reaching for an Object Template to share live values, or a Profile to clone a node's child components |
Match the tool to intent: a Profile shares live values, an Object Template clones structure |
yagni-template-profile-confusion |
Enabling generate_profile / generate_template before any Profile or template will use it |
Enable the flag when the defaults/cloning workflow actually exists |
yagni-unused-generate-flag |
These are the schema-side counterparts to the "Before
writing Python" guidance in the checks, transforms, and
generators skills. The repo auditor flags them as advisory
cost-to-fix findings; catching them at design time avoids
both the finding and the later migration.
Workflow
Follow these steps when creating or modifying a schema:
- Gather requirements — Identify the node types,
their attributes, and how they relate to each other.
Ask about hierarchies, dropdowns, and display needs.
- Check the marketplace first — Before modelling
any domain from scratch, search the whole Infrahub
Marketplace and reuse a published schema when one
covers it:
infrahubctl marketplace get <namespace>/<name>, then inherit_from the pulled
generics and add only site-specific attributes.
Discovery, collections (-c), the airgap fallback,
and the required SDK version live in
../infrahub-common/marketplace-reference.md.
- Read relevant rules — Read
rules/naming-conventions.md
for naming constraints,
rules/attribute-defaults-and-types.md
for attribute kinds and defaults, and
rules/relationship-identifiers.md
for bidirectional relationship setup.
- Build the schema YAML — Start with the
$schema
comment and version: "1.0". Define generics first
(if any), then nodes. Apply naming, display, and
relationship rules from step 3.
- Audit downstream consumers — Walk the table in
"Designing for Downstream Consumers" above. If any
node will become an artifact or generator target, add
CoreArtifactTarget to its inherit_from now, per
rules/extension-artifact-target.md.
Adding it later forces a schema migration on loaded data.
- Configure display properties — Set
human_friendly_id, display_label, and
order_weight per
rules/display-human-friendly-id.md
and rules/display-order-weight.md.
- Format the file — Put the keys in the canonical
order before committing so diffs stay small. Run
infrahubctl schema format when your infrahubctl
provides it (offline, no server); otherwise author the
order by hand. See
rules/format-schema-files.md.
- Validate and roll out on a branch — Run
infrahubctl schema check to fix errors per
validation.md and
rules/validation-common-errors.md.
Then apply the change on a dedicated branch, not the
default branch (main by convention, but it can be
renamed): infrahubctl branch create <name> →
schema check --branch <name> →
schema load --branch <name>, and merge via a proposed
change once it looks right. A schema load runs
migrations against loaded data immediately, so on a
shared server the default branch gives no preview and no
per-step undo — the branch does. See
rules/workflow-branch-first.md.
The default branch is only reasonable on a local
throwaway instance.
Production Patterns Worth Knowing
Seven recurring patterns — computed Jinja2 attributes,
cascade-vs-no-action deletes, menu visibility,
branch-agnostic identity, artifact targets, object
templates, and file objects — are documented at the top
of examples.md. Read those before
finalizing a schema; each pattern is easy to miss
when building from scratch and expensive to retrofit
after data is loaded.
Supporting References
1---2name: infrahub-managing-schemas-33description: Creates, validates, formats, and modifies Infrahub schema YAML files — nodes, generics, attributes, relationships, and extensions. Also checks the Infrahub Marketplace for an existing published schema to reuse before modelling a domain from scratch. TRIGGER when: designing data models, adding schema nodes, validating schema definitions, planning schema migrations, looking for an existing/off-the-shelf schema or checking the marketplace for a domain (DCIM, location, routing, etc.), modeling file objects / attachments / uploads (storing PDFs, diagrams, images, certificates, documents as Infrahub objects), formatting or tidying schema files, normalising / canonicalising schema key order, cleaning up noisy schema diffs where every edit reshuffles keys, or running `infrahubctl schema format` (including as a CI gate). DO NOT TRIGGER when: populating data objects, writing checks/generators/transforms, querying live data.4---56# Infrahub Schema Creator78## Overview910Expert guidance for designing and building Infrahub11schemas. Schemas are YAML files defining nodes (concrete12types), generics (abstract base types), attributes,13relationships, and extensions.1415## Project Context1617Existing schemas in this project:18!`find . -name "*.yml" -path "*/schemas/*" -o -name "*schema*" -name "*.yml" 2>/dev/null | head -20`1920Infrahub config (if present):21!`cat .infrahub.yml 2>/dev/null || echo "No .infrahub.yml found"`2223If invoked with arguments (e.g., `/infrahub:managing-schemas Ipam Vlan VlanGroup`),24use the first argument as the namespace and remaining arguments as node names.2526## When to Use2728- Designing new data models or schema nodes29- Adding attributes or relationships to existing schemas30- Setting up hierarchical location trees or component/parent patterns31- Configuring display properties (human_friendly_id, display_label)32- Migrating or refactoring existing schemas33- Debugging schema validation errors3435## Rule Categories3637| Priority | Category | Prefix | Description |38| -------- | -------- | ------ | ----------- |39| CRITICAL | Branch-First Changes | `workflow-` | Load schema onto a branch, not the default branch |40| CRITICAL | Naming | `naming-` | Namespace, node, attribute naming |41| CRITICAL | Relationships | `relationship-` | IDs, peers, component/parent, on_delete |42| HIGH | Attributes | `attribute-` | Defaults, dropdowns, computed Jinja2, branch-agnostic, deprecated |43| HIGH | Hierarchy | `hierarchy-` | Hierarchical generics, parent/children |44| HIGH | Display | `display-` | human_friendly_id, order_weight, menu placement |45| MEDIUM | Extensions | `extension-` | Cross-file via extensions block, artifact targets |46| MEDIUM | Uniqueness | `uniqueness-` | Constraint format, __value suffix |47| MEDIUM | Migration | `migration-` | Add/remove attributes, state: absent |48| MEDIUM | File Formatting | `format-` | Canonical key order; `infrahubctl schema format` (offline) |49| HIGH | Validation | `validation-` | Load-time string-length caps (description / label / identifier), common error messages, pre-check checklist |5051## Schema File Basics5253```yaml54---55# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json56version: "1.0"5758generics: # Abstract base definitions (shared attributes/relationships)59 - ...60nodes: # Concrete object types61 - ...62extensions: # Add attributes/relationships to existing nodes from other files63 nodes:64 - ...65```6667Always include the `$schema` comment for IDE validation.68Only `version` is required at the top level.6970## Designing for Downstream Consumers7172A schema node rarely lives alone. Before finalizing it,73walk through how it will be used by other parts of the74project and add the inheritance / configuration that75those features require:7677| If the node will... | Add to the schema | See |78| ------------------- | ----------------- | --- |79| Be the target of an artifact (group member referenced by an `artifact_definition`) | `inherit_from: [..., CoreArtifactTarget]` on the concrete node | [rules/extension-artifact-target.md](./rules/extension-artifact-target.md) |80| Be the target of a generator (group member referenced by a `generator_definition`) | `inherit_from: [..., CoreArtifactTarget]` on the concrete node | [rules/extension-artifact-target.md](./rules/extension-artifact-target.md) |81| Appear in a custom sidebar menu | `include_in_menu: false` so the auto-menu doesn't duplicate the manual entry | [../infrahub-managing-menus/rules/schema-integration.md](../infrahub-managing-menus/rules/schema-integration.md) |82| Be cloneable as an object template (node + its component children) | `generate_template: true` | [rules/extension-object-template.md](./rules/extension-object-template.md) |83| Provide shared default values across many instances | `generate_profile: true` (+ Profile instances) | [rules/extension-object-profile.md](./rules/extension-object-profile.md) |84| Store an uploaded file (PDF, image, Visio, KMZ, contract, …) | `inherit_from: [..., CoreFileObject]` on the concrete node | [rules/extension-file-object.md](./rules/extension-file-object.md) |85| Be displayed with a stable name across UI lists and APIs | `human_friendly_id` and `display_label` | [rules/display-human-friendly-id.md](./rules/display-human-friendly-id.md) |8687This audit is the difference between a schema that88"validates" and one that "actually works in the broader89project." Skipping it forces a schema migration once the90downstream feature is wired up — at which point the data91is already loaded.9293When the task spans multiple skills (schemas + transforms,94schemas + menus, etc.), load both skills' rules together95rather than treating the boundaries as exclusive.9697## Design for the cheaper layer9899A schema choice can remove the need for Python or100denormalized data downstream. The schema is the cheapest101place to get this right — fixing it later means a102migration on already-loaded data. Before adding a field or103node, check whether a built-in or structural feature104already covers it:105106| Signal | Cheaper layer | See rule |107| ------ | ------------- | -------- |108| Building any domain from scratch (the marketplace publishes far more than DCIM / location / org — routing, security, compute, and many more) | Search the whole marketplace and reuse a published schema: `infrahubctl marketplace get <ns>/<name>` then `inherit_from` | [yagni-reuse-existing-marketplace-schema](../infrahub-auditing-repo/rules/yagni-reuse-existing-marketplace-schema.md) |109| Copying a value onto a node that's reachable by traversing a relationship (`region_code` when `device.location.region.code` exists) | An indirect relationship traversal; let consumers follow the link | [yagni-denormalized-vs-indirect-relationship](../infrahub-auditing-repo/rules/yagni-denormalized-vs-indirect-relationship.md) |110| Several sibling nodes repeating the same attributes and relationships | Extract a generic and `inherit_from` it | [yagni-duplicate-shape-not-extracted-to-generic](../infrahub-auditing-repo/rules/yagni-duplicate-shape-not-extracted-to-generic.md) |111| Defining custom IP address / prefix / VLAN nodes | `inherit_from` the built-in primitive (`BuiltinIPAddress`, `BuiltinIPPrefix`, `IpamVLAN`) | [yagni-custom-domain-primitives-instead-of-builtin](../infrahub-auditing-repo/rules/yagni-custom-domain-primitives-instead-of-builtin.md) |112| An `Attribute` + `cardinality: one` relationship with no inverse on the peer | Declare the matching inverse so consumers filter in the query, not in Python | [yagni-missing-inverse-forces-python-filter](../infrahub-auditing-repo/rules/yagni-missing-inverse-forces-python-filter.md) |113| A Profile carrying a single value that never varies across objects | An attribute `default_value` — a Profile only earns its cost when values vary or are re-tuned centrally | [yagni-profile-over-default](../infrahub-auditing-repo/rules/yagni-profile-over-default.md) |114| Reaching for an Object Template to share live values, or a Profile to clone a node's child components | Match the tool to intent: a Profile shares live values, an Object Template clones structure | [yagni-template-profile-confusion](../infrahub-auditing-repo/rules/yagni-template-profile-confusion.md) |115| Enabling `generate_profile` / `generate_template` before any Profile or template will use it | Enable the flag when the defaults/cloning workflow actually exists | [yagni-unused-generate-flag](../infrahub-auditing-repo/rules/yagni-unused-generate-flag.md) |116117These are the schema-side counterparts to the "Before118writing Python" guidance in the checks, transforms, and119generators skills. The repo auditor flags them as advisory120cost-to-fix findings; catching them at design time avoids121both the finding and the later migration.122123## Workflow124125Follow these steps when creating or modifying a schema:1261271. **Gather requirements** — Identify the node types,128 their attributes, and how they relate to each other.129 Ask about hierarchies, dropdowns, and display needs.1302. **Check the marketplace first** — Before modelling131 *any* domain from scratch, search the whole Infrahub132 Marketplace and reuse a published schema when one133 covers it: `infrahubctl marketplace get134 <namespace>/<name>`, then `inherit_from` the pulled135 generics and add only site-specific attributes.136 Discovery, collections (`-c`), the airgap fallback,137 and the required SDK version live in138 [../infrahub-common/marketplace-reference.md](../infrahub-common/marketplace-reference.md).1393. **Read relevant rules** — Read140 [rules/naming-conventions.md](./rules/naming-conventions.md)141 for naming constraints,142 [rules/attribute-defaults-and-types.md](./rules/attribute-defaults-and-types.md)143 for attribute kinds and defaults, and144 [rules/relationship-identifiers.md](./rules/relationship-identifiers.md)145 for bidirectional relationship setup.1464. **Build the schema YAML** — Start with the `$schema`147 comment and `version: "1.0"`. Define generics first148 (if any), then nodes. Apply naming, display, and149 relationship rules from step 3.1505. **Audit downstream consumers** — Walk the table in151 "Designing for Downstream Consumers" above. If any152 node will become an artifact or generator target, add153 `CoreArtifactTarget` to its `inherit_from` now, per154 [rules/extension-artifact-target.md](./rules/extension-artifact-target.md).155 Adding it later forces a schema migration on loaded data.1566. **Configure display properties** — Set157 `human_friendly_id`, `display_label`, and158 `order_weight` per159 [rules/display-human-friendly-id.md](./rules/display-human-friendly-id.md)160 and [rules/display-order-weight.md](./rules/display-order-weight.md).1617. **Format the file** — Put the keys in the canonical162 order before committing so diffs stay small. Run163 `infrahubctl schema format` when your `infrahubctl`164 provides it (offline, no server); otherwise author the165 order by hand. See166 [rules/format-schema-files.md](./rules/format-schema-files.md).1678. **Validate and roll out on a branch** — Run168 `infrahubctl schema check` to fix errors per169 [validation.md](./validation.md) and170 [rules/validation-common-errors.md](./rules/validation-common-errors.md).171 Then apply the change on a dedicated branch, not the172 default branch (`main` by convention, but it can be173 renamed): `infrahubctl branch create <name>` →174 `schema check --branch <name>` →175 `schema load --branch <name>`, and merge via a proposed176 change once it looks right. A schema load runs177 migrations against loaded data immediately, so on a178 shared server the default branch gives no preview and no179 per-step undo — the branch does. See180 [rules/workflow-branch-first.md](./rules/workflow-branch-first.md).181 The default branch is only reasonable on a local182 throwaway instance.183184## Production Patterns Worth Knowing185186Seven recurring patterns — computed Jinja2 attributes,187cascade-vs-no-action deletes, menu visibility,188branch-agnostic identity, artifact targets, object189templates, and file objects — are documented at the top190of [examples.md](./examples.md). Read those before191finalizing a schema; each pattern is easy to miss192when building from scratch and expensive to retrofit193after data is loaded.194195## Supporting References196197- **[reference.md](./reference.md)** -- Complete property198 tables for nodes, generics, attributes, relationships199- **[examples.md](./examples.md)** -- Full schema patterns200 from production repos201- **[validation.md](./validation.md)** -- `infrahubctl`202 commands, migration strategies, pre-validation checklist203- **[../infrahub-common/infrahub-yml-reference.md](../infrahub-common/infrahub-yml-reference.md)**204 -- .infrahub.yml project configuration205- **[../infrahub-common/marketplace-reference.md](../infrahub-common/marketplace-reference.md)**206 -- reusing published marketplace schemas and collections207 (`infrahubctl marketplace get` / `list` / `search` / `show`, airgap)208- **[../infrahub-common/rules/](../infrahub-common/rules/)** -- Shared rules209 (git integration, caching) across all skills210- **[../infrahub-common/rules/workflow-information-priority.md](../infrahub-common/rules/workflow-information-priority.md)**211 -- Skill content first; how to consult `docs.infrahub.app`212 on a genuine gap (e.g. deleting nodes)213- **[rules/](./rules/)** -- Individual rules by category214 prefix