diff --git a/airflow-core/docs/howto/customize-ui.rst b/airflow-core/docs/howto/customize-ui.rst
index ef330a80517f6..24e4b03589000 100644
--- a/airflow-core/docs/howto/customize-ui.rst
+++ b/airflow-core/docs/howto/customize-ui.rst
@@ -70,7 +70,7 @@ We can provide a JSON configuration to customize the UI.
 
 .. important::
 
-  - Currently only the ``brand`` color palette and ``globalCss`` can be customized.
+  - You can customize the ``brand`` color palette, ``globalCss`` and the navigation icon via ``icon`` (and ``icon_dark_mode``).
   - You must supply ``50``-``950`` OKLCH color values for ``brand`` color.
   - OKLCH colors must have next format ``oklch(l c h)`` For more info see :ref:`config:api__theme`
   - There is also the ability to provide custom global CSS for a fine grained theme control.
@@ -180,6 +180,45 @@ Dark Mode
     }
   }'
 
+Icon (SVG-only)
+^^^^^^^^^^^^^^^
+
+You can replace the default Airflow icon in the navigation bar by providing an ``icon`` key (and optionally
+``icon_dark_mode`` for dark color mode) in the ``theme`` configuration. The value must be either an absolute
+``http(s)`` URL or an app-relative path starting with ``/``, and must point to an ``.svg`` file.
+
+.. code-block::
+
+  [api]
+
+  theme = {
+      "tokens": {
+        "colors": {
+          "brand": {
+            "50": { "value": "oklch(0.971 0.013 17.38)" },
+            "100": { "value": "oklch(0.936 0.032 17.717)" },
+            "200": { "value": "oklch(0.885 0.062 18.334)" },
+            "300": { "value": "oklch(0.808 0.114 19.571)" },
+            "400": { "value": "oklch(0.704 0.191 22.216)" },
+            "500": { "value": "oklch(0.637 0.237 25.331)" },
+            "600": { "value": "oklch(0.577 0.245 27.325)" },
+            "700": { "value": "oklch(0.505 0.213 27.518)" },
+            "800": { "value": "oklch(0.444 0.177 26.899)" },
+            "900": { "value": "oklch(0.396 0.141 25.723)" },
+            "950": { "value": "oklch(0.258 0.092 26.042)" }
+          }
+        }
+      },
+      "icon": "/static/company-icon.svg",
+      "icon_dark_mode": "/static/company-icon-dark.svg"
+    }
+
+.. note::
+
+  - Only SVG icons are supported.
+  - If the icon fails to load, Airflow falls back to its default icon.
+  - Icon sizing is controlled by the UI and cannot be configured via the theme.
+
 |
 
 Adding Dashboard Alert Messages
diff --git a/airflow-core/src/airflow/api_fastapi/common/types.py b/airflow-core/src/airflow/api_fastapi/common/types.py
index f31809f4a4f2d..269669c8ee50e 100644
--- a/airflow-core/src/airflow/api_fastapi/common/types.py
+++ b/airflow-core/src/airflow/api_fastapi/common/types.py
@@ -71,6 +71,28 @@ class TimeDelta(BaseModel):
 TimeDeltaWithValidation = Annotated[TimeDelta, BeforeValidator(_validate_timedelta_field)]
 
 
+# Common validator for theme icon fields (SVG-only, http(s) or app-relative path).
+def _validate_theme_icon(value: str | None) -> str | None:
+    if value is None:
+        return value
+    from urllib.parse import urlparse
+
+    parsed = urlparse(value)
+    if parsed.scheme in ("http", "https"):
+        path = parsed.path or ""
+    elif parsed.scheme == "" and value.startswith("/"):
+        path = value
+    else:
+        raise ValueError("theme.icon must be http(s) URL or app-relative path starting with '/'")
+    if not path.lower().endswith(".svg"):
+        raise ValueError("theme.icon must point to an SVG file (*.svg)")
+    return value
+
+
+# Alias type for theme icon fields with shared validation
+ThemeIconType = Annotated[str | None, BeforeValidator(_validate_theme_icon)]
+
+
 class Mimetype(str, Enum):
     """Mimetype for the `Content-Type` header."""
 
@@ -180,3 +202,5 @@ class Theme(BaseModel):
         ],
     ]
     globalCss: dict[str, dict] | None = None
+    icon: ThemeIconType = None
+    icon_dark_mode: ThemeIconType = None
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
index f60a778113021..96550601a0de8 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
+++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
@@ -3242,6 +3242,16 @@ components:
             type: object
           - type: 'null'
           title: Globalcss
+        icon:
+          anyOf:
+          - type: string
+          - type: 'null'
+          title: Icon
+        icon_dark_mode:
+          anyOf:
+          - type: string
+          - type: 'null'
+          title: Icon Dark Mode
       type: object
       required:
       - tokens
diff --git a/airflow-core/src/airflow/config_templates/config.yml b/airflow-core/src/airflow/config_templates/config.yml
index 7c66eb3938f03..8558a71236eb7 100644
--- a/airflow-core/src/airflow/config_templates/config.yml
+++ b/airflow-core/src/airflow/config_templates/config.yml
@@ -1480,7 +1480,9 @@ api:
     theme:
       description: |
         JSON config to customize the Chakra UI theme.
-        Currently only supports ``brand`` color customization and ``globalCss``.
+        Supports ``brand`` color customization, ``globalCss``, and optional navigation icons ``icon``
+        (for light mode) and ``icon_dark_mode`` (for dark mode). Icons must be SVG files and can be
+        either absolute http(s) URLs or app-relative paths starting with ``/``.
 
         Must supply ``50``-``950`` OKLCH color values for ``brand`` color.
 
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
index 0b41a478c253e..61dfb3c340613 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
@@ -8921,6 +8921,28 @@ export const $Theme = {
                 }
             ],
             title: 'Globalcss'
+        },
+        icon: {
+            anyOf: [
+                {
+                    type: 'string'
+                },
+                {
+                    type: 'null'
+                }
+            ],
+            title: 'Icon'
+        },
+        icon_dark_mode: {
+            anyOf: [
+                {
+                    type: 'string'
+                },
+                {
+                    type: 'null'
+                }
+            ],
+            title: 'Icon Dark Mode'
         }
     },
     type: 'object',
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index db34d1f254885..aaa5674dca198 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -2207,6 +2207,8 @@ export type Theme = {
         [key: string]: unknown;
     };
 } | null;
+    icon?: string | null;
+    icon_dark_mode?: string | null;
 };
 
 /**
diff --git a/airflow-core/src/airflow/ui/src/components/Logo.tsx b/airflow-core/src/airflow/ui/src/components/Logo.tsx
new file mode 100644
index 0000000000000..3047abbc698fd
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/Logo.tsx
@@ -0,0 +1,54 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import type { ComponentProps } from "react";
+import { useState } from "react";
+
+import { AirflowPin } from "src/assets/AirflowPin";
+import { useColorMode } from "src/context/colorMode";
+import { useConfig } from "src/queries/useConfig";
+
+type LogoProps = ComponentProps<typeof AirflowPin>;
+
+export const Logo = ({ height = "1.5em", width = "1.5em", ...rest }: LogoProps) => {
+  const theme = useConfig("theme") as unknown as { icon?: string; icon_dark_mode?: string } | undefined;
+  const { colorMode } = useColorMode();
+  const darkIcon = theme?.icon_dark_mode ?? undefined;
+  const lightIcon = theme?.icon ?? undefined;
+  const iconSrc = colorMode === "dark" && darkIcon !== undefined ? darkIcon : lightIcon;
+  const hasIconSrc = Boolean(iconSrc);
+  const [failedLoadingCustomIcon, setFailedLoadingCustomIcon] = useState({ dark: false, light: false });
+
+  if (hasIconSrc && colorMode && !failedLoadingCustomIcon[colorMode]) {
+    return (
+      // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
+      <img
+        alt="Logo"
+        onError={() => setFailedLoadingCustomIcon((prev) => ({ ...prev, [colorMode]: true }))}
+        src={iconSrc}
+        // Chakra allows object as 'height' and 'width' but 'img' tag only allows string or number, so we need to check the type before passing it to 'img'
+        style={{
+          height: typeof height === "string" || typeof height === "number" ? height : "1.5em",
+          width: typeof width === "string" || typeof width === "number" ? width : "1.5em",
+        }}
+      />
+    );
+  }
+
+  return <AirflowPin height={height} width={width} {...rest} />;
+};
diff --git a/airflow-core/src/airflow/ui/src/layouts/BaseLayout.tsx b/airflow-core/src/airflow/ui/src/layouts/BaseLayout.tsx
index cb645babc82ad..30023505e6273 100644
--- a/airflow-core/src/airflow/ui/src/layouts/BaseLayout.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/BaseLayout.tsx
@@ -32,6 +32,7 @@ export const BaseLayout = ({ children }: PropsWithChildren) => {
   const instanceName = useConfig("instance_name");
   const { i18n } = useTranslation();
   const { data: pluginData } = usePluginServiceGetPlugins();
+  const theme = useConfig("theme") as unknown as { icon?: string; icon_dark_mode?: string } | undefined;
 
   const baseReactPlugins =
     pluginData?.plugins
@@ -59,6 +60,47 @@ export const BaseLayout = ({ children }: PropsWithChildren) => {
     };
   }, [i18n]);
 
+  useEffect(() => {
+    const link = document.querySelector<HTMLLinkElement>("link[rel='icon']");
+
+    if (!link) {
+      return undefined;
+    }
+
+    const defaultFavicon = link.href;
+    const darkIcon = theme?.icon_dark_mode;
+    const lightIcon = theme?.icon;
+    // favicon color theme should follow system color scheme, not the one set in Airflow UI.
+    // (tab colors in browsers are based on system color scheme, not the one set in the website)
+    const darkModeQuery = globalThis.matchMedia("(prefers-color-scheme: dark)");
+
+    const updateFavicon = () => {
+      const customIcon =
+        darkModeQuery.matches && typeof darkIcon === "string" && darkIcon.length > 0 ? darkIcon : lightIcon;
+
+      if (typeof customIcon === "string" && customIcon.length > 0) {
+        link.href = customIcon;
+
+        const img = new Image();
+
+        img.addEventListener("error", () => {
+          link.href = defaultFavicon;
+        });
+        img.src = customIcon;
+      } else {
+        link.href = defaultFavicon;
+      }
+    };
+
+    updateFavicon();
+    darkModeQuery.addEventListener("change", updateFavicon);
+
+    return () => {
+      darkModeQuery.removeEventListener("change", updateFavicon);
+      link.href = defaultFavicon;
+    };
+  }, [theme?.icon, theme?.icon_dark_mode]);
+
   return (
     <LocaleProvider locale={i18n.language || "en"}>
       <Box display="flex" flexDirection="column" h="100vh">
diff --git a/airflow-core/src/airflow/ui/src/layouts/Nav/Nav.tsx b/airflow-core/src/airflow/ui/src/layouts/Nav/Nav.tsx
index 7053e6fc5cbe9..a408f8589c9c1 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Nav/Nav.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Nav/Nav.tsx
@@ -27,8 +27,8 @@ import {
   usePluginServiceGetPlugins,
 } from "openapi/queries";
 import type { ExternalViewResponse } from "openapi/requests/types.gen";
-import { AirflowPin } from "src/assets/AirflowPin";
 import { DagIcon } from "src/assets/DagIcon";
+import { Logo } from "src/components/Logo";
 import { useTimezone } from "src/context/timezone";
 import { getTimezoneOffsetString, getTimezoneTooltipLabel } from "src/utils/datetimeUtils";
 import type { NavItemResponse } from "src/utils/types";
@@ -157,7 +157,7 @@ export const Nav = () => {
       <Flex alignItems="center" flexDir="column" gap={1} width="100%">
         <Box alignItems="center" asChild boxSize={14} display="flex" justifyContent="center">
           <Link title={translate("nav.home")} to="/">
-            <AirflowPin
+            <Logo
               _motionSafe={{
                 _hover: {
                   transform: "rotate(360deg)",
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/DagNotFound.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/DagNotFound.tsx
index 0a667e8ef63a8..55ff3310ca172 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/DagNotFound.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/DagNotFound.tsx
@@ -20,7 +20,7 @@ import { Box, Button, Container, Heading, HStack, Text, VStack } from "@chakra-u
 import { useTranslation } from "react-i18next";
 import { useNavigate } from "react-router-dom";
 
-import { AirflowPin } from "src/assets/AirflowPin";
+import { Logo } from "src/components/Logo";
 
 type DagNotFoundProps = {
   readonly dagId: string;
@@ -34,7 +34,7 @@ export const DagNotFound = ({ dagId }: DagNotFoundProps) => {
     <Box alignItems="center" display="flex" justifyContent="center" pt={36} px={4}>
       <Container maxW="lg">
         <VStack gap={8} textAlign="center">
-          <AirflowPin height="50px" width="50px" />
+          <Logo height="50px" width="50px" />
 
           <VStack gap={4}>
             <Heading>404</Heading>
diff --git a/airflow-core/src/airflow/ui/src/pages/Error.tsx b/airflow-core/src/airflow/ui/src/pages/Error.tsx
index fb10bdb212a36..a6a1fdbe1d67c 100644
--- a/airflow-core/src/airflow/ui/src/pages/Error.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Error.tsx
@@ -20,7 +20,7 @@ import { Box, VStack, Heading, Text, Button, Container, HStack, Code } from "@ch
 import { useTranslation } from "react-i18next";
 import { useNavigate, useRouteError, isRouteErrorResponse } from "react-router-dom";
 
-import { AirflowPin } from "src/assets/AirflowPin";
+import { Logo } from "src/components/Logo";
 
 export const ErrorPage = () => {
   const navigate = useNavigate();
@@ -51,7 +51,7 @@ export const ErrorPage = () => {
     <Box alignItems="center" display="flex" justifyContent="center" pt={36} px={4}>
       <Container maxW="lg">
         <VStack gap={8} textAlign="center">
-          <AirflowPin height="50px" width="50px" />
+          <Logo height="50px" width="50px" />
 
           <VStack gap={4}>
             <Heading>{statusCode || translate("error.title")}</Heading>
diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_config.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_config.py
index 6ffe5932fbe88..ca5f44b1fc5b6 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_config.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_config.py
@@ -51,6 +51,8 @@
             "text-transform": "uppercase",
         },
     },
+    "icon": "https://somehost.com/static/custom-logo.svg",
+    "icon_dark_mode": "/static/custom-logo-dark.svg",
 }
 
 expected_config_response = {
