Cue Kind Definition
Grafana LabsOfficialPublished by Grafana Labs
- Skill
- Tool integration
- Cloud and DevOps
- Apache-2.0
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured approach to defining resource kinds within the grafana-app-sdk ecosystem using CUE. It covers the full lifecycle of kind management, including scaffolding new resources, defining complex schemas with field constraints, versioning, and configuring code generation for Go and TypeScript. It ensures that developers can maintain consistent, type-safe resource models while adhering to the framework's specific manifest and directory requirements.
What it does
- Scaffolds new resource kinds using the CLI.
- Defines spec and status schemas with advanced CUE constraints like regex, enums, and ranges.
- Supports reusable struct definitions using # prefixed types.
- Manages multi-version resource schemas and manifest registration.
- Configures per-kind code generation settings for Go and TypeScript.
- Facilitates the definition of custom API routes at the kind or version level.
When to use it
- When creating a new resource type for a grafana-app-sdk application.
- When adding a new version to an existing resource kind.
- When defining or updating schema field constraints and validation rules.
- When you need to define reusable struct types for complex resource models.
- When adding custom API routes to your application.
Advantages
- Ensures type safety through CUE's constraint-based language.
- Automates the generation of Go and TypeScript code from schema definitions.
- Provides a standardized directory structure for maintainable projects.
- Supports explicit versioning to handle breaking changes safely.
- Reduces manual boilerplate by integrating directly with the SDK's generation tools.
Usage examples
- “How do I add a new resource kind called 'DashboardConfig' to my app?”
- The agent will guide you to run 'grafana-app-sdk project kind add DashboardConfig', edit the generated .cue files in the kinds/ directory, and execute 'grafana-app-sdk generate' to create the necessary Go and TypeScript artifacts.
- “I need to add a regex constraint to a string field in my schema.”
- The agent will show you how to use the '&' operator with the regex pattern, such as 'myField: string & =~"^[a-z]+"'.
- “How do I create a new version for an existing kind?”
- The agent will instruct you to copy the existing version file, rename the top-level object, register the new version in 'kinds/manifest.cue', and re-run the generation command.
- “How can I define a reusable struct for my spec?”
- The agent will explain how to define a '#'-prefixed type within the schema block, which allows you to reuse the struct across multiple fields and ensures it is generated as a named type.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/grafana/cue-kind-definition/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip cue-kind-definition.zip -d .agents/skills
Documentation by Grafana Labs
CUE Kind Definition
Common Workflows
Adding a new kind
# 1. Scaffold the kind files
grafana-app-sdk project kind add MyKind --overwrite
# Produces kinds/mykind.cue + kinds/mykind_v1alpha1.cue + updates kinds/manifest.cue.
# 2. Edit the generated .cue files — fill in schema.spec / schema.status fields
# 3. Generate types and clients
grafana-app-sdk generate
# 4. Verify the generated artifacts exist
ls pkg/generated/ # should contain new types for MyKindIf generate fails with CUE errors:
Read the error — CUE prints the offending file + line + which constraint failed
Common causes: missing required field, type mismatch (e.g.
stringfield assigned anint), unresolved reference between version filesFix the
.cuesource, re-rungrafana-app-sdk generate. Never edit files underpkg/generated/— they're overwritten on every run.
Adding a new version to an existing kind
# 1. Copy the existing version file
cp kinds/mykind_v1alpha1.cue kinds/mykind_v1.cue
# 2. Edit kinds/mykind_v1.cue — rename the top-level object (e.g. myKindv1) and adjust the schema
# 3. Register the new version in kinds/manifest.cue
# Add a versions["v1"]: { schema: myKindv1 } entry
# 4. Re-generate
grafana-app-sdk generate
# 5. Verify both versions were generated — per-version Go types live under pkg/generated/<group>/<version>/
ls pkg/generated/ # should list both version directories (e.g. v1alpha1/ v1/)
# Optionally inspect the CRD spec under definitions/ to confirm both versions appear in `spec.versions[]`Breaking changes (removing fields, changing types, adding required fields) must go into a new version — never modify a stable version (v1, v2) in place.
Kind file structure
The CLI produces a flat layout under kinds/:
kinds/
├── manifest.cue # App manifest + version list declarations
├── mykind.cue # Common (cross-version) kind metadata
└── mykind_v1alpha1.cue # v1alpha1 schema + codegen configFor multi-version kinds, additional version files sit alongside (mykind_v1.cue, etc.). For very large kind sets (10+ kinds), consider the per-kind subdirectory layout — full kind anatomy reference in references/kind-layout.md.
CUE Kind Anatomy
Three layers per kind:
1. Common kind metadata
// kinds/mykind.cue
package kinds
myKind: {
kind: "MyKind" // Required: PascalCase kind name
// other cross-version fields (scope, pluralName, validation, mutation, conversion, …)
// Full field reference in references/kind-layout.md.
}2. Per-version schema
// kinds/mykind_v1alpha1.cue
package kinds
myKindv1alpha1: myKind & {
schema: {
spec: { // desired state — user-set
title: string
description: string | *""
count: int & >=0
enabled: bool | *true
}
status: { // observed state — operator-set
lastObservedGeneration: int | *0
state: string | *""
message: string | *""
}
}
codegen: {
ts: { enabled: true }
go: { enabled: true }
}
}3. App manifest
// kinds/manifest.cue
package kinds
App: {
appName: "my-app"
versions: {
"v1alpha1": { schema: myKindv1alpha1 }
}
}Codegen configuration
Control what gets generated per kind per version:
codegen: {
ts: { enabled: true | false } // TypeScript types
go: { enabled: true | false } // Go types + client
}Disabling go for frontend-only apps avoids unused Go code. Disabling ts for backend-only resources reduces bundle size. Both default to true when omitted.
References
references/kind-layout.md— full common-metadata field reference + app manifest fields + per-kind subdirectory layoutreferences/schema-types.md— CUE schema field types (basic types, constraints, regex, enums, maps, lists) +#-prefixed named type definitionsreferences/custom-routes.md— kind-level + version-level custom routes + handler registration inapp.go
External resources
Files
- ATTRIBUTION.mdAdded by AutoGineer861 B
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md5.3 KiB
- references/cue-constraints.md6.3 KiB
- references/custom-routes.md1.6 KiB
- references/kind-layout.md7.6 KiB
- references/schema-types.md2.0 KiB
- references/versioning-guide.md6.3 KiB
references/versioning-guide.md
Kind Versioning Guide
Overview
grafana-app-sdk kind versioning mirrors Kubernetes API versioning. Each version represents a distinct schema that is independently accessible via the API at /apis/<group>/<version>/. Multiple versions of the same kind can be served simultaneously, which enables non-disruptive schema evolution.
Version Naming Conventions
Follow Kubernetes conventions:
| Stage | Format | Example | Meaning |
|---|---|---|---|
| Alpha | v1alpha1, v1alpha2 | First experiment | May change without notice |
| Beta | v1beta1, v1beta2 | Maturing | Likely to become stable |
| Stable | v1, v2 | Shipped | Breaking changes require new version |
Start with v1alpha1 for new kinds. Graduate to v1beta1 and v1 as the schema stabilizes.
Default Layout (flat)
grafana-app-sdk project kind add generates files directly in kinds/, all sharing package kinds:
kinds/
├── manifest.cue # App manifest
├── myKind.cue # Common metadata (cross-version)
└── myKind_v1alpha1.cue # v1alpha1 schema + codegenMulti-Version Layout
When a kind needs a second version, add a new file alongside the existing ones:
kinds/
├── manifest.cue
├── myKind.cue
├── myKind_v1alpha1.cue
└── myKind_v1.cueUsers working with complex kinds may choose to organise into per-kind and per-version subdirectories (each with its own CUE package). That layout requires importing version packages in the app manifest. The flat default requires no imports — version objects are referenced directly since all files share
package kinds.
What Must Match Across Versions
Fields declared in the common kind metadata object (myKind.cue) must be identical in all versions:
// kinds/myKind.cue — these must match across ALL versions
myKind: {
kind: "MyKind" // identical in all versions
group: "my-app.grafana.app"
// scope, pluralName, etc.
}Per-version schema.spec fields can differ freely.
Backward-Compatible Changes (safe)
These changes can be made to an existing version's schema:
Adding an optional field — existing resources remain valid; new field defaults to its CUE default
Widening a constraint — e.g. changing
int & >=1toint & >=0(more permissive)Adding a new enum value — e.g. adding
"archived"to"pending" | "active"Loosening a regex — making a pattern less restrictive
Breaking Changes (require a new version)
These changes break existing clients and must be introduced via a new version:
Removing a field — existing resources stored with that field become invalid
Renaming a field — clients using the old name stop working
Adding a required field (no default) — existing resources missing the field fail validation
Narrowing a constraint — e.g. making an
intfield require>= 0when< 0was previously allowedChanging a field's type — e.g. changing
stringtointRemoving an enum value — resources with that value become invalid
Adding a New Version
Step 1: Create the new version schema
Create the new version CUE file, and copy the previous version as a starting point, ensuring that the type name has changed (e.g.
mykindv1alpha1: mykind & {...}tomykindv1beta1: mykind & {...}).Add the kind to
manifest.versions[<version>]in the manifest CUE file. If the new version does not exist inmanifest.versions, create a new object inmanifest.cuefor the version, e.g.v1beta1: { kinds: [mykindv1beta1] }And add it to
manifest.versionsusing the version as the map key.
# Scaffold the new version
grafana-app-sdk project kind add MyKind --overwrite
# Then manually copy the v1alpha1 schema to v1/ as a starting pointStep 2: Define the new schema
Create kinds/myKind_v1.cue (flat default layout), starting from the previous version's schema:
package kinds
myKindv1: myKind & {
schema: {
spec: {
// All fields from v1alpha1
title: string & != ""
description: string | *""
// New in v1 — must have a default to not break existing resources
category: "general" | "advanced" | *"general"
// Breaking change from v1alpha1 — that's why we created v1
// In v1alpha1, `config` was a free-form string; in v1 it's a struct
config?: {
timeout: int & >=1 | *30
retries: int & >=0 | *3
}
}
}
codegen: {
ts: { enabled: true }
go: { enabled: true }
}
}Step 3: Register the new version in the app manifest
In the flat layout all objects share package kinds — reference them directly, no imports needed:
// kinds/myapp.cue
package kinds
App: {
versions: {
"v1alpha1": { schema: myKindv1alpha1 }
"v1": { schema: myKindv1 }
}
}Step 4: Run generate
grafana-app-sdk generateBoth versions now have generated Go types and clients.
Deprecation Strategy
When deprecating an old version:
Add the new version to the manifest (both versions served simultaneously)
Announce deprecation in docs/changelog
Migrate existing resources using a conversion webhook or migration job
After all resources are migrated, remove the old version from the manifest
Run
generateto remove the old version's generated code
Storage Version
Only one version is the "storage version" — the canonical form stored in etcd. The Kubernetes API server converts between versions on read/write. The storage version should be the most recent stable version.
In grafana-app-sdk, the storage version is typically configured in the kind's CUE definition or the app manifest. Check the SDK documentation for the current mechanism.
Conversion
When multiple versions are served, resources stored in the storage version need to be converted to other versions on read. Conversion can be:
Automatic — for purely additive changes where the new fields have defaults; the SDK may handle this automatically
Manual — for structural changes; implement a conversion function in Go
Conversion logic lives outside the generated code and is registered when building the app.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- Grafana Labs
- Publisher
- Grafana Labs (Official)
- Source
- grafana/skills · skills/grafana-app-sdk/cue-kind-definition · commit 8fb72a5
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Grafana Labs.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download cue-kind-definition.zip