diff --git a/app/graphql/types/diff_stats_summary_type.rb b/app/graphql/types/diff_stats_summary_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5920f3de3da6fcea86dd6634fc7b9e94a3d0c798
--- /dev/null
+++ b/app/graphql/types/diff_stats_summary_type.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Types
+  # rubocop: disable Graphql/AuthorizeTypes
+  # Types that use DiffStatsType should have their own authorization
+  class DiffStatsSummaryType < BaseObject
+    graphql_name 'DiffStatsSummary'
+
+    description 'Aggregated summary of changes'
+
+    field :additions, GraphQL::INT_TYPE, null: false,
+          description: 'Number of lines added'
+    field :deletions, GraphQL::INT_TYPE, null: false,
+          description: 'Number of lines deleted'
+    field :changes, GraphQL::INT_TYPE, null: false,
+          description: 'Number of lines changed'
+
+    def changes
+      object[:additions] + object[:deletions]
+    end
+  end
+  # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/diff_stats_type.rb b/app/graphql/types/diff_stats_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6c79a4c389d17f312dbf8b404a7b1d1cbcea80c4
--- /dev/null
+++ b/app/graphql/types/diff_stats_type.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Types
+  # rubocop: disable Graphql/AuthorizeTypes
+  # Types that use DiffStatsType should have their own authorization
+  class DiffStatsType < BaseObject
+    graphql_name 'DiffStats'
+
+    description 'Changes to a single file'
+
+    field :path, GraphQL::STRING_TYPE, null: false,
+          description: 'File path, relative to repository root'
+    field :additions, GraphQL::INT_TYPE, null: false,
+          description: 'Number of lines added to this file'
+    field :deletions, GraphQL::INT_TYPE, null: false,
+          description: 'Number of lines deleted from this file'
+  end
+  # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index cb4ff7ea0c57ba7a40eadd2f47cc4afbe95bc96e..5c1f82cdf1d49723ce184622055a652007241205 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -54,6 +54,13 @@ class MergeRequestType < BaseObject
           description: 'Indicates if the merge has been set to be merged when its pipeline succeeds (MWPS)'
     field :diff_head_sha, GraphQL::STRING_TYPE, null: true,
           description: 'Diff head SHA of the merge request'
+    field :diff_stats, [Types::DiffStatsType], null: true, calls_gitaly: true,
+          description: 'Details about which files were changed in this merge request' do
+      argument :path, GraphQL::STRING_TYPE, required: false, description: 'A specific file-path'
+    end
+
+    field :diff_stats_summary, Types::DiffStatsSummaryType, null: true, calls_gitaly: true,
+          description: 'Summary of which files were changed in this merge request'
     field :merge_commit_sha, GraphQL::STRING_TYPE, null: true,
           description: 'SHA of the merge request commit (set once merged)'
     field :user_notes_count, GraphQL::INT_TYPE, null: true,
@@ -134,5 +141,24 @@ class MergeRequestType < BaseObject
     end
     field :task_completion_status, Types::TaskCompletionStatus, null: false,
           description: Types::TaskCompletionStatus.description
+
+    def diff_stats(path: nil)
+      stats = Array.wrap(object.diff_stats&.to_a)
+
+      if path.present?
+        stats.select { |s| s.path == path }
+      else
+        stats
+      end
+    end
+
+    def diff_stats_summary
+      nil_stats = { additions: 0, deletions: 0 }
+      return nil_stats unless object.diff_stats.present?
+
+      object.diff_stats.each_with_object(nil_stats) do |status, hash|
+        hash.merge!(additions: status.additions, deletions: status.deletions) { |_, x, y| x + y }
+      end
+    end
   end
 end
diff --git a/changelogs/unreleased/ajk-gql-mr-diff-stats.yml b/changelogs/unreleased/ajk-gql-mr-diff-stats.yml
new file mode 100644
index 0000000000000000000000000000000000000000..430919472741061c9902fb02f43b1e03512f6283
--- /dev/null
+++ b/changelogs/unreleased/ajk-gql-mr-diff-stats.yml
@@ -0,0 +1,5 @@
+---
+title: Add diff stats fields to merge request type
+merge_request: 34966
+author:
+type: added
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 502dac666ee1d0d8a008277ccea75fb511ed4095..bfe46f86de1a953bf81bd26cce1b931bdf9e4561 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -3076,6 +3076,46 @@ type DiffRefs {
   startSha: String!
 }
 
+"""
+Changes to a single file
+"""
+type DiffStats {
+  """
+  Number of lines added to this file
+  """
+  additions: Int!
+
+  """
+  Number of lines deleted from this file
+  """
+  deletions: Int!
+
+  """
+  File path, relative to repository root
+  """
+  path: String!
+}
+
+"""
+Aggregated summary of changes
+"""
+type DiffStatsSummary {
+  """
+  Number of lines added
+  """
+  additions: Int!
+
+  """
+  Number of lines changed
+  """
+  changes: Int!
+
+  """
+  Number of lines deleted
+  """
+  deletions: Int!
+}
+
 type Discussion implements ResolvableInterface {
   """
   Timestamp of the discussion's creation
@@ -6665,6 +6705,21 @@ type MergeRequest implements Noteable {
   """
   diffRefs: DiffRefs
 
+  """
+  Details about which files were changed in this merge request
+  """
+  diffStats(
+    """
+    A specific file-path
+    """
+    path: String
+  ): [DiffStats!]
+
+  """
+  Summary of which files were changed in this merge request
+  """
+  diffStatsSummary: DiffStatsSummary
+
   """
   Indicates if comments on the merge request are locked to members only
   """
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index aecc533931e9483d5ddde679004b7ce307a556d9..f2613304ef2367866f910ba8eb06e826861199de 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -8508,6 +8508,140 @@
           "enumValues": null,
           "possibleTypes": null
         },
+        {
+          "kind": "OBJECT",
+          "name": "DiffStats",
+          "description": "Changes to a single file",
+          "fields": [
+            {
+              "name": "additions",
+              "description": "Number of lines added to this file",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "SCALAR",
+                  "name": "Int",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "deletions",
+              "description": "Number of lines deleted from this file",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "SCALAR",
+                  "name": "Int",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "path",
+              "description": "File path, relative to repository root",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "SCALAR",
+                  "name": "String",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            }
+          ],
+          "inputFields": null,
+          "interfaces": [
+
+          ],
+          "enumValues": null,
+          "possibleTypes": null
+        },
+        {
+          "kind": "OBJECT",
+          "name": "DiffStatsSummary",
+          "description": "Aggregated summary of changes",
+          "fields": [
+            {
+              "name": "additions",
+              "description": "Number of lines added",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "SCALAR",
+                  "name": "Int",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "changes",
+              "description": "Number of lines changed",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "SCALAR",
+                  "name": "Int",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "deletions",
+              "description": "Number of lines deleted",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "SCALAR",
+                  "name": "Int",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            }
+          ],
+          "inputFields": null,
+          "interfaces": [
+
+          ],
+          "enumValues": null,
+          "possibleTypes": null
+        },
         {
           "kind": "OBJECT",
           "name": "Discussion",
@@ -18512,6 +18646,51 @@
               "isDeprecated": false,
               "deprecationReason": null
             },
+            {
+              "name": "diffStats",
+              "description": "Details about which files were changed in this merge request",
+              "args": [
+                {
+                  "name": "path",
+                  "description": "A specific file-path",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                }
+              ],
+              "type": {
+                "kind": "LIST",
+                "name": null,
+                "ofType": {
+                  "kind": "NON_NULL",
+                  "name": null,
+                  "ofType": {
+                    "kind": "OBJECT",
+                    "name": "DiffStats",
+                    "ofType": null
+                  }
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "diffStatsSummary",
+              "description": "Summary of which files were changed in this merge request",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "DiffStatsSummary",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
             {
               "name": "discussionLocked",
               "description": "Indicates if comments on the merge request are locked to members only",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index bba6cd98b2763d582a9c9d415b0544aba7306438..87479a08f17e58d0d37bc1c5755f502debee9b2a 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -517,6 +517,26 @@ Autogenerated return type of DestroySnippet
 | `headSha` | String! | SHA of the HEAD at the time the comment was made |
 | `startSha` | String! | SHA of the branch being compared against |
 
+## DiffStats
+
+Changes to a single file
+
+| Name  | Type  | Description |
+| ---   |  ---- | ----------  |
+| `additions` | Int! | Number of lines added to this file |
+| `deletions` | Int! | Number of lines deleted from this file |
+| `path` | String! | File path, relative to repository root |
+
+## DiffStatsSummary
+
+Aggregated summary of changes
+
+| Name  | Type  | Description |
+| ---   |  ---- | ----------  |
+| `additions` | Int! | Number of lines added |
+| `changes` | Int! | Number of lines changed |
+| `deletions` | Int! | Number of lines deleted |
+
 ## Discussion
 
 | Name  | Type  | Description |
@@ -1002,6 +1022,8 @@ Autogenerated return type of MarkAsSpamSnippet
 | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
 | `diffHeadSha` | String | Diff head SHA of the merge request |
 | `diffRefs` | DiffRefs | References of the base SHA, the head SHA, and the start SHA for this merge request |
+| `diffStats` | DiffStats! => Array | Details about which files were changed in this merge request |
+| `diffStatsSummary` | DiffStatsSummary | Summary of which files were changed in this merge request |
 | `discussionLocked` | Boolean! | Indicates if comments on the merge request are locked to members only |
 | `downvotes` | Int! | Number of downvotes for the merge request |
 | `forceRemoveSourceBranch` | Boolean | Indicates if the project settings will lead to source branch deletion after merge |
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index 6c3817392cd327f0df1a6709d934cf84ef6642d6..b3dccde8ce3f092af0cf3a72c77522ce6c5a051b 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -15,7 +15,8 @@
       description_html state created_at updated_at source_project target_project
       project project_id source_project_id target_project_id source_branch
       target_branch work_in_progress merge_when_pipeline_succeeds diff_head_sha
-      merge_commit_sha user_notes_count should_remove_source_branch diff_refs
+      merge_commit_sha user_notes_count should_remove_source_branch
+      diff_refs diff_stats diff_stats_summary
       force_remove_source_branch merge_status in_progress_merge_commit_sha
       merge_error allow_collaboration should_be_rebased rebase_commit_sha
       rebase_in_progress merge_commit_message default_merge_commit_message
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index f4f1cfaaab5ba5047ee7cf2cb2c15f37e759082f..068be1fa300e4823ad83c6811afb3a9d54934396 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -43,6 +43,54 @@
     expect(merge_request_graphql_data['author']['username']).to eq(merge_request.author.username)
   end
 
+  it 'includes diff stats' do
+    be_natural = an_instance_of(Integer).and(be >= 0)
+
+    post_graphql(query, current_user: current_user)
+
+    sums = merge_request_graphql_data['diffStats'].reduce([0, 0, 0]) do |(a, d, c), node|
+      a_, d_ = node.values_at('additions', 'deletions')
+      [a + a_, d + d_, c + a_ + d_]
+    end
+
+    expect(merge_request_graphql_data).to include(
+      'diffStats' => all(a_hash_including('path' => String, 'additions' => be_natural, 'deletions' => be_natural)),
+      'diffStatsSummary' => a_hash_including('additions' => be_natural, 'deletions' => be_natural, 'changes' => be_natural)
+    )
+
+    # diff_stats is consistent with summary
+    expect(merge_request_graphql_data['diffStatsSummary']
+      .values_at('additions', 'deletions', 'changes')).to eq(sums)
+
+    # diff_stats_summary is internally consistent
+    expect(merge_request_graphql_data['diffStatsSummary']
+      .values_at('additions', 'deletions').sum)
+      .to eq(merge_request_graphql_data.dig('diffStatsSummary', 'changes'))
+      .and be_positive
+  end
+
+  context 'requesting a specific diff stat' do
+    let(:diff_stat) { merge_request.diff_stats.first }
+
+    let(:query) do
+      graphql_query_for(:project, { full_path: project.full_path },
+        query_graphql_field(:merge_request, { iid: merge_request.iid.to_s }, [
+          query_graphql_field(:diff_stats, { path: diff_stat.path }, all_graphql_fields_for('DiffStats'))
+        ])
+      )
+    end
+
+    it 'includes only the requested stats' do
+      post_graphql(query, current_user: current_user)
+
+      expect(merge_request_graphql_data).to include(
+        'diffStats' => contain_exactly(
+          a_hash_including('path' => diff_stat.path, 'additions' => diff_stat.additions, 'deletions' => diff_stat.deletions)
+        )
+      )
+    end
+  end
+
   it 'includes correct mergedAt value when merged' do
     time = 1.week.ago
     merge_request.mark_as_merged