diff --git a/splunklib/client.py b/splunklib/client.py
index 038980ea4..8e52ae251 100644
--- a/splunklib/client.py
+++ b/splunklib/client.py
@@ -64,6 +64,7 @@
import socket
from datetime import datetime, timedelta
from time import sleep
+from typing import Any
from urllib import parse
try:
@@ -102,6 +103,7 @@ def deprecated(message): # pyright: ignore[reportUnknownParameterType]
PATH_APPS = "apps/local/"
PATH_CAPABILITIES = "authorization/capabilities/"
PATH_CONF = "configs/conf-%s/"
+PATH_DASHBOARDS = "data/ui/views/"
PATH_PROPERTIES = "properties/"
PATH_DEPLOYMENT_CLIENTS = "deployment/client/"
PATH_DEPLOYMENT_TENANTS = "deployment/tenants/"
@@ -481,6 +483,15 @@ def capabilities(self):
response = self.get(PATH_CAPABILITIES)
return _load_atom(response, MATCH_ENTRY_CONTENT).capabilities
+ @property
+ def dashboards(self):
+ """Returns the collection of dashboards for this Splunk instance.
+
+ :return: A :class:`Dashboards` collection of :class:`Dashboard`
+ entities.
+ """
+ return Dashboards(self)
+
@property
def event_types(self):
"""Returns the collection of event types defined in this Splunk instance.
@@ -3653,6 +3664,42 @@ def create(self, name, definition, **kwargs):
return Collection.create(self, name, definition=definition, **kwargs)
+class Dashboard(Entity):
+ """This class represents a dashboard (view) in Splunk."""
+
+ def __init__(self, service: "Service", path: str, **kwargs: Any) -> None:
+ Entity.__init__(self, service, path, **kwargs)
+
+ def export(self) -> str:
+ """Returns the dashboard XML content.
+
+ :return: The dashboard XML definition.
+ :rtype: ``string``
+ """
+ return self.content.get("eai:data", "") # pyright: ignore[reportUnknownVariableType]
+
+
+class Dashboards(Collection):
+ """This class represents a collection of dashboards. Retrieve this
+ collection using :meth:`Service.dashboards`."""
+
+ def __init__(self, service: "Service") -> None:
+ Collection.__init__(self, service, PATH_DASHBOARDS, item=Dashboard)
+
+ def create(self, name: str, xml: str, **kwargs: Any) -> Entity: # pyright: ignore[reportIncompatibleMethodOverride,reportImplicitOverride]
+ """Creates a dashboard.
+
+ :param name: The name for the dashboard.
+ :type name: ``string``
+ :param xml: The dashboard XML definition.
+ :type xml: ``string``
+ :param kwargs: Additional arguments (optional).
+ :type kwargs: ``dict``
+ :return: The :class:`Dashboard` entity.
+ """
+ return Collection.create(self, name, **{"eai:data": xml, **kwargs}) # pyright: ignore[reportUnknownVariableType]
+
+
class Settings(Entity):
"""This class represents configuration settings for a Splunk service.
Retrieve this collection using :meth:`Service.settings`."""
diff --git a/tests/unit/test_dashboard.py b/tests/unit/test_dashboard.py
new file mode 100644
index 000000000..441a8b4ae
--- /dev/null
+++ b/tests/unit/test_dashboard.py
@@ -0,0 +1,60 @@
+# Copyright © 2011-2026 Splunk, Inc.
+#
+# Licensed 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.
+
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+from splunklib.client import (
+ PATH_DASHBOARDS,
+ Collection,
+ Dashboard,
+ Dashboards,
+ Entity,
+)
+
+
+class TestDashboard:
+ def test_is_entity_subclass(self) -> None:
+ assert issubclass(Dashboard, Entity)
+
+ def test_path_constant(self) -> None:
+ assert PATH_DASHBOARDS == "data/ui/views/"
+
+ def test_export_returns_eai_data(self) -> None:
+ dashboard = MagicMock(spec=Dashboard)
+ dashboard.content = {"eai:data": ""}
+ assert Dashboard.export(dashboard) == ""
+
+ def test_export_returns_empty_when_missing(self) -> None:
+ dashboard = MagicMock(spec=Dashboard)
+ dashboard.content = {}
+ assert Dashboard.export(dashboard) == ""
+
+
+class TestDashboards:
+ def test_is_collection_subclass(self) -> None:
+ assert issubclass(Dashboards, Collection)
+
+ @patch.object(Collection, "create")
+ def test_create_passes_xml_as_eai_data(self, mock_create: MagicMock) -> None:
+ dashboards = Dashboards.__new__(Dashboards)
+ xml = ""
+ Dashboards.create(dashboards, "test_dash", xml)
+ mock_create.assert_called_once_with(dashboards, "test_dash", **{"eai:data": xml})
+
+ def test_create_raises_on_missing_xml(self) -> None:
+ dashboards = Dashboards.__new__(Dashboards)
+ with pytest.raises(TypeError):
+ Dashboards.create(dashboards, "test_dash") # pyright: ignore[reportCallIssue]