diff --git a/.strict-typing b/.strict-typing
index a5a2127eb68b052ca6a1c3c5f23071134658cb79..1213a8b73b7a216b667055a5fd243efa9458f2cb 100644
--- a/.strict-typing
+++ b/.strict-typing
@@ -127,10 +127,11 @@ homeassistant.components.lookin.*
 homeassistant.components.luftdaten.*
 homeassistant.components.mailbox.*
 homeassistant.components.media_player.*
+homeassistant.components.media_source.*
 homeassistant.components.mjpeg.*
 homeassistant.components.modbus.*
 homeassistant.components.modem_callerid.*
-homeassistant.components.media_source.*
+homeassistant.components.moon.*
 homeassistant.components.mysensors.*
 homeassistant.components.nam.*
 homeassistant.components.nanoleaf.*
diff --git a/CODEOWNERS b/CODEOWNERS
index 82930f875f42910cd2f2ba1e3ef3c0ecd51e6a1f..1c7256435355af6a17acae6ba69882a4e560f1ff 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -616,8 +616,8 @@ homeassistant/components/moehlenhoff_alpha2/* @j-a-n
 tests/components/moehlenhoff_alpha2/* @j-a-n
 homeassistant/components/monoprice/* @etsinko @OnFreund
 tests/components/monoprice/* @etsinko @OnFreund
-homeassistant/components/moon/* @fabaff
-tests/components/moon/* @fabaff
+homeassistant/components/moon/* @fabaff @frenck
+tests/components/moon/* @fabaff @frenck
 homeassistant/components/motion_blinds/* @starkillerOG
 tests/components/motion_blinds/* @starkillerOG
 homeassistant/components/motioneye/* @dermotduffy
diff --git a/homeassistant/components/moon/__init__.py b/homeassistant/components/moon/__init__.py
index f7049608dda90179b124282c63b735b1d9d049b8..0b36ba59198d3759b134e1136774915b1732afa6 100644
--- a/homeassistant/components/moon/__init__.py
+++ b/homeassistant/components/moon/__init__.py
@@ -1 +1,16 @@
-"""The moon component."""
+"""The Moon integration."""
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.core import HomeAssistant
+
+from .const import PLATFORMS
+
+
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
+    """Set up from a config entry."""
+    hass.config_entries.async_setup_platforms(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/moon/config_flow.py b/homeassistant/components/moon/config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f7c5715f9ea6103e2b0db37d53c281dcfa0155c
--- /dev/null
+++ b/homeassistant/components/moon/config_flow.py
@@ -0,0 +1,37 @@
+"""Config flow to configure the Moon integration."""
+from __future__ import annotations
+
+from typing import Any
+
+import voluptuous as vol
+
+from homeassistant.config_entries import ConfigFlow
+from homeassistant.const import CONF_NAME
+from homeassistant.data_entry_flow import FlowResult
+
+from .const import DEFAULT_NAME, DOMAIN
+
+
+class MoonConfigFlow(ConfigFlow, domain=DOMAIN):
+    """Config flow for Moon."""
+
+    VERSION = 1
+
+    async def async_step_user(
+        self, user_input: dict[str, Any] | None = None
+    ) -> FlowResult:
+        """Handle a flow initialized by the user."""
+        if self._async_current_entries():
+            return self.async_abort(reason="single_instance_allowed")
+
+        if user_input is not None:
+            return self.async_create_entry(
+                title=user_input.get(CONF_NAME, DEFAULT_NAME),
+                data={},
+            )
+
+        return self.async_show_form(step_id="user", data_schema=vol.Schema({}))
+
+    async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult:
+        """Handle import from configuration.yaml."""
+        return await self.async_step_user(user_input)
diff --git a/homeassistant/components/moon/const.py b/homeassistant/components/moon/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..87c525758b95480df323677c1c94dbc7c37e8f25
--- /dev/null
+++ b/homeassistant/components/moon/const.py
@@ -0,0 +1,9 @@
+"""Constants for the Moon integration."""
+from typing import Final
+
+from homeassistant.const import Platform
+
+DOMAIN: Final = "moon"
+PLATFORMS: Final = [Platform.SENSOR]
+
+DEFAULT_NAME: Final = "Moon"
diff --git a/homeassistant/components/moon/manifest.json b/homeassistant/components/moon/manifest.json
index 19fb952f59f013ceec9e34b97a318abc30d2c5fc..0402a87cf1a5d03a27098077f7301f715f0fb931 100644
--- a/homeassistant/components/moon/manifest.json
+++ b/homeassistant/components/moon/manifest.json
@@ -2,7 +2,8 @@
   "domain": "moon",
   "name": "Moon",
   "documentation": "https://www.home-assistant.io/integrations/moon",
-  "codeowners": ["@fabaff"],
+  "codeowners": ["@fabaff", "@frenck"],
   "quality_scale": "internal",
-  "iot_class": "local_polling"
+  "iot_class": "local_polling",
+  "config_flow": true
 }
diff --git a/homeassistant/components/moon/sensor.py b/homeassistant/components/moon/sensor.py
index cd10d9168d9c619b672f9af2f41bd061213f3633..c5078771af8d86ed57d0c1c5d2ae5e071d8ab0cd 100644
--- a/homeassistant/components/moon/sensor.py
+++ b/homeassistant/components/moon/sensor.py
@@ -8,6 +8,7 @@ from homeassistant.components.sensor import (
     PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
     SensorEntity,
 )
+from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
 from homeassistant.const import CONF_NAME
 from homeassistant.core import HomeAssistant
 import homeassistant.helpers.config_validation as cv
@@ -15,7 +16,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
 import homeassistant.util.dt as dt_util
 
-DEFAULT_NAME = "Moon"
+from .const import DEFAULT_NAME, DOMAIN
 
 STATE_FIRST_QUARTER = "first_quarter"
 STATE_FULL_MOON = "full_moon"
@@ -49,23 +50,37 @@ async def async_setup_platform(
     discovery_info: DiscoveryInfoType | None = None,
 ) -> None:
     """Set up the Moon sensor."""
-    name: str = config[CONF_NAME]
+    hass.async_create_task(
+        hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": SOURCE_IMPORT},
+            data=config,
+        )
+    )
 
-    async_add_entities([MoonSensor(name)], True)
+
+async def async_setup_entry(
+    hass: HomeAssistant,
+    entry: ConfigEntry,
+    async_add_entities: AddEntitiesCallback,
+) -> None:
+    """Set up the platform from config_entry."""
+    async_add_entities([MoonSensorEntity(entry)], True)
 
 
-class MoonSensor(SensorEntity):
+class MoonSensorEntity(SensorEntity):
     """Representation of a Moon sensor."""
 
     _attr_device_class = "moon__phase"
 
-    def __init__(self, name: str) -> None:
+    def __init__(self, entry: ConfigEntry) -> None:
         """Initialize the moon sensor."""
-        self._attr_name = name
+        self._attr_name = entry.title
+        self._attr_unique_id = entry.entry_id
 
-    async def async_update(self):
+    async def async_update(self) -> None:
         """Get the time and updates the states."""
-        today = dt_util.as_local(dt_util.utcnow()).date()
+        today = dt_util.now().date()
         state = moon.phase(today)
 
         if state < 0.5 or state > 27.5:
diff --git a/homeassistant/components/moon/strings.json b/homeassistant/components/moon/strings.json
new file mode 100644
index 0000000000000000000000000000000000000000..d5bb204a740748669a2990239809b647815c7fd4
--- /dev/null
+++ b/homeassistant/components/moon/strings.json
@@ -0,0 +1,13 @@
+{
+  "title": "Moon",
+  "config": {
+    "step": {
+      "user": {
+        "description": "[%key:common::config_flow::description::confirm_setup%]"
+      }
+    },
+    "abort": {
+      "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
+    }
+  }
+}
diff --git a/homeassistant/components/moon/translations/en.json b/homeassistant/components/moon/translations/en.json
new file mode 100644
index 0000000000000000000000000000000000000000..0f324f7b64b38d77891d8acafcc2addc6e4d2e2b
--- /dev/null
+++ b/homeassistant/components/moon/translations/en.json
@@ -0,0 +1,13 @@
+{
+    "config": {
+        "abort": {
+            "single_instance_allowed": "Already configured. Only a single configuration possible."
+        },
+        "step": {
+            "user": {
+                "description": "Do you want to start set up?"
+            }
+        }
+    },
+    "title": "Moon"
+}
\ No newline at end of file
diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py
index 5530c73ed5098f16c5acbd4e7a1284d105e60ae1..7a36cfebc7b83bee2659054678bd811386f77d1e 100644
--- a/homeassistant/generated/config_flows.py
+++ b/homeassistant/generated/config_flows.py
@@ -203,6 +203,7 @@ FLOWS = [
     "modern_forms",
     "moehlenhoff_alpha2",
     "monoprice",
+    "moon",
     "motion_blinds",
     "motioneye",
     "mqtt",
diff --git a/mypy.ini b/mypy.ini
index 045e9a4eee8091664ed5fe1e1ca0be0cce99fcc2..7a927a3c6a77ec389d2a7ed6f058e37d35f1aa9c 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -1198,6 +1198,17 @@ no_implicit_optional = true
 warn_return_any = true
 warn_unreachable = true
 
+[mypy-homeassistant.components.media_source.*]
+check_untyped_defs = true
+disallow_incomplete_defs = true
+disallow_subclassing_any = true
+disallow_untyped_calls = true
+disallow_untyped_decorators = true
+disallow_untyped_defs = true
+no_implicit_optional = true
+warn_return_any = true
+warn_unreachable = true
+
 [mypy-homeassistant.components.mjpeg.*]
 check_untyped_defs = true
 disallow_incomplete_defs = true
@@ -1231,7 +1242,7 @@ no_implicit_optional = true
 warn_return_any = true
 warn_unreachable = true
 
-[mypy-homeassistant.components.media_source.*]
+[mypy-homeassistant.components.moon.*]
 check_untyped_defs = true
 disallow_incomplete_defs = true
 disallow_subclassing_any = true
diff --git a/tests/components/moon/conftest.py b/tests/components/moon/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c8157f257df5c2ef6ed63697617a38dc45027e4
--- /dev/null
+++ b/tests/components/moon/conftest.py
@@ -0,0 +1,27 @@
+"""Fixtures for Moon integration tests."""
+from __future__ import annotations
+
+from collections.abc import Generator
+from unittest.mock import patch
+
+import pytest
+
+from homeassistant.components.moon.const import DOMAIN
+
+from tests.common import MockConfigEntry
+
+
+@pytest.fixture
+def mock_config_entry() -> MockConfigEntry:
+    """Return the default mocked config entry."""
+    return MockConfigEntry(
+        title="Moon",
+        domain=DOMAIN,
+    )
+
+
+@pytest.fixture
+def mock_setup_entry() -> Generator[None, None, None]:
+    """Mock setting up a config entry."""
+    with patch("homeassistant.components.moon.async_setup_entry", return_value=True):
+        yield
diff --git a/tests/components/moon/test_config_flow.py b/tests/components/moon/test_config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..4bfb61166aa1680dcedd07cf42484e6707e91ec5
--- /dev/null
+++ b/tests/components/moon/test_config_flow.py
@@ -0,0 +1,72 @@
+"""Tests for the Moon config flow."""
+from unittest.mock import MagicMock
+
+import pytest
+
+from homeassistant.components.moon.const import DOMAIN
+from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
+from homeassistant.const import CONF_NAME
+from homeassistant.core import HomeAssistant
+from homeassistant.data_entry_flow import (
+    RESULT_TYPE_ABORT,
+    RESULT_TYPE_CREATE_ENTRY,
+    RESULT_TYPE_FORM,
+)
+
+from tests.common import MockConfigEntry
+
+
+async def test_full_user_flow(
+    hass: HomeAssistant,
+    mock_setup_entry: MagicMock,
+) -> None:
+    """Test the full user configuration flow."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}
+    )
+
+    assert result.get("type") == RESULT_TYPE_FORM
+    assert result.get("step_id") == SOURCE_USER
+    assert "flow_id" in result
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={},
+    )
+
+    assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY
+    assert result2.get("title") == "Moon"
+    assert result2.get("data") == {}
+
+
+@pytest.mark.parametrize("source", [SOURCE_USER, SOURCE_IMPORT])
+async def test_single_instance_allowed(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    source: str,
+) -> None:
+    """Test we abort if already setup."""
+    mock_config_entry.add_to_hass(hass)
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": source}
+    )
+
+    assert result.get("type") == RESULT_TYPE_ABORT
+    assert result.get("reason") == "single_instance_allowed"
+
+
+async def test_import_flow(
+    hass: HomeAssistant,
+    mock_setup_entry: MagicMock,
+) -> None:
+    """Test the import configuration flow."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": SOURCE_IMPORT},
+        data={CONF_NAME: "My Moon"},
+    )
+
+    assert result.get("type") == RESULT_TYPE_CREATE_ENTRY
+    assert result.get("title") == "My Moon"
+    assert result.get("data") == {}
diff --git a/tests/components/moon/test_init.py b/tests/components/moon/test_init.py
new file mode 100644
index 0000000000000000000000000000000000000000..f0f7e5935458fae2dd1312970cc121a4038d1589
--- /dev/null
+++ b/tests/components/moon/test_init.py
@@ -0,0 +1,55 @@
+"""Tests for the Moon integration."""
+from unittest.mock import AsyncMock
+
+from homeassistant.components.moon.const import DOMAIN
+from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
+from homeassistant.config_entries import ConfigEntryState
+from homeassistant.const import CONF_NAME
+from homeassistant.core import HomeAssistant
+from homeassistant.setup import async_setup_component
+
+from tests.common import MockConfigEntry
+
+
+async def test_load_unload_config_entry(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+) -> None:
+    """Test the Moon configuration entry loading/unloading."""
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    assert mock_config_entry.state is ConfigEntryState.LOADED
+
+    await hass.config_entries.async_unload(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    assert not hass.data.get(DOMAIN)
+    assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
+
+
+async def test_import_config(
+    hass: HomeAssistant,
+    mock_setup_entry: AsyncMock,
+) -> None:
+    """Test Moon being set up from config via import."""
+    assert await async_setup_component(
+        hass,
+        SENSOR_DOMAIN,
+        {
+            SENSOR_DOMAIN: {
+                "platform": DOMAIN,
+                CONF_NAME: "My Moon",
+            }
+        },
+    )
+    await hass.async_block_till_done()
+
+    config_entries = hass.config_entries.async_entries(DOMAIN)
+    assert len(config_entries) == 1
+
+    entry = config_entries[0]
+    assert entry.title == "My Moon"
+    assert entry.unique_id is None
+    assert entry.data == {}
diff --git a/tests/components/moon/test_sensor.py b/tests/components/moon/test_sensor.py
index 066620b1051bb97e88312a80ff60c685793302a6..bb9e5dcc1570dabd8aa5c837aec0b7257da37277 100644
--- a/tests/components/moon/test_sensor.py
+++ b/tests/components/moon/test_sensor.py
@@ -5,10 +5,6 @@ from unittest.mock import patch
 
 import pytest
 
-from homeassistant.components.homeassistant import (
-    DOMAIN as HA_DOMAIN,
-    SERVICE_UPDATE_ENTITY,
-)
 from homeassistant.components.moon.sensor import (
     MOON_ICONS,
     STATE_FIRST_QUARTER,
@@ -20,9 +16,11 @@ from homeassistant.components.moon.sensor import (
     STATE_WAXING_CRESCENT,
     STATE_WAXING_GIBBOUS,
 )
-from homeassistant.const import ATTR_ENTITY_ID
+from homeassistant.const import ATTR_ICON
 from homeassistant.core import HomeAssistant
-from homeassistant.setup import async_setup_component
+from homeassistant.helpers import entity_registry as er
+
+from tests.common import MockConfigEntry
 
 
 @pytest.mark.parametrize(
@@ -39,33 +37,27 @@ from homeassistant.setup import async_setup_component
     ],
 )
 async def test_moon_day(
-    hass: HomeAssistant, moon_value: float, native_value: str, icon: str
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    moon_value: float,
+    native_value: str,
+    icon: str,
 ) -> None:
     """Test the Moon sensor."""
-    config = {"sensor": {"platform": "moon"}}
-
-    await async_setup_component(hass, HA_DOMAIN, {})
-    assert await async_setup_component(hass, "sensor", config)
-    await hass.async_block_till_done()
-
-    assert hass.states.get("sensor.moon")
+    mock_config_entry.add_to_hass(hass)
 
     with patch(
         "homeassistant.components.moon.sensor.moon.phase", return_value=moon_value
     ):
-        await async_update_entity(hass, "sensor.moon")
+        await hass.config_entries.async_setup(mock_config_entry.entry_id)
+        await hass.async_block_till_done()
 
     state = hass.states.get("sensor.moon")
+    assert state
     assert state.state == native_value
-    assert state.attributes["icon"] == icon
-
+    assert state.attributes[ATTR_ICON] == icon
 
-async def async_update_entity(hass: HomeAssistant, entity_id: str) -> None:
-    """Run an update action for an entity."""
-    await hass.services.async_call(
-        HA_DOMAIN,
-        SERVICE_UPDATE_ENTITY,
-        {ATTR_ENTITY_ID: entity_id},
-        blocking=True,
-    )
-    await hass.async_block_till_done()
+    entity_registry = er.async_get(hass)
+    entry = entity_registry.async_get("sensor.moon")
+    assert entry
+    assert entry.unique_id == mock_config_entry.entry_id