diff --git a/app/graphql/types/ci/catalog/resource_sort_enum.rb b/app/graphql/types/ci/catalog/resource_sort_enum.rb
index 04af70137a2f594cb4ee6efb0c896a18d83906b7..454414923a01785838a6064292fb0c314e500993 100644
--- a/app/graphql/types/ci/catalog/resource_sort_enum.rb
+++ b/app/graphql/types/ci/catalog/resource_sort_enum.rb
@@ -15,6 +15,8 @@ class ResourceSortEnum < BaseEnum
         value 'CREATED_DESC', 'Created date by descending order.', value: :created_at_desc
         value 'STAR_COUNT_ASC', 'Star count by ascending order.', value: :star_count_asc
         value 'STAR_COUNT_DESC', 'Star count by descending order.', value: :star_count_desc
+        value 'USAGE_COUNT_ASC', 'Last 30-day usage count by ascending order.', value: :usage_count_asc
+        value 'USAGE_COUNT_DESC', 'Last 30-day usage count by descending order.', value: :usage_count_desc
       end
     end
   end
diff --git a/app/graphql/types/ci/catalog/resource_type.rb b/app/graphql/types/ci/catalog/resource_type.rb
index c35699e6e01d2dbf50926e3776334c5cde105032..e4d0fff0f2e7c243f5e31d6a159895be7350b9e9 100644
--- a/app/graphql/types/ci/catalog/resource_type.rb
+++ b/app/graphql/types/ci/catalog/resource_type.rb
@@ -57,6 +57,11 @@ class ResourceType < BaseObject
           description: 'Relative path to the starrers page for the catalog resource project.',
           alpha: { milestone: '16.10' }
 
+        field :last_30_day_usage_count, GraphQL::Types::Int, null: false,
+          description: 'Number of projects that used a component from this catalog resource in a pipeline, by using ' \
+                       '`include:component`, in the last 30 days.',
+          alpha: { milestone: '17.0' }
+
         def open_issues_count
           BatchLoader::GraphQL.wrap(object.project.open_issues_count)
         end
diff --git a/app/models/ci/catalog/listing.rb b/app/models/ci/catalog/listing.rb
index 72af569b170ce49bb59415eca21fc6ec6a52f84b..1f3cc4c559e24d5e13ef5bbe2752aa7e6eb69b0e 100644
--- a/app/models/ci/catalog/listing.rb
+++ b/app/models/ci/catalog/listing.rb
@@ -22,6 +22,8 @@ def resources(sort: nil, search: nil, scope: :all)
         when 'latest_released_at_asc' then relation.order_by_latest_released_at_asc
         when 'created_at_asc' then relation.order_by_created_at_asc
         when 'created_at_desc' then relation.order_by_created_at_desc
+        when 'usage_count_asc' then relation.order_by_last_30_day_usage_count_asc
+        when 'usage_count_desc' then relation.order_by_last_30_day_usage_count_desc
         when 'star_count_asc' then relation.order_by_star_count(:asc)
         else
           relation.order_by_star_count(:desc)
diff --git a/app/models/ci/catalog/resource.rb b/app/models/ci/catalog/resource.rb
index b50aac09cedb383597429f9f1ecbfb4f135b118d..f7faef5ff2840d226b1f2abbac1c462dc1c6d760 100644
--- a/app/models/ci/catalog/resource.rb
+++ b/app/models/ci/catalog/resource.rb
@@ -48,6 +48,11 @@ class Resource < ::ApplicationRecord
         )
       end
 
+      # TODO: The usage counts will be populated by a worker that aggregates the data daily.
+      # See https://gitlab.com/gitlab-org/gitlab/-/issues/452545.
+      scope :order_by_last_30_day_usage_count_desc, -> { reorder(last_30_day_usage_count: :desc) }
+      scope :order_by_last_30_day_usage_count_asc, -> { reorder(last_30_day_usage_count: :asc) }
+
       delegate :avatar_path, :star_count, :full_path, to: :project
 
       enum state: { unpublished: 0, published: 1 }
diff --git a/app/models/ci/catalog/resources/components/usage.rb b/app/models/ci/catalog/resources/components/usage.rb
index b721e57d0a75eadb56aa380555ce346b979dfc0c..6ec2b7397022e575cf59dc54b10c889c0c4afe1f 100644
--- a/app/models/ci/catalog/resources/components/usage.rb
+++ b/app/models/ci/catalog/resources/components/usage.rb
@@ -4,9 +4,11 @@ module Ci
   module Catalog
     module Resources
       module Components
-        # This model is used to track when a project includes a component in a pipeline.
-        # The column `used_by_project_id` does not have an FK constraint because we want
-        # to preserve historical usage data.
+        # This model is used to track when a project includes a catalog component in
+        # a pipeline with the keyword `include:component`. Usage data is recorded
+        # during pipeline creation in Gitlab::Ci::Pipeline::Chain::ComponentUsage.
+        # The column `used_by_project_id` does not have an FK constraint because
+        # we want to preserve historical usage data.
         class Usage < ::ApplicationRecord
           include PartitionedTable
 
diff --git a/db/post_migrate/20240504042340_add_index_catalog_resources_on_usage_count.rb b/db/post_migrate/20240504042340_add_index_catalog_resources_on_usage_count.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6e844f3fcc74620744226258e31cb404ae56b8a3
--- /dev/null
+++ b/db/post_migrate/20240504042340_add_index_catalog_resources_on_usage_count.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexCatalogResourcesOnUsageCount < Gitlab::Database::Migration[2.2]
+  disable_ddl_transaction!
+
+  milestone '17.0'
+
+  INDEX_NAME = 'index_catalog_resources_on_last_30_day_usage_count'
+
+  def up
+    add_concurrent_index :catalog_resources, :last_30_day_usage_count, where: 'state = 1', name: INDEX_NAME
+  end
+
+  def down
+    remove_concurrent_index_by_name :catalog_resources, INDEX_NAME
+  end
+end
diff --git a/db/schema_migrations/20240504042340 b/db/schema_migrations/20240504042340
new file mode 100644
index 0000000000000000000000000000000000000000..4132cb4ef4f7955823e9a6318b5b231c15e3116e
--- /dev/null
+++ b/db/schema_migrations/20240504042340
@@ -0,0 +1 @@
+41bc8518c30c6a10db8eae14b971418ef6ae65cf6cb70ddd42f72ecd848a01b4
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index e0568bd8061618c7b7f1b725101bebfbfe3d3460..a3f06e99b24739d7f38e246b16c632ff1c8da2f8 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -24657,6 +24657,8 @@ CREATE UNIQUE INDEX index_catalog_resource_versions_on_release_id ON catalog_res
 
 CREATE INDEX index_catalog_resource_versions_on_resource_id_and_released_at ON catalog_resource_versions USING btree (catalog_resource_id, released_at);
 
+CREATE INDEX index_catalog_resources_on_last_30_day_usage_count ON catalog_resources USING btree (last_30_day_usage_count) WHERE (state = 1);
+
 CREATE UNIQUE INDEX index_catalog_resources_on_project_id ON catalog_resources USING btree (project_id);
 
 CREATE INDEX index_catalog_resources_on_search_vector ON catalog_resources USING gin (search_vector);
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index ee1edeb80baac764cdc4eac115113d390df7bc4e..1bc4bcf821f2a98d95d8aef40e3ac4dd1017b846 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -17154,6 +17154,7 @@ Represents the total number of issues and their weights for a particular day.
 | <a id="cicatalogresourcefullpath"></a>`fullPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 16.11. **Status**: Experiment. Full project path of the catalog resource. |
 | <a id="cicatalogresourceicon"></a>`icon` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 15.11. **Status**: Experiment. Icon for the catalog resource. |
 | <a id="cicatalogresourceid"></a>`id` **{warning-solid}** | [`ID!`](#id) | **Introduced** in GitLab 15.11. **Status**: Experiment. ID of the catalog resource. |
+| <a id="cicatalogresourcelast30dayusagecount"></a>`last30DayUsageCount` **{warning-solid}** | [`Int!`](#int) | **Introduced** in GitLab 17.0. **Status**: Experiment. Number of projects that used a component from this catalog resource in a pipeline, by using `include:component`, in the last 30 days. |
 | <a id="cicatalogresourcelatestreleasedat"></a>`latestReleasedAt` **{warning-solid}** | [`Time`](#time) | **Introduced** in GitLab 16.5. **Status**: Experiment. Release date of the catalog resource's latest version. |
 | <a id="cicatalogresourcename"></a>`name` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 15.11. **Status**: Experiment. Name of the catalog resource. |
 | <a id="cicatalogresourceopenissuescount"></a>`openIssuesCount` **{warning-solid}** | [`Int!`](#int) | **Introduced** in GitLab 16.3. **Status**: Experiment. Count of open issues that belong to the the catalog resource. |
@@ -32494,6 +32495,8 @@ Values for sorting catalog resources.
 | <a id="cicatalogresourcesortname_desc"></a>`NAME_DESC` | Name by descending order. |
 | <a id="cicatalogresourcesortstar_count_asc"></a>`STAR_COUNT_ASC` | Star count by ascending order. |
 | <a id="cicatalogresourcesortstar_count_desc"></a>`STAR_COUNT_DESC` | Star count by descending order. |
+| <a id="cicatalogresourcesortusage_count_asc"></a>`USAGE_COUNT_ASC` | Last 30-day usage count by ascending order. |
+| <a id="cicatalogresourcesortusage_count_desc"></a>`USAGE_COUNT_DESC` | Last 30-day usage count by descending order. |
 
 ### `CiCatalogResourceVerificationLevel`
 
diff --git a/spec/graphql/types/ci/catalog/resource_sort_enum_spec.rb b/spec/graphql/types/ci/catalog/resource_sort_enum_spec.rb
index 020b00e704346e547bf6a547acdd4718283599e3..122757c4f6ed79331331f8724ce76630deb265eb 100644
--- a/spec/graphql/types/ci/catalog/resource_sort_enum_spec.rb
+++ b/spec/graphql/types/ci/catalog/resource_sort_enum_spec.rb
@@ -8,7 +8,7 @@
   it 'exposes all the existing catalog resource sort orders' do
     expect(described_class.values.keys).to include(
       *%w[NAME_ASC NAME_DESC LATEST_RELEASED_AT_ASC LATEST_RELEASED_AT_DESC CREATED_ASC CREATED_DESC
-        STAR_COUNT_ASC STAR_COUNT_DESC]
+        STAR_COUNT_ASC STAR_COUNT_DESC USAGE_COUNT_ASC USAGE_COUNT_DESC]
     )
   end
 end
diff --git a/spec/graphql/types/ci/catalog/resource_type_spec.rb b/spec/graphql/types/ci/catalog/resource_type_spec.rb
index f81d885e31a74cde6457e50fd8ff03f16228c958..a2c701537c628f07c74e98922cf19e7a6223d640 100644
--- a/spec/graphql/types/ci/catalog/resource_type_spec.rb
+++ b/spec/graphql/types/ci/catalog/resource_type_spec.rb
@@ -20,6 +20,7 @@
       starrers_path
       open_issues_count
       open_merge_requests_count
+      last_30_day_usage_count
     ]
 
     expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/models/ci/catalog/listing_spec.rb b/spec/models/ci/catalog/listing_spec.rb
index f052f9580def8336b134013d79d7c8993e30ff63..9cff299c9ce7a29a6adb0908f93fd35c7212f0e4 100644
--- a/spec/models/ci/catalog/listing_spec.rb
+++ b/spec/models/ci/catalog/listing_spec.rb
@@ -35,10 +35,22 @@
 
     let(:params) { {} }
 
-    let_it_be(:public_resource_a) { create(:ci_catalog_resource, :published, project: public_namespace_project) }
-    let_it_be(:public_resource_b) { create(:ci_catalog_resource, :published, project: public_project) }
-    let_it_be(:internal_resource) { create(:ci_catalog_resource, :published, project: internal_project) }
-    let_it_be(:private_namespace_resource) { create(:ci_catalog_resource, :published, project: namespace_project_a) }
+    let_it_be(:public_resource_a) do
+      create(:ci_catalog_resource, :published, project: public_namespace_project, last_30_day_usage_count: 100)
+    end
+
+    let_it_be(:public_resource_b) do
+      create(:ci_catalog_resource, :published, project: public_project, last_30_day_usage_count: 70)
+    end
+
+    let_it_be(:internal_resource) do
+      create(:ci_catalog_resource, :published, project: internal_project, last_30_day_usage_count: 80)
+    end
+
+    let_it_be(:private_namespace_resource) do
+      create(:ci_catalog_resource, :published, project: namespace_project_a, last_30_day_usage_count: 90)
+    end
+
     let_it_be(:unpublished_resource) { create(:ci_catalog_resource, project: namespace_project_b) }
 
     it 'by default returns all resources visible to the current user' do
@@ -158,6 +170,22 @@
           is_expected.to eq([internal_resource, public_resource_a, public_resource_b, private_namespace_resource])
         end
       end
+
+      context 'when the sort is usage_count descending' do
+        let_it_be(:sort) { :usage_count_desc }
+
+        it 'contains catalog resources sorted by last_30_day_usage_count descending' do
+          is_expected.to eq([public_resource_a, private_namespace_resource, internal_resource, public_resource_b])
+        end
+      end
+
+      context 'when the sort is usage_count ascending' do
+        let_it_be(:sort) { :usage_count_asc }
+
+        it 'contains catalog resources sorted by last_30_day_usage_count ascending' do
+          is_expected.to eq([public_resource_b, internal_resource, private_namespace_resource, public_resource_a])
+        end
+      end
     end
   end
 
diff --git a/spec/models/ci/catalog/resource_spec.rb b/spec/models/ci/catalog/resource_spec.rb
index f07a9faeed94adab51d1df8ae8e3dd4368e2e7d0..dcb140e1fb84dbeb87c53ca2edc56b0047f17c93 100644
--- a/spec/models/ci/catalog/resource_spec.rb
+++ b/spec/models/ci/catalog/resource_spec.rb
@@ -10,11 +10,13 @@
   let_it_be(:project_c) { create(:project, name: 'C', description: 'B', star_count: 30) }
 
   let_it_be_with_reload(:resource_a) do
-    create(:ci_catalog_resource, project: project_a, latest_released_at: '2023-02-01T00:00:00Z')
+    create(:ci_catalog_resource, project: project_a, latest_released_at: '2023-02-01T00:00:00Z',
+      last_30_day_usage_count: 150)
   end
 
   let_it_be(:resource_b) do
-    create(:ci_catalog_resource, project: project_b, latest_released_at: '2023-01-01T00:00:00Z')
+    create(:ci_catalog_resource, project: project_b, latest_released_at: '2023-01-01T00:00:00Z',
+      last_30_day_usage_count: 100)
   end
 
   let_it_be(:resource_c) { create(:ci_catalog_resource, project: project_c) }
@@ -144,6 +146,22 @@
     end
   end
 
+  describe 'order_by_last_30_day_usage_count_desc' do
+    it 'returns catalog resources sorted by last 30-day usage count in descending order' do
+      ordered_resources = described_class.order_by_last_30_day_usage_count_desc
+
+      expect(ordered_resources).to eq([resource_a, resource_b, resource_c])
+    end
+  end
+
+  describe 'order_by_last_30_day_usage_count_asc' do
+    it 'returns catalog resources sorted by last 30-day usage count in ascending order' do
+      ordered_resources = described_class.order_by_last_30_day_usage_count_asc
+
+      expect(ordered_resources).to eq([resource_c, resource_b, resource_a])
+    end
+  end
+
   describe 'authorized catalog resources' do
     let_it_be(:namespace) { create(:group) }
     let_it_be(:other_namespace) { create(:group) }
diff --git a/spec/requests/api/graphql/ci/catalog/resource_spec.rb b/spec/requests/api/graphql/ci/catalog/resource_spec.rb
index c3d8edb2175fb72138e332108567da7f49d6148c..facf96f60ea92a85be4234438ec1c0af321edfa4 100644
--- a/spec/requests/api/graphql/ci/catalog/resource_spec.rb
+++ b/spec/requests/api/graphql/ci/catalog/resource_spec.rb
@@ -22,7 +22,9 @@
     )
   end
 
-  let_it_be_with_reload(:resource) { create(:ci_catalog_resource, :published, project: project) }
+  let_it_be_with_reload(:resource) do
+    create(:ci_catalog_resource, :published, project: project, last_30_day_usage_count: 15)
+  end
 
   let(:query) do
     <<~GQL
@@ -47,7 +49,8 @@
           fullPath: project.full_path,
           webPath: "/#{project.full_path}",
           verificationLevel: "UNVERIFIED",
-          starCount: project.star_count
+          starCount: project.star_count,
+          last30DayUsageCount: resource.last_30_day_usage_count
         )
       )
     end
diff --git a/spec/requests/api/graphql/ci/catalog/resources_spec.rb b/spec/requests/api/graphql/ci/catalog/resources_spec.rb
index fe071ea575819ad1587a337480b6a824da58db60..f0488ef23f6c7093b1773aa7f4e15c58e187dc3c 100644
--- a/spec/requests/api/graphql/ci/catalog/resources_spec.rb
+++ b/spec/requests/api/graphql/ci/catalog/resources_spec.rb
@@ -30,7 +30,8 @@
   end
 
   let_it_be(:resource1) do
-    create(:ci_catalog_resource, :published, project: project1, latest_released_at: '2023-01-01T00:00:00Z')
+    create(:ci_catalog_resource, :published, project: project1, latest_released_at: '2023-01-01T00:00:00Z',
+      last_30_day_usage_count: 15)
   end
 
   let_it_be(:public_resource) { create(:ci_catalog_resource, :published, project: public_project) }
@@ -50,6 +51,7 @@
             latestReleasedAt
             starCount
             starrersPath
+            last30DayUsageCount
           }
         }
       }
@@ -88,7 +90,8 @@
         starrersPath: Gitlab::Routing.url_helpers.project_starrers_path(project1),
         verificationLevel: 'UNVERIFIED',
         fullPath: project1.full_path,
-        webPath: "/#{project1.full_path}"
+        webPath: "/#{project1.full_path}",
+        last30DayUsageCount: resource1.last_30_day_usage_count
       ),
       a_graphql_entity_for(public_resource)
     )