Repository: crossplane/crossplane

Issue:
Promote Usage API to Beta
### What problem are you facing?

This is the tracking issue for promoting the Usage API (a.k.a. deletion ordering) to Beta.
With the feature recently merged into the master, we don't have any issues yet, but we will list them here as they appear.

### How could Crossplane help solve your problem?

Promote Usage to Beta once we feel confident about it.

```[tasklist]
### Tasks
- [ ] https://github.com/crossplane/crossplane/issues/5042
- [ ] https://github.com/crossplane/crossplane/issues/5193
- [ ] https://github.com/crossplane/crossplane/issues/6140
- [ ] https://github.com/crossplane/crossplane/issues/5880
- [ ] https://github.com/crossplane/crossplane/issues/5962
```


Discussion:
@lsviben: Added an issue based on [Community Slack](https://crossplane.slack.com/archives/CEG3T90A1/p1699996775424049) feedback:
https://github.com/crossplane/crossplane/issues/5042 



@stevendborrelli: I need to be able to duplicate the issue, but I have seen instances where due to long running deletions like for DBs or K8s Clusters when having usage dependences via nested XRs, there can be a 15-minute pause between attempted deletes. We should address it before beta.@bobh66: In my local implementation of this feature using a kopf operator we see this same problem - it's caused by the garbage collector doing an exponential backoff on the delete retries that maxes out at 1000 seconds (16+ minutes).  So when you have something that takes a long time to delete the GC backoff will make it even longer.

I worked around this by explicitly retrying the delete on the "protected" resource when the dependency/usage itself is successfully deleted (the webhook determines that the Usage is no longer required).  That seems to speed things up significantly.

I think we discussed this in the original implementation and decided to defer any solution until we had some data on how it was actually working.  Explicitly retrying the delete feels risky, but if the resource is already marked for deletion (which it should be) then forcing GC to retry again sooner than it's backoff period should not cause any issues.@stevendborrelli: Created issue #5193, this has impacted several users and should be addressed prior to beta graduation. @bobh66: @turkenh any thoughts on adding this to the 1.18 roadmap?  It would be nice to get it promoted.@jbw976: Agreed @bobh66 - looks like it was set to "target release" of v1.18 in the roadmap project, but not set to the v1.18 milestone too.

I tend to set both of these fields, but sometimes I forget. I like the "target release" field more often because it spans across all the Crossplane repos, and milestones are per repo 😇 

Solution patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7c4b619ae..67449960f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -223,7 +223,6 @@ jobs:
       matrix:
         test-suite:
           - base
-          - usage
           - ssa-claims
           - realtime-compositions
           - package-dependency-upgrades
diff --git a/apis/apiextensions/v1alpha1/zz_generated.usage_types.go b/apis/apiextensions/v1alpha1/zz_generated.usage_types.go
new file mode 100644
index 000000000..8226ca498
--- /dev/null
+++ b/apis/apiextensions/v1alpha1/zz_generated.usage_types.go
@@ -0,0 +1,114 @@
+/*
+Copyright 2023 The Crossplane Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Generated from apiextensions/v1beta1/usage_types.go by ../hack/duplicate_api_type.sh. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
+)
+
+// ResourceRef is a reference to a resource.
+type ResourceRef struct {
+	// Name of the referent.
+	Name string `json:"name"`
+}
+
+// ResourceSelector is a selector to a resource.
+type ResourceSelector struct {
+	// MatchLabels ensures an object with matching labels is selected.
+	MatchLabels map[string]string `json:"matchLabels,omitempty"`
+
+	// MatchControllerRef ensures an object with the same controller reference
+	// as the selecting object is selected.
+	MatchControllerRef *bool `json:"matchControllerRef,omitempty"`
+}
+
+// Resource defines a cluster-scoped resource.
+type Resource struct {
+	// API version of the referent.
+	// +optional
+	APIVersion string `json:"apiVersion,omitempty"`
+	// Kind of the referent.
+	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+	// +optional
+	Kind string `json:"kind,omitempty"`
+	// Reference to the resource.
+	// +optional
+	ResourceRef *ResourceRef `json:"resourceRef,omitempty"`
+	// Selector to the resource.
+	// This field will be ignored if ResourceRef is set.
+	// +optional
+	ResourceSelector *ResourceSelector `json:"resourceSelector,omitempty"`
+}
+
+// UsageSpec defines the desired state of Usage.
+type UsageSpec struct {
+	// Of is the resource that is "being used".
+	// +kubebuilder:validation:XValidation:rule="has(self.resourceRef) || has(self.resourceSelector)",message="either a resource reference or a resource selector should be set."
+	Of Resource `json:"of"`
+	// By is the resource that is "using the other resource".
+	// +optional
+	// +kubebuilder:validation:XValidation:rule="has(self.resourceRef) || has(self.resourceSelector)",message="either a resource reference or a resource selector should be set."
+	By *Resource `json:"by,omitempty"`
+	// Reason is the reason for blocking deletion of the resource.
+	// +optional
+	Reason *string `json:"reason,omitempty"`
+	// ReplayDeletion will trigger a deletion on the used resource during the deletion of the usage itself, if it was attempted to be deleted at least once.
+	// +optional
+	ReplayDeletion *bool `json:"replayDeletion,omitempty"`
+}
+
+// UsageStatus defines the observed state of Usage.
+type UsageStatus struct {
+	xpv1.ConditionedStatus `json:",inline"`
+}
+
+// A Usage defines a deletion blocking relationship between two resources.
+//
+// Usages prevent accidental deletion of a single resource or deletion of
+// resources with dependent resources.
+//
+// Read the Crossplane documentation for
+// [more information about Compositions](https://docs.crossplane.io/latest/concepts/usages).
+// +kubebuilder:object:root=true
+// +kubebuilder:storageversion
+// +genclient
+// +genclient:nonNamespaced
+// +kubebuilder:printcolumn:name="DETAILS",type="string",JSONPath=".metadata.annotations.crossplane\\.io/usage-details"
+// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"
+// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
+// +kubebuilder:resource:scope=Cluster,categories=crossplane
+// +kubebuilder:subresource:status
+type Usage struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+	// +kubebuilder:validation:XValidation:rule="has(self.by) || has(self.reason)",message="either \"spec.by\" or \"spec.reason\" must be specified."
+	Spec   UsageSpec   `json:"spec"`
+	Status UsageStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// UsageList contains a list of Usage.
+type UsageList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []Usage `json:"items"`
+}
diff --git a/apis/apiextensions/v1beta1/register.go b/apis/apiextensions/v1beta1/register.go
index 092291563..eedf1622c 100644
--- a/apis/apiextensions/v1beta1/register.go
+++ b/apis/apiextensions/v1beta1/register.go
@@ -56,7 +56,16 @@ var (
 	EnvironmentConfigGroupVersionKind = SchemeGroupVersion.WithKind(EnvironmentConfigKind)
 )
 
+// Usage type metadata.
+var (
+	UsageKind             = reflect.TypeOf(Usage{}).Name()
+	UsageGroupKind        = schema.GroupKind{Group: Group, Kind: UsageKind}.String()
+	UsageKindAPIVersion   = UsageKind + "." + SchemeGroupVersion.String()
+	UsageGroupVersionKind = SchemeGroupVersion.WithKind(UsageKind)
+)
+
 func init() {
 	SchemeBuilder.Register(&CompositionRevision{}, &CompositionRevisionList{})
 	SchemeBuilder.Register(&EnvironmentConfig{}, &EnvironmentConfigList{})
+	SchemeBuilder.Register(&Usage{}, &UsageList{})
 }
diff --git a/apis/apiextensions/v1alpha1/usage_types.go b/apis/apiextensions/v1beta1/usage_types.go
similarity index 98%
rename from apis/apiextensions/v1alpha1/usage_types.go
rename to apis/apiextensions/v1beta1/usage_types.go
index f1d1370e3..3195be1c7 100644
--- a/apis/apiextensions/v1alpha1/usage_types.go
+++ b/apis/apiextensions/v1beta1/usage_types.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1alpha1
+package v1beta1
 
 import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -86,7 +86,8 @@ type UsageStatus struct {
 // Read the Crossplane documentation for
 // [more information about Compositions](https://docs.crossplane.io/latest/concepts/usages).
 // +kubebuilder:object:root=true
-// +kubebuilder:storageversion
+// +genclient
+// +genclient:nonNamespaced
 // +kubebuilder:printcolumn:name="DETAILS",type="string",JSONPath=".metadata.annotations.crossplane\\.io/usage-details"
 // +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"
 // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
diff --git a/apis/apiextensions/v1beta1/zz_generated.deepcopy.go b/apis/apiextensions/v1beta1/zz_generated.deepcopy.go
index 025c73cde..0ed50a98a 100644
--- a/apis/apiextensions/v1beta1/zz_generated.deepcopy.go
+++ b/apis/apiextensions/v1beta1/zz_generated.deepcopy.go
@@ -652,6 +652,73 @@ func (in *ReadinessCheck) DeepCopy() *ReadinessCheck {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Resource) DeepCopyInto(out *Resource) {
+	*out = *in
+	if in.ResourceRef != nil {
+		in, out := &in.ResourceRef, &out.ResourceRef
+		*out = new(ResourceRef)
+		**out = **in
+	}
+	if in.ResourceSelector != nil {
+		in, out := &in.ResourceSelector, &out.ResourceSelector
+		*out = new(ResourceSelector)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Resource.
+func (in *Resource) DeepCopy() *Resource {
+	if in == nil {
+		return nil
+	}
+	out := new(Resource)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ResourceRef) DeepCopyInto(out *ResourceRef) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRef.
+func (in *ResourceRef) DeepCopy() *ResourceRef {
+	if in == nil {
+		return nil
+	}
+	out := new(ResourceRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ResourceSelector) DeepCopyInto(out *ResourceSelector) {
+	*out = *in
+	if in.MatchLabels != nil {
+		in, out := &in.MatchLabels, &out.MatchLabels
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+	if in.MatchControllerRef != nil {
+		in, out := &in.MatchControllerRef, &out.MatchControllerRef
+		*out = new(bool)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSelector.
+func (in *ResourceSelector) DeepCopy() *ResourceSelector {
+	if in == nil {
+		return nil
+	}
+	out := new(ResourceSelector)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *StoreConfigReference) DeepCopyInto(out *StoreConfigReference) {
 	*out = *in
@@ -811,3 +878,109 @@ func (in *TypeReference) DeepCopy() *TypeReference {
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Usage) DeepCopyInto(out *Usage) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Usage.
+func (in *Usage) DeepCopy() *Usage {
+	if in == nil {
+		return nil
+	}
+	out := new(Usage)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Usage) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *UsageList) DeepCopyInto(out *UsageList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Usage, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UsageList.
+func (in *UsageList) DeepCopy() *UsageList {
+	if in == nil {
+		return nil
+	}
+	out := new(UsageList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *UsageList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *UsageSpec) DeepCopyInto(out *UsageSpec) {
+	*out = *in
+	in.Of.DeepCopyInto(&out.Of)
+	if in.By != nil {
+		in, out := &in.By, &out.By
+		*out = new(Resource)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Reason != nil {
+		in, out := &in.Reason, &out.Reason
+		*out = new(string)
+		**out = **in
+	}
+	if in.ReplayDeletion != nil {
+		in, out := &in.ReplayDeletion, &out.ReplayDeletion
+		*out = new(bool)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UsageSpec.
+func (in *UsageSpec) DeepCopy() *UsageSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(UsageSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *UsageStatus) DeepCopyInto(out *UsageStatus) {
+	*out = *in
+	in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UsageStatus.
+func (in *UsageStatus) DeepCopy() *UsageStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(UsageStatus)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/apis/generate.go b/apis/generate.go
index 588cd2540..eb25fea67 100644
--- a/apis/generate.go
+++ b/apis/generate.go
@@ -32,6 +32,7 @@ limitations under the License.
 //go:generate ../hack/duplicate_api_type.sh apiextensions/v1/composition_transforms.go apiextensions/v1beta1
 
 //go:generate ../hack/duplicate_api_type.sh apiextensions/v1beta1/environment_config_types.go apiextensions/v1alpha1 true
+//go:generate ../hack/duplicate_api_type.sh apiextensions/v1beta1/usage_types.go apiextensions/v1alpha1 true
 
 //go:generate ../hack/duplicate_api_type.sh pkg/v1/package_types.go pkg/v1beta1
 //go:generate ../hack/duplicate_api_type.sh pkg/v1/package_runtime_types.go pkg/v1beta1
diff --git a/cluster/crds/apiextensions.crossplane.io_usages.yaml b/cluster/crds/apiextensions.crossplane.io_usages.yaml
index dec53609e..a7624da79 100644
--- a/cluster/crds/apiextensions.crossplane.io_usages.yaml
+++ b/cluster/crds/apiextensions.crossplane.io_usages.yaml
@@ -213,3 +213,200 @@ spec:
     storage: true
     subresources:
       status: {}
+  - additionalPrinterColumns:
+    - jsonPath: .metadata.annotations.crossplane\.io/usage-details
+      name: DETAILS
+      type: string
+    - jsonPath: .status.conditions[?(@.type=='Ready')].status
+      name: READY
+      type: string
+    - jsonPath: .metadata.creationTimestamp
+      name: AGE
+      type: date
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: |-
+          A Usage defines a deletion blocking relationship between two resources.
+
+          Usages prevent accidental deletion of a single resource or deletion of
+          resources with dependent resources.
+
+          Read the Crossplane documentation for
+          [more information about Compositions](https://docs.crossplane.io/latest/concepts/usages).
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: UsageSpec defines the desired state of Usage.
+            properties:
+              by:
+                description: By is the resource that is "using the other resource".
+                properties:
+                  apiVersion:
+                    description: API version of the referent.
+                    type: string
+                  kind:
+                    description: |-
+                      Kind of the referent.
+                      More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+                    type: string
+                  resourceRef:
+                    description: Reference to the resource.
+                    properties:
+                      name:
+                        description: Name of the referent.
+                        type: string
+                    required:
+                    - name
+                    type: object
+                  resourceSelector:
+                    description: |-
+                      Selector to the resource.
+                      This field will be ignored if ResourceRef is set.
+                    properties:
+                      matchControllerRef:
+                        description: |-
+                          MatchControllerRef ensures an object with the same controller reference
+                          as the selecting object is selected.
+                        type: boolean
+                      matchLabels:
+                        additionalProperties:
+                          type: string
+                        description: MatchLabels ensures an object with matching labels
+                          is selected.
+                        type: object
+                    type: object
+                type: object
+                x-kubernetes-validations:
+                - message: either a resource reference or a resource selector should
+                    be set.
+                  rule: has(self.resourceRef) || has(self.resourceSelector)
+              of:
+                description: Of is the resource that is "being used".
+                properties:
+                  apiVersion:
+                    description: API version of the referent.
+                    type: string
+                  kind:
+                    description: |-
+                      Kind of the referent.
+                      More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+                    type: string
+                  resourceRef:
+                    description: Reference to the resource.
+                    properties:
+                      name:
+                        description: Name of the referent.
+                        type: string
+                    required:
+                    - name
+                    type: object
+                  resourceSelector:
+                    description: |-
+                      Selector to the resource.
+                      This field will be ignored if ResourceRef is set.
+                    properties:
+                      matchControllerRef:
+                        description: |-
+                          MatchControllerRef ensures an object with the same controller reference
+                          as the selecting object is selected.
+                        type: boolean
+                      matchLabels:
+                        additionalProperties:
+                          type: string
+                        description: MatchLabels ensures an object with matching labels
+                          is selected.
+                        type: object
+                    type: object
+                type: object
+                x-kubernetes-validations:
+                - message: either a resource reference or a resource selector should
+                    be set.
+                  rule: has(self.resourceRef) || has(self.resourceSelector)
+              reason:
+                description: Reason is the reason for blocking deletion of the resource.
+                type: string
+              replayDeletion:
+                description: ReplayDeletion will trigger a deletion on the used resource
+                  during the deletion of the usage itself, if it was attempted to
+                  be deleted at least once.
+                type: boolean
+            required:
+            - of
+            type: object
+            x-kubernetes-validations:
+            - message: either "spec.by" or "spec.reason" must be specified.
+              rule: has(self.by) || has(self.reason)
+          status:
+            description: UsageStatus defines the observed state of Usage.
+            properties:
+              conditions:
+                description: Conditions of the resource.
+                items:
+                  description: A Condition that may apply to a resource.
+                  properties:
+                    lastTransitionTime:
+                      description: |-
+                        LastTransitionTime is the last time this condition transitioned from one
+                        status to another.
+                      format: date-time
+                      type: string
+                    message:
+                      description: |-
+                        A Message containing details about this condition's last transition from
+                        one status to another, if any.
+                      type: string
+                    observedGeneration:
+                      description: |-
+                        ObservedGeneration represents the .metadata.generation that the condition was set based upon.
+                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+                        with respect to the current state of the instance.
+                      format: int64
+                      type: integer
+                    reason:
+                      description: A Reason for this condition's last transition from
+                        one status to another.
+                      type: string
+                    status:
+                      description: Status of this condition; is it currently True,
+                        False, or Unknown?
+                      type: string
+                    type:
+                      description: |-
+                        Type of this condition. At most one of each condition type may apply to
+                        a resource at any point in time.
+                      type: string
+                  required:
+                  - lastTransitionTime
+                  - reason
+                  - status
+                  - type
+                  type: object
+                type: array
+                x-kubernetes-list-map-keys:
+                - type
+                x-kubernetes-list-type: map
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: false
+    subresources:
+      status: {}
diff --git a/cmd/crossplane/core/core.go b/cmd/crossplane/core/core.go
index fb34ebae3..0a447979e 100644
--- a/cmd/crossplane/core/core.go
+++ b/cmd/crossplane/core/core.go
@@ -110,7 +110,6 @@ type startCommand struct {
 	TLSClientCertsDir   string `env:"TLS_CLIENT_CERTS_DIR"   help:"The path of the folder which will store TLS client certificate of Crossplane."`
 
 	EnableExternalSecretStores      bool `group:"Alpha Features:" help:"Enable support for External Secret Stores."`
-	EnableUsages                    bool `group:"Alpha Features:" help:"Enable support for deletion ordering and resource protection with Usages."`
 	EnableRealtimeCompositions      bool `group:"Alpha Features:" help:"Enable support for realtime compositions, i.e. watching composed resources and reconciling compositions immediately when any of the composed resources is updated."`
 	EnableSSAClaims                 bool `group:"Alpha Features:" help:"Enable support for using Kubernetes server-side apply to sync claims with composite resources (XRs)."`
 	EnableDependencyVersionUpgrades bool `group:"Alpha Features:" help:"Enable support for upgrading dependency versions when the parent package is updated."`
@@ -118,6 +117,7 @@ type startCommand struct {
 
 	EnableCompositionWebhookSchemaValidation bool `default:"true" group:"Beta Features:" help:"Enable support for Composition validation using schemas."`
 	EnableDeploymentRuntimeConfigs           bool `default:"true" group:"Beta Features:" help:"Enable support for Deployment Runtime Configs."`
+	EnableUsages                             bool `default:"true" group:"Beta Features:" help:"Enable support for deletion ordering and resource protection with Usages."`
 
 	// These are GA features that previously had alpha or beta feature flags.
 	// You can't turn off a GA feature. We maintain the flags to avoid breaking
@@ -250,8 +250,8 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
 		log.Info("Beta feature enabled", "flag", features.EnableBetaCompositionWebhookSchemaValidation)
 	}
 	if c.EnableUsages {
-		o.Features.Enable(features.EnableAlphaUsages)
-		log.Info("Alpha feature enabled", "flag", features.EnableAlphaUsages)
+		o.Features.Enable(features.EnableBetaUsages)
+		log.Info("Beta feature enabled", "flag", features.EnableBetaUsages)
 	}
 	if c.EnableExternalSecretStores {
 		o.Features.Enable(features.EnableAlphaExternalSecretStores)
@@ -438,7 +438,7 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
 		if err := composition.SetupWebhookWithManager(mgr, o); err != nil {
 			return errors.Wrap(err, "cannot setup webhook for compositions")
 		}
-		if o.Features.Enabled(features.EnableAlphaUsages) {
+		if o.Features.Enabled(features.EnableBetaUsages) {
 			if err := usage.SetupWebhookWithManager(mgr, o); err != nil {
 				return errors.Wrap(err, "cannot setup webhook for usages")
 			}
diff --git a/internal/controller/apiextensions/apiextensions.go b/internal/controller/apiextensions/apiextensions.go
index fb0752c2d..423fffbc2 100644
--- a/internal/controller/apiextensions/apiextensions.go
+++ b/internal/controller/apiextensions/apiextensions.go
@@ -38,7 +38,7 @@ func Setup(mgr ctrl.Manager, o controller.Options) error {
 		return err
 	}
 
-	if o.Features.Enabled(features.EnableAlphaUsages) {
+	if o.Features.Enabled(features.EnableBetaUsages) {
 		if err := usage.Setup(mgr, o); err != nil {
 			return err
 		}
diff --git a/internal/controller/apiextensions/usage/reconciler.go b/internal/controller/apiextensions/usage/reconciler.go
index 1d678c777..715cdbcfc 100644
--- a/internal/controller/apiextensions/usage/reconciler.go
+++ b/internal/controller/apiextensions/usage/reconciler.go
@@ -44,7 +44,7 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composed"
 
-	"github.com/crossplane/crossplane/apis/apiextensions/v1alpha1"
+	"github.com/crossplane/crossplane/apis/apiextensions/v1beta1"
 	apiextensionscontroller "github.com/crossplane/crossplane/internal/controller/apiextensions/controller"
 	"github.com/crossplane/crossplane/internal/usage"
 	"github.com/crossplane/crossplane/internal/xcrd"
@@ -94,13 +94,13 @@ const (
 )
 
 type selectorResolver interface {
-	resolveSelectors(ctx context.Context, u *v1alpha1.Usage) error
+	resolveSelectors(ctx context.Context, u *v1beta1.Usage) error
 }
 
 // Setup adds a controller that reconciles Usages by
 // defining a composite resource and starting a controller to reconcile it.
 func Setup(mgr ctrl.Manager, o apiextensionscontroller.Options) error {
-	name := "usage/" + strings.ToLower(v1alpha1.UsageGroupKind)
+	name := "usage/" + strings.ToLower(v1beta1.UsageGroupKind)
 	r := NewReconciler(mgr,
 		WithLogger(o.Logger.WithValues("controller", name)),
 		WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))),
@@ -108,7 +108,7 @@ func Setup(mgr ctrl.Manager, o apiextensionscontroller.Options) error {
 
 	return ctrl.NewControllerManagedBy(mgr).
 		Named(name).
-		For(&v1alpha1.Usage{}).
+		For(&v1beta1.Usage{}).
 		WithOptions(o.ForControllerRuntime()).
 		Complete(ratelimiter.NewReconciler(name, errors.WithSilentRequeueOnConflict(r), o.GlobalRateLimiter))
 }
@@ -221,7 +221,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	defer cancel()
 
 	// Get the usageResource resource for this request.
-	u := &v1alpha1.Usage{}
+	u := &v1beta1.Usage{}
 	if err := r.client.Get(ctx, req.NamespacedName, u); err != nil {
 		log.Debug(errGetUsage, "error", err)
 		return reconcile.Result{}, errors.Wrap(xpresource.IgnoreNotFound(err), errGetUsage)
@@ -311,7 +311,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 		// Remove the in-use label from the used resource if no other usages
 		// exists.
 		if err == nil {
-			usageList := &v1alpha1.UsageList{}
+			usageList := &v1beta1.UsageList{}
 			if err = r.client.List(ctx, usageList, client.MatchingFields{usage.InUseIndexKey: usage.IndexValueForObject(used.GetUnstructured())}); err != nil {
 				log.Debug(errListUsages, "error", err)
 				err = errors.Wrap(err, errListUsages)
@@ -464,7 +464,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	return reconcile.Result{RequeueAfter: r.pollInterval}, nil
 }
 
-func detailsAnnotation(u *v1alpha1.Usage) string {
+func detailsAnnotation(u *v1beta1.Usage) string {
 	if u.Spec.Reason != nil {
 		return *u.Spec.Reason
 	}
@@ -482,7 +482,7 @@ func detailsAnnotation(u *v1alpha1.Usage) string {
 func RespectOwnerRefs() xpresource.ApplyOption {
 	return func(_ context.Context, current, desired runtime.Object) error {
 		cu, ok := current.(*composed.Unstructured)
-		if !ok || cu.GetObjectKind().GroupVersionKind() != v1alpha1.UsageGroupVersionKind {
+		if !ok || cu.GetObjectKind().GroupVersionKind() != v1beta1.UsageGroupVersionKind {
 			return nil
 		}
 		// This is a Usage resource, so we need to respect existing owner
diff --git a/internal/controller/apiextensions/usage/selector.go b/internal/controller/apiextensions/usage/selector.go
index 6d9593496..ca6228365 100644
--- a/internal/controller/apiextensions/usage/selector.go
+++ b/internal/controller/apiextensions/usage/selector.go
@@ -26,7 +26,7 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/meta"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composed"
 
-	"github.com/crossplane/crossplane/apis/apiextensions/v1alpha1"
+	"github.com/crossplane/crossplane/apis/apiextensions/v1beta1"
 )
 
 const (
@@ -47,7 +47,7 @@ func newAPISelectorResolver(c client.Client) *apiSelectorResolver {
 	return &apiSelectorResolver{client: c}
 }
 
-func (r *apiSelectorResolver) resolveSelectors(ctx context.Context, u *v1alpha1.Usage) error {
+func (r *apiSelectorResolver) resolveSelectors(ctx context.Context, u *v1beta1.Usage) error {
 	of := u.Spec.Of
 	by := u.Spec.By
 
@@ -78,7 +78,7 @@ func (r *apiSelectorResolver) resolveSelectors(ctx context.Context, u *v1alpha1.
 	return nil
 }
 
-func (r *apiSelectorResolver) resolveSelector(ctx context.Context, u *v1alpha1.Usage, rs *v1alpha1.Resource) error {
+func (r *apiSelectorResolver) resolveSelector(ctx context.Context, u *v1beta1.Usage, rs *v1beta1.Resource) error {
 	l := composed.NewList(composed.FromReferenceToList(v1.ObjectReference{
 		APIVersion: rs.APIVersion,
 		Kind:       rs.Kind,
@@ -100,7 +100,7 @@ func (r *apiSelectorResolver) resolveSelector(ctx context.Context, u *v1alpha1.U
 		if controllersMustMatch(rs.ResourceSelector) && !meta.HaveSameController(&o, u) {
 			continue
 		}
-		rs.ResourceRef = &v1alpha1.ResourceRef{
+		rs.ResourceRef = &v1beta1.ResourceRef{
 			Name: o.GetName(),
 		}
 		break
@@ -116,7 +116,7 @@ func (r *apiSelectorResolver) resolveSelector(ctx context.Context, u *v1alpha1.U
 // ControllersMustMatch returns true if the supplied Selector requires that a
 // reference be to a resource whose controller reference matches the
 // referencing resource.
-func controllersMustMatch(s *v1alpha1.ResourceSelector) bool {
+func controllersMustMatch(s *v1beta1.ResourceSelector) bool {
 	if s == nil {
 		return false
 	}
diff --git a/internal/features/features.go b/internal/features/features.go
index e17689fbf..e3bed2586 100644
--- a/internal/features/features.go
+++ b/internal/features/features.go
@@ -26,11 +26,6 @@ const (
 	// https://github.com/crossplane/crossplane/blob/390ddd/design/design-doc-external-secret-stores.md
 	EnableAlphaExternalSecretStores feature.Flag = "EnableAlphaExternalSecretStores"
 
-	// EnableAlphaUsages enables alpha support for deletion ordering and
-	// protection with Usage resource. See the below design for more details.
-	// https://github.com/crossplane/crossplane/blob/19ea23/design/one-pager-generic-usage-type.md
-	EnableAlphaUsages feature.Flag = "EnableAlphaUsages"
-
 	// EnableAlphaRealtimeCompositions enables alpha support for realtime
 	// compositions, i.e. watching MRs and reconciling compositions immediately
 	// when any MR is updated.
@@ -61,4 +56,9 @@ const (
 	// runtime configs. See the below design for more details.
 	// https://github.com/crossplane/crossplane/blob/c2e206/design/one-pager-package-runtime-config.md
 	EnableBetaDeploymentRuntimeConfigs feature.Flag = "EnableBetaDeploymentRuntimeConfigs"
+
+	// EnableBetaUsages enables beta support for deletion ordering and
+	// protection with Usage resource. See the below design for more details.
+	// https://github.com/crossplane/crossplane/blob/19ea23/design/one-pager-generic-usage-type.md
+	EnableBetaUsages feature.Flag = "EnableBetaUsages"
 )
diff --git a/internal/usage/handler.go b/internal/usage/handler.go
index cbc84f9ca..b7e41d986 100644
--- a/internal/usage/handler.go
+++ b/internal/usage/handler.go
@@ -38,7 +38,7 @@ import (
 	xpmeta "github.com/crossplane/crossplane-runtime/pkg/meta"
 	xpunstructured "github.com/crossplane/crossplane-runtime/pkg/resource/unstructured"
 
-	"github.com/crossplane/crossplane/apis/apiextensions/v1alpha1"
+	"github.com/crossplane/crossplane/apis/apiextensions/v1beta1"
 )
 
 const (
@@ -74,8 +74,8 @@ func indexValue(apiVersion, kind, name string) string {
 // SetupWebhookWithManager sets up the webhook with the manager.
 func SetupWebhookWithManager(mgr ctrl.Manager, options controller.Options) error {
 	indexer := mgr.GetFieldIndexer()
-	if err := indexer.IndexField(context.Background(), &v1alpha1.Usage{}, InUseIndexKey, func(obj client.Object) []string {
-		u := obj.(*v1alpha1.Usage) //nolint:forcetypeassert // Will always be a Usage.
+	if err := indexer.IndexField(context.Background(), &v1beta1.Usage{}, InUseIndexKey, func(obj client.Object) []string {
+		u := obj.(*v1beta1.Usage) //nolint:forcetypeassert // Will always be a Usage.
 		if u.Spec.Of.ResourceRef == nil || len(u.Spec.Of.ResourceRef.Name) == 0 {
 			return []string{}
 		}
@@ -145,7 +145,7 @@ func (h *Handler) Handle(ctx context.Context, request admission.Request) admissi
 
 func (h *Handler) validateNoUsages(ctx context.Context, u *unstructured.Unstructured, opts *metav1.DeleteOptions) admission.Response {
 	h.log.Debug("Validating no usages", "apiVersion", u.GetAPIVersion(), "kind", u.GetKind(), "name", u.GetName(), "policy", opts.PropagationPolicy)
-	usageList := &v1alpha1.UsageList{}
+	usageList := &v1beta1.UsageList{}
 	if err := h.client.List(ctx, usageList, client.MatchingFields{InUseIndexKey: IndexValueForObject(u)}); err != nil {
 		h.log.Debug("Error when getting Usages", "apiVersion", u.GetAPIVersion(), "kind", u.GetKind(), "name", u.GetName(), "err", err)
 		return admission.Errored(http.StatusInternalServerError, err)
@@ -185,7 +185,7 @@ func (h *Handler) validateNoUsages(ctx context.Context, u *unstructured.Unstruct
 	return admission.Allowed("")
 }
 
-func inUseMessage(usages *v1alpha1.UsageList) string {
+func inUseMessage(usages *v1beta1.UsageList) string {
 	first := usages.Items[0]
 	if first.Spec.By != nil {
 		return fmt.Sprintf("This resource is in-use by %d Usage(s), including the Usage %q by resource %s/%s.", len(usages.Items), first.Name, first.Spec.By.Kind, first.Spec.By.ResourceRef.Name)



Test patch:
diff --git a/internal/controller/apiextensions/usage/reconciler_test.go b/internal/controller/apiextensions/usage/reconciler_test.go
index 69dd06de7..bc250c47d 100644
--- a/internal/controller/apiextensions/usage/reconciler_test.go
+++ b/internal/controller/apiextensions/usage/reconciler_test.go
@@ -38,16 +38,16 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composed"
 	"github.com/crossplane/crossplane-runtime/pkg/test"
 
-	"github.com/crossplane/crossplane/apis/apiextensions/v1alpha1"
+	"github.com/crossplane/crossplane/apis/apiextensions/v1beta1"
 	"github.com/crossplane/crossplane/internal/usage"
 	"github.com/crossplane/crossplane/internal/xcrd"
 )
 
 type fakeSelectorResolver struct {
-	resourceSelectorFn func(_ context.Context, _ *v1alpha1.Usage) error
+	resourceSelectorFn func(_ context.Context, _ *v1beta1.Usage) error
 }
 
-func (f fakeSelectorResolver) resolveSelectors(ctx context.Context, u *v1alpha1.Usage) error {
+func (f fakeSelectorResolver) resolveSelectors(ctx context.Context, u *v1beta1.Usage) error {
 	return f.resourceSelectorFn(ctx, u)
 }
 
@@ -92,9 +92,9 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								o := obj.(*v1alpha1.Usage)
+								o := obj.(*v1beta1.Usage)
 								o.Spec.Of.APIVersion = "/invalid/"
-								o.Spec.Of.ResourceSelector = &v1alpha1.ResourceSelector{MatchLabels: map[string]string{"foo": "bar"}}
+								o.Spec.Of.ResourceSelector = &v1beta1.ResourceSelector{MatchLabels: map[string]string{"foo": "bar"}}
 								return nil
 							}),
 						},
@@ -113,14 +113,14 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								o := obj.(*v1alpha1.Usage)
-								o.Spec.Of.ResourceSelector = &v1alpha1.ResourceSelector{MatchLabels: map[string]string{"foo": "bar"}}
+								o := obj.(*v1beta1.Usage)
+								o.Spec.Of.ResourceSelector = &v1beta1.ResourceSelector{MatchLabels: map[string]string{"foo": "bar"}}
 								return nil
 							}),
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return errBoom
 						},
 					}),
@@ -138,14 +138,14 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								o := obj.(*v1alpha1.Usage)
-								o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "cool"}
+								o := obj.(*v1beta1.Usage)
+								o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "cool"}
 								return nil
 							}),
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -166,15 +166,15 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								o := obj.(*v1alpha1.Usage)
-								o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "cool"}
+								o := obj.(*v1beta1.Usage)
+								o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "cool"}
 								return nil
 							}),
 							MockUpdate: test.NewMockUpdateFn(errBoom),
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -196,8 +196,8 @@ func TestReconcile(t *testing.T) {
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
 								switch o := obj.(type) {
-								case *v1alpha1.Usage:
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "cool"}
+								case *v1beta1.Usage:
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "cool"}
 								case *composed.Unstructured:
 									return errBoom
 								}
@@ -209,7 +209,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -231,8 +231,8 @@ func TestReconcile(t *testing.T) {
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
 								switch o := obj.(type) {
-								case *v1alpha1.Usage:
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "cool"}
+								case *v1beta1.Usage:
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "cool"}
 								case *composed.Unstructured:
 									return nil
 								}
@@ -247,7 +247,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -269,10 +269,10 @@ func TestReconcile(t *testing.T) {
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
 								switch o := obj.(type) {
-								case *v1alpha1.Usage:
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "used"}
-									o.Spec.By = &v1alpha1.Resource{
-										ResourceRef: &v1alpha1.ResourceRef{Name: "using"},
+								case *v1beta1.Usage:
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "used"}
+									o.Spec.By = &v1beta1.Resource{
+										ResourceRef: &v1beta1.ResourceRef{Name: "using"},
 									}
 								case *composed.Unstructured:
 									if o.GetName() == "using" {
@@ -285,7 +285,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -306,10 +306,10 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "used"}
-									o.Spec.By = &v1alpha1.Resource{
-										ResourceRef: &v1alpha1.ResourceRef{Name: "using"},
+								if o, ok := obj.(*v1beta1.Usage); ok {
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "used"}
+									o.Spec.By = &v1beta1.Resource{
+										ResourceRef: &v1beta1.ResourceRef{Name: "using"},
 									}
 									return nil
 								}
@@ -324,7 +324,7 @@ func TestReconcile(t *testing.T) {
 								return errors.New("unexpected object type")
 							}),
 							MockUpdate: test.NewMockUpdateFn(nil, func(obj client.Object) error {
-								if u, ok := obj.(*v1alpha1.Usage); ok {
+								if u, ok := obj.(*v1beta1.Usage); ok {
 									if u.GetOwnerReferences() != nil {
 										return errBoom
 									}
@@ -334,7 +334,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -355,10 +355,10 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "used"}
-									o.Spec.By = &v1alpha1.Resource{
-										ResourceRef: &v1alpha1.ResourceRef{Name: "using"},
+								if o, ok := obj.(*v1beta1.Usage); ok {
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "used"}
+									o.Spec.By = &v1beta1.Resource{
+										ResourceRef: &v1beta1.ResourceRef{Name: "using"},
 									}
 									return nil
 								}
@@ -373,7 +373,7 @@ func TestReconcile(t *testing.T) {
 								return errors.New("unexpected object type")
 							}),
 							MockUpdate: test.NewMockUpdateFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
+								if o, ok := obj.(*v1beta1.Usage); ok {
 									if o.GetOwnerReferences() != nil {
 										owner := o.GetOwnerReferences()[0]
 										if owner.APIVersion != "v1" || owner.Kind != "AnotherKind" || owner.UID != "some-uid" {
@@ -384,7 +384,7 @@ func TestReconcile(t *testing.T) {
 								return nil
 							}),
 							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(obj client.Object) error {
-								o := obj.(*v1alpha1.Usage)
+								o := obj.(*v1beta1.Usage)
 								if o.Status.GetCondition(xpv1.TypeReady).Status != corev1.ConditionTrue {
 									t.Fatalf("expected ready condition to be true")
 								}
@@ -393,7 +393,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -414,8 +414,8 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "cool"}
+								if o, ok := obj.(*v1beta1.Usage); ok {
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "cool"}
 									o.Spec.Reason = &reason
 									return nil
 								}
@@ -433,7 +433,7 @@ func TestReconcile(t *testing.T) {
 								return nil
 							}),
 							MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(obj client.Object) error {
-								o := obj.(*v1alpha1.Usage)
+								o := obj.(*v1beta1.Usage)
 								if o.Status.GetCondition(xpv1.TypeReady).Status != corev1.ConditionTrue {
 									t.Fatalf("expected ready condition to be true")
 								}
@@ -442,7 +442,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -463,9 +463,9 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
+								if o, ok := obj.(*v1beta1.Usage); ok {
 									o.SetDeletionTimestamp(&now)
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "cool"}
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "cool"}
 									return nil
 								}
 								if _, ok := obj.(*composed.Unstructured); ok {
@@ -476,7 +476,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -497,9 +497,9 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
+								if o, ok := obj.(*v1beta1.Usage); ok {
 									o.SetDeletionTimestamp(&now)
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "cool"}
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "cool"}
 									return nil
 								}
 								if _, ok := obj.(*composed.Unstructured); ok {
@@ -510,7 +510,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -531,14 +531,14 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
+								if o, ok := obj.(*v1beta1.Usage); ok {
 									o.SetDeletionTimestamp(&now)
 									o.SetLabels(map[string]string{xcrd.LabelKeyNamePrefixForComposed: "some-composite"})
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "used"}
-									o.Spec.By = &v1alpha1.Resource{
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "used"}
+									o.Spec.By = &v1beta1.Resource{
 										APIVersion:  "v1",
 										Kind:        "AnotherKind",
-										ResourceRef: &v1alpha1.ResourceRef{Name: "using"},
+										ResourceRef: &v1beta1.ResourceRef{Name: "using"},
 									}
 									return nil
 								}
@@ -553,7 +553,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -574,13 +574,13 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
+								if o, ok := obj.(*v1beta1.Usage); ok {
 									o.SetDeletionTimestamp(&now)
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "used"}
-									o.Spec.By = &v1alpha1.Resource{
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "used"}
+									o.Spec.By = &v1beta1.Resource{
 										APIVersion:  "v1",
 										Kind:        "AnotherKind",
-										ResourceRef: &v1alpha1.ResourceRef{Name: "using"},
+										ResourceRef: &v1beta1.ResourceRef{Name: "using"},
 									}
 									return nil
 								}
@@ -597,7 +597,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -615,9 +615,9 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
+								if o, ok := obj.(*v1beta1.Usage); ok {
 									o.SetDeletionTimestamp(&now)
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "cool"}
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "cool"}
 									return nil
 								}
 								if o, ok := obj.(*composed.Unstructured); ok {
@@ -632,7 +632,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -653,9 +653,9 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
+								if o, ok := obj.(*v1beta1.Usage); ok {
 									o.SetDeletionTimestamp(&now)
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "cool"}
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "cool"}
 									return nil
 								}
 								if o, ok := obj.(*composed.Unstructured); ok {
@@ -673,7 +673,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -694,9 +694,9 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
+								if o, ok := obj.(*v1beta1.Usage); ok {
 									o.SetDeletionTimestamp(&now)
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "cool"}
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "cool"}
 									return nil
 								}
 								if _, ok := obj.(*composed.Unstructured); ok {
@@ -707,7 +707,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -728,9 +728,9 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
+								if o, ok := obj.(*v1beta1.Usage); ok {
 									o.SetDeletionTimestamp(&now)
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "cool"}
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "cool"}
 									return nil
 								}
 								if o, ok := obj.(*composed.Unstructured); ok {
@@ -754,7 +754,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -775,10 +775,10 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
+								if o, ok := obj.(*v1beta1.Usage); ok {
 									o.SetDeletionTimestamp(&now)
 									o.Spec.ReplayDeletion = ptr.To(true)
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "cool"}
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "cool"}
 									return nil
 								}
 								if o, ok := obj.(*composed.Unstructured); ok {
@@ -806,7 +806,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
@@ -827,14 +827,14 @@ func TestReconcile(t *testing.T) {
 					WithClientApplicator(xpresource.ClientApplicator{
 						Client: &test.MockClient{
 							MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
-								if o, ok := obj.(*v1alpha1.Usage); ok {
+								if o, ok := obj.(*v1beta1.Usage); ok {
 									o.SetDeletionTimestamp(&now)
 									o.SetLabels(map[string]string{xcrd.LabelKeyNamePrefixForComposed: "some-composite"})
-									o.Spec.Of.ResourceRef = &v1alpha1.ResourceRef{Name: "used"}
-									o.Spec.By = &v1alpha1.Resource{
+									o.Spec.Of.ResourceRef = &v1beta1.ResourceRef{Name: "used"}
+									o.Spec.By = &v1beta1.Resource{
 										APIVersion:  "v1",
 										Kind:        "AnotherKind",
-										ResourceRef: &v1alpha1.ResourceRef{Name: "using"},
+										ResourceRef: &v1beta1.ResourceRef{Name: "using"},
 									}
 									return nil
 								}
@@ -864,7 +864,7 @@ func TestReconcile(t *testing.T) {
 						},
 					}),
 					WithSelectorResolver(fakeSelectorResolver{
-						resourceSelectorFn: func(_ context.Context, _ *v1alpha1.Usage) error {
+						resourceSelectorFn: func(_ context.Context, _ *v1beta1.Usage) error {
 							return nil
 						},
 					}),
diff --git a/internal/controller/apiextensions/usage/selector_test.go b/internal/controller/apiextensions/usage/selector_test.go
index e6ce4bfb7..ef0f94b4e 100644
--- a/internal/controller/apiextensions/usage/selector_test.go
+++ b/internal/controller/apiextensions/usage/selector_test.go
@@ -29,7 +29,7 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composed"
 	"github.com/crossplane/crossplane-runtime/pkg/test"
 
-	"github.com/crossplane/crossplane/apis/apiextensions/v1alpha1"
+	"github.com/crossplane/crossplane/apis/apiextensions/v1beta1"
 )
 
 var errBoom = errors.New("boom")
@@ -38,10 +38,10 @@ func TestResolveSelectors(t *testing.T) {
 	valueTrue := true
 	type args struct {
 		client client.Client
-		u      *v1alpha1.Usage
+		u      *v1beta1.Usage
 	}
 	type want struct {
-		u   *v1alpha1.Usage
+		u   *v1beta1.Usage
 		err error
 	}
 	cases := map[string]struct {
@@ -52,27 +52,27 @@ func TestResolveSelectors(t *testing.T) {
 		"AlreadyResolved": {
 			reason: "If selectors resolved already, we should do nothing.",
 			args: args{
-				u: &v1alpha1.Usage{
-					Spec: v1alpha1.UsageSpec{
-						Of: v1alpha1.Resource{
+				u: &v1beta1.Usage{
+					Spec: v1beta1.UsageSpec{
+						Of: v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "SomeKind",
-							ResourceRef: &v1alpha1.ResourceRef{
+							ResourceRef: &v1beta1.ResourceRef{
 								Name: "some",
 							},
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"foo": "bar",
 								},
 							},
 						},
-						By: &v1alpha1.Resource{
+						By: &v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "AnotherKind",
-							ResourceRef: &v1alpha1.ResourceRef{
+							ResourceRef: &v1beta1.ResourceRef{
 								Name: "another",
 							},
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"baz": "qux",
 								},
@@ -82,27 +82,27 @@ func TestResolveSelectors(t *testing.T) {
 				},
 			},
 			want: want{
-				u: &v1alpha1.Usage{
-					Spec: v1alpha1.UsageSpec{
-						Of: v1alpha1.Resource{
+				u: &v1beta1.Usage{
+					Spec: v1beta1.UsageSpec{
+						Of: v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "SomeKind",
-							ResourceRef: &v1alpha1.ResourceRef{
+							ResourceRef: &v1beta1.ResourceRef{
 								Name: "some",
 							},
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"foo": "bar",
 								},
 							},
 						},
-						By: &v1alpha1.Resource{
+						By: &v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "AnotherKind",
-							ResourceRef: &v1alpha1.ResourceRef{
+							ResourceRef: &v1beta1.ResourceRef{
 								Name: "another",
 							},
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"baz": "qux",
 								},
@@ -115,15 +115,15 @@ func TestResolveSelectors(t *testing.T) {
 		"AlreadyResolvedNoUsing": {
 			reason: "If selectors resolved already, we should do nothing.",
 			args: args{
-				u: &v1alpha1.Usage{
-					Spec: v1alpha1.UsageSpec{
-						Of: v1alpha1.Resource{
+				u: &v1beta1.Usage{
+					Spec: v1beta1.UsageSpec{
+						Of: v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "SomeKind",
-							ResourceRef: &v1alpha1.ResourceRef{
+							ResourceRef: &v1beta1.ResourceRef{
 								Name: "some",
 							},
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"foo": "bar",
 								},
@@ -133,15 +133,15 @@ func TestResolveSelectors(t *testing.T) {
 				},
 			},
 			want: want{
-				u: &v1alpha1.Usage{
-					Spec: v1alpha1.UsageSpec{
-						Of: v1alpha1.Resource{
+				u: &v1beta1.Usage{
+					Spec: v1beta1.UsageSpec{
+						Of: v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "SomeKind",
-							ResourceRef: &v1alpha1.ResourceRef{
+							ResourceRef: &v1beta1.ResourceRef{
 								Name: "some",
 							},
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"foo": "bar",
 								},
@@ -159,12 +159,12 @@ func TestResolveSelectors(t *testing.T) {
 						return errBoom
 					},
 				},
-				u: &v1alpha1.Usage{
-					Spec: v1alpha1.UsageSpec{
-						Of: v1alpha1.Resource{
+				u: &v1beta1.Usage{
+					Spec: v1beta1.UsageSpec{
+						Of: v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "SomeKind",
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"foo": "bar",
 								},
@@ -185,19 +185,19 @@ func TestResolveSelectors(t *testing.T) {
 						return errBoom
 					},
 				},
-				u: &v1alpha1.Usage{
-					Spec: v1alpha1.UsageSpec{
-						Of: v1alpha1.Resource{
+				u: &v1beta1.Usage{
+					Spec: v1beta1.UsageSpec{
+						Of: v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "SomeKind",
-							ResourceRef: &v1alpha1.ResourceRef{
+							ResourceRef: &v1beta1.ResourceRef{
 								Name: "some",
 							},
 						},
-						By: &v1alpha1.Resource{
+						By: &v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "AnotherKind",
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"baz": "qux",
 								},
@@ -238,12 +238,12 @@ func TestResolveSelectors(t *testing.T) {
 						return errBoom
 					},
 				},
-				u: &v1alpha1.Usage{
-					Spec: v1alpha1.UsageSpec{
-						Of: v1alpha1.Resource{
+				u: &v1beta1.Usage{
+					Spec: v1beta1.UsageSpec{
+						Of: v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "SomeKind",
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"foo": "bar",
 								},
@@ -284,19 +284,19 @@ func TestResolveSelectors(t *testing.T) {
 						return errBoom
 					},
 				},
-				u: &v1alpha1.Usage{
-					Spec: v1alpha1.UsageSpec{
-						Of: v1alpha1.Resource{
+				u: &v1beta1.Usage{
+					Spec: v1beta1.UsageSpec{
+						Of: v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "SomeKind",
-							ResourceRef: &v1alpha1.ResourceRef{
+							ResourceRef: &v1beta1.ResourceRef{
 								Name: "some",
 							},
 						},
-						By: &v1alpha1.Resource{
+						By: &v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "AnotherKind",
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"baz": "qux",
 								},
@@ -317,12 +317,12 @@ func TestResolveSelectors(t *testing.T) {
 						return nil
 					},
 				},
-				u: &v1alpha1.Usage{
-					Spec: v1alpha1.UsageSpec{
-						Of: v1alpha1.Resource{
+				u: &v1beta1.Usage{
+					Spec: v1beta1.UsageSpec{
+						Of: v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "SomeKind",
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"foo": "bar",
 								},
@@ -364,12 +364,12 @@ func TestResolveSelectors(t *testing.T) {
 						return nil
 					},
 				},
-				u: &v1alpha1.Usage{
-					Spec: v1alpha1.UsageSpec{
-						Of: v1alpha1.Resource{
+				u: &v1beta1.Usage{
+					Spec: v1beta1.UsageSpec{
+						Of: v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "SomeKind",
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"foo": "bar",
 								},
@@ -441,7 +441,7 @@ func TestResolveSelectors(t *testing.T) {
 						return nil
 					},
 				},
-				u: &v1alpha1.Usage{
+				u: &v1beta1.Usage{
 					ObjectMeta: metav1.ObjectMeta{
 						OwnerReferences: []metav1.OwnerReference{
 							{
@@ -453,32 +453,32 @@ func TestResolveSelectors(t *testing.T) {
 							},
 						},
 					},
-					Spec: v1alpha1.UsageSpec{
-						Of: v1alpha1.Resource{
+					Spec: v1beta1.UsageSpec{
+						Of: v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "SomeKind",
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"foo": "bar",
 								},
 								MatchControllerRef: &valueTrue,
 							},
 						},
-						By: &v1alpha1.Resource{
+						By: &v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "AnotherKind",
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"baz": "qux",
 								},
 							},
 						},
 					},
-					Status: v1alpha1.UsageStatus{},
+					Status: v1beta1.UsageStatus{},
 				},
 			},
 			want: want{
-				u: &v1alpha1.Usage{
+				u: &v1beta1.Usage{
 					ObjectMeta: metav1.ObjectMeta{
 						OwnerReferences: []metav1.OwnerReference{
 							{
@@ -490,27 +490,27 @@ func TestResolveSelectors(t *testing.T) {
 							},
 						},
 					},
-					Spec: v1alpha1.UsageSpec{
-						Of: v1alpha1.Resource{
+					Spec: v1beta1.UsageSpec{
+						Of: v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "SomeKind",
-							ResourceRef: &v1alpha1.ResourceRef{
+							ResourceRef: &v1beta1.ResourceRef{
 								Name: "some",
 							},
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"foo": "bar",
 								},
 								MatchControllerRef: &valueTrue,
 							},
 						},
-						By: &v1alpha1.Resource{
+						By: &v1beta1.Resource{
 							APIVersion: "v1",
 							Kind:       "AnotherKind",
-							ResourceRef: &v1alpha1.ResourceRef{
+							ResourceRef: &v1beta1.ResourceRef{
 								Name: "another",
 							},
-							ResourceSelector: &v1alpha1.ResourceSelector{
+							ResourceSelector: &v1beta1.ResourceSelector{
 								MatchLabels: map[string]string{
 									"baz": "qux",
 								},
diff --git a/internal/usage/handler_test.go b/internal/usage/handler_test.go
index 734f3da30..c2c63cfe9 100644
--- a/internal/usage/handler_test.go
+++ b/internal/usage/handler_test.go
@@ -32,7 +32,7 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/logging"
 	"github.com/crossplane/crossplane-runtime/pkg/test"
 
-	"github.com/crossplane/crossplane/apis/apiextensions/v1alpha1"
+	"github.com/crossplane/crossplane/apis/apiextensions/v1beta1"
 )
 
 var _ admission.Handler = &Handler{}
@@ -178,24 +178,24 @@ func TestHandle(t *testing.T) {
 						return nil
 					},
 					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
-						l := list.(*v1alpha1.UsageList)
-						l.Items = []v1alpha1.Usage{
+						l := list.(*v1beta1.UsageList)
+						l.Items = []v1beta1.Usage{
 							{
 								ObjectMeta: metav1.ObjectMeta{
 									Name: "used-by-some-resource",
 								},
-								Spec: v1alpha1.UsageSpec{
-									Of: v1alpha1.Resource{
+								Spec: v1beta1.UsageSpec{
+									Of: v1beta1.Resource{
 										APIVersion: "nop.crossplane.io/v1alpha1",
 										Kind:       "NopResource",
-										ResourceRef: &v1alpha1.ResourceRef{
+										ResourceRef: &v1beta1.ResourceRef{
 											Name: "used-resource",
 										},
 									},
-									By: &v1alpha1.Resource{
+									By: &v1beta1.Resource{
 										APIVersion: "nop.crossplane.io/v1alpha1",
 										Kind:       "NopResource",
-										ResourceRef: &v1alpha1.ResourceRef{
+										ResourceRef: &v1beta1.ResourceRef{
 											Name: "using-resource",
 										},
 									},
@@ -239,17 +239,17 @@ func TestHandle(t *testing.T) {
 						return nil
 					},
 					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
-						l := list.(*v1alpha1.UsageList)
-						l.Items = []v1alpha1.Usage{
+						l := list.(*v1beta1.UsageList)
+						l.Items = []v1beta1.Usage{
 							{
 								ObjectMeta: metav1.ObjectMeta{
 									Name: "used-by-some-resource",
 								},
-								Spec: v1alpha1.UsageSpec{
-									Of: v1alpha1.Resource{
+								Spec: v1beta1.UsageSpec{
+									Of: v1beta1.Resource{
 										APIVersion: "nop.crossplane.io/v1alpha1",
 										Kind:       "NopResource",
-										ResourceRef: &v1alpha1.ResourceRef{
+										ResourceRef: &v1beta1.ResourceRef{
 											Name: "used-resource",
 										},
 									},
@@ -294,17 +294,17 @@ func TestHandle(t *testing.T) {
 						return nil
 					},
 					MockList: func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
-						l := list.(*v1alpha1.UsageList)
-						l.Items = []v1alpha1.Usage{
+						l := list.(*v1beta1.UsageList)
+						l.Items = []v1beta1.Usage{
 							{
 								ObjectMeta: metav1.ObjectMeta{
 									Name: "used-by-some-resource",
 								},
-								Spec: v1alpha1.UsageSpec{
-									Of: v1alpha1.Resource{
+								Spec: v1beta1.UsageSpec{
+									Of: v1beta1.Resource{
 										APIVersion: "nop.crossplane.io/v1alpha1",
 										Kind:       "NopResource",
-										ResourceRef: &v1alpha1.ResourceRef{
+										ResourceRef: &v1beta1.ResourceRef{
 											Name: "used-resource",
 										},
 									},
diff --git a/test/e2e/manifests/apiextensions/usage/composition-pipeline/composition-updated.yaml b/test/e2e/manifests/apiextensions/usage/composition-pipeline/composition-updated.yaml
index af27abd61..63e89e0f2 100644
--- a/test/e2e/manifests/apiextensions/usage/composition-pipeline/composition-updated.yaml
+++ b/test/e2e/manifests/apiextensions/usage/composition-pipeline/composition-updated.yaml
@@ -63,7 +63,7 @@ spec:
                           time: "10s"
               usage-resource-updated:
                 resource:
-                  apiVersion: apiextensions.crossplane.io/v1alpha1
+                  apiVersion: apiextensions.crossplane.io/v1beta1
                   kind: Usage
                   metadata:
                     labels:
diff --git a/test/e2e/manifests/apiextensions/usage/composition-pipeline/setup/composition.yaml b/test/e2e/manifests/apiextensions/usage/composition-pipeline/setup/composition.yaml
index 12bb35f90..1b33cde4b 100644
--- a/test/e2e/manifests/apiextensions/usage/composition-pipeline/setup/composition.yaml
+++ b/test/e2e/manifests/apiextensions/usage/composition-pipeline/setup/composition.yaml
@@ -59,7 +59,7 @@ spec:
                           time: "10s"
               usage-resource:
                 resource:
-                  apiVersion: apiextensions.crossplane.io/v1alpha1
+                  apiVersion: apiextensions.crossplane.io/v1beta1
                   kind: Usage
                   metadata:
                     labels:
diff --git a/test/e2e/manifests/apiextensions/usage/composition/setup/composition.yaml b/test/e2e/manifests/apiextensions/usage/composition/setup/composition.yaml
index 6042960cd..2f4b0d565 100644
--- a/test/e2e/manifests/apiextensions/usage/composition/setup/composition.yaml
+++ b/test/e2e/manifests/apiextensions/usage/composition/setup/composition.yaml
@@ -47,7 +47,7 @@ spec:
             time: "10s"
   - name: usage-resource
     base:
-      apiVersion: apiextensions.crossplane.io/v1alpha1
+      apiVersion: apiextensions.crossplane.io/v1beta1
       kind: Usage
       spec:
         of:
diff --git a/test/e2e/manifests/apiextensions/usage/standalone/with-by/usage.yaml b/test/e2e/manifests/apiextensions/usage/standalone/with-by/usage.yaml
index e1cd38bcc..8ce199631 100644
--- a/test/e2e/manifests/apiextensions/usage/standalone/with-by/usage.yaml
+++ b/test/e2e/manifests/apiextensions/usage/standalone/with-by/usage.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.crossplane.io/v1alpha1
+apiVersion: apiextensions.crossplane.io/v1beta1
 kind: Usage
 metadata:
   name: using-uses-used
diff --git a/test/e2e/manifests/apiextensions/usage/standalone/with-reason/usage.yaml b/test/e2e/manifests/apiextensions/usage/standalone/with-reason/usage.yaml
index d0057e913..12770d99c 100644
--- a/test/e2e/manifests/apiextensions/usage/standalone/with-reason/usage.yaml
+++ b/test/e2e/manifests/apiextensions/usage/standalone/with-reason/usage.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.crossplane.io/v1alpha1
+apiVersion: apiextensions.crossplane.io/v1beta1
 kind: Usage
 metadata:
   name: protect-a-resource
diff --git a/test/e2e/usage_test.go b/test/e2e/usage_test.go
index f0c616269..783f0fab5 100644
--- a/test/e2e/usage_test.go
+++ b/test/e2e/usage_test.go
@@ -9,7 +9,6 @@ import (
 	"sigs.k8s.io/e2e-framework/klient/k8s"
 	"sigs.k8s.io/e2e-framework/klient/k8s/resources"
 	"sigs.k8s.io/e2e-framework/pkg/features"
-	"sigs.k8s.io/e2e-framework/third_party/helm"
 
 	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
 	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composed"
@@ -20,23 +19,6 @@ import (
 	"github.com/crossplane/crossplane/test/e2e/funcs"
 )
 
-const (
-	// SuiteUsage is the value for the config.LabelTestSuite label to be
-	// assigned to tests that should be part of the Usage test suite.
-	SuiteUsage = "usage"
-)
-
-func init() {
-	environment.AddTestSuite(SuiteUsage,
-		config.WithHelmInstallOpts(
-			helm.WithArgs("--set args={--debug,--enable-usages}"),
-		),
-		config.WithLabelsToSelect(features.Labels{
-			config.LabelTestSuite: []string{SuiteUsage, config.TestSuiteDefault},
-		}),
-	)
-}
-
 // TestUsageStandalone tests scenarios for Crossplane's `Usage` resource without
 // a composition involved.
 func TestUsageStandalone(t *testing.T) {
@@ -88,16 +70,10 @@ func TestUsageStandalone(t *testing.T) {
 
 	environment.Test(t,
 		cases.Build(t.Name()).
-			WithLabel(LabelStage, LabelStageAlpha).
+			WithLabel(LabelStage, LabelStageBeta).
 			WithLabel(LabelArea, LabelAreaAPIExtensions).
 			WithLabel(LabelSize, LabelSizeSmall).
-			WithLabel(LabelModifyCrossplaneInstallation, LabelModifyCrossplaneInstallationTrue).
-			WithLabel(config.LabelTestSuite, SuiteUsage).
-			// Enable the usage feature flag.
-			WithSetup("EnableAlphaUsages", funcs.AllOf(
-				funcs.AsFeaturesFunc(environment.HelmUpgradeCrossplaneToSuite(SuiteUsage)),
-				funcs.ReadyToTestWithin(1*time.Minute, namespace),
-			)).
+			WithLabel(config.LabelTestSuite, config.TestSuiteDefault).
 			WithSetup("PrerequisitesAreCreated", funcs.AllOf(
 				funcs.ApplyResources(FieldManager, manifests, "setup/*.yaml"),
 				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "setup/*.yaml"),
@@ -126,16 +102,10 @@ func TestUsageComposition(t *testing.T) {
 
 	environment.Test(t,
 		features.NewWithDescription(t.Name(), "Tests scenarios for Crossplane's `Usage` resource as part of a composition.").
-			WithLabel(LabelStage, LabelStageAlpha).
+			WithLabel(LabelStage, LabelStageBeta).
 			WithLabel(LabelArea, LabelAreaAPIExtensions).
 			WithLabel(LabelSize, LabelSizeSmall).
-			WithLabel(LabelModifyCrossplaneInstallation, LabelModifyCrossplaneInstallationTrue).
-			WithLabel(config.LabelTestSuite, SuiteUsage).
-			// Enable the usage feature flag.
-			WithSetup("EnableAlphaUsages", funcs.AllOf(
-				funcs.AsFeaturesFunc(environment.HelmUpgradeCrossplaneToSuite(SuiteUsage)),
-				funcs.ReadyToTestWithin(1*time.Minute, namespace),
-			)).
+			WithLabel(config.LabelTestSuite, config.TestSuiteDefault).
 			WithSetup("PrerequisitesAreCreated", funcs.AllOf(
 				funcs.ApplyResources(FieldManager, manifests, "setup/*.yaml"),
 				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "setup/*.yaml"),
@@ -209,16 +179,10 @@ func TestUsageCompositionWithPipeline(t *testing.T) {
 
 	environment.Test(t,
 		features.NewWithDescription(t.Name(), "Tests scenarios for Crossplane's `Usage` resource as part of a composition pipeline and decomposed properly.").
-			WithLabel(LabelStage, LabelStageAlpha).
+			WithLabel(LabelStage, LabelStageBeta).
 			WithLabel(LabelArea, LabelAreaAPIExtensions).
 			WithLabel(LabelSize, LabelSizeSmall).
-			WithLabel(LabelModifyCrossplaneInstallation, LabelModifyCrossplaneInstallationTrue).
-			WithLabel(config.LabelTestSuite, SuiteUsage).
-			// Enable the usage feature flag.
-			WithSetup("EnableAlphaUsages", funcs.AllOf(
-				funcs.AsFeaturesFunc(environment.HelmUpgradeCrossplaneToSuite(SuiteUsage)),
-				funcs.ReadyToTestWithin(1*time.Minute, namespace),
-			)).
+			WithLabel(config.LabelTestSuite, config.TestSuiteDefault).
 			WithSetup("PrerequisitesAreCreated", funcs.AllOf(
 				funcs.ApplyResources(FieldManager, manifests, "setup/*.yaml"),
 				funcs.ResourcesCreatedWithin(30*time.Second, manifests, "setup/*.yaml"),