Back to blog

Custom Resource Definitions: Extending Kubernetes With Your Own APIs

May 6, 2026 11 min read Kubexer Team
CRDOperatorsKubernetes APIExtensibility

One of the most powerful ideas in Kubernetes is that its API is extensible. You are not limited to the built-in kinds like Pod, Service, and Deployment. With a Custom Resource Definition (CRD) you can teach the API server an entirely new object type — a Database, a Certificate, a Kafka cluster — and then manage it with the same kubectl, the same RBAC, and the same declarative model as everything else. This is the mechanism behind nearly every operator and add-on you have ever installed.

What a CRD actually is

A CRD is itself a Kubernetes object. When you apply one, you are registering a new endpoint on the API server. After that, the API knows about your new kind and will store, validate, and serve instances of it — the custom resources — exactly like native objects. The CRD is the schema; the custom resources are the data.

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: backups.ops.example.com
spec:
  group: ops.example.com
  scope: Namespaced
  names:
    plural: backups
    singular: backup
    kind: Backup
    shortNames: ["bk"]
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                schedule:
                  type: string
                retentionDays:
                  type: integer
              required: ["schedule"]
      additionalPrinterColumns:
        - name: Schedule
          type: string
          jsonPath: .spec.schedule
        - name: Retention
          type: integer
          jsonPath: .spec.retentionDays

Apply that, and you can immediately create instances:

apiVersion: ops.example.com/v1
kind: Backup
metadata:
  name: nightly
spec:
  schedule: "0 2 * * *"
  retentionDays: 14
kubectl get backups
# NAME      SCHEDULE      RETENTION
# nightly   0 2 * * *     14

The pieces that make CRDs feel native

  • Group, version, kind. Just like built-ins, your resource has an API group (ops.example.com), a version (v1), and a kind (Backup). Versions let you evolve the schema safely.
  • Scope. A CRD is either Namespaced or Cluster-scoped, mirroring how Deployments are namespaced but Nodes are not.
  • Schema validation. The openAPIV3Schema means the API server rejects malformed resources before they are ever stored — required fields, types, and value constraints are enforced for you.
  • Printer columns. additionalPrinterColumns control what kubectl get shows, so each custom kind displays the fields that matter for it instead of just name and age.
  • RBAC, labels, events. Custom resources work with the same RBAC verbs, label selectors, and event machinery as anything else.

A CRD alone does nothing — enter the operator

This is the part newcomers miss. Creating a Backup resource stores a record, but nothing actually takes a backup. A CRD is just an API; you need a controller to give it behavior. A controller is a program running in the cluster that watches for custom resources and reconciles the real world to match them:

  1. Watch for Backup objects.
  2. For each one, ensure a CronJob exists that runs on its schedule.
  3. Enforce retentionDays by pruning old backups.
  4. Write status back to the resource so users can see what happened.

A CRD plus a controller that encodes operational knowledge is exactly what the community calls an operator. cert-manager (Certificate), Prometheus Operator (ServiceMonitor), and most database operators are all this pattern: a custom API, plus a controller that does the work.

The reconciliation mindset

Controllers follow the same level-based reconciliation loop the rest of Kubernetes uses: observe desired state (the spec), observe actual state, and take action to close the gap — repeatedly, idempotently. They do not run once; they continuously converge. This is why the model is so robust: delete the CronJob the operator created and it will recreate it, because reconciliation never stops.

Versioning and conversion

Real CRDs evolve. The versions list lets you serve v1alpha1 and v1 simultaneously, mark one as the storage version, and — with a conversion webhook — translate between them transparently. Plan for this early: schema changes are far easier when you have a versioning story than when you are retrofitting one onto resources already in production.

Working with CRDs day to day

The challenge with custom resources is discovery. A busy cluster might have dozens of CRDs from operators you installed months ago, each with its own kinds, scopes, and fields. kubectl get crds lists the definitions, but browsing instances means knowing each plural name and remembering which fields matter. Tooling that lists every CRD, lets you browse instances with their own printer columns, and adapts its navigation to whatever is installed turns that sprawl into something navigable. In Kubexer, the Custom Resources view does exactly this — it reads the CRDs your cluster exposes and renders their instances with the same live tables and detail drawers as built-in resources, including each CRD's printer columns, so operator-backed objects are as easy to inspect as a Pod.

Wrapping up

CRDs turn Kubernetes from a container orchestrator into a platform for building your own declarative APIs. Define the schema, add printer columns and validation so the resource feels native, and pair it with a controller to give it behavior — that combination is the operator pattern powering most of the ecosystem. Once you internalize that custom resources are just APIs waiting for a reconciler, a huge amount of the Kubernetes landscape suddenly makes sense.