Repository: PrefectHQ/prefect

Issue:
Support trigger definitions via CLI with `prefect deploy`
### First check

- [X] I added a descriptive title to this issue.
- [X] I used the GitHub search to find a similar request and didn't find it.
- [X] I searched the Prefect documentation for this feature.

### Prefect Version

2.x

### Describe the current behavior

Currently, users must edit yaml to add trigger policies to deployments

### Describe the proposed behavior

Users should be able to pass in a reference to a trigger policy file or raw json of a trigger. The trigger should be validated on deploy.

### Example Use

prefect deploy --trigger trigger.yaml
prefect deploy --trigger '{after:...}'


### Additional context

_No response_


Discussion:
Hey @WillRaphaelson, thanks for creating the issue.

Just spotted that it should be the command 
`prefect deployment --trigger [...]` instead of `prefect deploy --trigger [...]`
Hey @serinamarie,

I just tried out what this could look like. Please find a draft PR here: https://github.com/PrefectHQ/prefect/pull/10634/files
Happy for any feedback!

I have only tested it with the unit tests yet. I will make tests with the actual command tomorrow.
Hi @toro-berlin, thanks so much, we'll take a look! 

I have made a separate issue for the older style of deployments you're mentioning and any further discussion can be had on that issue :)
[Support trigger definitions via CLI with `prefect deployment build`](https://github.com/PrefectHQ/prefect/issues/10635)

Resolving patch:
diff --git a/src/prefect/cli/deploy.py b/src/prefect/cli/deploy.py
--- a/src/prefect/cli/deploy.py
+++ b/src/prefect/cli/deploy.py
@@ -150,6 +150,15 @@ async def deploy(
         "--timezone",
         help="Deployment schedule timezone string e.g. 'America/New_York'",
     ),
+    trigger: List[str] = typer.Option(
+        None,
+        "--trigger",
+        help=(
+            "Specifies a trigger for the deployment. The value can be a"
+            " json string or path to `.yaml`/`.json` file. This flag can be used"
+            " multiple times."
+        ),
+    ),
     param: List[str] = typer.Option(
         None,
         "--param",
@@ -217,6 +226,7 @@ async def deploy(
         "anchor_date": interval_anchor,
         "rrule": rrule,
         "timezone": timezone,
+        "triggers": trigger,
         "param": param,
         "params": params,
     }
@@ -482,9 +492,9 @@ async def _run_single_deploy(
             build_steps = deploy_config.get("build", actions.get("build")) or []
             push_steps = deploy_config.get("push", actions.get("push")) or []
 
-    triggers: List[DeploymentTrigger] = []
-    trigger_specs = deploy_config.get("triggers")
-    if trigger_specs:
+    if trigger_specs := _gather_deployment_trigger_definitions(
+        options.get("triggers"), deploy_config.get("triggers")
+    ):
         triggers = _initialize_deployment_triggers(deployment_name, trigger_specs)
         if client.server_type != ServerType.CLOUD:
             app.console.print(
@@ -497,6 +507,8 @@ async def _run_single_deploy(
                 ),
                 style="yellow",
             )
+    else:
+        triggers = []
 
     ## RUN BUILD AND PUSH STEPS
     step_outputs = {}
@@ -612,6 +624,7 @@ async def _run_single_deploy(
                 build_steps=build_steps or None,
                 push_steps=push_steps or None,
                 pull_steps=pull_steps or None,
+                triggers=trigger_specs or None,
             )
             app.console.print(
                 (
@@ -1340,3 +1353,38 @@ async def _create_deployment_triggers(
         for trigger in triggers:
             trigger.set_deployment_id(deployment_id)
             await client.create_automation(trigger.as_automation())
+
+
+def _gather_deployment_trigger_definitions(
+    trigger_flags: List[str], existing_triggers: List[Dict[str, Any]]
+) -> List[Dict[str, Any]]:
+    """Parses trigger flags from CLI and existing deployment config in `prefect.yaml`.
+
+    Args:
+        trigger_flags: Triggers passed via CLI, either as JSON strings or file paths.
+        existing_triggers: Triggers from existing deployment configuration.
+
+    Returns:
+        List of trigger specifications.
+
+    Raises:
+        ValueError: If trigger flag is not a valid JSON string or file path.
+    """
+
+    if trigger_flags:
+        trigger_specs = []
+        for t in trigger_flags:
+            try:
+                if t.endswith(".yaml"):
+                    with open(t, "r") as f:
+                        trigger_specs.extend(yaml.safe_load(f).get("triggers", []))
+                elif t.endswith(".json"):
+                    with open(t, "r") as f:
+                        trigger_specs.extend(json.load(f).get("triggers", []))
+                else:
+                    trigger_specs.append(json.loads(t))
+            except Exception as e:
+                raise ValueError(f"Failed to parse trigger: {t}. Error: {str(e)}")
+        return trigger_specs
+
+    return existing_triggers
diff --git a/src/prefect/deployments/base.py b/src/prefect/deployments/base.py
--- a/src/prefect/deployments/base.py
+++ b/src/prefect/deployments/base.py
@@ -382,6 +382,7 @@ def _save_deployment_to_prefect_file(
     build_steps: Optional[List[Dict]] = None,
     push_steps: Optional[List[Dict]] = None,
     pull_steps: Optional[List[Dict]] = None,
+    triggers: Optional[List[Dict]] = None,
 ):
     """
     Save a deployment configuration to the `prefect.yaml` file in the
@@ -441,6 +442,9 @@ def _save_deployment_to_prefect_file(
         if pull_steps != parsed_prefect_file_contents.get("pull"):
             deployment["pull"] = pull_steps
 
+        if triggers and triggers != parsed_prefect_file_contents.get("triggers"):
+            deployment["triggers"] = triggers
+
         deployments = parsed_prefect_file_contents.get("deployments")
         if deployments is None:
             parsed_prefect_file_contents["deployments"] = [deployment]


Test patch:
diff --git a/tests/cli/test_deploy.py b/tests/cli/test_deploy.py
--- a/tests/cli/test_deploy.py
+++ b/tests/cli/test_deploy.py
@@ -1,4 +1,5 @@
 import datetime
+import json
 import os
 import shutil
 import subprocess
@@ -117,6 +118,30 @@ def project_dir_with_single_deployment_format(tmp_path):
     os.chdir(original_dir)
 
 
+@pytest.fixture
+def uninitialized_project_dir(project_dir):
+    Path(project_dir, "prefect.yaml").unlink()
+    return project_dir
+
+
+@pytest.fixture
+def uninitialized_project_dir_with_git_no_remote(uninitialized_project_dir):
+    subprocess.run(["git", "init"], cwd=uninitialized_project_dir)
+    assert Path(uninitialized_project_dir, ".git").exists()
+    return uninitialized_project_dir
+
+
+@pytest.fixture
+def uninitialized_project_dir_with_git_with_remote(
+    uninitialized_project_dir_with_git_no_remote,
+):
+    subprocess.run(
+        ["git", "remote", "add", "origin", "https://example.com/org/repo.git"],
+        cwd=uninitialized_project_dir_with_git_no_remote,
+    )
+    return uninitialized_project_dir_with_git_no_remote
+
+
 @pytest.fixture
 async def default_agent_pool(prefect_client):
     return await prefect_client.create_work_pool(
@@ -628,29 +653,6 @@ async def test_project_deploy_with_no_prefect_yaml(self, project_dir, work_pool)
         )
 
     class TestGeneratedPullAction:
-        @pytest.fixture
-        def uninitialized_project_dir(self, project_dir):
-            Path(project_dir, "prefect.yaml").unlink()
-            return project_dir
-
-        @pytest.fixture
-        def uninitialized_project_dir_with_git_no_remote(
-            self, uninitialized_project_dir
-        ):
-            subprocess.run(["git", "init"], cwd=uninitialized_project_dir)
-            assert Path(uninitialized_project_dir, ".git").exists()
-            return uninitialized_project_dir
-
-        @pytest.fixture
-        def uninitialized_project_dir_with_git_with_remote(
-            self, uninitialized_project_dir_with_git_no_remote
-        ):
-            subprocess.run(
-                ["git", "remote", "add", "origin", "https://example.com/org/repo.git"],
-                cwd=uninitialized_project_dir_with_git_no_remote,
-            )
-            return uninitialized_project_dir_with_git_no_remote
-
         async def test_project_deploy_generates_pull_action(
             self, work_pool, prefect_client, uninitialized_project_dir
         ):
@@ -4236,194 +4238,520 @@ async def test_no_matching_deployment_in_prefect_file_returns_false(self):
         assert not matching_deployment_exists_2
 
 
-class TestDeploymentTriggerSyncing:
-    async def test_initialize_named_deployment_triggers(self):
-        trigger_spec = {
-            "name": "Trigger McTriggerson",
-            "enabled": True,
-            "match": {"prefect.resource.id": "prefect.flow-run.*"},
-            "expect": ["prefect.flow-run.Completed"],
-            "match_related": {
-                "prefect.resource.name": "seed",
-                "prefect.resource.role": "flow",
-            },
-        }
-
-        triggers = _initialize_deployment_triggers("my_deployment", [trigger_spec])
-        assert triggers == [
-            {
+class TestDeploymentTrigger:
+    class TestDeploymentTriggerSyncing:
+        async def test_initialize_named_deployment_triggers(self):
+            trigger_spec = {
                 "name": "Trigger McTriggerson",
-                "description": "",
                 "enabled": True,
                 "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                "expect": ["prefect.flow-run.Completed"],
                 "match_related": {
                     "prefect.resource.name": "seed",
                     "prefect.resource.role": "flow",
                 },
-                "after": set(),
-                "expect": {"prefect.flow-run.Completed"},
-                "for_each": set(),
-                "posture": Posture.Reactive,
-                "threshold": 1,
-                "within": datetime.timedelta(0),
-                "parameters": None,
             }
-        ]
 
-    async def test_initialize_deployment_triggers_implicit_name(self):
-        trigger_spec = {
-            "enabled": True,
-            "match": {"prefect.resource.id": "prefect.flow-run.*"},
-            "expect": ["prefect.flow-run.Completed"],
-            "match_related": {
-                "prefect.resource.name": "seed",
-                "prefect.resource.role": "flow",
-            },
-        }
+            triggers = _initialize_deployment_triggers("my_deployment", [trigger_spec])
+            assert triggers == [
+                {
+                    "name": "Trigger McTriggerson",
+                    "description": "",
+                    "enabled": True,
+                    "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                    "match_related": {
+                        "prefect.resource.name": "seed",
+                        "prefect.resource.role": "flow",
+                    },
+                    "after": set(),
+                    "expect": {"prefect.flow-run.Completed"},
+                    "for_each": set(),
+                    "posture": Posture.Reactive,
+                    "threshold": 1,
+                    "within": datetime.timedelta(0),
+                    "parameters": None,
+                }
+            ]
+
+        async def test_initialize_deployment_triggers_implicit_name(self):
+            trigger_spec = {
+                "enabled": True,
+                "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                "expect": ["prefect.flow-run.Completed"],
+                "match_related": {
+                    "prefect.resource.name": "seed",
+                    "prefect.resource.role": "flow",
+                },
+            }
 
-        triggers = _initialize_deployment_triggers("my_deployment", [trigger_spec])
-        assert triggers[0].name == "my_deployment__automation_1"
+            triggers = _initialize_deployment_triggers("my_deployment", [trigger_spec])
+            assert triggers[0].name == "my_deployment__automation_1"
 
-    async def test_create_deployment_triggers(self):
-        client = AsyncMock()
-        client.server_type = ServerType.CLOUD
+        async def test_create_deployment_triggers(self):
+            client = AsyncMock()
+            client.server_type = ServerType.CLOUD
 
-        trigger_spec = {
-            "enabled": True,
-            "match": {"prefect.resource.id": "prefect.flow-run.*"},
-            "expect": ["prefect.flow-run.Completed"],
-            "match_related": {
-                "prefect.resource.name": "seed",
-                "prefect.resource.role": "flow",
-            },
-        }
+            trigger_spec = {
+                "enabled": True,
+                "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                "expect": ["prefect.flow-run.Completed"],
+                "match_related": {
+                    "prefect.resource.name": "seed",
+                    "prefect.resource.role": "flow",
+                },
+            }
 
-        triggers = _initialize_deployment_triggers("my_deployment", [trigger_spec])
-        deployment_id = uuid4()
+            triggers = _initialize_deployment_triggers("my_deployment", [trigger_spec])
+            deployment_id = uuid4()
 
-        await _create_deployment_triggers(client, deployment_id, triggers)
+            await _create_deployment_triggers(client, deployment_id, triggers)
 
-        assert triggers[0]._deployment_id == deployment_id
-        client.delete_resource_owned_automations.assert_called_once_with(
-            f"prefect.deployment.{deployment_id}"
-        )
-        client.create_automation.assert_called_once_with(triggers[0].as_automation())
+            assert triggers[0]._deployment_id == deployment_id
+            client.delete_resource_owned_automations.assert_called_once_with(
+                f"prefect.deployment.{deployment_id}"
+            )
+            client.create_automation.assert_called_once_with(
+                triggers[0].as_automation()
+            )
 
-    async def test_create_deployment_triggers_not_cloud_noop(self):
-        client = AsyncMock()
-        client.server_type = ServerType.SERVER
+        async def test_create_deployment_triggers_not_cloud_noop(self):
+            client = AsyncMock()
+            client.server_type = ServerType.SERVER
 
-        trigger_spec = {
-            "enabled": True,
-            "match": {"prefect.resource.id": "prefect.flow-run.*"},
-            "expect": ["prefect.flow-run.Completed"],
-            "match_related": {
-                "prefect.resource.name": "seed",
-                "prefect.resource.role": "flow",
-            },
-        }
+            trigger_spec = {
+                "enabled": True,
+                "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                "expect": ["prefect.flow-run.Completed"],
+                "match_related": {
+                    "prefect.resource.name": "seed",
+                    "prefect.resource.role": "flow",
+                },
+            }
 
-        triggers = _initialize_deployment_triggers("my_deployment", [trigger_spec])
-        deployment_id = uuid4()
+            triggers = _initialize_deployment_triggers("my_deployment", [trigger_spec])
+            deployment_id = uuid4()
 
-        await _create_deployment_triggers(client, deployment_id, triggers)
+            await _create_deployment_triggers(client, deployment_id, triggers)
 
-        client.delete_resource_owned_automations.assert_not_called()
-        client.create_automation.assert_not_called()
+            client.delete_resource_owned_automations.assert_not_called()
+            client.create_automation.assert_not_called()
 
-    async def test_triggers_creation_orchestrated(
-        self, project_dir, prefect_client, work_pool
-    ):
-        prefect_file = Path("prefect.yaml")
-        with prefect_file.open(mode="r") as f:
-            contents = yaml.safe_load(f)
+        async def test_triggers_creation_orchestrated(
+            self, project_dir, prefect_client, work_pool
+        ):
+            prefect_file = Path("prefect.yaml")
+            with prefect_file.open(mode="r") as f:
+                contents = yaml.safe_load(f)
 
-        contents["deployments"] = [
-            {
-                "name": "test-name-1",
-                "work_pool": {
-                    "name": work_pool.name,
-                },
-                "triggers": [
-                    {
-                        "enabled": True,
-                        "match": {"prefect.resource.id": "prefect.flow-run.*"},
-                        "expect": ["prefect.flow-run.Completed"],
-                        "match_related": {
-                            "prefect.resource.name": "seed",
-                            "prefect.resource.role": "flow",
-                        },
-                    }
-                ],
-            }
-        ]
+            contents["deployments"] = [
+                {
+                    "name": "test-name-1",
+                    "work_pool": {
+                        "name": work_pool.name,
+                    },
+                    "triggers": [
+                        {
+                            "enabled": True,
+                            "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                            "expect": ["prefect.flow-run.Completed"],
+                            "match_related": {
+                                "prefect.resource.name": "seed",
+                                "prefect.resource.role": "flow",
+                            },
+                        }
+                    ],
+                }
+            ]
 
-        expected_triggers = _initialize_deployment_triggers(
-            "test-name-1", contents["deployments"][0]["triggers"]
-        )
+            expected_triggers = _initialize_deployment_triggers(
+                "test-name-1", contents["deployments"][0]["triggers"]
+            )
 
-        with prefect_file.open(mode="w") as f:
-            yaml.safe_dump(contents, f)
+            with prefect_file.open(mode="w") as f:
+                yaml.safe_dump(contents, f)
+
+            with mock.patch(
+                "prefect.cli.deploy._create_deployment_triggers",
+                AsyncMock(),
+            ) as create_triggers:
+                await run_sync_in_worker_thread(
+                    invoke_and_assert,
+                    command="deploy ./flows/hello.py:my_flow -n test-name-1",
+                    expected_code=0,
+                )
+
+                assert create_triggers.call_count == 1
+
+                client, deployment_id, triggers = create_triggers.call_args[0]
+                assert isinstance(client, PrefectClient)
+                assert isinstance(deployment_id, UUID)
+
+                expected_triggers[0].set_deployment_id(deployment_id)
+
+                assert triggers == expected_triggers
 
-        with mock.patch(
-            "prefect.cli.deploy._create_deployment_triggers",
-            AsyncMock(),
-        ) as create_triggers:
+        async def test_deploy_command_warns_triggers_not_created_not_cloud(
+            self, project_dir, prefect_client, work_pool
+        ):
+            prefect_file = Path("prefect.yaml")
+            with prefect_file.open(mode="r") as f:
+                contents = yaml.safe_load(f)
+
+            contents["deployments"] = [
+                {
+                    "name": "test-name-1",
+                    "work_pool": {
+                        "name": work_pool.name,
+                    },
+                    "triggers": [
+                        {
+                            "enabled": True,
+                            "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                            "expect": ["prefect.flow-run.Completed"],
+                            "match_related": {
+                                "prefect.resource.name": "seed",
+                                "prefect.resource.role": "flow",
+                            },
+                        }
+                    ],
+                }
+            ]
+
+            with prefect_file.open(mode="w") as f:
+                yaml.safe_dump(contents, f)
+
+            # Deploy the deployment with a name
             await run_sync_in_worker_thread(
                 invoke_and_assert,
                 command="deploy ./flows/hello.py:my_flow -n test-name-1",
                 expected_code=0,
+                expected_output_contains=[
+                    "Deployment triggers are only supported on Prefect Cloud"
+                ],
             )
 
-            assert create_triggers.call_count == 1
+    class TestDeploymentTriggerPassedViaCLI:
+        @pytest.mark.usefixtures("project_dir")
+        async def test_json_string_trigger(self, docker_work_pool):
+            client = AsyncMock()
+            client.server_type = ServerType.CLOUD
 
-            client, deployment_id, triggers = create_triggers.call_args[0]
-            assert isinstance(client, PrefectClient)
-            assert isinstance(deployment_id, UUID)
+            trigger_spec = {
+                "enabled": True,
+                "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                "expect": ["prefect.flow-run.Completed"],
+            }
 
-            expected_triggers[0].set_deployment_id(deployment_id)
+            expected_triggers = _initialize_deployment_triggers(
+                "test-name-1", [trigger_spec]
+            )
 
-            assert triggers == expected_triggers
+            with mock.patch(
+                "prefect.cli.deploy._create_deployment_triggers",
+                AsyncMock(),
+            ) as create_triggers:
+                await run_sync_in_worker_thread(
+                    invoke_and_assert,
+                    command=(
+                        "deploy ./flows/hello.py:my_flow -n test-name-1 --trigger"
+                        f" '{json.dumps(trigger_spec)}' -p {docker_work_pool.name}"
+                    ),
+                    expected_code=0,
+                )
 
-    async def test_deploy_command_warns_triggers_not_created_not_cloud(
-        self, project_dir, prefect_client, work_pool
-    ):
-        prefect_file = Path("prefect.yaml")
-        with prefect_file.open(mode="r") as f:
-            contents = yaml.safe_load(f)
+                assert create_triggers.call_count == 1
 
-        contents["deployments"] = [
-            {
-                "name": "test-name-1",
-                "work_pool": {
-                    "name": work_pool.name,
-                },
-                "triggers": [
-                    {
-                        "enabled": True,
-                        "match": {"prefect.resource.id": "prefect.flow-run.*"},
-                        "expect": ["prefect.flow-run.Completed"],
-                        "match_related": {
-                            "prefect.resource.name": "seed",
-                            "prefect.resource.role": "flow",
-                        },
-                    }
-                ],
+                client, deployment_id, triggers = create_triggers.call_args[0]
+
+                expected_triggers[0].set_deployment_id(deployment_id)
+
+                assert triggers == expected_triggers
+
+        @pytest.mark.usefixtures("project_dir")
+        async def test_json_file_trigger(self, docker_work_pool):
+            client = AsyncMock()
+            client.server_type = ServerType.CLOUD
+
+            trigger_spec = {
+                "enabled": True,
+                "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                "expect": ["prefect.flow-run.Completed"],
             }
-        ]
 
-        with prefect_file.open(mode="w") as f:
-            yaml.safe_dump(contents, f)
+            with open("triggers.json", "w") as f:
+                json.dump({"triggers": [trigger_spec]}, f)
 
-        # Deploy the deployment with a name
-        await run_sync_in_worker_thread(
-            invoke_and_assert,
-            command="deploy ./flows/hello.py:my_flow -n test-name-1",
-            expected_code=0,
-            expected_output_contains=[
-                "Deployment triggers are only supported on Prefect Cloud"
-            ],
-        )
+            expected_triggers = _initialize_deployment_triggers(
+                "test-name-1", [trigger_spec]
+            )
+
+            with mock.patch(
+                "prefect.cli.deploy._create_deployment_triggers",
+                AsyncMock(),
+            ) as create_triggers:
+                await run_sync_in_worker_thread(
+                    invoke_and_assert,
+                    command=(
+                        "deploy ./flows/hello.py:my_flow -n test-name-1"
+                        f" --trigger triggers.json -p {docker_work_pool.name}"
+                    ),
+                    expected_code=0,
+                )
+
+                assert create_triggers.call_count == 1
+
+                client, deployment_id, triggers = create_triggers.call_args[0]
+
+                expected_triggers[0].set_deployment_id(deployment_id)
+
+                assert triggers == expected_triggers
+
+        @pytest.mark.usefixtures("project_dir")
+        async def test_yaml_file_trigger(self, docker_work_pool):
+            client = AsyncMock()
+            client.server_type = ServerType.CLOUD
+
+            trigger_spec = {
+                "enabled": True,
+                "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                "expect": ["prefect.flow-run.Completed"],
+            }
+
+            with open("triggers.yaml", "w") as f:
+                yaml.safe_dump({"triggers": [trigger_spec]}, f)
+
+            expected_triggers = _initialize_deployment_triggers(
+                "test-name-1", [trigger_spec]
+            )
+
+            with mock.patch(
+                "prefect.cli.deploy._create_deployment_triggers",
+                AsyncMock(),
+            ) as create_triggers:
+                await run_sync_in_worker_thread(
+                    invoke_and_assert,
+                    command=(
+                        "deploy ./flows/hello.py:my_flow -n test-name-1"
+                        f" --trigger triggers.yaml -p {docker_work_pool.name}"
+                    ),
+                    expected_code=0,
+                )
+
+                assert create_triggers.call_count == 1
+
+                client, deployment_id, triggers = create_triggers.call_args[0]
+
+                expected_triggers[0].set_deployment_id(deployment_id)
+
+                assert triggers == expected_triggers
+
+        @pytest.mark.usefixtures("project_dir")
+        async def test_nested_yaml_file_trigger(self, docker_work_pool, tmpdir):
+            client = AsyncMock()
+            client.server_type = ServerType.CLOUD
+
+            trigger_spec = {
+                "enabled": True,
+                "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                "expect": ["prefect.flow-run.Completed"],
+            }
+            triggers_file = tmpdir.mkdir("my_stuff") / "triggers.yaml"
+            with open(triggers_file, "w") as f:
+                yaml.safe_dump({"triggers": [trigger_spec]}, f)
+
+            expected_triggers = _initialize_deployment_triggers(
+                "test-name-1", [trigger_spec]
+            )
+
+            with mock.patch(
+                "prefect.cli.deploy._create_deployment_triggers",
+                AsyncMock(),
+            ) as create_triggers:
+                await run_sync_in_worker_thread(
+                    invoke_and_assert,
+                    command=(
+                        "deploy ./flows/hello.py:my_flow -n test-name-1"
+                        f" --trigger my_stuff/triggers.yaml -p {docker_work_pool.name}"
+                    ),
+                    expected_code=0,
+                )
+
+                assert create_triggers.call_count == 1
+
+                client, deployment_id, triggers = create_triggers.call_args[0]
+
+                expected_triggers[0].set_deployment_id(deployment_id)
+
+                assert triggers == expected_triggers
+
+        @pytest.mark.usefixtures("project_dir")
+        async def test_multiple_trigger_flags(self, docker_work_pool):
+            client = AsyncMock()
+            client.server_type = ServerType.CLOUD
+
+            trigger_spec_1 = {
+                "enabled": True,
+                "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                "expect": ["prefect.flow-run.Completed"],
+            }
+
+            trigger_spec_2 = {
+                "enabled": False,
+                "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                "expect": ["prefect.flow-run.Failed"],
+            }
+
+            with open("triggers.yaml", "w") as f:
+                yaml.safe_dump({"triggers": [trigger_spec_2]}, f)
+
+            expected_triggers = _initialize_deployment_triggers(
+                "test-name-1", [trigger_spec_1, trigger_spec_2]
+            )
+
+            with mock.patch(
+                "prefect.cli.deploy._create_deployment_triggers",
+                AsyncMock(),
+            ) as create_triggers:
+                await run_sync_in_worker_thread(
+                    invoke_and_assert,
+                    command=(
+                        "deploy ./flows/hello.py:my_flow -n test-name-1 --trigger"
+                        f" '{json.dumps(trigger_spec_1)}' --trigger triggers.yaml -p"
+                        f" {docker_work_pool.name}"
+                    ),
+                    expected_code=0,
+                )
+
+                assert create_triggers.call_count == 1
+
+                client, deployment_id, triggers = create_triggers.call_args[0]
+
+                for expected_trigger in expected_triggers:
+                    expected_trigger.set_deployment_id(deployment_id)
+
+                assert triggers == expected_triggers
+
+        @pytest.mark.usefixtures("project_dir")
+        async def test_override_on_trigger_conflict(self, docker_work_pool):
+            client = AsyncMock()
+            client.server_type = ServerType.CLOUD
+
+            cli_trigger_spec = {
+                "enabled": True,
+                "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                "expect": ["prefect.flow-run.Failed"],
+            }
+
+            expected_triggers = _initialize_deployment_triggers(
+                "test-name-1", [cli_trigger_spec]
+            )
+
+            prefect_file = Path("prefect.yaml")
+            with prefect_file.open(mode="r") as f:
+                contents = yaml.safe_load(f)
+
+            contents["deployments"] = [
+                {
+                    "name": "test-name-1",
+                    "work_pool": {
+                        "name": docker_work_pool.name,
+                    },
+                    "triggers": [
+                        {**cli_trigger_spec, "expect": ["prefect.flow-run.Completed"]}
+                    ],
+                }
+            ]
+
+            with prefect_file.open(mode="w") as f:
+                yaml.safe_dump(contents, f)
+
+            with mock.patch(
+                "prefect.cli.deploy._create_deployment_triggers",
+                AsyncMock(),
+            ) as create_triggers:
+                await run_sync_in_worker_thread(
+                    invoke_and_assert,
+                    command=(
+                        "deploy ./flows/hello.py:my_flow -n test-name-1"
+                        f" --trigger '{json.dumps(cli_trigger_spec)}'"
+                    ),
+                    expected_code=0,
+                )
+
+                _, _, triggers = create_triggers.call_args[0]
+                assert len(triggers) == 1
+                assert triggers == expected_triggers
+
+        @pytest.mark.usefixtures("project_dir")
+        async def test_invalid_trigger_parsing(self, docker_work_pool):
+            client = AsyncMock()
+            client.server_type = ServerType.CLOUD
+
+            invalid_json_str_trigger = "{enabled: true, match: woodchonk.move.*}"
+            invalid_yaml_trigger = "invalid.yaml"
+
+            with open(invalid_yaml_trigger, "w") as f:
+                f.write("pretty please, trigger my flow when you see the woodchonk")
+
+            for invalid_trigger in [invalid_json_str_trigger, invalid_yaml_trigger]:
+                with mock.patch(
+                    "prefect.cli.deploy._create_deployment_triggers",
+                    AsyncMock(),
+                ):
+                    await run_sync_in_worker_thread(
+                        invoke_and_assert,
+                        command=(
+                            "deploy ./flows/hello.py:my_flow -n test-name-1"
+                            f" -p {docker_work_pool.name} --trigger '{invalid_trigger}'"
+                        ),
+                        expected_code=1,
+                        expected_output_contains=["Failed to parse trigger"],
+                    )
+
+        @pytest.mark.usefixtures("interactive_console", "project_dir")
+        async def test_triggers_saved_to_prefect_yaml(self, docker_work_pool):
+            client = AsyncMock()
+            client.server_type = ServerType.CLOUD
+
+            cli_trigger_spec = {
+                "name": "Trigger McTriggerson",
+                "match": {"prefect.resource.id": "prefect.flow-run.*"},
+                "expect": ["prefect.flow-run.Completed"],
+            }
+
+            with mock.patch(
+                "prefect.cli.deploy._create_deployment_triggers",
+                AsyncMock(),
+            ):
+                await run_sync_in_worker_thread(
+                    invoke_and_assert,
+                    command=(
+                        "deploy ./flows/hello.py:my_flow -n test-name-1 -p"
+                        f" {docker_work_pool.name} --trigger"
+                        f" '{json.dumps(cli_trigger_spec)}'"
+                    ),
+                    user_input=(
+                        # Decline schedule
+                        "n"
+                        + readchar.key.ENTER
+                        # Decline docker build
+                        + "n"
+                        + readchar.key.ENTER
+                        # Accept save configuration
+                        + "y"
+                        + readchar.key.ENTER
+                    ),
+                    expected_code=0,
+                )
+
+            # Read the updated prefect.yaml
+            prefect_file = Path("prefect.yaml")
+            with prefect_file.open(mode="r") as f:
+                contents = yaml.safe_load(f)
+
+            assert "deployments" in contents
+            assert "triggers" in contents["deployments"][-1]
+            assert contents["deployments"][-1]["triggers"] == [cli_trigger_spec]
 
 
 @pytest.mark.usefixtures("project_dir", "interactive_console", "work_pool")