diff --git "a/gso/migrations/versions/2024-10-08_bf05800fe9fc_add_edge_port_and_g\303\251ant_ip_workflows.py" "b/gso/migrations/versions/2024-10-08_bf05800fe9fc_add_edge_port_and_g\303\251ant_ip_workflows.py"
index b07a22c35ef3ba867227da5b31fe0b4f012eb087..c4cf724be531946f26f475a9d58e484b37e5e1fc 100644
--- "a/gso/migrations/versions/2024-10-08_bf05800fe9fc_add_edge_port_and_g\303\251ant_ip_workflows.py"
+++ "b/gso/migrations/versions/2024-10-08_bf05800fe9fc_add_edge_port_and_g\303\251ant_ip_workflows.py"
@@ -66,6 +66,12 @@ new_workflows = [
         "description": "Modify G\u00c9ANT IP",
         "product_type": "GeantIP"
     },
+    {
+        "name": "migrate_geant_ip",
+        "target": "MODIFY",
+        "description": "Migrate G\u00c9ANT IP",
+        "product_type": "GeantIP"
+    },
     {
         "name": "create_imported_geant_ip",
         "target": "CREATE",
diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json
index 2ace1604b581d09e4d57cde9ca58eb27bbe4718b..d136f8819af0f934b75725113fb5fcc94098c5c8 100644
--- a/gso/translations/en-GB.json
+++ b/gso/translations/en-GB.json
@@ -48,6 +48,7 @@
         "create_geant_ip": "Create GÉANT IP",
         "deploy_twamp": "Deploy TWAMP",
         "migrate_iptrunk": "Migrate IP Trunk",
+        "migrate_geant_ip": "Migrate GÉANT IP",
         "modify_isis_metric": "Modify the ISIS metric",
         "modify_site": "Modify Site",
         "modify_trunk_interface": "Modify IP Trunk interface",
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index e778e142fb2502584f63e95989bc06e6a4a1e514..377edf17a4c929ad9f146ee76b491f9aa763f10a 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -92,3 +92,4 @@ LazyWorkflowInstance("gso.workflows.geant_ip.create_geant_ip", "create_geant_ip"
 LazyWorkflowInstance("gso.workflows.geant_ip.modify_geant_ip", "modify_geant_ip")
 LazyWorkflowInstance("gso.workflows.geant_ip.create_imported_geant_ip", "create_imported_geant_ip")
 LazyWorkflowInstance("gso.workflows.geant_ip.import_geant_ip", "import_geant_ip")
+LazyWorkflowInstance("gso.workflows.geant_ip.migrate_geant_ip", "migrate_geant_ip")
diff --git a/gso/workflows/geant_ip/migrate_geant_ip.py b/gso/workflows/geant_ip/migrate_geant_ip.py
new file mode 100644
index 0000000000000000000000000000000000000000..b207635fd46a040a13d823afd40fd65594e004c1
--- /dev/null
+++ b/gso/workflows/geant_ip/migrate_geant_ip.py
@@ -0,0 +1,99 @@
+"""A modification workflow that migrates a GÉANT IP subscription to a different set of Edge Ports."""
+
+from typing import Annotated
+
+from annotated_types import Len
+from orchestrator import workflow
+from orchestrator.targets import Target
+from orchestrator.workflow import StepList, begin, done, step
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic import AfterValidator, BaseModel, ConfigDict, Field
+from pydantic_forms.core import FormPage
+from pydantic_forms.types import FormGenerator, State, UUIDstr
+from pydantic_forms.validators import Choice, Divider
+
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.geant_ip import GeantIP
+from gso.services.subscriptions import get_active_edge_port_subscriptions
+from gso.utils.types.tt_number import TTNumber
+
+
+def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    """Gather input from the operator on what new Edge Ports this GÉANT IP should migrate to."""
+    subscription = GeantIP.from_subscription(subscription_id)
+    partner_id = subscription.customer_id
+    edge_port_count = len(subscription.geant_ip.geant_ip_ap_list)
+
+    def _new_edge_port_selector(pid: UUIDstr) -> Choice:
+        existing_ep_name_list = [
+            ap.geant_ip_sbp.edge_port.owner_subscription_id for ap in subscription.geant_ip.geant_ip_ap_list
+        ]
+        edge_port_subscriptions = list(
+            filter(
+                lambda ep: bool(ep["customer_id"] == pid) and ep["subscription_id"] not in existing_ep_name_list,
+                get_active_edge_port_subscriptions(includes=["subscription_id", "description", "customer_id"]),
+            )
+        )
+
+        edge_ports = {str(port["subscription_id"]): port["description"] for port in edge_port_subscriptions}
+
+        return Choice(
+            "Select an Edge Port",
+            zip(edge_ports.keys(), edge_ports.items(), strict=True),  # type: ignore[arg-type]
+        )
+
+    class NewEdgePortSelection(BaseModel):
+        old_edge_port: str
+        new_edge_port: _new_edge_port_selector(partner_id) | str  # type: ignore[valid-type]
+
+    def _validate_new_edge_ports_are_unique(edge_ports: list[NewEdgePortSelection]) -> list[NewEdgePortSelection]:
+        new_edge_ports = [str(port.new_edge_port) for port in edge_ports]
+        if len(new_edge_ports) != len(set(new_edge_ports)):
+            msg = "New Edge Ports must be unique"
+            raise ValueError(msg)
+        return edge_ports
+
+    class GeantIPEdgePortSelectionForm(FormPage):
+        model_config = ConfigDict(title="Migrating GÉANT IP to a new set of Edge Ports")
+
+        tt_number: TTNumber
+        divider: Divider = Field(None, exclude=True)
+        edge_port_selection: Annotated[
+            list[NewEdgePortSelection],
+            AfterValidator(_validate_new_edge_ports_are_unique),
+            Len(min_length=edge_port_count, max_length=edge_port_count),
+        ] = [  # noqa: RUF012
+            NewEdgePortSelection(
+                old_edge_port=f"{
+                    EdgePort.from_subscription(ap.geant_ip_sbp.edge_port.owner_subscription_id).description
+                } ({ap.nren_ap_type})",
+                new_edge_port="",
+            )
+            for ap in subscription.geant_ip.geant_ip_ap_list
+        ]
+
+    ep_user_input = yield GeantIPEdgePortSelectionForm
+
+    return {"subscription_id": subscription_id, "subscription": subscription} | ep_user_input.model_dump()
+
+
+@step("Update subscription model")
+def update_subscription_model(subscription: GeantIP, edge_port_selection: list[dict]) -> State:
+    """Update the subscription model with the new list of Access Ports."""
+    for index, selected_port in enumerate(edge_port_selection):
+        subscription.geant_ip.geant_ip_ap_list[index].geant_ip_sbp.edge_port = EdgePort.from_subscription(
+            selected_port["new_edge_port"]
+        ).edge_port
+
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Migrate GÉANT IP",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.MODIFY,
+)
+def migrate_geant_ip() -> StepList:
+    """Migrate a GÉANT IP to a new set of Edge Ports."""
+    return begin >> store_process_subscription(Target.MODIFY) >> unsync >> update_subscription_model >> resync >> done