diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb
index 2ccaea64d14acf462d0d38d6388871584e0c27a9..54dab581686642698959b332f7bdc81129b2406d 100644
--- a/app/services/boards/create_service.rb
+++ b/app/services/boards/create_service.rb
@@ -13,11 +13,11 @@ def execute
     private
 
     def can_create_board?
-      parent.boards.empty? || parent.multiple_issue_boards_available?
+      parent_board_collection.empty? || parent.multiple_issue_boards_available?
     end
 
     def create_board!
-      board = parent.boards.create(params)
+      board = parent_board_collection.create(params)
 
       unless board.persisted?
         return ServiceResponse.error(message: "There was an error when creating a board.", payload: board)
@@ -30,6 +30,10 @@ def create_board!
 
       ServiceResponse.success(payload: board)
     end
+
+    def parent_board_collection
+      parent.boards
+    end
   end
 end
 
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 23673071eaa6bcf6adf031e7121abf958fe0bdc2..bf7966d2a6796dac96083f48ed2920843ddc9aa6 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -8967,6 +8967,56 @@ type EpicBoardConnection {
   pageInfo: PageInfo!
 }
 
+"""
+Autogenerated input type of EpicBoardCreate
+"""
+input EpicBoardCreateInput {
+  """
+  A unique identifier for the client performing the mutation.
+  """
+  clientMutationId: String
+
+  """
+  Full path of the group with which the resource is associated.
+  """
+  groupPath: ID
+
+  """
+  Whether or not backlog list is hidden.
+  """
+  hideBacklogList: Boolean
+
+  """
+  Whether or not closed list is hidden.
+  """
+  hideClosedList: Boolean
+
+  """
+  The board name.
+  """
+  name: String
+}
+
+"""
+Autogenerated return type of EpicBoardCreate
+"""
+type EpicBoardCreatePayload {
+  """
+  A unique identifier for the client performing the mutation.
+  """
+  clientMutationId: String
+
+  """
+  The created epic board.
+  """
+  epicBoard: EpicBoard
+
+  """
+  Errors encountered during execution of the mutation.
+  """
+  errors: [String!]!
+}
+
 """
 An edge in a connection.
 """
@@ -16051,6 +16101,7 @@ type Mutation {
   dismissVulnerability(input: DismissVulnerabilityInput!): DismissVulnerabilityPayload @deprecated(reason: "Use vulnerabilityDismiss. Deprecated in 13.5.")
   environmentsCanaryIngressUpdate(input: EnvironmentsCanaryIngressUpdateInput!): EnvironmentsCanaryIngressUpdatePayload
   epicAddIssue(input: EpicAddIssueInput!): EpicAddIssuePayload
+  epicBoardCreate(input: EpicBoardCreateInput!): EpicBoardCreatePayload
   epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload
   epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload
   exportRequirements(input: ExportRequirementsInput!): ExportRequirementsPayload
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 720b8ee53adf46a808883bda617d00ea966b82e5..aa4dd9d1133d7900e1a25fbcba2415c83b98e439 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -24811,6 +24811,134 @@
           "enumValues": null,
           "possibleTypes": null
         },
+        {
+          "kind": "INPUT_OBJECT",
+          "name": "EpicBoardCreateInput",
+          "description": "Autogenerated input type of EpicBoardCreate",
+          "fields": null,
+          "inputFields": [
+            {
+              "name": "name",
+              "description": "The board name.",
+              "type": {
+                "kind": "SCALAR",
+                "name": "String",
+                "ofType": null
+              },
+              "defaultValue": null
+            },
+            {
+              "name": "hideBacklogList",
+              "description": "Whether or not backlog list is hidden.",
+              "type": {
+                "kind": "SCALAR",
+                "name": "Boolean",
+                "ofType": null
+              },
+              "defaultValue": null
+            },
+            {
+              "name": "hideClosedList",
+              "description": "Whether or not closed list is hidden.",
+              "type": {
+                "kind": "SCALAR",
+                "name": "Boolean",
+                "ofType": null
+              },
+              "defaultValue": null
+            },
+            {
+              "name": "groupPath",
+              "description": "Full path of the group with which the resource is associated.",
+              "type": {
+                "kind": "SCALAR",
+                "name": "ID",
+                "ofType": null
+              },
+              "defaultValue": null
+            },
+            {
+              "name": "clientMutationId",
+              "description": "A unique identifier for the client performing the mutation.",
+              "type": {
+                "kind": "SCALAR",
+                "name": "String",
+                "ofType": null
+              },
+              "defaultValue": null
+            }
+          ],
+          "interfaces": null,
+          "enumValues": null,
+          "possibleTypes": null
+        },
+        {
+          "kind": "OBJECT",
+          "name": "EpicBoardCreatePayload",
+          "description": "Autogenerated return type of EpicBoardCreate",
+          "fields": [
+            {
+              "name": "clientMutationId",
+              "description": "A unique identifier for the client performing the mutation.",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "SCALAR",
+                "name": "String",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "epicBoard",
+              "description": "The created epic board.",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "EpicBoard",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "errors",
+              "description": "Errors encountered during execution of the mutation.",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "LIST",
+                  "name": null,
+                  "ofType": {
+                    "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": "EpicBoardEdge",
@@ -45574,6 +45702,33 @@
               "isDeprecated": false,
               "deprecationReason": null
             },
+            {
+              "name": "epicBoardCreate",
+              "description": null,
+              "args": [
+                {
+                  "name": "input",
+                  "description": null,
+                  "type": {
+                    "kind": "NON_NULL",
+                    "name": null,
+                    "ofType": {
+                      "kind": "INPUT_OBJECT",
+                      "name": "EpicBoardCreateInput",
+                      "ofType": null
+                    }
+                  },
+                  "defaultValue": null
+                }
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "EpicBoardCreatePayload",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
             {
               "name": "epicSetSubscription",
               "description": null,
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 317d94ec28f5bca6408770f30cc948bc699bf478..1b6be9237928c5223a4b520541b58a7dd31e5307 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1448,6 +1448,16 @@ Represents an epic board.
 | `lists` | EpicListConnection | Epic board lists. |
 | `name` | String | Name of the board. |
 
+### EpicBoardCreatePayload
+
+Autogenerated return type of EpicBoardCreate.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `epicBoard` | EpicBoard | The created epic board. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+
 ### EpicDescendantCount
 
 Counts of descendent epics.
diff --git a/ee/app/graphql/ee/types/mutation_type.rb b/ee/app/graphql/ee/types/mutation_type.rb
index 49f1ce12e8a3c88ba189f6d08fd88759184d9fc1..f06f2d4b541e1ae543eff6b2545916ff0ed6601f 100644
--- a/ee/app/graphql/ee/types/mutation_type.rb
+++ b/ee/app/graphql/ee/types/mutation_type.rb
@@ -35,8 +35,9 @@ module MutationType
         mount_mutation ::Mutations::Vulnerabilities::CreateExternalIssueLink
         mount_mutation ::Mutations::Vulnerabilities::DestroyExternalIssueLink
         mount_mutation ::Mutations::Boards::Update
-        mount_mutation ::Mutations::Boards::Lists::UpdateLimitMetrics
         mount_mutation ::Mutations::Boards::UpdateEpicUserPreferences
+        mount_mutation ::Mutations::Boards::EpicBoards::Create
+        mount_mutation ::Mutations::Boards::Lists::UpdateLimitMetrics
         mount_mutation ::Mutations::InstanceSecurityDashboard::AddProject
         mount_mutation ::Mutations::InstanceSecurityDashboard::RemoveProject
         mount_mutation ::Mutations::DastOnDemandScans::Create
diff --git a/ee/app/graphql/mutations/boards/epic_boards/create.rb b/ee/app/graphql/mutations/boards/epic_boards/create.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0a7ba86b49979856d0812c6bd64e7959ed1583c2
--- /dev/null
+++ b/ee/app/graphql/mutations/boards/epic_boards/create.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Mutations
+  module Boards
+    module EpicBoards
+      class Create < ::Mutations::BaseMutation
+        include Mutations::ResolvesGroup
+        include Mutations::Boards::CommonMutationArguments
+
+        graphql_name 'EpicBoardCreate'
+
+        authorize :admin_epic_board
+
+        argument :group_path, GraphQL::ID_TYPE,
+                 required: false,
+                 description: 'Full path of the group with which the resource is associated.'
+
+        field :epic_board,
+              Types::Boards::EpicBoardType,
+              null: true,
+              description: 'The created epic board.'
+
+        def resolve(args)
+          group_path = args.delete(:group_path)
+
+          group = authorized_find!(group_path: group_path)
+          service_response = ::Boards::EpicBoards::CreateService.new(group, current_user, args).execute
+
+          {
+            epic_board: service_response.payload,
+            errors: service_response.errors
+          }
+        end
+
+        private
+
+        def find_object(group_path:)
+          resolve_group(full_path: group_path)
+        end
+      end
+    end
+  end
+end
diff --git a/ee/app/models/boards/epic_board.rb b/ee/app/models/boards/epic_board.rb
index 5aedadb7a152668cfd01911855dbb1bdc3086049..a592a4a29ee99725deb5fa4efa2ef71fbf8a2dc9 100644
--- a/ee/app/models/boards/epic_board.rb
+++ b/ee/app/models/boards/epic_board.rb
@@ -7,7 +7,7 @@ class EpicBoard < ApplicationRecord
     has_many :epic_board_positions, foreign_key: :epic_board_id, inverse_of: :epic_board
     has_many :epic_lists, -> { ordered }, foreign_key: :epic_board_id, inverse_of: :epic_board
 
-    validates :name, length: { maximum: 255 }
+    validates :name, length: { maximum: 255 }, presence: true
 
     scope :order_by_name_asc, -> { order(arel_table[:name].lower.asc).order(id: :asc) }
 
diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb
index 2c9dde505d0e3c6618000da8bb8bcf9191f5c229..89ca8b84d86f8eb4083f927a05086c1f1bbf6bcd 100644
--- a/ee/app/policies/ee/group_policy.rb
+++ b/ee/app/policies/ee/group_policy.rb
@@ -197,6 +197,7 @@ module GroupPolicy
         enable :update_epic
         enable :read_confidential_epic
         enable :destroy_epic_link
+        enable :admin_epic_board
       end
 
       rule { reporter & subepics_available }.policy do
diff --git a/ee/app/services/boards/epic_boards/create_service.rb b/ee/app/services/boards/epic_boards/create_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..07fcae6dc0eb4be08d68318c3d6ff998a52126b1
--- /dev/null
+++ b/ee/app/services/boards/epic_boards/create_service.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Boards
+  module EpicBoards
+    class CreateService < Boards::CreateService
+      extend ::Gitlab::Utils::Override
+
+      override :can_create_board?
+      def can_create_board?
+        Feature.enabled?(:epic_boards, parent)
+      end
+
+      override :parent_board_collection
+      def parent_board_collection
+        parent.epic_boards
+      end
+    end
+  end
+end
diff --git a/ee/changelogs/unreleased/233434-cablett-create_epic_board.yml b/ee/changelogs/unreleased/233434-cablett-create_epic_board.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a059e0b9a11b90d3efe9516c57b269257e2b0b8a
--- /dev/null
+++ b/ee/changelogs/unreleased/233434-cablett-create_epic_board.yml
@@ -0,0 +1,5 @@
+---
+title: Add create epic board via GraphQL
+merge_request: 52258
+author:
+type: added
diff --git a/ee/spec/graphql/mutations/boards/epic_boards/create_spec.rb b/ee/spec/graphql/mutations/boards/epic_boards/create_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..26f234bf294c386f4bb89b50d14d4bba20738500
--- /dev/null
+++ b/ee/spec/graphql/mutations/boards/epic_boards/create_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Mutations::Boards::EpicBoards::Create do
+  include GraphqlHelpers
+
+  let_it_be(:current_user) { create(:user) }
+  let_it_be(:group) { create(:group, :private) }
+
+  let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
+  let(:name) { 'A glorious epic board' }
+
+  subject { mutation.resolve(group_path: group.full_path, name: name) }
+
+  shared_examples 'epic board creation error' do
+    it 'raises error' do
+      expect { mutation.resolve(group_path: group.full_path, name: name) }
+        .to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+    end
+  end
+
+  context 'field tests' do
+    subject { described_class }
+
+    it { is_expected.to have_graphql_arguments(:groupPath, :name, :hideBacklogList, :hideClosedList) }
+    it { is_expected.to have_graphql_fields(:epic_board).at_least }
+  end
+
+  context 'with epic feature enabled and epic_boards feature flag enabled' do
+    before do
+      stub_licensed_features(epics: true)
+      stub_feature_flags(epic_boards: true)
+    end
+
+    context 'when user does not have permission to create epic board' do
+      it_behaves_like 'epic board creation error'
+    end
+
+    context 'when user has permission to create epic board' do
+      before do
+        group.add_reporter(current_user)
+      end
+
+      it 'creates an epic board' do
+        result = mutation.resolve(group_path: group.full_path, name: name)
+
+        expect(result[:epic_board]).to be_valid
+        expect(result[:epic_board].group).to eq(group)
+        expect(result[:epic_board].name).to eq(name)
+      end
+    end
+  end
+
+  context 'with epic_boards feature flag disabled' do
+    before do
+      stub_feature_flags(epic_boards: false)
+    end
+
+    it_behaves_like 'epic board creation error'
+  end
+
+  context 'with epic feature disabled' do
+    before do
+      stub_licensed_features(epics: false)
+    end
+
+    it_behaves_like 'epic board creation error'
+  end
+end
diff --git a/ee/spec/policies/group_policy_spec.rb b/ee/spec/policies/group_policy_spec.rb
index 5033519b59ee7dfd1676db6ee6cfd0f22f9ec87a..0b7ebb7635facfeba391f233c374fbd29b1a3bad 100644
--- a/ee/spec/policies/group_policy_spec.rb
+++ b/ee/spec/policies/group_policy_spec.rb
@@ -7,7 +7,7 @@
 
   let(:epic_rules) do
     %i(read_epic create_epic admin_epic destroy_epic read_confidential_epic
-       destroy_epic_link read_epic_board read_epic_list)
+       destroy_epic_link read_epic_board read_epic_list admin_epic_board)
   end
 
   context 'when epics feature is disabled' do
diff --git a/ee/spec/services/boards/create_service_spec.rb b/ee/spec/services/boards/create_service_spec.rb
index a3e424abc9e82a622c06e0f80df5a9afb028a087..d80ddeb8c59a4c9f2d08053b5ad0d33b8cc8da5f 100644
--- a/ee/spec/services/boards/create_service_spec.rb
+++ b/ee/spec/services/boards/create_service_spec.rb
@@ -13,60 +13,7 @@ def created_board
         stub_licensed_features(multiple_group_issue_boards: true)
       end
 
-      context 'with valid params' do
-        subject(:service) { described_class.new(parent, double, name: 'Backend') }
-
-        it 'creates a new board' do
-          expect { service.execute }.to change(parent.boards, :count).by(1)
-        end
-
-        it 'returns a successful response' do
-          expect(service.execute).to be_success
-        end
-
-        it 'creates the default lists' do
-          board = created_board
-
-          expect(board.lists.size).to eq 2
-          expect(board.lists.first).to be_backlog
-          expect(board.lists.last).to be_closed
-        end
-      end
-
-      context 'with invalid params' do
-        subject(:service) { described_class.new(parent, double, name: nil) }
-
-        it 'does not create a new parent board' do
-          expect { service.execute }.not_to change(parent.boards, :count)
-        end
-
-        it 'returns an error response' do
-          expect(service.execute).to be_error
-        end
-
-        it "does not create board's default lists" do
-          expect(created_board.lists.size).to eq 0
-        end
-      end
-
-      context 'without params' do
-        subject(:service) { described_class.new(parent, double) }
-
-        it 'creates a new parent board' do
-          expect { service.execute }.to change(parent.boards, :count).by(1)
-        end
-
-        it 'returns a successful response' do
-          expect(service.execute).to be_success
-        end
-
-        it "creates board's default lists" do
-          board = created_board
-
-          expect(board.lists.size).to eq 2
-          expect(board.lists.last).to be_closed
-        end
-      end
+      it_behaves_like 'create a board', :boards
     end
   end
 
diff --git a/ee/spec/services/boards/epic_boards/create_service_spec.rb b/ee/spec/services/boards/epic_boards/create_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0820784a2346eae8b02bbdc9f6aa355cd35f07e8
--- /dev/null
+++ b/ee/spec/services/boards/epic_boards/create_service_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Boards::EpicBoards::CreateService, services: true do
+  def created_board
+    service.execute.payload
+  end
+
+  let(:parent) { create(:group) }
+  let(:epic_boards_enabled) { false }
+
+  before do
+    stub_feature_flags(epic_boards: epic_boards_enabled)
+  end
+
+  context 'with epic boards feature not available' do
+    it 'does not create a board' do
+      service = described_class.new(parent, double)
+
+      expect(service.execute.payload).not_to be_nil
+      expect { service.execute }.not_to change(parent.epic_boards, :count)
+    end
+  end
+
+  context 'with epic boards feature available' do
+    let(:epic_boards_enabled) { true }
+
+    it_behaves_like 'create a board', :epic_boards
+  end
+end
diff --git a/ee/spec/support/shared_examples/services/boards/create_boards_shared_examples.rb b/ee/spec/support/shared_examples/services/boards/create_boards_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ab2536f65bcf564ea20386c5845d732697d3587d
--- /dev/null
+++ b/ee/spec/support/shared_examples/services/boards/create_boards_shared_examples.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'create a board' do |scope|
+  context 'with valid params' do
+    subject(:service) { described_class.new(parent, double, name: 'Backend') }
+
+    it 'creates a new board' do
+      expect { service.execute }.to change(parent.send(scope), :count).by(1)
+    end
+
+    it 'returns a successful response' do
+      expect(service.execute).to be_success
+    end
+
+    it 'creates the default lists' do
+      board = created_board
+
+      expect(board.lists.size).to eq 2
+      expect(board.lists.first).to be_backlog
+      expect(board.lists.last).to be_closed
+    end
+  end
+
+  context 'with invalid params' do
+    subject(:service) { described_class.new(parent, double, name: nil) }
+
+    it 'does not create a new parent board' do
+      expect { service.execute }.not_to change(parent.send(scope), :count)
+    end
+
+    it 'returns an error response' do
+      expect(service.execute).to be_error
+    end
+
+    it "does not create board's default lists" do
+      expect(created_board.lists.size).to eq 0
+    end
+  end
+
+  context 'without params' do
+    subject(:service) { described_class.new(parent, double) }
+
+    it 'creates a new parent board' do
+      expect { service.execute }.to change(parent.send(scope), :count).by(1)
+    end
+
+    it 'returns a successful response' do
+      expect(service.execute).to be_success
+    end
+
+    it "creates board's default lists" do
+      board = created_board
+
+      expect(board.lists.size).to eq 2
+      expect(board.lists.last).to be_closed
+    end
+  end
+end