Skip to content
Back to skills

Cue Kind Definition

ASecurity

Use when working with CUE kind definitions, schemas, or versioning in grafana-app-sdk projects (app platform apps). This skill should be used when the user asks to "define a kind", "add a CUE kind", "write a kind schema", "create a CUE schema", "model a resource", "add a new resource type", "edit kinds/", "what is a kind in grafana-app-sdk", "add a version to a kind", or asks about CUE kind structure, versioning, schema fields, validation constraints, or the codegen configuration section. Pro...

  • 268 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 11, 2026
devopstypescriptgobashkubernetesgitapifrontendbackend

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned June 11, 2026

npx -y skills add grafana/skills --skill cue-kind-definition --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cue Kind Definition?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Cue Kind Definition
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/grafana-cue-kind-definition/badge)](https://www.skillsdirectory.com/skills/grafana-cue-kind-definition)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: cue-kind-definition
license: Apache-2.0
description: Use when working with CUE kind definitions, schemas, or versioning in grafana-app-sdk projects (app platform apps). This skill should be used when the user asks to "define a kind", "add a CUE kind", "write a kind schema", "create a CUE schema", "model a resource", "add a new resource type", "edit kinds/", "what is a kind in grafana-app-sdk", "add a version to a kind", or asks about CUE kind structure, versioning, schema fields, validation constraints, or the codegen configuration section. Provides guidance on authoring CUE kind definitions for grafana-app-sdk projects.
---

# CUE Kind Definition

Kinds are the schema definitions that drive the entire grafana-app-sdk code generation pipeline. Each kind describes a Kubernetes-style resource type: its name, versions, and per-version schema. All Go types, TypeScript types, API clients, CRD manifests, and the AppManifest are generated from these CUE files.

## Adding a Kind

Use the CLI to scaffold a kind before editing:

```bash
grafana-app-sdk project kind add <KindName> --overwrite
```

This creates a `.cue` file with scaffolding, field comments, and example values. Read the generated comments carefully — they explain every field's purpose.

> Always use `--overwrite` when re-running to regenerate scaffolding without losing manual additions.

## Kind File Structure

`grafana-app-sdk project kind add` creates files directly in `kinds/` — the default layout is flat, all in `package kinds`:

```
kinds/
├── manifest.cue           # App manifest + version list declarations
├── mykind.cue             # Common (cross-version) kind metadata
└── mykind_v1alpha1.cue    # v1alpha1 schema + codegen config
```

For multi-version kinds the additional version files sit alongside:

```
kinds/
├── manifest.cue
├── mykind.cue
├── mykind_v1alpha1.cue
└── mykind_v1.cue
```

> For larger, more complex kind definitions users may choose to organise kinds into per-kind and per-version subdirectories, each with their own package. The default CLI output uses the flat layout above.

## CUE Kind Anatomy

A complete kind definition has three layers:

### 1. Common kind metadata (shared across versions)

```cue
// kinds/mykind.cue
package kinds

myKind: {
    kind: "MyKind"               // Required: the kind name (PascalCase)
    // other cross-version fields (scope, pluralName, validation, mutation, conversion, etc.)
    // See references/kind-layout.md for the full field reference
}
```

### 2. Per-version schema (one file per version)

Each version joins the common metadata with its own schema via CUE's `&` operator:

```cue
// kinds/mykind_v1alpha1.cue
package kinds

myKindv1alpha1: myKind & {
    // Version-specific schema
    schema: {
        // spec: desired state — set by users/clients, never by the operator
        spec: {
            title:       string
            description: string | *""     // optional with default
            count:       int & >=0
            enabled:     bool | *true
        }
        // status: observed state — written only by the operator/reconciler,
        // never by users. Mirrors Kubernetes spec/status conventions.
        status: {
            lastObservedGeneration: int | *0
            state:                  string | *""
            message:                string | *""
        }
    }

    // Code generation config
    codegen: {
        ts: { enabled: true }   // generate TypeScript types
        go: { enabled: true }   // generate Go types and client
    }
}
```

### 3. App manifest (version registration)

Since all files share `package kinds`, version objects are referenced directly — no imports needed in the flat layout:

```cue
// kinds/manifest.cue
package kinds

App: {
    appName: "my-app"
    versions: {
        "v1alpha1": {
            schema: myKindv1alpha1
        }
    }
}
```

## spec vs status

This distinction follows Kubernetes conventions exactly:

**`spec`** — desired state. Written by users and clients. The operator reads `spec` and works to make the world match it. Admission handlers validate and mutate `spec`. Never write to `spec` from a reconciler.

**`status`** — observed state. Written only by the operator/reconciler after it has done work. Users and clients should treat `status` as read-only. Admission handlers must not modify `status`.

Typical `status` fields:

```cue
status: {
    // Generation of the spec that was last successfully reconciled.
    // Set to metadata.generation after a successful reconcile loop.
    lastObservedGeneration: int | *0

    // Human-readable summary of current state
    state:   string | *""   // e.g. "Ready", "Provisioning", "Error"
    message: string | *""   // detail, especially on error

    // References to objects created by the reconciler.
    // e.g. the name of a ConfigMap or Deployment the reconciler provisioned.
    provisionedConfigMap: string | *""
    provisionedServiceAccount: string | *""
}
```

Fields that belong in `status`, not `spec`:
- Anything the operator computes or creates (IDs, names, URLs of provisioned resources)
- `lastObservedGeneration` / `observedGeneration`
- `conditions` (Kubernetes-style condition arrays)
- Current health or lifecycle state (`"Ready"`, `"Degraded"`, etc.)
- Timestamps of when the operator last acted

Fields that belong in `spec`, not `status`:
- Everything the user configures as desired state
- References to *existing* resources the user wants the app to interact with (the operator looks these up, it doesn't create them)

## Type Definitions with `#`

CUE supports named type definitions using the `#` prefix inside a `schema` block. Each `#Definition` generates a named Go struct and TypeScript interface alongside the kind's `Spec` type.

```cue
schema: {
    #Threshold: {
        value:    float & >=0
        severity: "info" | "warning" | "critical"
        message:  string | *""
    }

    #ResourceRef: {
        name:      string & != ""
        namespace: string | *"default"
    }

    spec: {
        title:          string & != ""
        alertThreshold: #Threshold
        thresholds:     [...#Threshold]  // list of a defined type
        targetRef?:     #ResourceRef     // optional
    }
}
```

`#` definitions are scoped to the `schema` block they are declared in.

**Prefer `#` definitions when:**
- A struct is used in more than one field
- A struct is large or complex enough that inlining hurts readability
- A struct appears in a list (`[...#MyType]`)

**Inline structs are fine when:**
- The struct is small and simple (2-3 fields) or shallow
- It is used in only one place and unlikely to be reused

Maps (`{[string]: string}`) and lists of scalars (`[...string]`) are always fine inline.

## Schema Field Types

CUE is a superset of JSON. Commonly used types and constraints:

```cue
// Basic types
myString:  string
myInt:     int
myFloat:   float
myBool:    bool
myBytes:   bytes

// Optional with default
name: string | *"default-value"

// Constraints (using & to intersect)
port:     int & >=1 & <=65535
label:    string & =~"^[a-z][a-z0-9-]*$"  // regex constraint

// Enums (disjunctions)
status:   "pending" | "active" | "archived"

// Maps (always fine inline)
labels: {[string]: string}
attrs:  {[string]: _}

// Lists of scalars (fine inline)
tags: [...string]

// Optional field
description?: string
```

## Custom Routes in CUE

Routes can be defined at two levels. Both require corresponding Go handlers registered in `app.go`.

### Kind-level routes

```cue
MyKind: {
    kind: "MyKind"
    schema: { ... }

    routes: {
        "/actions/process": {
            "POST": {
                name: "processMyKind"  // unique within version; must start with a k8s verb
                request: {
                    body: {
                        reason: string
                    }
                }
                response: {
                    jobId:  string
                    status: string
                }
            }
        }
    }
}
```

### Version-level routes

```cue
versions: {
    "v1alpha1": {
        routes: {
            namespaced: {
                "/summary": {
                    "GET": {
                        name: "getNamespacedSummary"
                        response: { count: int }
                    }
                }
            }
            cluster: {
                "/health": {
                    "GET": {
                        name: "getHealth"
                        response: { status: string }
                    }
                }
            }
        }
    }
}
```

After adding routes, run `grafana-app-sdk generate` — routes are included in the AppManifest and `ValidateManifest` will fail if a handler is missing.

## Version Compatibility Rules

When a kind has multiple versions, **fields declared in the common metadata object must match across all versions**. Schema fields (inside `schema.spec`) can differ per version, but:

- The `kind` field must be identical in every version
- Breaking changes (removing fields, changing types, adding required fields) must be introduced via a new version — never by modifying a stable version (`v1`, `v2`)
- Use `status` for server-managed fields; never put mutable server state in `spec`

## Codegen Configuration

Control what gets generated per kind per version:

```cue
codegen: {
    ts: { enabled: true | false }   // TypeScript types
    go: { enabled: true | false }   // Go types + client
}
```

Disabling `go` for frontend-only apps avoids generating unused Go code. Disabling `ts` for backend-only resources reduces TypeScript bundle size. Both default to `true` when omitted.

## After Editing Kinds

Always run generate after any change to `.cue` files:

```bash
grafana-app-sdk generate
```

The generated files in `pkg/generated/` must never be edited manually — they are overwritten on every generate run.

## Resources

- [grafana-app-sdk GitHub](https://github.com/grafana/grafana-app-sdk)
- [CUE Language Reference](https://cuelang.org/docs/)
- [Example kinds layout](https://github.com/grafana/grafana/tree/main/apps/example/kinds)

Files in this skill

  • SKILL.md10 KB
  • references/cue-constraints.md6.3 KB
  • references/kind-layout.md7.6 KB
  • references/versioning-guide.md6.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.