
The combination of YAML+CEL provides a simple experience to define Kubernetes resources — declare what you want, wire dependencies with CEL expressions, and let the system figure out the order. It's an intuitive model, and we've been hearing from our community that they want this authoring experience inside Crossplane, alongside the many existing languages, functions, and experiences that Crossplane already supports.
We're excited to announce function-kro — a Crossplane composition function that brings the YAML+CEL authoring model of kro into Crossplane's pipeline architecture. Your kro-style resource definitions drop straight into a Composition pipeline step — same syntax, same CEL expressions, unchanged and now running inside Crossplane. function-kro embeds kro's graph builder, CEL evaluator, and runtime engine to offer full feature parity with the latest kro release.
function-kro has been donated to the Crossplane community as a community extension project at crossplane-contrib/function-kro. We've shared it with the kro community and we welcome collaboration and contributions from anyone interested in this experience!
The Function Pipeline
Below is a Crossplane Composition that uses function-kro to define a NetworkingStack platform API — a VPC, Subnet, and SecurityGroup, all wired together with CEL expressions:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: networkingstack
spec:
compositeTypeRef:
apiVersion: example.crossplane.io/v1
kind: NetworkingStack
mode: Pipeline
pipeline:
- step: kro-run
functionRef:
name: function-kro
input:
apiVersion: kro.fn.crossplane.io/v1beta1
kind: ResourceGraph
status:
networkingInfo:
vpcID: ${vpc.status.atProvider.id}
subnetID: ${subnet.status.atProvider.id}
securityGroupID: ${securityGroup.status.atProvider.id}
resources:
- id: vpc
template:
apiVersion: ec2.aws.m.upbound.io/v1beta1
kind: VPC
metadata: {}
spec:
forProvider:
region: ${schema.spec.region}
cidrBlock: 192.168.0.0/16
enableDnsHostnames: false
enableDnsSupport: true
- id: subnet
template:
apiVersion: ec2.aws.m.upbound.io/v1beta1
kind: Subnet
metadata: {}
spec:
forProvider:
region: ${schema.spec.region}
cidrBlock: 192.168.0.0/18
vpcId: ${vpc.status.atProvider.id}
- id: securityGroup
template:
apiVersion: ec2.aws.m.upbound.io/v1beta1
kind: SecurityGroup
metadata: {}
spec:
forProvider:
name: my-sg-${schema.metadata.name}
region: ${schema.spec.region}
description: Default security group for NetworkingStack
vpcId: ${vpc.status.atProvider.id}
- step: auto-ready
functionRef:
name: function-auto-ready
This is a two-step pipeline. Step one — function-kro — handles resource composition using kro's YAML+CEL model. Each resource is defined as a template with ${...} CEL expressions that wire dependencies between them. ${schema.spec.region} pulls from the Composite Resource (XR) spec, while ${vpc.status.atProvider.id} creates a dependency on the VPC's output — the subnets and security group won't be created until the VPC is ready and its ID is available. The status block aggregates data from composed resources back to the XR, so consumers of the NetworkingStack API can read the IDs of each resource directly from their XR’s status.
Step two — function-auto-ready — handles automatic readiness detection. But the pipeline could have ten steps. Need to enforce policy on all resources? Add a step. Need to inject cost-allocation tags? Add a step. Need to pull secrets from an external API? Add a step. Each step is independent — you can add the capabilities you need to support your simple resource definitions.
Again, the resources block in that Composition is identical to what you'd write in a standalone kro ResourceGraphDefinition (RGD). If you already have kro resource definitions, they drop into the pipeline input without changes. And when you need more — multi-cloud implementations, safe rollouts, operational controls — you add pipeline steps without rewriting what you already have. Same composition language, with a growth path when you need more.
SimpleSchema for Crossplane
kro bundles schema definition into the RGD using SimpleSchema — a compact shorthand (e.g., region: string | default=us-west-2) that avoids writing full OpenAPIv3. This is a straightforward experience to define a schema, so we’re making it available in Crossplane’s developer tooling as well.
Given a kro-style RGD that includes SimpleSchema:
apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
name: networkingstack
spec:
schema:
apiVersion: v1
kind: NetworkingStack
spec:
region: string
status:
vpcID: ${vpc.status.atProvider.id}
subnetID: ${subnet.status.atProvider.id}
securityGroupID: ${securityGroup.status.atProvider.id}
resources:
- id: vpc
template:
# ... same resource templates as above
You’d be able to generate the full Crossplane XRD with OpenAPIv3 schema automatically:
crossplane xrd generate --input=rgd rgd.yaml
In other words, SimpleSchema becomes just another schema authoring option in Crossplane, similar to how function-kro is just another composition logic option. Write your schema and logic the way you prefer, and Crossplane handles the rest.
Running Inside Crossplane's Architecture
Now that we've seen the syntax, let's look at how these resource definitions fit into the broader Crossplane platform. Because function-kro is a function pipeline step, your YAML+CEL definitions automatically participate in the capabilities that Crossplane provides to all compositions. Crossplane does add some structure beyond a standalone RGD — it separates your API schema from its implementation — but that structure is what makes the following possible.
Safe rollouts. CompositionRevisions let you pin existing instances to a known-good revision, roll forward team-by-team, and roll back without a rewrite. When you update your composition logic, it doesn't immediately affect every running instance. You control the blast radius.
Pipeline composability. Add functions from a vast ecosystem of pipeline steps alongside your YAML+CEL definitions. Need to call an external API, query a cloud provider, connect to a secret store, or enforce policy? Add a pipeline step — your resource definitions stay untouched. For cross-cutting concerns that push beyond what CEL can express, general-purpose languages like Go and Python are available as pipeline steps in the same Composition.
Multi-implementation APIs. Define one API backed by multiple Compositions. The same NetworkingStack API can be backed by AWS, GCP, and Azure. One schema, many implementations. Platform teams define the API once and provide region or cloud-specific implementations behind it.
Operational controls. Pausing reconciliation, management policies, and resource lifecycle control. When something goes wrong at 2 AM, you can stop reconciling a specific instance with a single annotation without touching the Composition or affecting other instances.
Developer experience. Shift-left your testing and validation with crossplane render, unit tests, and integration tests. Run a Composition locally against a sample XR before deploying and see the same outputs that would surface at runtime. Diff your logic against a live environment with crossplane-diff. Develop your logic in a modular fashion with Crossplane’s DevEx tooling.
Extensibility Offers Choice
Crossplane's composition function architecture was designed around a simple idea: the platform team should pick the authoring model that fits their needs and skills. function-kro adds YAML+CEL to that already expansive menu.
Today, Crossplane supports KCL for streamlined expressiveness, Go and Python for teams that want full programming languages, YAML templating for those familiar with Helm, HCL for those coming from Terraform, and many others. Now YAML+CEL can be added to that list for teams that like declarative resource definitions with simple expressions to wire them all up.
You don't have to choose one approach for everything - different Compositions can use different functions, and a single pipeline can mix multiple languages across steps.
Get Involved
We're continuing to track upstream kro releases and will keep function-kro current as the kro project evolves. We're also expanding our library of working examples — YAML+CEL has been added to the getting started with composition guide in the Crossplane docs and the function repo includes multiple examples for basic composition, conditionals, readiness checks, collections, and external references to help you get started.
We welcome the kro community to start using and contributing to this new function! We love to hear from all users, as they are exactly what makes this project great. Whether you are a developer, user, or just interested in what we're up to, feel free to reach out via any of the following: