diff --git a/homeassistant/components/motioneye/camera.py b/homeassistant/components/motioneye/camera.py
index 3e8df99e0aa392d670915df3bfe426a52aab53fd..f21769b24f21f929dfe8b289976600db7a846776 100644
--- a/homeassistant/components/motioneye/camera.py
+++ b/homeassistant/components/motioneye/camera.py
@@ -5,7 +5,8 @@ from types import MappingProxyType
 from typing import Any
 
 import aiohttp
-from motioneye_client.client import MotionEyeClient
+from jinja2 import Template
+from motioneye_client.client import MotionEyeClient, MotionEyeClientURLParseError
 from motioneye_client.const import (
     DEFAULT_SURVEILLANCE_USERNAME,
     KEY_MOTION_DETECTION,
@@ -41,6 +42,7 @@ from . import (
 from .const import (
     CONF_CLIENT,
     CONF_COORDINATOR,
+    CONF_STREAM_URL_TEMPLATE,
     CONF_SURVEILLANCE_PASSWORD,
     CONF_SURVEILLANCE_USERNAME,
     DOMAIN,
@@ -129,11 +131,24 @@ class MotionEyeMjpegCamera(MotionEyeEntity, MjpegCamera):
         ):
             auth = camera[KEY_STREAMING_AUTH_MODE]
 
+        streaming_template = self._options.get(CONF_STREAM_URL_TEMPLATE, "").strip()
+        streaming_url = None
+
+        if streaming_template:
+            # Note: Can't use homeassistant.helpers.template as it requires hass
+            # which is not available during entity construction.
+            streaming_url = Template(streaming_template).render(**camera)
+        else:
+            try:
+                streaming_url = self._client.get_camera_stream_url(camera)
+            except MotionEyeClientURLParseError:
+                pass
+
         return {
             CONF_NAME: camera[KEY_NAME],
             CONF_USERNAME: self._surveillance_username if auth is not None else None,
             CONF_PASSWORD: self._surveillance_password if auth is not None else None,
-            CONF_MJPEG_URL: self._client.get_camera_stream_url(camera) or "",
+            CONF_MJPEG_URL: streaming_url or "",
             CONF_STILL_IMAGE_URL: self._client.get_camera_snapshot_url(camera),
             CONF_AUTHENTICATION: auth,
         }
diff --git a/homeassistant/components/motioneye/config_flow.py b/homeassistant/components/motioneye/config_flow.py
index a767cf7ecad508d170c42b252c319785ad4af68e..54b4a9f0b0969fd4cbd90b29f5a64a84f8b47414 100644
--- a/homeassistant/components/motioneye/config_flow.py
+++ b/homeassistant/components/motioneye/config_flow.py
@@ -26,6 +26,7 @@ from . import create_motioneye_client
 from .const import (
     CONF_ADMIN_PASSWORD,
     CONF_ADMIN_USERNAME,
+    CONF_STREAM_URL_TEMPLATE,
     CONF_SURVEILLANCE_PASSWORD,
     CONF_SURVEILLANCE_USERNAME,
     CONF_WEBHOOK_SET,
@@ -218,4 +219,19 @@ class MotionEyeOptionsFlow(OptionsFlow):
             ): bool,
         }
 
+        if self.show_advanced_options:
+            # The input URL is not validated as being a URL, to allow for the possibility
+            # the template input won't be a valid URL until after it's rendered.
+            schema.update(
+                {
+                    vol.Required(
+                        CONF_STREAM_URL_TEMPLATE,
+                        default=self._config_entry.options.get(
+                            CONF_STREAM_URL_TEMPLATE,
+                            "",
+                        ),
+                    ): str
+                }
+            )
+
         return self.async_show_form(step_id="init", data_schema=vol.Schema(schema))
diff --git a/homeassistant/components/motioneye/const.py b/homeassistant/components/motioneye/const.py
index 41fb2c18d6370111f76e8b4fa62e0246949d8280..f9f25a3b7ee2603a1f67fa8d6b8c0890ef35571f 100644
--- a/homeassistant/components/motioneye/const.py
+++ b/homeassistant/components/motioneye/const.py
@@ -32,6 +32,7 @@ CONF_CLIENT: Final = "client"
 CONF_COORDINATOR: Final = "coordinator"
 CONF_ADMIN_PASSWORD: Final = "admin_password"
 CONF_ADMIN_USERNAME: Final = "admin_username"
+CONF_STREAM_URL_TEMPLATE: Final = "stream_url_template"
 CONF_SURVEILLANCE_USERNAME: Final = "surveillance_username"
 CONF_SURVEILLANCE_PASSWORD: Final = "surveillance_password"
 CONF_WEBHOOK_SET: Final = "webhook_set"
diff --git a/homeassistant/components/motioneye/strings.json b/homeassistant/components/motioneye/strings.json
index 9763e1caf3489efcb7b48c22a4949364486ceeca..0f17699e652620e812048ee553960dfffbae1158 100644
--- a/homeassistant/components/motioneye/strings.json
+++ b/homeassistant/components/motioneye/strings.json
@@ -31,9 +31,10 @@
       "init": {
         "data": {
           "webhook_set": "Configure motionEye webhooks to report events to Home Assistant",
-          "webhook_set_overwrite": "Overwrite unrecognized webhooks"
+          "webhook_set_overwrite": "Overwrite unrecognized webhooks",
+          "stream_url_template": "Stream URL template"
         }
       }
     }
   }
-}
+}
\ No newline at end of file
diff --git a/tests/components/motioneye/test_camera.py b/tests/components/motioneye/test_camera.py
index b2264e78556db02beb7c1983fa89258291cec638..c14290afddebd2b562f4695685c7500fea1f15a9 100644
--- a/tests/components/motioneye/test_camera.py
+++ b/tests/components/motioneye/test_camera.py
@@ -8,6 +8,7 @@ from aiohttp.web_exceptions import HTTPBadGateway
 from motioneye_client.client import (
     MotionEyeClientError,
     MotionEyeClientInvalidAuthError,
+    MotionEyeClientURLParseError,
 )
 from motioneye_client.const import (
     KEY_CAMERAS,
@@ -20,6 +21,7 @@ import pytest
 from homeassistant.components.camera import async_get_image, async_get_mjpeg_stream
 from homeassistant.components.motioneye import get_motioneye_device_identifier
 from homeassistant.components.motioneye.const import (
+    CONF_STREAM_URL_TEMPLATE,
     CONF_SURVEILLANCE_USERNAME,
     DEFAULT_SCAN_INTERVAL,
     DOMAIN,
@@ -320,3 +322,60 @@ async def test_device_info(hass: HomeAssistant) -> None:
         for entry in er.async_entries_for_device(entity_registry, device.id)
     ]
     assert TEST_CAMERA_ENTITY_ID in entities_from_device
+
+
+async def test_camera_option_stream_url_template(
+    aiohttp_server: Any, hass: HomeAssistant
+) -> None:
+    """Verify camera with a stream URL template option."""
+    client = create_mock_motioneye_client()
+
+    stream_handler = Mock(return_value="")
+    app = web.Application()
+    app.add_routes([web.get(f"/{TEST_CAMERA_NAME}/{TEST_CAMERA_ID}", stream_handler)])
+    stream_server = await aiohttp_server(app)
+
+    client = create_mock_motioneye_client()
+
+    config_entry = create_mock_motioneye_config_entry(
+        hass,
+        data={
+            CONF_URL: f"http://localhost:{stream_server.port}",
+            # The port won't be used as the client is a mock.
+            CONF_SURVEILLANCE_USERNAME: TEST_SURVEILLANCE_USERNAME,
+        },
+        options={
+            CONF_STREAM_URL_TEMPLATE: (
+                f"http://localhost:{stream_server.port}/" "{{ name }}/{{ id }}"
+            )
+        },
+    )
+
+    await setup_mock_motioneye_config_entry(
+        hass, config_entry=config_entry, client=client
+    )
+    await hass.async_block_till_done()
+
+    # It won't actually get a stream from the dummy handler, so just catch
+    # the expected exception, then verify the right handler was called.
+    with pytest.raises(HTTPBadGateway):
+        await async_get_mjpeg_stream(hass, Mock(), TEST_CAMERA_ENTITY_ID)
+    assert stream_handler.called
+    assert not client.get_camera_stream_url.called
+
+
+async def test_get_stream_from_camera_with_broken_host(
+    aiohttp_server: Any, hass: HomeAssistant
+) -> None:
+    """Test getting a stream with a broken URL (no host)."""
+
+    client = create_mock_motioneye_client()
+    config_entry = create_mock_motioneye_config_entry(hass, data={CONF_URL: "http://"})
+    client.get_camera_stream_url = Mock(side_effect=MotionEyeClientURLParseError)
+
+    await setup_mock_motioneye_config_entry(
+        hass, config_entry=config_entry, client=client
+    )
+    await hass.async_block_till_done()
+    with pytest.raises(HTTPBadGateway):
+        await async_get_mjpeg_stream(hass, Mock(), TEST_CAMERA_ENTITY_ID)
diff --git a/tests/components/motioneye/test_config_flow.py b/tests/components/motioneye/test_config_flow.py
index 591bbaa4c7dbbf96a48d31e0841ef153c82b509e..878795a5a70f3b155702b9d216c7f7eb4fa0557e 100644
--- a/tests/components/motioneye/test_config_flow.py
+++ b/tests/components/motioneye/test_config_flow.py
@@ -11,6 +11,7 @@ from homeassistant import config_entries, data_entry_flow
 from homeassistant.components.motioneye.const import (
     CONF_ADMIN_PASSWORD,
     CONF_ADMIN_USERNAME,
+    CONF_STREAM_URL_TEMPLATE,
     CONF_SURVEILLANCE_PASSWORD,
     CONF_SURVEILLANCE_USERNAME,
     CONF_WEBHOOK_SET,
@@ -460,3 +461,39 @@ async def test_options(hass: HomeAssistant) -> None:
         assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
         assert result["data"][CONF_WEBHOOK_SET]
         assert result["data"][CONF_WEBHOOK_SET_OVERWRITE]
+        assert CONF_STREAM_URL_TEMPLATE not in result["data"]
+
+
+async def test_advanced_options(hass: HomeAssistant) -> None:
+    """Check an options flow with advanced options."""
+
+    config_entry = create_mock_motioneye_config_entry(hass)
+
+    mock_client = create_mock_motioneye_client()
+    with patch(
+        "homeassistant.components.motioneye.MotionEyeClient",
+        return_value=mock_client,
+    ) as mock_setup, patch(
+        "homeassistant.components.motioneye.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        await hass.async_block_till_done()
+
+        result = await hass.config_entries.options.async_init(
+            config_entry.entry_id, context={"show_advanced_options": True}
+        )
+        result = await hass.config_entries.options.async_configure(
+            result["flow_id"],
+            user_input={
+                CONF_WEBHOOK_SET: True,
+                CONF_WEBHOOK_SET_OVERWRITE: True,
+                CONF_STREAM_URL_TEMPLATE: "http://moo",
+            },
+        )
+        await hass.async_block_till_done()
+        assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+        assert result["data"][CONF_WEBHOOK_SET]
+        assert result["data"][CONF_WEBHOOK_SET_OVERWRITE]
+        assert result["data"][CONF_STREAM_URL_TEMPLATE] == "http://moo"
+        assert len(mock_setup.mock_calls) == 0
+        assert len(mock_setup_entry.mock_calls) == 0