diff --git a/gso/migrations/versions/2023-08-16_e68720f2ec32_add_ip_trunk_migration_workflow.py b/gso/migrations/versions/2023-08-16_e68720f2ec32_add_ip_trunk_migration_workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..f2ad05f776ede60ab8c2d4f696454347a48f4e57
--- /dev/null
+++ b/gso/migrations/versions/2023-08-16_e68720f2ec32_add_ip_trunk_migration_workflow.py
@@ -0,0 +1,39 @@
+"""Add IP Trunk migration workflow.
+
+Revision ID: e68720f2ec32
+Revises: a6eefd32c4f7
+Create Date: 2023-08-16 14:48:00.227803
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'e68720f2ec32'
+down_revision = 'a6eefd32c4f7'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+new_workflows = [
+    {
+        "name": "migrate_iptrunk",
+        "target": "MODIFY",
+        "description": "Migrate an IP Trunk",
+        "product_type": "Iptrunk"
+    }
+]
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        create_workflow(conn, workflow)
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        delete_workflow(conn, workflow["name"])
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index b578903d1dc2a72038bd0c872cf8974b513f3885..32ffca79cc43d7e566515c88ee31206f029ac5f4 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -1,12 +1,13 @@
 """Initialisation class that imports all workflows into {term}`GSO`."""
 from orchestrator.workflows import LazyWorkflowInstance
 
-LazyWorkflowInstance("gso.workflows.router.create_router", "create_router")
-LazyWorkflowInstance("gso.workflows.router.terminate_router", "terminate_router")
 LazyWorkflowInstance("gso.workflows.iptrunk.create_iptrunk", "create_iptrunk")
+LazyWorkflowInstance("gso.workflows.iptrunk.modify_isis_metric", "modify_isis_metric")
 LazyWorkflowInstance("gso.workflows.iptrunk.modify_trunk_interface", "modify_trunk_interface")
+LazyWorkflowInstance("gso.workflows.iptrunk.migrate_iptrunk", "migrate_iptrunk")
 LazyWorkflowInstance("gso.workflows.iptrunk.terminate_iptrunk", "terminate_iptrunk")
-LazyWorkflowInstance("gso.workflows.iptrunk.modify_isis_metric", "modify_isis_metric")
+LazyWorkflowInstance("gso.workflows.router.create_router", "create_router")
+LazyWorkflowInstance("gso.workflows.router.terminate_router", "terminate_router")
 LazyWorkflowInstance("gso.workflows.site.create_site", "create_site")
 LazyWorkflowInstance("gso.workflows.tasks.import_site", "import_site")
 LazyWorkflowInstance("gso.workflows.tasks.import_router", "import_router")
diff --git a/gso/workflows/iptrunk/migrate_iptrunk.py b/gso/workflows/iptrunk/migrate_iptrunk.py
new file mode 100644
index 0000000000000000000000000000000000000000..46945c724962c5fff742009b25e8ebf355fbe5e4
--- /dev/null
+++ b/gso/workflows/iptrunk/migrate_iptrunk.py
@@ -0,0 +1,83 @@
+import re
+from typing import NoReturn
+
+from orchestrator import step, workflow
+from orchestrator.db import SubscriptionTable, ProductTable
+from orchestrator.forms import FormPage
+from orchestrator.forms.validators import Choice, UniqueConstrainedList
+from orchestrator.targets import Target
+from orchestrator.types import UUIDstr, FormGenerator
+from orchestrator.workflow import StepList, init, done
+from orchestrator.workflows.steps import store_process_subscription, unsync, resync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic import validator
+
+from products import Iptrunk
+
+
+def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    subscription = Iptrunk.from_subscription(subscription_id)
+
+    routers = {}
+    for router_id, router_description in (
+            SubscriptionTable.query.join(ProductTable)
+                    .filter(
+                ProductTable.product_type == "Router",
+                SubscriptionTable.status == "active",
+            )
+                    .with_entities(SubscriptionTable.subscription_id, SubscriptionTable.description)
+                    .all()
+    ):
+        if router_id not in [subscription.iptrunk.iptrunk_sideA_node, subscription.iptrunk.iptrunk_sideB_node]:
+            routers[str(router_id)] = router_description
+
+    NewRouterEnum = Choice("Select a new router", zip(routers.keys(), routers.items()))  # type: ignore
+
+    class LagMemberList(UniqueConstrainedList[str]):
+        min_items = len(subscription.iptrunk.iptrunk_sideA_ae_members)
+        max_items = len(subscription.iptrunk.iptrunk_sideA_ae_members)
+
+    class ModifyIptrunkForm(FormPage):
+        class Config:
+            title = f"Subscription {subscription.iptrunk.geant_s_sid} from " \
+                    f"{subscription.iptrunk.iptrunk_sideA_node.router_fqdn} to " \
+                    f"{subscription.iptrunk.iptrunk_sideB_node.router_fqdn}"
+
+        replace_side = Choice("Select the side of the IP trunk to be replaced",
+                              [subscription.iptrunk.iptrunk_sideA_node, subscription.iptrunk.iptrunk_sideB_node])
+        new_node: NewRouterEnum  # type: ignore
+        new_lag_interface: str
+        new_lag_member_interfaces = LagMemberList
+
+        @validator("new_lag_interface", pre=True, always=True)
+        def lag_interface_proper_name(cls, new_lag_name: str) -> str | NoReturn:
+            nokia_lag_re = re.compile("^lag-\\d+$")
+            juniper_lag_re = re.compile("^ae\\d{1,2}$")
+
+            if nokia_lag_re.match(new_lag_name) or juniper_lag_re.match(new_lag_name):
+                return new_lag_name
+
+            raise ValueError("Invalid LAG name, please try again.")
+
+    yield ModifyIptrunkForm
+
+
+@step("Hilversum Stinks")
+def temp_test_step(subscription: Iptrunk) -> Iptrunk:
+    return subscription
+
+
+@workflow(
+    "Migrate an IP Trunk",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.MODIFY,
+)
+def migrate_iptrunk() -> StepList:
+    return (
+        init
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> temp_test_step
+        >> resync
+        >> done
+    )