diff --git a/homeassistant/components/totalconnect/.translations/en.json b/homeassistant/components/totalconnect/.translations/en.json
new file mode 100644
index 00000000000..5aca06df513
--- /dev/null
+++ b/homeassistant/components/totalconnect/.translations/en.json
@@ -0,0 +1,20 @@
+{
+    "config": {
+        "abort": {
+            "already_configured": "Account already configured"
+        },
+        "error": {
+            "login": "Login error: please check your username & password"
+        },
+        "step": {
+            "user": {
+                "data": {
+                    "password": "Password",
+                    "username": "Username"
+                },
+                "title": "Total Connect"
+            }
+        },
+        "title": "Total Connect"
+    }
+}
diff --git a/homeassistant/components/totalconnect/__init__.py b/homeassistant/components/totalconnect/__init__.py
index e6cfbbc629a..fce67f71b24 100644
--- a/homeassistant/components/totalconnect/__init__.py
+++ b/homeassistant/components/totalconnect/__init__.py
@@ -1,16 +1,20 @@
 """The totalconnect component."""
+import asyncio
 import logging
 
 from total_connect_client import TotalConnectClient
 import voluptuous as vol
 
+from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
 from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
-from homeassistant.helpers import discovery
+from homeassistant.core import HomeAssistant
 import homeassistant.helpers.config_validation as cv
 
+from .const import DOMAIN
+
 _LOGGER = logging.getLogger(__name__)
 
-DOMAIN = "totalconnect"
+PLATFORMS = ["alarm_control_panel", "binary_sensor"]
 
 CONFIG_SCHEMA = vol.Schema(
     {
@@ -20,39 +24,61 @@ CONFIG_SCHEMA = vol.Schema(
                 vol.Required(CONF_PASSWORD): cv.string,
             }
         )
-    },
-    extra=vol.ALLOW_EXTRA,
+    }
 )
 
-TOTALCONNECT_PLATFORMS = ["alarm_control_panel", "binary_sensor"]
 
+async def async_setup(hass: HomeAssistant, config: dict):
+    """Set up by configuration file."""
+    if DOMAIN not in config:
+        return True
 
-def setup(hass, config):
-    """Set up TotalConnect component."""
-    conf = config[DOMAIN]
-
-    username = conf[CONF_USERNAME]
-    password = conf[CONF_PASSWORD]
-
-    client = TotalConnectClient.TotalConnectClient(username, password)
-
-    if client.token is False:
-        _LOGGER.error("TotalConnect authentication failed")
-        return False
-
-    hass.data[DOMAIN] = TotalConnectSystem(username, password, client)
-
-    for platform in TOTALCONNECT_PLATFORMS:
-        discovery.load_platform(hass, platform, DOMAIN, {}, config)
+    hass.async_create_task(
+        hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN],
+        )
+    )
 
     return True
 
 
-class TotalConnectSystem:
-    """TotalConnect System class."""
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
+    """Set up upon config entry in user interface."""
+    hass.data.setdefault(DOMAIN, {})
 
-    def __init__(self, username, password, client):
-        """Initialize the TotalConnect system."""
-        self._username = username
-        self._password = password
-        self.client = client
+    conf = entry.data
+    username = conf[CONF_USERNAME]
+    password = conf[CONF_PASSWORD]
+
+    client = await hass.async_add_executor_job(
+        TotalConnectClient.TotalConnectClient, username, password
+    )
+
+    if not client.is_valid_credentials():
+        _LOGGER.error("TotalConnect authentication failed")
+        return False
+
+    hass.data[DOMAIN][entry.entry_id] = client
+
+    for component in PLATFORMS:
+        hass.async_create_task(
+            hass.config_entries.async_forward_entry_setup(entry, component)
+        )
+
+    return True
+
+
+async def async_unload_entry(hass, entry: ConfigEntry):
+    """Unload a config entry."""
+    unload_ok = all(
+        await asyncio.gather(
+            *[
+                hass.config_entries.async_forward_entry_unload(entry, platform)
+                for platform in PLATFORMS
+            ]
+        )
+    )
+    if unload_ok:
+        hass.data[DOMAIN].pop(entry.entry_id)
+
+    return unload_ok
diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py
index 2ab06e2f6bd..2a32ae89b4a 100644
--- a/homeassistant/components/totalconnect/alarm_control_panel.py
+++ b/homeassistant/components/totalconnect/alarm_control_panel.py
@@ -23,19 +23,17 @@ from .const import DOMAIN
 _LOGGER = logging.getLogger(__name__)
 
 
-def setup_platform(hass, config, add_entities, discovery_info=None):
-    """Set up an alarm control panel for a TotalConnect device."""
-    if discovery_info is None:
-        return
-
+async def async_setup_entry(hass, entry, async_add_entities) -> None:
+    """Set up TotalConnect alarm panels based on a config entry."""
     alarms = []
 
-    client = hass.data[DOMAIN].client
+    client = hass.data[DOMAIN][entry.entry_id]
 
     for location_id, location in client.locations.items():
         location_name = location.location_name
         alarms.append(TotalConnectAlarm(location_name, location_id, client))
-    add_entities(alarms)
+
+    async_add_entities(alarms, True)
 
 
 class TotalConnectAlarm(alarm.AlarmControlPanel):
diff --git a/homeassistant/components/totalconnect/binary_sensor.py b/homeassistant/components/totalconnect/binary_sensor.py
index 28bd58cfff8..48d9a96a483 100644
--- a/homeassistant/components/totalconnect/binary_sensor.py
+++ b/homeassistant/components/totalconnect/binary_sensor.py
@@ -8,24 +8,22 @@ from homeassistant.components.binary_sensor import (
     BinarySensorDevice,
 )
 
-from . import DOMAIN as TOTALCONNECT_DOMAIN
+from .const import DOMAIN
 
 _LOGGER = logging.getLogger(__name__)
 
 
-def setup_platform(hass, config, add_entities, discovery_info=None):
-    """Set up a sensor for a TotalConnect device."""
-    if discovery_info is None:
-        return
-
+async def async_setup_entry(hass, entry, async_add_entities) -> None:
+    """Set up TotalConnect device sensors based on a config entry."""
     sensors = []
 
-    client_locations = hass.data[TOTALCONNECT_DOMAIN].client.locations
+    client_locations = hass.data[DOMAIN][entry.entry_id].locations
 
     for location_id, location in client_locations.items():
         for zone_id, zone in location.zones.items():
             sensors.append(TotalConnectBinarySensor(zone_id, location_id, zone))
-    add_entities(sensors, True)
+
+    async_add_entities(sensors, True)
 
 
 class TotalConnectBinarySensor(BinarySensorDevice):
diff --git a/homeassistant/components/totalconnect/config_flow.py b/homeassistant/components/totalconnect/config_flow.py
new file mode 100644
index 00000000000..03ddd0a432a
--- /dev/null
+++ b/homeassistant/components/totalconnect/config_flow.py
@@ -0,0 +1,60 @@
+"""Config flow for the Total Connect component."""
+import logging
+
+from total_connect_client import TotalConnectClient
+import voluptuous as vol
+
+from homeassistant import config_entries
+from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
+
+from .const import DOMAIN  # pylint: disable=unused-import
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class TotalConnectConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
+    """Total Connect config flow."""
+
+    VERSION = 1
+
+    async def async_step_user(self, user_input=None):
+        """Handle a flow initiated by the user."""
+        errors = {}
+
+        if user_input is not None:
+            # Validate user input
+            username = user_input[CONF_USERNAME]
+            password = user_input[CONF_PASSWORD]
+
+            await self.async_set_unique_id(username)
+            self._abort_if_unique_id_configured()
+
+            valid = await self.is_valid(username, password)
+
+            if valid:
+                # authentication success / valid
+                return self.async_create_entry(
+                    title="Total Connect",
+                    data={CONF_USERNAME: username, CONF_PASSWORD: password},
+                )
+            # authentication failed / invalid
+            errors["base"] = "login"
+
+        data_schema = vol.Schema(
+            {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
+        )
+
+        return self.async_show_form(
+            step_id="user", data_schema=data_schema, errors=errors
+        )
+
+    async def async_step_import(self, user_input):
+        """Import a config entry."""
+        return await self.async_step_user(user_input)
+
+    async def is_valid(self, username="", password=""):
+        """Return true if the given username and password are valid."""
+        client = await self.hass.async_add_executor_job(
+            TotalConnectClient.TotalConnectClient, username, password
+        )
+        return client.is_valid_credentials()
diff --git a/homeassistant/components/totalconnect/manifest.json b/homeassistant/components/totalconnect/manifest.json
index bd60e1331f4..fc19c889d8b 100644
--- a/homeassistant/components/totalconnect/manifest.json
+++ b/homeassistant/components/totalconnect/manifest.json
@@ -3,5 +3,7 @@
   "name": "Honeywell Total Connect Alarm",
   "documentation": "https://www.home-assistant.io/integrations/totalconnect",
   "requirements": ["total_connect_client==0.54.1"],
-  "codeowners": ["@austinmroczek"]
+  "dependencies": [],
+  "codeowners": ["@austinmroczek"],
+  "config_flow": true
 }
diff --git a/homeassistant/components/totalconnect/strings.json b/homeassistant/components/totalconnect/strings.json
new file mode 100644
index 00000000000..893aba77368
--- /dev/null
+++ b/homeassistant/components/totalconnect/strings.json
@@ -0,0 +1,20 @@
+{
+  "config": {
+    "title": "Total Connect",
+    "step": {
+      "user": {
+        "title": "Total Connect",
+        "data": {
+          "username": "Username",
+          "password": "Password"
+        }
+      }
+    },
+    "error": {
+      "login": "Login error: please check your username & password"
+    },
+    "abort": {
+      "already_configured": "Account already configured"
+    }
+  }
+}
diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py
index a7dbd486089..569457d291c 100644
--- a/homeassistant/generated/config_flows.py
+++ b/homeassistant/generated/config_flows.py
@@ -117,6 +117,7 @@ FLOWS = [
     "tellduslive",
     "tesla",
     "toon",
+    "totalconnect",
     "tplink",
     "traccar",
     "tradfri",
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index e93f9ea8b4c..5c9c69c3efb 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -761,6 +761,9 @@ teslajsonpy==0.6.0
 # homeassistant.components.toon
 toonapilib==3.2.4
 
+# homeassistant.components.totalconnect
+total_connect_client==0.54.1
+
 # homeassistant.components.transmission
 transmissionrpc==0.11
 
diff --git a/tests/components/totalconnect/__init__.py b/tests/components/totalconnect/__init__.py
new file mode 100644
index 00000000000..180a00188cd
--- /dev/null
+++ b/tests/components/totalconnect/__init__.py
@@ -0,0 +1 @@
+"""Tests for the totalconnect component."""
diff --git a/tests/components/totalconnect/test_config_flow.py b/tests/components/totalconnect/test_config_flow.py
new file mode 100644
index 00000000000..b77198fa9b2
--- /dev/null
+++ b/tests/components/totalconnect/test_config_flow.py
@@ -0,0 +1,104 @@
+"""Tests for the iCloud config flow."""
+from unittest.mock import patch
+
+from homeassistant import data_entry_flow
+from homeassistant.components.totalconnect.const import DOMAIN
+from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
+from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
+
+from tests.common import MockConfigEntry
+
+USERNAME = "username@me.com"
+PASSWORD = "password"
+
+
+async def test_user(hass):
+    """Test user config."""
+    # no data provided so show the form
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}
+    )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
+    assert result["step_id"] == "user"
+
+    # now data is provided, so check if login is correct and create the entry
+    with patch(
+        "homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient"
+    ) as client_mock:
+        client_mock.return_value.is_valid_credentials.return_value = True
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": SOURCE_USER},
+            data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+
+
+async def test_import(hass):
+    """Test import step with good username and password."""
+    with patch(
+        "homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient"
+    ) as client_mock:
+        client_mock.return_value.is_valid_credentials.return_value = True
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": SOURCE_IMPORT},
+            data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+
+
+async def test_abort_if_already_setup(hass):
+    """Test abort if the account is already setup."""
+    MockConfigEntry(
+        domain=DOMAIN,
+        data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
+        unique_id=USERNAME,
+    ).add_to_hass(hass)
+
+    # Should fail, same USERNAME (import)
+    with patch(
+        "homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient"
+    ) as client_mock:
+        client_mock.return_value.is_valid_credentials.return_value = True
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": SOURCE_IMPORT},
+            data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
+    assert result["reason"] == "already_configured"
+
+    # Should fail, same USERNAME (flow)
+    with patch(
+        "homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient"
+    ) as client_mock:
+        client_mock.return_value.is_valid_credentials.return_value = True
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": SOURCE_USER},
+            data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
+    assert result["reason"] == "already_configured"
+
+
+async def test_login_failed(hass):
+    """Test when we have errors during login."""
+    with patch(
+        "homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient"
+    ) as client_mock:
+        client_mock.return_value.is_valid_credentials.return_value = False
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": SOURCE_USER},
+            data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
+    assert result["errors"] == {"base": "login"}