This document is for an unreleased version of Crossplane.

This document applies to the Crossplane master branch and not to the latest release v2.0.

Crossplane v2 introduces significant improvements while maintaining backward compatibility with most v1 configurations. This guide helps you upgrade from Crossplane v1.x to v2.

Learn about new features in Crossplane v2 including namespaced resources, the ability to compose any Kubernetes resource, and new operational workflows.

Important
Only upgrade to Crossplane v2 from Crossplane v1.20, the final v1.x release. If you’re running an earlier version, upgrade to v1.20 first.
Note
There’s no automated tooling yet to migrate existing v1 cluster-scoped XRs and MRs to v2 namespaced style. You can upgrade to Crossplane v2 and start using the new namespaced features right away - your existing v1 resources continue working unchanged. See crossplane/crossplane#6726 for migration tooling progress.

Prerequisites

Before upgrading, ensure:

  • You’re running Crossplane v1.20
  • You’re not using deprecated features (see removed features)
  • All packages use fully qualified image names
  • Helm version v3.2.0 or later

Removed features

Crossplane v2 removes these deprecated features:

Native patch and transform composition

Deprecated in: v1.17
Replaced by: Composition functions

If you’re using spec.mode: Resources in your Compositions, migrate to composition functions before upgrading.

Migration help: use the Crossplane v1.20 CLI to automatically convert your Compositions:

1# Convert patch and transform to function pipelines  
2crossplane beta convert pipeline-composition old-composition.yaml -o new-composition.yaml

ControllerConfig type

Deprecated in: v1.11
Replaced by: DeploymentRuntimeConfig

Update any ControllerConfig resources to use DeploymentRuntimeConfig instead.

Migration help: use the Crossplane v1.20 CLI to automatically convert your ControllerConfigs:

1# Convert ControllerConfig to DeploymentRuntimeConfig
2crossplane beta convert deployment-runtime controller-config.yaml -o deployment-runtime-config.yaml

External secret stores

Status: alpha feature, unmaintained

If you’re using external secret stores, migrate to native Kubernetes secrets or External Secrets Operator before upgrading.

Default registry flag

Removed: --registry flag for default package registry

All packages must now use fully qualified names including the registry host name. Check your packages with:

1kubectl get pkg -o wide

Update any packages without registry host names before upgrading. For example:

  • crossplane-contrib/provider-aws-s3:v1.23.0
  • xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v1.23.0

Who can upgrade

You can upgrade to Crossplane v2 if you meet these criteria:

  • ✅ Running Crossplane v1.20
  • ✅ Not using native patch and transform composition
  • ✅ Not using ControllerConfig resources
  • ✅ Not using external secret stores
  • ✅ All packages use fully qualified image names

If you’re using any removed features, migrate away from them first.

Upgrade approach

The recommended upgrade approach:

  1. Prepare for upgrade
  2. Upgrade Crossplane core
  3. Configure managed resource activation policies
  4. Upgrade providers
  5. Start using v2 features

1. Prepare for upgrade

Review your cluster for removed features and address any that you’re using. Each removed feature section includes commands to inspect your cluster and migration tools to help convert resources.

2. Upgrade Crossplane core

Add the Crossplane Helm repository:

1helm repo add crossplane-stable https://charts.crossplane.io/stable
2helm repo update

Upgrade to Crossplane v2:

1helm upgrade crossplane \
2  --namespace crossplane-system \
3  crossplane-stable/crossplane

Verify the upgrade:

1kubectl get pods -n crossplane-system

3. Configure managed resource activation policies

Crossplane v2 automatically creates a default MRAP that activates all managed resources (*). Before installing v2 providers, you can optionally customize this for better cluster resource efficiency.

Check what managed resources you use:

1# See your managed resource types
2kubectl get managed

Optionally, replace the default MRAP with a targeted one that activates only the resources you need:

1# Delete the default catch-all MRAP
2kubectl delete mrap default

Create a targeted MRAP:

 1apiVersion: apiextensions.crossplane.io/v1alpha1
 2kind: ManagedResourceActivationPolicy
 3metadata:
 4  name: my-resources
 5spec:
 6  activate:
 7  # Legacy cluster-scoped resources (existing v1 resources)
 8  - buckets.s3.aws.upbound.io
 9  - instances.ec2.aws.upbound.io
10  
11  # Modern namespaced resources (new v2 resources)  
12  - buckets.s3.aws.m.upbound.io
13  - instances.ec2.aws.m.upbound.io
Tip
Notice the distinction: s3.aws.upbound.io (legacy cluster-scoped) vs s3.aws.m.upbound.io (v2 namespaced). The .m. indicates modern namespaced managed resources.

4. Upgrade providers

Upgrade your providers to versions that support both namespaced and cluster-scoped managed resources:

1# Check current provider versions
2kubectl get providers

Update your provider manifests to use v2 versions:

1apiVersion: pkg.crossplane.io/v1
2kind: Provider
3metadata:
4  name: crossplane-contrib-provider-aws-s3
5spec:
6  package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0
Note
Provider v2 releases support both legacy cluster-scoped and new namespaced managed resources. Your existing cluster-scoped MRs continue working unchanged.

5. Start using v2 features

After upgrading, you can begin using Crossplane v2 features:

Updating compositions for v2

Existing Compositions work with Crossplane v2 with minimal changes. v2 managed resources are schematically identical to v1 managed resources - they’re just namespaced.

To use v2 namespaced managed resources in compositions:

  1. Update the API group from .crossplane.io to .m.crossplane.io
  2. Check the API version - v2 namespaced providers often reset the API version to v1beta1

For example provider-aws-s3:v2.0.0 has two Bucket MRs:

  • apiVersion: s3.aws.upbound.io/v1beta2 - Legacy, cluster scoped
  • apiVersion: s3.aws.m.upbound.io/v1beta1 - Namespaced

The spec.forProvider and status.atProvider fields are schematically identical.

Tip
Use kubectl get mrds to see available MR API versions.
Note
Not all providers use .crossplane.io domains. For example, provider-aws-s3 uses .upbound.io domains for historical reasons. The general pattern for namespaced resources is adding .m to the existing domain: <domain> becomes m.<domain> (like upbound.iom.upbound.io or crossplane.iom.crossplane.io).

Before (v1 cluster-scoped):

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  name: my-app
 5spec:
 6  compositeTypeRef:
 7    apiVersion: example.crossplane.io/v1
 8    kind: XBucket
 9  mode: Pipeline
10  pipeline:
11  - step: create-bucket
12    functionRef:
13      name: crossplane-contrib-function-go-templating
14    input:
15      apiVersion: gotemplating.fn.crossplane.io/v1beta1
16      kind: GoTemplate
17      source: Inline
18      inline:
19        template: |
20          apiVersion: s3.aws.upbound.io/v1beta2
21          kind: Bucket
22          metadata:
23            name: {{ .observed.composite.resource.metadata.name }}
24          spec:
25            forProvider:
26              region: us-east-2

After (v2 namespaced):

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  name: my-app
 5spec:
 6  compositeTypeRef:
 7    apiVersion: example.crossplane.io/v1
 8    kind: Bucket
 9  mode: Pipeline
10  pipeline:
11  - step: create-bucket
12    functionRef:
13      name: crossplane-contrib-function-go-templating
14    input:
15      apiVersion: gotemplating.fn.crossplane.io/v1beta1
16      kind: GoTemplate
17      source: Inline
18      inline:
19        template: |
20          apiVersion: s3.aws.m.upbound.io/v1beta1  # Added .m, reset to v1beta1
21          kind: Bucket
22          metadata:
23            name: {{ .observed.composite.resource.metadata.name }}
24          spec:
25            forProvider:
26              region: us-east-2
Tip

Namespace handling in compositions:

  • Namespaced XRs: Don’t specify metadata.namespace in templates. Crossplane ignores template namespaces and uses the XR’s namespace.
  • Modern cluster-scoped XRs (scope: Cluster): Can compose resources in any namespace. Include metadata.namespace in templates to specify the target namespace.
  • Legacy cluster-scoped XRs (scope: LegacyCluster): Can’t compose namespaced resources.

Legacy resource behavior

Your existing v1 resources continue working in Crossplane v2:

  • Legacy cluster-scoped XRs: Continue working with claims support
  • Legacy cluster-scoped MRs: Continue working unchanged
  • Existing Compositions: Continue working with legacy XRs

These resources use LegacyCluster scope internally and maintain full backward compatibility.

For example, existing v1-style XRDs continue working with claims:

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: CompositeResourceDefinition
 3metadata:
 4  name: xdatabases.example.crossplane.io
 5spec:
 6  # v1 XRDs default to LegacyCluster scope (shown explicitly)
 7  scope: LegacyCluster
 8  group: example.crossplane.io
 9  names:
10    kind: XDatabase
11    plural: xdatabases
12  claimNames:
13    kind: Database
14    plural: databases
15  # schema definition...

Users can create claims that work as before:

1apiVersion: example.crossplane.io/v1
2kind: Database
3metadata:
4  name: my-database
5  namespace: production
6spec:
7  engine: postgres
8  size: large

Next steps

After upgrading:

  1. Explore namespaced resources: Try creating XRs and MRs in namespaces
  2. Build app compositions: Use v2’s ability to compose any Kubernetes resource
  3. Try Operations: Experiment with operational workflows
  4. Plan migration: Consider which existing resources to migrate to v2 patterns

Read more about what’s new in v2 and explore the updated composition documentation.