diff --git a/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
index a2670bd628f2c4b0fed074aee9ae4019b6d7c96b..66ec2c45cf8ed6a50a2e0f808973322b69e1c1b8 100644
--- a/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
+++ b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
@@ -24,6 +24,9 @@ module UpdateArguments
         argument :hierarchy_widget, ::Types::WorkItems::Widgets::HierarchyUpdateInputType,
                  required: false,
                  description: 'Input for hierarchy widget.'
+        argument :start_and_due_date_widget, ::Types::WorkItems::Widgets::StartAndDueDateUpdateInputType,
+                 required: false,
+                 description: 'Input for start and due date widget.'
       end
     end
   end
diff --git a/app/graphql/types/work_items/widgets/start_and_due_date_update_input_type.rb b/app/graphql/types/work_items/widgets/start_and_due_date_update_input_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bccd4afe8f34e912b036a3f7f605c952e9d2701b
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/start_and_due_date_update_input_type.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Types
+  module WorkItems
+    module Widgets
+      class StartAndDueDateUpdateInputType < BaseInputObject
+        graphql_name 'WorkItemWidgetStartAndDueDateUpdateInput'
+
+        argument :due_date, Types::DateType,
+                 required: false,
+                 description: 'Due date for the work item.'
+        argument :start_date, Types::DateType,
+                 required: false,
+                 description: 'Start date for the work item.'
+      end
+    end
+  end
+end
diff --git a/app/services/concerns/work_items/widgetable_service.rb b/app/services/concerns/work_items/widgetable_service.rb
index 5665b07dce1ed38840871a1ffca896a79f796f98..ffddf79cdf3025923a724cc474570c8b571fdca2 100644
--- a/app/services/concerns/work_items/widgetable_service.rb
+++ b/app/services/concerns/work_items/widgetable_service.rb
@@ -18,7 +18,7 @@ def widget_service(widget)
     # rubocop:enable Gitlab/ModuleWithInstanceVariables
 
     def widget_service_class(widget)
-      "WorkItems::Widgets::#{widget.type.capitalize}Service::#{self.class.name.demodulize}".constantize
+      "WorkItems::Widgets::#{widget.type.to_s.classify}Service::#{self.class.name.demodulize}".constantize
     rescue NameError
       nil
     end
diff --git a/app/services/work_items/widgets/start_and_due_date_service/update_service.rb b/app/services/work_items/widgets/start_and_due_date_service/update_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6a5dc0d5ef3682d713badaa01d949db8fef9da96
--- /dev/null
+++ b/app/services/work_items/widgets/start_and_due_date_service/update_service.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module WorkItems
+  module Widgets
+    module StartAndDueDateService
+      class UpdateService < WorkItems::Widgets::BaseService
+        def before_update_callback(params: {})
+          return if params.blank?
+
+          widget.work_item.assign_attributes(params.slice(:start_date, :due_date))
+        end
+      end
+    end
+  end
+end
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 00c329253e5457ba4c6b3e3cdaa3c1c2b1255892..1a023a8aa4023762114a38ab5c109675a0485d98 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -5746,6 +5746,7 @@ Input type: `WorkItemUpdateInput`
 | <a id="mutationworkitemupdatedescriptionwidget"></a>`descriptionWidget` | [`WorkItemWidgetDescriptionInput`](#workitemwidgetdescriptioninput) | Input for description widget. |
 | <a id="mutationworkitemupdatehierarchywidget"></a>`hierarchyWidget` | [`WorkItemWidgetHierarchyUpdateInput`](#workitemwidgethierarchyupdateinput) | Input for hierarchy widget. |
 | <a id="mutationworkitemupdateid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
+| <a id="mutationworkitemupdatestartandduedatewidget"></a>`startAndDueDateWidget` | [`WorkItemWidgetStartAndDueDateUpdateInput`](#workitemwidgetstartandduedateupdateinput) | Input for start and due date widget. |
 | <a id="mutationworkitemupdatestateevent"></a>`stateEvent` | [`WorkItemStateEvent`](#workitemstateevent) | Close or reopen a work item. |
 | <a id="mutationworkitemupdatetitle"></a>`title` | [`String`](#string) | Title of the work item. |
 | <a id="mutationworkitemupdateweightwidget"></a>`weightWidget` | [`WorkItemWidgetWeightInput`](#workitemwidgetweightinput) | Input for weight widget. |
@@ -22465,6 +22466,7 @@ A time-frame defined as a closed inclusive range of two dates.
 | <a id="workitemupdatedtaskinputdescriptionwidget"></a>`descriptionWidget` | [`WorkItemWidgetDescriptionInput`](#workitemwidgetdescriptioninput) | Input for description widget. |
 | <a id="workitemupdatedtaskinputhierarchywidget"></a>`hierarchyWidget` | [`WorkItemWidgetHierarchyUpdateInput`](#workitemwidgethierarchyupdateinput) | Input for hierarchy widget. |
 | <a id="workitemupdatedtaskinputid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
+| <a id="workitemupdatedtaskinputstartandduedatewidget"></a>`startAndDueDateWidget` | [`WorkItemWidgetStartAndDueDateUpdateInput`](#workitemwidgetstartandduedateupdateinput) | Input for start and due date widget. |
 | <a id="workitemupdatedtaskinputstateevent"></a>`stateEvent` | [`WorkItemStateEvent`](#workitemstateevent) | Close or reopen a work item. |
 | <a id="workitemupdatedtaskinputtitle"></a>`title` | [`String`](#string) | Title of the work item. |
 
@@ -22493,6 +22495,15 @@ A time-frame defined as a closed inclusive range of two dates.
 | <a id="workitemwidgethierarchyupdateinputchildrenids"></a>`childrenIds` | [`[WorkItemID!]`](#workitemid) | Global IDs of children work items. |
 | <a id="workitemwidgethierarchyupdateinputparentid"></a>`parentId` | [`WorkItemID`](#workitemid) | Global ID of the parent work item. Use `null` to remove the association. |
 
+### `WorkItemWidgetStartAndDueDateUpdateInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="workitemwidgetstartandduedateupdateinputduedate"></a>`dueDate` | [`Date`](#date) | Due date for the work item. |
+| <a id="workitemwidgetstartandduedateupdateinputstartdate"></a>`startDate` | [`Date`](#date) | Start date for the work item. |
+
 ### `WorkItemWidgetWeightInput`
 
 #### Arguments
diff --git a/spec/graphql/types/work_items/widgets/start_and_due_date_update_input_type_spec.rb b/spec/graphql/types/work_items/widgets/start_and_due_date_update_input_type_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..91631093e4ea9af029028f28a8fe4c67514fc1d9
--- /dev/null
+++ b/spec/graphql/types/work_items/widgets/start_and_due_date_update_input_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Types::WorkItems::Widgets::StartAndDueDateUpdateInputType do
+  it { expect(described_class.graphql_name).to eq('WorkItemWidgetStartAndDueDateUpdateInput') }
+
+  it { expect(described_class.arguments.keys).to contain_exactly('startDate', 'dueDate') }
+end
diff --git a/spec/requests/api/graphql/mutations/work_items/update_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_spec.rb
index 6ee3d5b1d35910d2355e31424a25eeb1f9ae3bfa..d5e1ec25a1383eee6989966104a1028951357779 100644
--- a/spec/requests/api/graphql/mutations/work_items/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/update_spec.rb
@@ -144,6 +144,94 @@
       end
     end
 
+    context 'with due and start date widget input' do
+      let(:start_date) { Date.today }
+      let(:due_date) { 1.week.from_now.to_date }
+      let(:fields) do
+        <<~FIELDS
+          workItem {
+            widgets {
+              type
+              ... on WorkItemWidgetStartAndDueDate {
+                startDate
+                dueDate
+              }
+            }
+          }
+          errors
+        FIELDS
+      end
+
+      let(:input) do
+        { 'startAndDueDateWidget' => { 'startDate' => start_date.to_s, 'dueDate' => due_date.to_s } }
+      end
+
+      it 'updates start and due date' do
+        expect do
+          post_graphql_mutation(mutation, current_user: current_user)
+          work_item.reload
+        end.to change(work_item, :start_date).from(nil).to(start_date).and(
+          change(work_item, :due_date).from(nil).to(due_date)
+        )
+
+        expect(response).to have_gitlab_http_status(:success)
+        expect(mutation_response['workItem']['widgets']).to include(
+          {
+            'startDate' => start_date.to_s,
+            'dueDate' => due_date.to_s,
+            'type' => 'START_AND_DUE_DATE'
+          }
+        )
+      end
+
+      context 'when provided input is invalid' do
+        let(:due_date) { 1.week.ago.to_date }
+
+        it 'returns validation errors without the work item' do
+          post_graphql_mutation(mutation, current_user: current_user)
+
+          expect(mutation_response['workItem']).to be_nil
+          expect(mutation_response['errors']).to contain_exactly('Due date must be greater than or equal to start date')
+        end
+      end
+
+      context 'when dates were already set for the work item' do
+        before do
+          work_item.update!(start_date: start_date, due_date: due_date)
+        end
+
+        context 'when updating only start date' do
+          let(:input) do
+            { 'startAndDueDateWidget' => { 'startDate' => nil } }
+          end
+
+          it 'allows setting a single date to null' do
+            expect do
+              post_graphql_mutation(mutation, current_user: current_user)
+              work_item.reload
+            end.to change(work_item, :start_date).from(start_date).to(nil).and(
+              not_change(work_item, :due_date).from(due_date)
+            )
+          end
+        end
+
+        context 'when updating only due date' do
+          let(:input) do
+            { 'startAndDueDateWidget' => { 'dueDate' => nil } }
+          end
+
+          it 'allows setting a single date to null' do
+            expect do
+              post_graphql_mutation(mutation, current_user: current_user)
+              work_item.reload
+            end.to change(work_item, :due_date).from(due_date).to(nil).and(
+              not_change(work_item, :start_date).from(start_date)
+            )
+          end
+        end
+      end
+    end
+
     context 'with hierarchy widget input' do
       let(:widgets_response) { mutation_response['workItem']['widgets'] }
       let(:fields) do
diff --git a/spec/services/work_items/widgets/start_and_due_date_service/update_service_spec.rb b/spec/services/work_items/widgets/start_and_due_date_service/update_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..972a17c30522655f6ff349446b14b81b91615de7
--- /dev/null
+++ b/spec/services/work_items/widgets/start_and_due_date_service/update_service_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WorkItems::Widgets::StartAndDueDateService::UpdateService do
+  let_it_be(:user) { create(:user) }
+  let_it_be(:project) { create(:project) }
+  let_it_be_with_reload(:work_item) { create(:work_item, project: project) }
+
+  let(:widget) { work_item.widgets.find {|widget| widget.is_a?(WorkItems::Widgets::StartAndDueDate) } }
+
+  describe '#before_update_callback' do
+    let(:start_date) { Date.today }
+    let(:due_date) { 1.week.from_now.to_date }
+
+    subject(:update_params) do
+      described_class.new(widget: widget, current_user: user).before_update_callback(params: params)
+    end
+
+    context 'when start and due date params are present' do
+      let(:params) { { start_date: Date.today, due_date: 1.week.from_now.to_date } }
+
+      it 'correctly sets date values' do
+        expect do
+          update_params
+        end.to change(work_item, :start_date).from(nil).to(start_date).and(
+          change(work_item, :due_date).from(nil).to(due_date)
+        )
+      end
+    end
+
+    context 'when date params are not present' do
+      let(:params) { {} }
+
+      it 'does not change work item date values' do
+        expect do
+          update_params
+        end.to not_change(work_item, :start_date).from(nil).and(
+          not_change(work_item, :due_date).from(nil)
+        )
+      end
+    end
+
+    context 'when work item had both date values already set' do
+      before do
+        work_item.update!(start_date: start_date, due_date: due_date)
+      end
+
+      context 'when one of the two params is null' do
+        let(:params) { { start_date: nil } }
+
+        it 'sets only one date to null' do
+          expect do
+            update_params
+          end.to change(work_item, :start_date).from(start_date).to(nil).and(
+            not_change(work_item, :due_date).from(due_date)
+          )
+        end
+      end
+    end
+  end
+end