diff --git a/airflow-core/package-lock.json b/airflow-core/package-lock.json
new file mode 100644
index 0000000000000..47020b70c4680
--- /dev/null
+++ b/airflow-core/package-lock.json
@@ -0,0 +1,6 @@
+{
+  "name": "airflow-core",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {}
+}
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/gantt.py b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/gantt.py
index f33b12e6f7e8a..2207370e0965a 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/gantt.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/gantt.py
@@ -17,7 +17,10 @@
 
 from __future__ import annotations
 
-from fastapi import Depends, HTTPException, status
+from datetime import datetime
+from typing import Annotated
+
+from fastapi import Depends, HTTPException, Query, status
 from sqlalchemy import or_, select, union_all
 
 from airflow.api_fastapi.auth.managers.models.resource_details import DagAccessEntity
@@ -59,8 +62,16 @@ def get_gantt_data(
     dag_id: str,
     run_id: str,
     session: SessionDep,
+    start_date_range: Annotated[datetime | None, Query(alias="start_date")] = None,
+    end_date_range: Annotated[datetime | None, Query(alias="end_date")] = None,
 ) -> GanttResponse:
     """Get all task instance tries for Gantt chart."""
+    if start_date_range and end_date_range and start_date_range > end_date_range:
+        raise HTTPException(
+            status.HTTP_400_BAD_REQUEST,
+            "start_date cannot be greater than end_date",
+        )
+
     # Exclude mapped tasks (use grid summaries) and UP_FOR_RETRY (already in history)
     current_tis = select(
         TaskInstance.task_id.label("task_id"),
@@ -74,6 +85,16 @@ def get_gantt_data(
         TaskInstance.run_id == run_id,
         TaskInstance.map_index == -1,
         or_(TaskInstance.state != TaskInstanceState.UP_FOR_RETRY, TaskInstance.state.is_(None)),
+        *(
+            [or_(TaskInstance.end_date >= start_date_range, TaskInstance.end_date.is_(None))]
+            if start_date_range is not None
+            else []
+        ),
+        *(
+            [or_(TaskInstance.start_date <= end_date_range, TaskInstance.start_date.is_(None))]
+            if end_date_range is not None
+            else []
+        ),
     )
 
     history_tis = select(
@@ -87,6 +108,21 @@ def get_gantt_data(
         TaskInstanceHistory.dag_id == dag_id,
         TaskInstanceHistory.run_id == run_id,
         TaskInstanceHistory.map_index == -1,
+        *(
+            [or_(TaskInstanceHistory.end_date >= start_date_range, TaskInstanceHistory.end_date.is_(None))]
+            if start_date_range is not None
+            else []
+        ),
+        *(
+            [
+                or_(
+                    TaskInstanceHistory.start_date <= end_date_range,
+                    TaskInstanceHistory.start_date.is_(None),
+                )
+            ]
+            if end_date_range is not None
+            else []
+        ),
     )
 
     combined = union_all(current_tis, history_tis).subquery()
@@ -97,7 +133,7 @@ def get_gantt_data(
     if not results:
         raise HTTPException(
             status.HTTP_404_NOT_FOUND,
-            f"No task instances for dag_id={dag_id} run_id={run_id}",
+            detail="DAG or DagRun not found",
         )
 
     task_instances = [
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
index e44f9cc3856e1..624d0e70c900a 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
@@ -96,6 +96,7 @@
   "duration": "Duration",
   "edit": "Edit",
   "endDate": "End Date",
+  "endDateBeforeStartDate": "End date cannot be before start date",
   "error": {
     "back": "Back",
     "defaultMessage": "An unexpected error occurred",
@@ -212,6 +213,7 @@
   "sourceAssetEvent_one": "Source Asset Event",
   "sourceAssetEvent_other": "Source Asset Events",
   "startDate": "Start Date",
+  "startDateAfterEndDate": "Start date cannot be after end date",
   "state": "State",
   "states": {
     "deferred": "Deferred",
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
index 62de32cdb98bb..f994020c5d92a 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Box, useToken } from "@chakra-ui/react";
+import { Box, Field, Input, useToken } from "@chakra-ui/react";
 import {
   Chart as ChartJS,
   CategoryScale,
@@ -33,7 +33,7 @@ import {
 import "chart.js/auto";
 import "chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm";
 import annotationPlugin from "chartjs-plugin-annotation";
-import { useDeferredValue } from "react";
+import { useDeferredValue, useState } from "react";
 import { Bar } from "react-chartjs-2";
 import { useTranslation } from "react-i18next";
 import { useParams, useNavigate, useLocation, useSearchParams } from "react-router-dom";
@@ -71,8 +71,6 @@ ChartJS.register(
 type Props = {
   readonly dagRunState?: DagRunState | undefined;
   readonly limit: number;
-  readonly runAfterGte?: string | undefined;
-  readonly runAfterLte?: string | undefined;
   readonly runType?: DagRunType | undefined;
   readonly triggeringUser?: string | undefined;
 };
@@ -81,8 +79,12 @@ const CHART_PADDING = 36;
 const CHART_ROW_HEIGHT = 20;
 const MIN_BAR_WIDTH = 10;
 
-export const Gantt = ({ dagRunState, limit, runAfterGte, runAfterLte, runType, triggeringUser }: Props) => {
+export const Gantt = ({ dagRunState, limit, runType, triggeringUser }: Props) => {
   const { dagId = "", groupId: selectedGroupId, runId = "", taskId: selectedTaskId } = useParams();
+  const [filterStartDate, setFilterStartDate] = useState("");
+  const [filterEndDate, setFilterEndDate] = useState("");
+  const [dateError, setDateError] = useState("");
+
   const [searchParams] = useSearchParams();
   const { openGroupIds } = useOpenGroups();
   const deferredOpenGroupIds = useDeferredValue(openGroupIds);
@@ -116,8 +118,6 @@ export const Gantt = ({ dagRunState, limit, runAfterGte, runAfterLte, runType, t
   const { data: gridRuns, isLoading: runsLoading } = useGridRuns({
     dagRunState,
     limit,
-    runAfterGte,
-    runAfterLte,
     runType,
     triggeringUser,
   });
@@ -143,9 +143,15 @@ export const Gantt = ({ dagRunState, limit, runAfterGte, runAfterLte, runType, t
   const gridTiSummaries = summariesByRunId.get(runId);
   const summariesLoading = Boolean(runId && selectedRun && !summariesByRunId.has(runId));
 
-  // Single fetch for all Gantt data (individual task tries)
+  // startDate and endDate are sent to the backend as query parameters.
+  // The server filters the data — NOT the browser.
   const { data: ganttData, isLoading: ganttLoading } = useGanttServiceGetGanttData(
-    { dagId, runId },
+    {
+      dagId,
+      runId,
+      startDate: filterStartDate ? `${filterStartDate}T00:00:00Z` : undefined,
+      endDate: filterEndDate ? `${filterEndDate}T23:59:59Z` : undefined,
+    },
     undefined,
     {
       enabled: Boolean(dagId) && Boolean(runId) && Boolean(selectedRun),
@@ -212,7 +218,7 @@ export const Gantt = ({ dagRunState, limit, runAfterGte, runAfterLte, runType, t
     translate,
   });
 
-  if (runId === "" || (!isLoading && !selectedRun)) {
+  if (runId === "") {
     return undefined;
   }
 
@@ -228,21 +234,74 @@ export const Gantt = ({ dagRunState, limit, runAfterGte, runAfterLte, runType, t
   };
 
   return (
-    <Box
-      height={`${fixedHeight}px`}
-      minW="250px"
-      ml={-2}
-      mt={`${GRID_BODY_OFFSET_PX}px`}
-      onMouseLeave={handleChartMouseLeave}
-      w="100%"
-    >
-      <Bar
-        data={chartData}
-        options={chartOptions}
-        style={{
-          paddingTop: flatNodes.length === 1 ? 15 : 1.5,
-        }}
-      />
-    </Box>
+    <>
+      {/* Date range inputs — values are sent to backend as query params, no client-side filtering */}
+      <Box alignItems="flex-start" display="flex" gap="4" mb="4">
+        <Field.Root invalid={Boolean(dateError)} maxW="200px">
+          <Field.Label color="fg.muted" fontSize="xs">
+            {translate("startDate")}
+          </Field.Label>
+          <Input
+            fontSize="sm"
+            fontWeight="medium"
+            onChange={(e) => {
+              const value = e.target.value;
+
+              if (value && filterEndDate && value > filterEndDate) {
+                setDateError(translate("startDateAfterEndDate"));
+                return;
+              }
+              setDateError("");
+              setFilterStartDate(value);
+            }}
+            placeholder="YYYY-MM-DD"
+            size="sm"
+            type="date"
+            value={filterStartDate}
+          />
+        </Field.Root>
+
+        <Field.Root invalid={Boolean(dateError)} maxW="200px">
+          <Field.Label color="fg.muted" fontSize="xs">
+            {translate("endDate")}
+          </Field.Label>
+          <Input
+            fontSize="sm"
+            fontWeight="medium"
+            onChange={(e) => {
+              const value = e.target.value;
+
+              if (value && filterStartDate && value < filterStartDate) {
+                setDateError(translate("endDateBeforeStartDate"));
+                return;
+              }
+              setDateError("");
+              setFilterEndDate(value);
+            }}
+            placeholder="YYYY-MM-DD"
+            size="sm"
+            type="date"
+            value={filterEndDate}
+          />
+          {dateError ? <Field.ErrorText fontSize="xs">{dateError}</Field.ErrorText> : undefined}
+        </Field.Root>
+      </Box>
+      <Box
+        height={`${fixedHeight}px`}
+        minW="250px"
+        ml={-2}
+        mt={`${GRID_BODY_OFFSET_PX}px`}
+        onMouseLeave={handleChartMouseLeave}
+        w="100%"
+      >
+        <Bar
+          data={chartData}
+          options={chartOptions}
+          style={{
+            paddingTop: flatNodes.length === 1 ? 15 : 1.5,
+          }}
+        />
+      </Box>
+    </>
   );
 };
diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_gantt.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_gantt.py
index 162c82682afcf..ade423c7fb73b 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_gantt.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_gantt.py
@@ -326,3 +326,65 @@ def test_should_response_404(self, test_client, dag_id, run_id):
         with assert_queries_count(3):
             response = test_client.get(f"/gantt/{dag_id}/{run_id}")
         assert response.status_code == 404
+
+    def test_gantt_with_start_date(self, test_client):
+        """Filtering by start_date excludes tasks that ended before the filter."""
+        # Get all tasks without filter
+        unfiltered = test_client.get(f"/gantt/{DAG_ID}/run_1")
+        assert unfiltered.status_code == 200
+        unfiltered_ids = [ti["task_id"] for ti in unfiltered.json()["task_instances"]]
+
+        # Filter: only tasks with end_date >= 10:06 (or NULL end_date for running tasks)
+        # task  ends at 10:05 → excluded
+        # task2 ends at 10:10 → included
+        # task3 has NULL end_date (running) → always included
+        response = test_client.get(
+            f"/gantt/{DAG_ID}/run_1",
+            params={"start_date": "2024-11-30T10:06:00Z"},
+        )
+        assert response.status_code == 200
+
+        data = response.json()
+        task_ids = [ti["task_id"] for ti in data["task_instances"]]
+
+        # running task (NULL end_date) must always be included
+        assert "task3" in task_ids
+        # task2 ended after the filter start → included
+        assert "task2" in task_ids
+        # task ended before the filter start → excluded
+        assert "task" not in task_ids
+        # filtered result must have fewer tasks than unfiltered
+        assert len(task_ids) < len(unfiltered_ids)
+
+    def test_gantt_with_end_date(self, test_client):
+        """Filtering by end_date excludes tasks that started after the filter."""
+        # Filter: only tasks with start_date <= 10:04
+        # task  starts at 10:00 → included
+        # task2 starts at 10:05 → excluded
+        # task3 starts at 10:10 → excluded
+        response = test_client.get(
+            f"/gantt/{DAG_ID}/run_1",
+            params={"end_date": "2024-11-30T10:04:00Z"},
+        )
+        assert response.status_code == 200
+
+        data = response.json()
+        task_ids = [ti["task_id"] for ti in data["task_instances"]]
+
+        # task started before end_date → included
+        assert "task" in task_ids
+        # task3 started after end_date → excluded
+        assert "task3" not in task_ids
+        # task2 started after end_date → excluded
+        assert "task2" not in task_ids
+
+    def test_invalid_date_range_returns_400(self, test_client):
+        """start_date after end_date must return 400."""
+        response = test_client.get(
+            f"/gantt/{DAG_ID}/run_1",
+            params={
+                "start_date": "2024-12-01T00:00:00Z",
+                "end_date": "2024-11-01T00:00:00Z",
+            },
+        )
+        assert response.status_code == 400
diff --git a/scripts/ci/prek/check_excluded_provider_markers.py b/scripts/ci/prek/check_excluded_provider_markers.py
old mode 100644
new mode 100755
diff --git a/scripts/ci/prek/check_metrics_synced_with_the_registry.py b/scripts/ci/prek/check_metrics_synced_with_the_registry.py
old mode 100644
new mode 100755
diff --git a/scripts/ci/prek/check_registry_types_json_sync.py b/scripts/ci/prek/check_registry_types_json_sync.py
old mode 100644
new mode 100755
