diff --git a/app/serializers/analytics/cycle_analytics/configuration_entity.rb b/app/serializers/analytics/cycle_analytics/configuration_entity.rb
index 45ea7c927586a9d322782f41b75725d4d1a8a350..6a9ec3f5e9e8d712d298b06f842ae794830eea78 100644
--- a/app/serializers/analytics/cycle_analytics/configuration_entity.rb
+++ b/app/serializers/analytics/cycle_analytics/configuration_entity.rb
@@ -11,11 +11,7 @@ class ConfigurationEntity < Grape::Entity
       private
 
       def events
-        (stage_events.events - stage_events.internal_events).sort_by(&:name)
-      end
-
-      def stage_events
-        Gitlab::Analytics::CycleAnalytics::StageEvents
+        Gitlab::Analytics::CycleAnalytics::StageEvents.selectable_events
       end
     end
   end
diff --git a/ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb b/ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb
index 4aa20643d6248b33818ca5f79cbb1cf17967c354..dbf6033923ed7eea2a733f6e5b61c1309b3ea840 100644
--- a/ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb
+++ b/ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb
@@ -7,10 +7,27 @@ class Groups::Analytics::CycleAnalytics::ValueStreamsController < Groups::Analyt
     render_403 unless can?(current_user, :read_group_cycle_analytics, @group)
   end
 
+  before_action :load_stage_events, only: %i[new edit]
+  before_action :load_value_stream, only: %i[show edit update]
+
+  layout 'group'
+
   def index
     render json: Analytics::CycleAnalytics::ValueStreamSerializer.new.represent(value_streams)
   end
 
+  def new
+    render :new
+  end
+
+  def show
+    render json: Analytics::CycleAnalytics::ValueStreamSerializer.new.represent(@value_stream)
+  end
+
+  def edit
+    render :edit
+  end
+
   def create
     result = Analytics::CycleAnalytics::ValueStreams::CreateService.new(group: @group, params: create_params, current_user: current_user).execute
 
@@ -22,8 +39,7 @@ def create
   end
 
   def update
-    value_stream = @group.value_streams.find(params[:id])
-    result = Analytics::CycleAnalytics::ValueStreams::UpdateService.new(group: @group, params: update_params, current_user: current_user, value_stream: value_stream).execute
+    result = Analytics::CycleAnalytics::ValueStreams::UpdateService.new(group: @group, params: update_params, current_user: current_user, value_stream: @value_stream).execute
 
     if result.success?
       render json: serialize_value_stream(result), status: result.http_status
@@ -107,4 +123,15 @@ def serialize_value_stream(result)
   def serialize_value_stream_error(result)
     Analytics::CycleAnalytics::ValueStreamErrorsSerializer.new(result.payload[:value_stream])
   end
+
+  def load_value_stream
+    @value_stream ||= @group.value_streams.find(params[:id])
+  end
+
+  def load_stage_events
+    @stage_events ||= begin
+      selectable_events = Gitlab::Analytics::CycleAnalytics::StageEvents.selectable_events
+      Analytics::CycleAnalytics::EventEntity.represent(selectable_events)
+    end
+  end
 end
diff --git a/ee/app/views/groups/analytics/cycle_analytics/value_streams/edit.html.haml b/ee/app/views/groups/analytics/cycle_analytics/value_streams/edit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..4c295e7050ce3581355bafc4feced36a2404e5bd
--- /dev/null
+++ b/ee/app/views/groups/analytics/cycle_analytics/value_streams/edit.html.haml
@@ -0,0 +1,7 @@
+- page_title s_("ValueStreamAnalytics|Edit Value Stream: %{name}") % { name: @value_stream.name }
+- add_page_specific_style 'page_bundles/cycle_analytics'
+- data_attributes = group_cycle_analytics_data(@group).merge(value_stream_id: @value_stream.id)
+- data_attributes[:value_stream_url] = group_analytics_cycle_analytics_value_stream_path(group_id: @group, id: @value_stream.id)
+- data_attributes[:stage_events] = @stage_events.to_json
+
+#js-cycle-analytics-edit-vs-app{ data: data_attributes }
diff --git a/ee/app/views/groups/analytics/cycle_analytics/value_streams/new.html.haml b/ee/app/views/groups/analytics/cycle_analytics/value_streams/new.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..24528cd6d2304840a02489fd6a6ff684fad465fc
--- /dev/null
+++ b/ee/app/views/groups/analytics/cycle_analytics/value_streams/new.html.haml
@@ -0,0 +1,8 @@
+- page_title s_("ValueStreamAnalytics|New Value Stream")
+- add_page_specific_style 'page_bundles/cycle_analytics'
+- data_attributes = group_cycle_analytics_data(@group)
+- data_attributes[:create_value_stream_url] = group_analytics_cycle_analytics_value_streams_path(group_id: @group)
+- data_attributes[:stage_events] = @stage_events.to_json
+
+#js-cycle-analytics-new-vs-app{ data: data_attributes }
+
diff --git a/ee/config/routes/group.rb b/ee/config/routes/group.rb
index 2ffb3111212451e5337c30143510f9267dbeb1c0..0a905ec2d74fd7ac5a49e2deeb99980da6057070 100644
--- a/ee/config/routes/group.rb
+++ b/ee/config/routes/group.rb
@@ -48,7 +48,7 @@
             get :count
           end
         end
-        resources :value_streams, only: [:index, :create, :update, :destroy] do
+        resources :value_streams, only: [:index, :new, :edit, :show, :create, :update, :destroy] do
           resources :stages, only: [:index, :create, :update, :destroy] do
             member do
               get :average_duration_chart
diff --git a/ee/spec/controllers/groups/analytics/cycle_analytics/value_streams_controller_spec.rb b/ee/spec/controllers/groups/analytics/cycle_analytics/value_streams_controller_spec.rb
index 2d44a63dfaa58a1236e517f812c492c013c6c9c8..945dd6784feb41aaf8087f5d5ac87e8e2ee54818 100644
--- a/ee/spec/controllers/groups/analytics/cycle_analytics/value_streams_controller_spec.rb
+++ b/ee/spec/controllers/groups/analytics/cycle_analytics/value_streams_controller_spec.rb
@@ -38,6 +38,53 @@
     end
   end
 
+  describe 'GET #show' do
+    let!(:value_stream) { create(:cycle_analytics_group_value_stream, group: group) }
+
+    it 'succeeds' do
+      get :show, params: params.merge(id: value_stream.id)
+
+      expect(response).to have_gitlab_http_status(:ok)
+      expect(response).to match_response_schema('analytics/cycle_analytics/value_stream', dir: 'ee')
+    end
+
+    context 'when value stream is not found' do
+      it 'renders 404' do
+        get :show, params: params.merge(id: non_existing_record_id)
+
+        expect(response).to have_gitlab_http_status(:not_found)
+      end
+    end
+  end
+
+  describe 'GET #edit' do
+    let!(:value_stream) { create(:cycle_analytics_group_value_stream, group: group) }
+
+    it 'succeeds' do
+      get :edit, params: params.merge(id: value_stream.id)
+
+      expect(response).to have_gitlab_http_status(:ok)
+      expect(response).to render_template(:edit)
+    end
+
+    context 'when value stream is not found' do
+      it 'renders 404' do
+        get :edit, params: params.merge(id: non_existing_record_id)
+
+        expect(response).to have_gitlab_http_status(:not_found)
+      end
+    end
+  end
+
+  describe 'GET #new' do
+    it 'succeeds' do
+      get :new, params: params
+
+      expect(response).to have_gitlab_http_status(:ok)
+      expect(response).to render_template(:new)
+    end
+  end
+
   describe 'POST #create' do
     context 'with valid params' do
       it 'returns a successful 200 response' do
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events.rb b/lib/gitlab/analytics/cycle_analytics/stage_events.rb
index b7a11bc0418bac648d0f8b0c408a081da69354a0..9ac433944a87d66b4b97c99f95c39945c185032a 100644
--- a/lib/gitlab/analytics/cycle_analytics/stage_events.rb
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events.rb
@@ -80,6 +80,10 @@ def self.enum_mapping
         def self.internal_events
           INTERNAL_EVENTS
         end
+
+        def self.selectable_events
+          (events - internal_events).sort_by(&:name)
+        end
       end
     end
   end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 341fbf69fd5d80419ee6bc87ea1d36d4ab4dd6c7..fcf8323049345cc06001472f9293fb3a66897d3b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -45033,6 +45033,9 @@ msgstr ""
 msgid "ValueStreamAnalytics|Dashboard"
 msgstr ""
 
+msgid "ValueStreamAnalytics|Edit Value Stream: %{name}"
+msgstr ""
+
 msgid "ValueStreamAnalytics|Go to docs"
 msgstr ""
 
@@ -45051,6 +45054,9 @@ msgstr ""
 msgid "ValueStreamAnalytics|Median time from the earliest commit of a linked issue's merge request to when that issue is closed."
 msgstr ""
 
+msgid "ValueStreamAnalytics|New Value Stream"
+msgstr ""
+
 msgid "ValueStreamAnalytics|Number of commits pushed to the default branch"
 msgstr ""
 
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f5a8035b9b493eef0f6121db77d1b13478d9ecf3
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents, feature_category: :value_stream_management do
+  describe '#selectable_events' do
+    subject(:selectable_events) { described_class.selectable_events }
+
+    it 'excludes internal events' do
+      expect(selectable_events).to include(Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated)
+      expect(selectable_events).to exclude(Gitlab::Analytics::CycleAnalytics::StageEvents::IssueStageEnd)
+    end
+  end
+end