Config entry functions now return None
The config entry functions in the __init__.py of an integration, async_setup_entry, async_unload_entry, and async_migrate_entry, now return None instead of a boolean. They either succeed, or raise one of the ConfigEntry* exceptions. Starting with Home Assistant 2026.11.0, core integrations must follow the new pattern. Custom integrations can keep returning a boolean.
Returning True or False dates back to the first days of config entries. An integration logged a problem, swallowed the exception, and returned False. Home Assistant then knew the setup failed, but not why, and neither did the user.
Since then, ConfigEntryNotReady, ConfigEntryAuthFailed, and ConfigEntryError took over that job. They support translations, decide whether Home Assistant retries or starts reauthentication, and show the user what went wrong. Almost every integration already raised them, and only ended with return True because the type hints asked for it. The architecture discussion has the full background.
All core integrations now return None from these functions. A return False became a raised ConfigEntryError, and the Pylint type hint checker requires -> None. When a core integration returns a boolean anyway, Home Assistant reports it as an error, and the setup, unload, or migration fails.
Setting up looks like this:
async def async_setup_entry(
hass: HomeAssistant, entry: ExampleConfigEntry
) -> None:
"""Set up Example from a config entry."""
coordinator = ExampleCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)For unloading, use the new async_unload_platforms_or_raise. It raises a translated ConfigEntryError when a platform fails to unload, instead of returning False:
async def async_unload_entry(
hass: HomeAssistant, entry: ExampleConfigEntry
) -> None:
"""Unload a config entry."""
await hass.config_entries.async_unload_platforms_or_raise(entry, PLATFORMS)The scaffold templates generate this pattern for new integrations.
When you patch async_setup_entry of a core integration in a test, for example in the mock_setup_entry fixture of your config flow tests, return None instead of True:
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.example.async_setup_entry",
return_value=None,
) as mock_setup_entry:
yield mock_setup_entryWith return_value=True, the patched function returns a boolean, and the setup of the entry fails.
Nothing breaks for custom integrations, and there is no deprecation. Returning a boolean stays supported:
Truemeans success, as before.Falsenow becomes aConfigEntryErrorwith a generic, translated message, like "Failed to set up the config entry." The entry ends in the same state as before:SETUP_ERROR,FAILED_UNLOAD, orMIGRATION_ERROR. The difference is that the user now sees a reason.
Still, consider switching. Raise your own exception with a translated message, and users see what actually went wrong instead of the generic message.
Read more in the config entries documentation, and in setup failures and exceptions.