1. What is a Patch?#
In Crossplane, a Patch is an instruction inside a Composition that tells the reconciliation engine to copy a value from one place and paste it into another.
There are four primary types of patches:
- FromCompositeFieldPath: (The most common). Takes data from the XR (the Developer’s request) and writes it into the MR (AWS).
- ToCompositeFieldPath: Takes data from the physical AWS MR (like an auto-generated VPC ID) and writes it up to the XR, so the Developer can see it.
- CombineFromComposite: Takes multiple fields from the XR, concatenates them together, and writes them to the MR.
- Environment: Pulls data from a global cluster environment configuration (covered in Episode 7).
2. Implementing a Basic Patch#
Let’s fix the Composition we wrote in Episode 5. We want the storageGB parameter from the developer’s Claim to dictate the allocatedStorage on the AWS RDS instance.
Open your composition-postgres.yaml and add a patches block to the rds-instance resource:
resources:
- name: rds-instance
base:
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
spec:
forProvider:
# We REMOVE allocatedStorage from the base entirely!
region: us-east-1
engine: postgres
instanceClass: db.t3.micro
# 1. Add the Patches Block
patches:
# 2. Define the Patch
- type: FromCompositeFieldPath
# 3. Where is the data coming from? (Inside the XR)
fromFieldPath: spec.parameters.storageGB
# 4. Where is the data going? (Inside the MR)
toFieldPath: spec.forProvider.allocatedStorageHow it Works#
- The developer submits a claim with
spec.parameters.storageGB: 50. - Crossplane evaluates the Composition.
- The Patcher reads the value
50from the XR. - It injects
allocatedStorage: 50into thebaseof the MR before sending it to the AWS Provider.
3. What is a Transform?#
A Patch moves data 1-to-1. But what if the data types don’t match, or you need to run logic?
For example, the Developer requests environment: prod.
But the AWS RDS API doesn’t know what “prod” means. AWS expects an instanceClass string, like db.m5.large.
We must Transform the input data while it is in transit during the Patch.
The Map Transform#
The map transform acts like a switch statement in programming.
Let’s update our patches block to handle the environment string:
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.environment
toFieldPath: spec.forProvider.instanceClass
# Add a Transform!
transforms:
- type: map
map:
# If XR says "dev", send "db.t3.micro" to AWS
dev: db.t3.micro
# If XR says "prod", send "db.m5.large" to AWS
prod: db.m5.largeThe String Transform#
Often, you need to enforce naming conventions. If a developer names their database my-db, you might want the physical AWS bucket to be named acmecorp-my-db-prod.
You can use a string transform with fmt (Go-style string formatting):
- type: FromCompositeFieldPath
fromFieldPath: metadata.name
toFieldPath: spec.forProvider.identifier
transforms:
- type: string
string:
fmt: "acmecorp-%s-db"If the XR is named auth-service, the AWS RDS identifier will become acmecorp-auth-service-db.
4. Bi-Directional Patching (ToComposite)#
So far, we have only passed data down from the XR to the MR. But what happens after the database boots? AWS will dynamically assign an Endpoint URL. The application developer needs that URL!
We must patch data up from AWS to the XR.
First, you must update your XRD (xrd-postgres.yaml) to define a place to hold the output data:
# Inside xrd-postgres.yaml -> openAPIV3Schema -> properties
status:
type: object
properties:
connectionUrl:
type: string
description: "The live AWS endpoint for the database."Then, in your Composition, add a ToCompositeFieldPath patch:
patches:
- type: ToCompositeFieldPath
# Read from the physical AWS MR status
fromFieldPath: status.atProvider.endpoint
# Write to the XR status
toFieldPath: status.connectionUrlNow, the Application Developer can run kubectl describe postgresqlinstance app-db and they will see the live AWS Endpoint URL printed in the status of their Claim!
Troubleshooting & Common Errors#
cannot apply patch: invalid type- Root Cause: You tried to patch a
stringfrom the XR into anintegerfield in the MR (or vice versa). - Solution: Use a
converttransform inside your patch block to explicitly cast the variable (e.g.,transforms: [{ type: convert, toType: int }]).
- Root Cause: You tried to patch a
The
maptransform returns an empty value- Root Cause: The developer provided a value that doesn’t exist in your map dictionary, and you did not provide a fallback.
- Solution: Always validate the input in your XRD using
enum, or provide a default value in your Composition using themathormapfallback properties.
Conclusion & Next Steps#
Patches and Transforms are the programming logic of Crossplane Compositions. By mastering them, you can build incredibly complex, intelligent vending machines that translate simple developer intents into robust cloud architectures.
However, writing region: us-east-1 50 times across 10 different Compositions is a massive violation of the DRY (Don’t Repeat Yourself) principle. What happens if the company decides to migrate to us-west-2? You would have to rewrite every YAML file.
In Episode 7: Environment Configs, we will learn how to extract global variables into cluster-wide configurations, allowing your Compositions to dynamically adapt based on the Kubernetes cluster they are deployed in.

