diff --git a/.coveragerc b/.coveragerc
index 34b6dde985469be98bd8e8b25dcc435e46cb2ed1..bcd4e349668672d2e9554aeeb2433b19f8a8ecc3 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -481,6 +481,7 @@ omit =
     homeassistant/components/google_cloud/tts.py
     homeassistant/components/google_maps/device_tracker.py
     homeassistant/components/google_pubsub/__init__.py
+    homeassistant/components/gpsd/__init__.py
     homeassistant/components/gpsd/sensor.py
     homeassistant/components/greenwave/light.py
     homeassistant/components/growatt_server/__init__.py
diff --git a/CODEOWNERS b/CODEOWNERS
index 9691a8d72f6e3e60d63e04b3188607d48b3b9bac..af196548bb33a8e5dc367d967ca8d9a4b56d179e 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -507,7 +507,8 @@ build.json @home-assistant/supervisor
 /tests/components/govee_ble/ @bdraco @PierreAronnax
 /homeassistant/components/govee_light_local/ @Galorhallen
 /tests/components/govee_light_local/ @Galorhallen
-/homeassistant/components/gpsd/ @fabaff
+/homeassistant/components/gpsd/ @fabaff @jrieger
+/tests/components/gpsd/ @fabaff @jrieger
 /homeassistant/components/gree/ @cmroche
 /tests/components/gree/ @cmroche
 /homeassistant/components/greeneye_monitor/ @jkeljo
diff --git a/homeassistant/components/gpsd/__init__.py b/homeassistant/components/gpsd/__init__.py
index 71656d4d13d720c7ad847815f67ba2746b77fa4e..bdd5ddb13b00ceee4d1116e1ed207fd4976eaf92 100644
--- a/homeassistant/components/gpsd/__init__.py
+++ b/homeassistant/components/gpsd/__init__.py
@@ -1 +1,19 @@
-"""The gpsd component."""
+"""The GPSD integration."""
+from __future__ import annotations
+
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import Platform
+from homeassistant.core import HomeAssistant
+
+PLATFORMS: list[Platform] = [Platform.SENSOR]
+
+
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
+    """Set up GPSD from a config entry."""
+    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
+    return True
+
+
+async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
+    """Unload a config entry."""
+    return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
diff --git a/homeassistant/components/gpsd/config_flow.py b/homeassistant/components/gpsd/config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..db1f9c5b0c1882ad84bbb8bf24f2d0a0bdb55670
--- /dev/null
+++ b/homeassistant/components/gpsd/config_flow.py
@@ -0,0 +1,57 @@
+"""Config flow for GPSD integration."""
+from __future__ import annotations
+
+import socket
+from typing import Any
+
+from gps3.agps3threaded import GPSD_PORT as DEFAULT_PORT, HOST as DEFAULT_HOST
+import voluptuous as vol
+
+from homeassistant import config_entries
+from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
+from homeassistant.data_entry_flow import FlowResult
+from homeassistant.helpers import config_validation as cv
+
+from .const import DOMAIN
+
+STEP_USER_DATA_SCHEMA = vol.Schema(
+    {
+        vol.Optional(CONF_HOST, default=DEFAULT_HOST): str,
+        vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
+    }
+)
+
+
+class GPSDConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
+    """Handle a config flow for GPSD."""
+
+    VERSION = 1
+
+    async def async_step_import(self, import_data: dict[str, Any]) -> FlowResult:
+        """Import a config entry from configuration.yaml."""
+        return await self.async_step_user(import_data)
+
+    async def async_step_user(
+        self, user_input: dict[str, Any] | None = None
+    ) -> FlowResult:
+        """Handle the initial step."""
+        if user_input is not None:
+            self._async_abort_entries_match(user_input)
+
+            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            try:
+                sock.connect((user_input[CONF_HOST], user_input[CONF_PORT]))
+                sock.shutdown(2)
+            except OSError:
+                return self.async_abort(reason="cannot_connect")
+
+            port = ""
+            if user_input[CONF_PORT] != DEFAULT_PORT:
+                port = f":{user_input[CONF_PORT]}"
+
+            return self.async_create_entry(
+                title=user_input.get(CONF_NAME, f"GPS {user_input[CONF_HOST]}{port}"),
+                data=user_input,
+            )
+
+        return self.async_show_form(step_id="user", data_schema=STEP_USER_DATA_SCHEMA)
diff --git a/homeassistant/components/gpsd/const.py b/homeassistant/components/gpsd/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a2aec140b55dc4bc3952a2a48db868c7a01218f
--- /dev/null
+++ b/homeassistant/components/gpsd/const.py
@@ -0,0 +1,3 @@
+"""Constants for the GPSD integration."""
+
+DOMAIN = "gpsd"
diff --git a/homeassistant/components/gpsd/manifest.json b/homeassistant/components/gpsd/manifest.json
index d202a6b04285644bed11dfe12aa4299db29d18dc..3f22c5bfab2b3a89b2155e00ab27d52104a7a26a 100644
--- a/homeassistant/components/gpsd/manifest.json
+++ b/homeassistant/components/gpsd/manifest.json
@@ -1,7 +1,8 @@
 {
   "domain": "gpsd",
   "name": "GPSD",
-  "codeowners": ["@fabaff"],
+  "codeowners": ["@fabaff", "@jrieger"],
+  "config_flow": true,
   "documentation": "https://www.home-assistant.io/integrations/gpsd",
   "iot_class": "local_polling",
   "loggers": ["gps3"],
diff --git a/homeassistant/components/gpsd/sensor.py b/homeassistant/components/gpsd/sensor.py
index 64b86434c3c5d07b806b37086b13188ae8ea8abf..2b3fe756d8db9cb688aa495bbde250c660a224fb 100644
--- a/homeassistant/components/gpsd/sensor.py
+++ b/homeassistant/components/gpsd/sensor.py
@@ -2,13 +2,17 @@
 from __future__ import annotations
 
 import logging
-import socket
 from typing import Any
 
-from gps3.agps3threaded import AGPS3mechanism
+from gps3.agps3threaded import (
+    GPSD_PORT as DEFAULT_PORT,
+    HOST as DEFAULT_HOST,
+    AGPS3mechanism,
+)
 import voluptuous as vol
 
 from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
+from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
 from homeassistant.const import (
     ATTR_LATITUDE,
     ATTR_LONGITUDE,
@@ -17,11 +21,15 @@ from homeassistant.const import (
     CONF_NAME,
     CONF_PORT,
 )
-from homeassistant.core import HomeAssistant
+from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
 import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
 from homeassistant.helpers.entity_platform import AddEntitiesCallback
+from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
 
+from .const import DOMAIN
+
 _LOGGER = logging.getLogger(__name__)
 
 ATTR_CLIMB = "climb"
@@ -29,9 +37,7 @@ ATTR_ELEVATION = "elevation"
 ATTR_GPS_TIME = "gps_time"
 ATTR_SPEED = "speed"
 
-DEFAULT_HOST = "localhost"
 DEFAULT_NAME = "GPS"
-DEFAULT_PORT = 2947
 
 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
     {
@@ -42,64 +48,74 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
 )
 
 
-def setup_platform(
+async def async_setup_entry(
+    hass: HomeAssistant,
+    config_entry: ConfigEntry,
+    async_add_entities: AddEntitiesCallback,
+) -> None:
+    """Set up the GPSD component."""
+    async_add_entities(
+        [
+            GpsdSensor(
+                config_entry.data[CONF_HOST],
+                config_entry.data[CONF_PORT],
+                config_entry.entry_id,
+            )
+        ]
+    )
+
+
+async def async_setup_platform(
     hass: HomeAssistant,
     config: ConfigType,
-    add_entities: AddEntitiesCallback,
+    async_add_entities: AddEntitiesCallback,
     discovery_info: DiscoveryInfoType | None = None,
 ) -> None:
-    """Set up the GPSD component."""
-    name = config[CONF_NAME]
-    host = config[CONF_HOST]
-    port = config[CONF_PORT]
-
-    # Will hopefully be possible with the next gps3 update
-    # https://github.com/wadda/gps3/issues/11
-    # from gps3 import gps3
-    # try:
-    #     gpsd_socket = gps3.GPSDSocket()
-    #     gpsd_socket.connect(host=host, port=port)
-    # except GPSError:
-    #     _LOGGER.warning('Not able to connect to GPSD')
-    #     return False
-
-    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-    try:
-        sock.connect((host, port))
-        sock.shutdown(2)
-        _LOGGER.debug("Connection to GPSD possible")
-    except OSError:
-        _LOGGER.error("Not able to connect to GPSD")
-        return
-
-    add_entities([GpsdSensor(hass, name, host, port)])
+    """Initialize gpsd import from config."""
+    async_create_issue(
+        hass,
+        HOMEASSISTANT_DOMAIN,
+        f"deprecated_yaml_{DOMAIN}",
+        is_fixable=False,
+        breaks_in_ha_version="2024.9.0",
+        severity=IssueSeverity.WARNING,
+        translation_key="deprecated_yaml",
+        translation_placeholders={
+            "domain": DOMAIN,
+            "integration_title": "GPSD",
+        },
+    )
+
+    hass.async_create_task(
+        hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": SOURCE_IMPORT}, data=config
+        )
+    )
 
 
 class GpsdSensor(SensorEntity):
     """Representation of a GPS receiver available via GPSD."""
 
+    _attr_has_entity_name = True
+    _attr_name = None
+
     def __init__(
         self,
-        hass: HomeAssistant,
-        name: str,
         host: str,
         port: int,
+        unique_id: str,
     ) -> None:
         """Initialize the GPSD sensor."""
-        self.hass = hass
-        self._name = name
-        self._host = host
-        self._port = port
+        self._attr_device_info = DeviceInfo(
+            identifiers={(DOMAIN, unique_id)},
+            entry_type=DeviceEntryType.SERVICE,
+        )
+        self._attr_unique_id = unique_id
 
         self.agps_thread = AGPS3mechanism()
-        self.agps_thread.stream_data(host=self._host, port=self._port)
+        self.agps_thread.stream_data(host=host, port=port)
         self.agps_thread.run_thread()
 
-    @property
-    def name(self) -> str:
-        """Return the name."""
-        return self._name
-
     @property
     def native_value(self) -> str | None:
         """Return the state of GPSD."""
diff --git a/homeassistant/components/gpsd/strings.json b/homeassistant/components/gpsd/strings.json
new file mode 100644
index 0000000000000000000000000000000000000000..ff91b239d0a55d5295b7778f18dbc6f3dbc4fb3e
--- /dev/null
+++ b/homeassistant/components/gpsd/strings.json
@@ -0,0 +1,19 @@
+{
+  "config": {
+    "step": {
+      "user": {
+        "data": {
+          "host": "[%key:common::config_flow::data::host%]",
+          "port": "[%key:common::config_flow::data::port%]"
+        },
+        "data_description": {
+          "host": "The hostname or IP address of GPSD."
+        }
+      }
+    },
+    "abort": {
+      "already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
+      "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
+    }
+  }
+}
diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py
index 80d3f7310b09508b14c54bd6dc6260e8600a8d86..aa3efde99bc862f3062f683df8c254bcb0dee01c 100644
--- a/homeassistant/generated/config_flows.py
+++ b/homeassistant/generated/config_flows.py
@@ -203,6 +203,7 @@ FLOWS = {
         "google_travel_time",
         "govee_ble",
         "govee_light_local",
+        "gpsd",
         "gpslogger",
         "gree",
         "growatt_server",
diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json
index fa143ddf1512a0e5e3ae62ef54e444ba40c3a812..21186272bb6c0cac3325158fc188082523aaae41 100644
--- a/homeassistant/generated/integrations.json
+++ b/homeassistant/generated/integrations.json
@@ -2321,7 +2321,7 @@
     "gpsd": {
       "name": "GPSD",
       "integration_type": "hub",
-      "config_flow": false,
+      "config_flow": true,
       "iot_class": "local_polling"
     },
     "gpslogger": {
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 1a535c5cd018782586eb463e0dbe594b7a58289d..c7207fc5398431a318a452dab3b36796a5125a42 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -783,6 +783,9 @@ govee-ble==0.27.3
 # homeassistant.components.govee_light_local
 govee-local-api==1.4.1
 
+# homeassistant.components.gpsd
+gps3==0.33.3
+
 # homeassistant.components.gree
 greeclimate==1.4.1
 
diff --git a/tests/components/gpsd/__init__.py b/tests/components/gpsd/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d78331c94d968c0043c76e1979bc8a24cf6a24b9
--- /dev/null
+++ b/tests/components/gpsd/__init__.py
@@ -0,0 +1 @@
+"""Tests for the GPSD integration."""
diff --git a/tests/components/gpsd/conftest.py b/tests/components/gpsd/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2bd2b8564a861ceaacf6ac1ab5ee5deb2be49cd
--- /dev/null
+++ b/tests/components/gpsd/conftest.py
@@ -0,0 +1,14 @@
+"""Common fixtures for the GPSD tests."""
+from collections.abc import Generator
+from unittest.mock import AsyncMock, patch
+
+import pytest
+
+
+@pytest.fixture
+def mock_setup_entry() -> Generator[AsyncMock, None, None]:
+    """Override async_setup_entry."""
+    with patch(
+        "homeassistant.components.gpsd.async_setup_entry", return_value=True
+    ) as mock_setup_entry:
+        yield mock_setup_entry
diff --git a/tests/components/gpsd/test_config_flow.py b/tests/components/gpsd/test_config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b0465b026d3d018c7cc09ae71d6967572624406
--- /dev/null
+++ b/tests/components/gpsd/test_config_flow.py
@@ -0,0 +1,76 @@
+"""Test the GPSD config flow."""
+from unittest.mock import AsyncMock, patch
+
+from gps3.agps3threaded import GPSD_PORT as DEFAULT_PORT
+
+from homeassistant import config_entries, data_entry_flow
+from homeassistant.components.gpsd.const import DOMAIN
+from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
+from homeassistant.core import HomeAssistant
+from homeassistant.data_entry_flow import FlowResultType
+
+HOST = "gpsd.local"
+
+
+async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
+    """Test we get the form."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+    assert result["type"] == FlowResultType.FORM
+
+    with patch("socket.socket") as mock_socket:
+        mock_connect = mock_socket.return_value.connect
+        mock_connect.return_value = None
+
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            {
+                CONF_HOST: HOST,
+            },
+        )
+        await hass.async_block_till_done()
+
+    assert result2["type"] == FlowResultType.CREATE_ENTRY
+    assert result2["title"] == f"GPS {HOST}"
+    assert result2["data"] == {
+        CONF_HOST: HOST,
+        CONF_PORT: DEFAULT_PORT,
+    }
+    mock_setup_entry.assert_called_once()
+
+
+async def test_connection_error(hass: HomeAssistant) -> None:
+    """Test connection to host error."""
+    with patch("socket.socket") as mock_socket:
+        mock_connect = mock_socket.return_value.connect
+        mock_connect.side_effect = OSError
+
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": config_entries.SOURCE_USER},
+            data={CONF_HOST: "nonexistent.local", CONF_PORT: 1234},
+        )
+
+        assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
+        assert result["reason"] == "cannot_connect"
+
+
+async def test_import(hass: HomeAssistant) -> None:
+    """Test import step."""
+    with patch("homeassistant.components.gpsd.config_flow.socket") as mock_socket:
+        mock_connect = mock_socket.return_value.connect
+        mock_connect.return_value = None
+
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": config_entries.SOURCE_IMPORT},
+            data={CONF_HOST: HOST, CONF_PORT: 1234, CONF_NAME: "MyGPS"},
+        )
+        assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
+        assert result["title"] == "MyGPS"
+        assert result["data"] == {
+            CONF_HOST: HOST,
+            CONF_NAME: "MyGPS",
+            CONF_PORT: 1234,
+        }