diff --git a/.rubocop_todo/gitlab/bounded_contexts.yml b/.rubocop_todo/gitlab/bounded_contexts.yml
index ab76b38b46e94dba47e90cf4624b2afdf01bcc79..9e004c73c9ec87221e78adb9638798289052e9c7 100644
--- a/.rubocop_todo/gitlab/bounded_contexts.yml
+++ b/.rubocop_todo/gitlab/bounded_contexts.yml
@@ -1125,6 +1125,7 @@ Gitlab/BoundedContexts:
     - 'app/models/preloaders/commit_status_preloader.rb'
     - 'app/models/preloaders/environments/deployment_preloader.rb'
     - 'app/models/preloaders/group_policy_preloader.rb'
+    - 'app/models/preloaders/issuables_preloader.rb'
     - 'app/models/preloaders/labels_preloader.rb'
     - 'app/models/preloaders/merge_request_diff_preloader.rb'
     - 'app/models/preloaders/project_policy_preloader.rb'
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index b92c07bb81b6de568d372ccf60b1efde7785bc0c..e901cae482c4bb244517109bae6a295e0340d1a4 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -24,10 +24,7 @@ class IssuesResolver < Issues::BaseResolver
     type Types::IssueType.connection_type, null: true
 
     before_connection_authorization do |nodes, current_user|
-      projects = nodes.map(&:project)
-      ::Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects, current_user).execute
-      ::Preloaders::GroupPolicyPreloader.new(projects.filter_map(&:group), current_user).execute
-      ActiveRecord::Associations::Preloader.new(records: projects, associations: project_associations).call
+      ::Preloaders::IssuablesPreloader.new(nodes, current_user, project_associations).preload_all
     end
 
     def self.project_associations
diff --git a/app/graphql/resolvers/work_items/user_work_items_resolver.rb b/app/graphql/resolvers/work_items/user_work_items_resolver.rb
new file mode 100644
index 0000000000000000000000000000000000000000..09161b2b0359115353e69819621a38aab500b0f8
--- /dev/null
+++ b/app/graphql/resolvers/work_items/user_work_items_resolver.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Resolvers
+  module WorkItems
+    class UserWorkItemsResolver < BaseResolver
+      prepend ::WorkItems::LookAheadPreloads
+      include SearchArguments
+      include ::WorkItems::SharedFilterArguments
+
+      NON_FILTER_ARGUMENTS = %i[sort lookahead].freeze
+
+      argument :sort,
+        ::Types::WorkItems::SortEnum,
+        description: 'Sort work items by criteria.',
+        required: false,
+        default_value: :created_desc
+
+      type Types::WorkItemType.connection_type, null: true
+
+      before_connection_authorization do |nodes, current_user|
+        ::Preloaders::IssuablesPreloader.new(nodes, current_user, [:namespace]).preload_all
+      end
+
+      def ready?(**args)
+        unless filter_provided?(args)
+          raise Gitlab::Graphql::Errors::ArgumentError,
+            _('You must provide at least one filter argument for this query')
+        end
+
+        super
+      end
+
+      def resolve_with_lookahead(**args)
+        apply_lookahead(::WorkItems::WorkItemsFinder.new(current_user, prepare_finder_params(args)).execute)
+      end
+
+      private
+
+      def filter_provided?(args)
+        args.except(*NON_FILTER_ARGUMENTS).values.any?(&:present?)
+      end
+    end
+  end
+end
+
+Resolvers::WorkItems::UserWorkItemsResolver.prepend_mod
diff --git a/app/graphql/types/current_user_type.rb b/app/graphql/types/current_user_type.rb
index d601b637162bd6f67e0a50bd16a390ee9fa19925..3ade06cd02fc043d145fa683fb77901dfe1c5c0b 100644
--- a/app/graphql/types/current_user_type.rb
+++ b/app/graphql/types/current_user_type.rb
@@ -16,6 +16,12 @@ class CurrentUserType < ::Types::UserType
       resolver: Resolvers::Users::RecentlyViewedIssuesResolver,
       description: 'Most-recently viewed issues for the current user.',
       experiment: { milestone: '17.9' }
+
+    field :work_items,
+      null: true,
+      resolver: Resolvers::WorkItems::UserWorkItemsResolver,
+      description: 'Find work items visible to the current user.',
+      experiment: { milestone: '17.10' }
   end
   # rubocop:enable Graphql/AuthorizeTypes
 end
diff --git a/app/models/preloaders/issuables_preloader.rb b/app/models/preloaders/issuables_preloader.rb
new file mode 100644
index 0000000000000000000000000000000000000000..47125ac429982f637241dfdcd6560cf9f507ddff
--- /dev/null
+++ b/app/models/preloaders/issuables_preloader.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Preloaders
+  class IssuablesPreloader
+    attr_reader :projects, :current_user, :associations
+
+    def initialize(nodes, current_user, associations)
+      @projects = nodes.map(&:project)
+      @current_user = current_user
+      @associations = associations
+    end
+
+    def preload_all
+      ::Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects, current_user).execute
+      ::Preloaders::GroupPolicyPreloader.new(projects.filter_map(&:group), current_user).execute
+      ActiveRecord::Associations::Preloader.new(records: projects, associations: associations).call
+    end
+  end
+end
+
+Preloaders::IssuablesPreloader.prepend_mod
diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md
index c93ffc80de6bd9428d3ebb41355f1e8cc7d4e728..318fc5926ba2c6f2bf0f05c12e041179b45deacf 100644
--- a/doc/api/graphql/reference/_index.md
+++ b/doc/api/graphql/reference/_index.md
@@ -23642,6 +23642,51 @@ four standard [pagination arguments](#pagination-arguments):
 | ---- | ---- | ----------- |
 | <a id="currentuseruserachievementsincludehidden"></a>`includeHidden` | [`Boolean`](#boolean) | Indicates whether or not achievements hidden from the profile should be included in the result. |
 
+##### `CurrentUser.workItems`
+
+Find work items visible to the current user.
+
+{{< details >}}
+**Introduced** in GitLab 17.10.
+**Status**: Experiment.
+{{< /details >}}
+
+Returns [`WorkItemConnection`](#workitemconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="currentuserworkitemsassigneeusernames"></a>`assigneeUsernames` | [`[String!]`](#string) | Usernames of users assigned to the work item. |
+| <a id="currentuserworkitemsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee wildcard. Incompatible with `assigneeUsernames`. |
+| <a id="currentuserworkitemsauthorusername"></a>`authorUsername` | [`String`](#string) | Filter work items by author username. |
+| <a id="currentuserworkitemsclosedafter"></a>`closedAfter` | [`Time`](#time) | Work items closed after the date. |
+| <a id="currentuserworkitemsclosedbefore"></a>`closedBefore` | [`Time`](#time) | Work items closed before the date. |
+| <a id="currentuserworkitemsconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter for confidential work items. If `false`, excludes confidential work items. If `true`, returns only confidential work items. |
+| <a id="currentuserworkitemscreatedafter"></a>`createdAfter` | [`Time`](#time) | Work items created after the timestamp. |
+| <a id="currentuserworkitemscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Work items created before the timestamp. |
+| <a id="currentuserworkitemsdueafter"></a>`dueAfter` | [`Time`](#time) | Work items due after the timestamp. |
+| <a id="currentuserworkitemsduebefore"></a>`dueBefore` | [`Time`](#time) | Work items due before the timestamp. |
+| <a id="currentuserworkitemsiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of work items. For example, `["1", "2"]`. |
+| <a id="currentuserworkitemsin"></a>`in` | [`[IssuableSearchableField!]`](#issuablesearchablefield) | Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'. |
+| <a id="currentuserworkitemslabelname"></a>`labelName` | [`[String!]`](#string) | Labels applied to the work item. |
+| <a id="currentuserworkitemsmilestonetitle"></a>`milestoneTitle` | [`[String!]`](#string) | Milestone applied to the work item. |
+| <a id="currentuserworkitemsmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter by milestone ID wildcard. Incompatible with `milestoneTitle`. |
+| <a id="currentuserworkitemsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. |
+| <a id="currentuserworkitemsnot"></a>`not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. |
+| <a id="currentuserworkitemsor"></a>`or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. |
+| <a id="currentuserworkitemssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
+| <a id="currentuserworkitemssort"></a>`sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. |
+| <a id="currentuserworkitemsstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of the work item. |
+| <a id="currentuserworkitemssubscribed"></a>`subscribed` | [`SubscriptionStatus`](#subscriptionstatus) | Work items the current user is subscribed to. |
+| <a id="currentuserworkitemstypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter work items by the given work item types. |
+| <a id="currentuserworkitemsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Work items updated after the timestamp. |
+| <a id="currentuserworkitemsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Work items updated before the timestamp. |
+
 ##### `CurrentUser.workspaces`
 
 Workspaces owned by the current user.
diff --git a/spec/graphql/resolvers/work_items/user_work_items_resolver_spec.rb b/spec/graphql/resolvers/work_items/user_work_items_resolver_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bae424800f44026f75f1cf7dda88d57c95754c5a
--- /dev/null
+++ b/spec/graphql/resolvers/work_items/user_work_items_resolver_spec.rb
@@ -0,0 +1,157 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::WorkItems::UserWorkItemsResolver, feature_category: :team_planning do
+  include GraphqlHelpers
+
+  let_it_be(:group)         { create(:group) }
+  let_it_be(:other_group)   { create(:group) }
+  let_it_be(:project)       { create(:project, group: group) }
+  let_it_be(:other_project) { create(:project, group: group) }
+
+  let_it_be(:current_user)  { create(:user, developer_of: project) }
+
+  let(:default_filter) { { created_before: 1.year.from_now } }
+
+  let_it_be(:project_work_item1) do
+    create(
+      :work_item,
+      project: project,
+      state: :opened,
+      created_at: 3.hours.ago,
+      updated_at: 3.hours.ago,
+      title: 'foo'
+    )
+  end
+
+  let_it_be(:project_work_item2) do
+    create(
+      :work_item,
+      project: project,
+      state: :closed,
+      created_at: 1.hour.ago,
+      updated_at: 1.hour.ago,
+      closed_at: 1.hour.ago,
+      title: 'bar'
+    )
+  end
+
+  let_it_be(:other_project_work_item1) do
+    create(
+      :work_item,
+      project: other_project,
+      state: :closed,
+      created_at: 1.hour.ago,
+      updated_at: 1.hour.ago,
+      closed_at: 1.hour.ago,
+      title: 'baz'
+    )
+  end
+
+  let_it_be(:other_project_work_item2) do
+    create(
+      :work_item,
+      project: other_project,
+      confidential: true,
+      title: 'Baz 2'
+    )
+  end
+
+  let_it_be(:other_group_work_item1) do
+    create(
+      :work_item,
+      :group_level,
+      :epic,
+      namespace: other_group,
+      title: 'Baz 3'
+    )
+  end
+
+  specify do
+    expect(described_class).to have_nullable_graphql_type(Types::WorkItemType.connection_type)
+  end
+
+  context "with project access" do
+    describe '#resolve' do
+      it 'finds only the items within the project we have access to' do
+        expect(batch_sync { resolve_items.to_a }).to contain_exactly(project_work_item1, project_work_item2)
+      end
+
+      it 'respects the confidentiality of work items' do
+        other_project.add_guest(current_user)
+
+        expect(resolve_items).to contain_exactly(project_work_item1, project_work_item2, other_project_work_item1)
+      end
+    end
+  end
+
+  context "with group access" do
+    before do
+      stub_feature_flags(namespace_level_work_items: true, work_item_epics: true)
+      stub_licensed_features(epics: true)
+    end
+
+    let_it_be(:developer) { create(:user, developer_of: group) }
+
+    describe '#resolve' do
+      it 'finds only the items within the group we have access to' do
+        expect(batch_sync do
+          resolve_items(default_filter, { current_user: developer }).to_a
+        end).to contain_exactly(project_work_item1, project_work_item2, other_project_work_item1,
+          other_project_work_item2)
+      end
+
+      # TODO: Enable this spec when the work items finder supports returning group level work items across groups
+      it 'returns group level work items' do
+        pending('changes in work items finder to support fetching work items at the group level cross group')
+
+        other_group.add_developer(developer)
+
+        expect(batch_sync do
+          resolve_items(default_filter, { current_user: developer }).to_a
+        end).to contain_exactly(project_work_item1, project_work_item2, other_project_work_item1,
+          other_project_work_item2, other_group_work_item1)
+      end
+    end
+  end
+
+  describe '#resolve' do
+    describe 'sorting' do
+      context 'when sorting by created' do
+        it 'sorts items ascending' do
+          expect(resolve_items(default_filter.merge(sort: :created_asc)).to_a).to eq [project_work_item1,
+            project_work_item2]
+        end
+
+        it 'sorts items descending' do
+          expect(resolve_items(default_filter.merge(sort: :created_desc)).to_a).to eq [project_work_item2,
+            project_work_item1]
+        end
+      end
+
+      context 'when sorting by title' do
+        it 'sorts items ascending' do
+          expect(resolve_items(default_filter.merge(sort: :title_asc)).to_a).to eq [project_work_item2,
+            project_work_item1]
+        end
+
+        it 'sorts items descending' do
+          expect(resolve_items(default_filter.merge(sort: :title_desc)).to_a).to eq [project_work_item1,
+            project_work_item2]
+        end
+      end
+    end
+
+    it 'raises an error if a filter is not provided' do
+      expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError,
+        'You must provide at least one filter argument for this query') do
+        resolve_items({})
+      end
+    end
+  end
+
+  def resolve_items(args = default_filter, context = { current_user: current_user })
+    resolve(described_class, args: args, ctx: context, arg_style: :internal)
+  end
+end
diff --git a/spec/graphql/types/current_user_type_spec.rb b/spec/graphql/types/current_user_type_spec.rb
index ff7a529a057793bcacb94aa22c15a05a90b1b60e..1f6596ea07916cafd37728e74e617a66fa3fbe67 100644
--- a/spec/graphql/types/current_user_type_spec.rb
+++ b/spec/graphql/types/current_user_type_spec.rb
@@ -8,4 +8,43 @@
   it "inherits authorization policies from the UserType superclass" do
     expect(described_class).to require_graphql_authorizations(:read_user)
   end
+
+  describe 'work_items field' do
+    subject { described_class.fields['workItems'] }
+
+    it "finds work_items" do
+      expected_fields = %i[after
+        assigneeUsernames
+        assigneeWildcardId
+        authorUsername
+        before
+        closedAfter
+        closedBefore
+        confidential
+        createdAfter
+        createdBefore
+        dueAfter
+        dueBefore
+        first
+        iids
+        in
+        labelName
+        last
+        milestoneTitle
+        milestoneWildcardId
+        myReactionEmoji
+        not
+        or
+        search
+        sort
+        state
+        subscribed
+        types
+        updatedAfter
+        updatedBefore]
+
+      is_expected.to have_graphql_arguments(expected_fields)
+      is_expected.to have_graphql_type(Types::WorkItemType.connection_type)
+    end
+  end
 end
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index 74e9522c16c7cd6cb7a717da85656480fdfc6ea8..deeed58fe0b5821645a3e86d0c3faaca66f520ab 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -132,7 +132,20 @@
     subject { described_class.fields['timelogs'] }
 
     it 'returns timelogs' do
-      is_expected.to have_graphql_arguments(:startDate, :endDate, :startTime, :endTime, :username, :projectId, :groupId, :after, :before, :first, :last, :sort)
+      is_expected.to have_graphql_arguments(
+        :startDate,
+        :endDate,
+        :startTime,
+        :endTime,
+        :username,
+        :projectId,
+        :groupId,
+        :after,
+        :before,
+        :first,
+        :last,
+        :sort
+      )
       is_expected.to have_graphql_type(Types::TimelogType.connection_type)
       is_expected.to have_graphql_resolver(Resolvers::TimelogResolver)
     end
@@ -177,4 +190,66 @@
       is_expected.to have_graphql_resolver(Resolvers::FeatureFlagResolver)
     end
   end
+
+  describe 'issues field' do
+    subject { described_class.fields['issues'] }
+
+    it "finds issues" do
+      expected_fields = %i[
+        after
+        assigneeId
+        assigneeUsername
+        assigneeUsernames
+        assigneeWildcardId
+        authorUsername
+        before
+        closedAfter
+        closedBefore
+        confidential
+        createdAfter
+        createdBefore
+        crmContactId
+        crmOrganizationId
+        dueAfter
+        dueBefore
+        first
+        iid
+        iids
+        in
+        includeArchived
+        labelName
+        last
+        milestoneTitle
+        milestoneWildcardId
+        myReactionEmoji
+        not
+        or
+        search
+        sort
+        state
+        subscribed
+        types
+        updatedAfter
+        updatedBefore
+      ]
+
+      if Gitlab.ee?
+        expected_fields += %i[
+          epicId
+          epicWildcardId
+          healthStatusFilter
+          includeSubepics
+          iterationCadenceId
+          iterationId
+          iterationTitle
+          iterationWildcardId
+          weight
+          weightWildcardId
+        ]
+      end
+
+      is_expected.to have_graphql_arguments(*expected_fields)
+      is_expected.to have_graphql_type(Types::IssueType.connection_type)
+    end
+  end
 end
diff --git a/spec/models/preloaders/issuables_preloader_spec.rb b/spec/models/preloaders/issuables_preloader_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0e71780326fa6a26f4a5cbb950fd21cd646f43a6
--- /dev/null
+++ b/spec/models/preloaders/issuables_preloader_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Preloaders::IssuablesPreloader, feature_category: :team_planning do
+  let_it_be(:user) { create(:user) }
+
+  let_it_be(:projects) { create_list(:project, 3, :public, :repository) }
+  let_it_be(:issues) { projects.map { |p| create(:issue, project: p) } }
+  let_it_be(:associations) { [:namespace] }
+
+  it 'does not produce N+1 queries' do
+    first_issue = issues_with_preloaded_data.first
+    clean_issues = issues_with_preloaded_data
+
+    expect { access_data(clean_issues) }.to issue_same_number_of_queries_as { access_data([first_issue]) }
+  end
+
+  private
+
+  def issues_with_preloaded_data
+    i = Issue.where(id: issues.map(&:id))
+    described_class.new(i, user, associations).preload_all
+    i
+  end
+
+  def access_data(issues)
+    issues.each { |i| i.project.namespace }
+  end
+end