diff --git a/app/models.py b/app/models.py
index 54b0db77495ec61887d85b1f2d496be991cc14ab..d574e9b5e70ddaaded255759959b4673e0ae87ed 100644
--- a/app/models.py
+++ b/app/models.py
@@ -943,6 +943,21 @@ class Network(CreatedMixin, db.Model):
             raise ValidationError(r"Vlan name shall match [A-Za-z0-9\-]{3,25}")
         return string
 
+    @validates("vlan_id")
+    def validate_vlan_id(self, key, value):
+        """Ensure the vlan_id is in the scope range"""
+        if value is None or self.scope is None:
+            # If scope is None, we can't do any validation
+            # This will occur when vlan_id is passed before scope
+            # We could ensure it's not the case but main use case
+            # is when editing network. This won't happen then.
+            return value
+        if int(value) not in self.scope.vlan_range():
+            raise ValidationError(
+                f"Vlan id shall be in the range [{self.scope.first_vlan} - {self.scope.last_vlan}]"
+            )
+        return value
+
     def to_dict(self, recursive=False):
         d = super().to_dict()
         d.update(
diff --git a/app/network/forms.py b/app/network/forms.py
index 71180a6f40dcbbc302a7ce873d4c5d002178ff99..a5df1888d33aeb41d1754594e407d4dd8e176298 100644
--- a/app/network/forms.py
+++ b/app/network/forms.py
@@ -126,6 +126,31 @@ class NetworkForm(CSEntryForm):
         self.domain_id.choices = utils.get_model_choices(models.Domain, attr="name")
 
 
+class EditNetworkForm(CSEntryForm):
+    vlan_name = StringField(
+        "Vlan name",
+        description="vlan name must be 3-25 characters long and contain only letters, numbers and dash",
+        validators=[
+            validators.InputRequired(),
+            validators.Regexp(VLAN_NAME_RE),
+            Unique(models.Network, column="vlan_name"),
+        ],
+    )
+    vlan_id = IntegerField("Vlan id")
+    description = TextAreaField("Description")
+    address = StringField("Address")
+    first_ip = StringField("First IP")
+    last_ip = StringField("Last IP")
+    gateway = StringField("Gateway IP")
+    domain_id = SelectField("Domain")
+    admin_only = BooleanField("Admin only")
+    sensitive = BooleanField("Sensitive")
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.domain_id.choices = utils.get_model_choices(models.Domain, attr="name")
+
+
 class HostForm(CSEntryForm):
     name = StringField(
         "Hostname",
diff --git a/app/network/views.py b/app/network/views.py
index 1e69490eaec0e3ce1bf4fc11320c788b2f16f51c..ae678730969be148a1d5a76444b7db7d75460939 100644
--- a/app/network/views.py
+++ b/app/network/views.py
@@ -30,6 +30,7 @@ from .forms import (
     InterfaceForm,
     HostInterfaceForm,
     NetworkForm,
+    EditNetworkForm,
     NetworkScopeForm,
     DomainForm,
     CreateVMForm,
@@ -700,6 +701,44 @@ def create_network():
     return render_template("network/create_network.html", form=form)
 
 
+@bp.route("/networks/edit/<vlan_name>", methods=("GET", "POST"))
+@login_groups_accepted("admin")
+def edit_network(vlan_name):
+    network = models.Network.query.filter_by(vlan_name=vlan_name).first_or_404()
+    form = EditNetworkForm(request.form, obj=network)
+    if form.validate_on_submit():
+        try:
+            for field in (
+                "vlan_name",
+                "vlan_id",
+                "description",
+                "address",
+                "first_ip",
+                "last_ip",
+                "gateway",
+                "domain_id",
+                "admin_only",
+                "sensitive",
+            ):
+                setattr(network, field, getattr(form, field).data)
+        except ValidationError as e:
+            # Check for error raised by model validation (not implemented in form validation)
+            current_app.logger.warning(f"{e}")
+            flash(f"{e}", "error")
+            return render_template("network/edit_network.html", form=form)
+        current_app.logger.debug(f"Trying to update: {network!r}")
+        try:
+            db.session.commit()
+        except sa.exc.IntegrityError as e:
+            db.session.rollback()
+            current_app.logger.warning(f"{e}")
+            flash(f"{e}", "error")
+        else:
+            flash(f"Network {network} updated!", "success")
+        return redirect(url_for("network.view_network", vlan_name=network.vlan_name))
+    return render_template("network/edit_network.html", form=form)
+
+
 @bp.route("/_retrieve_scope_defaults/<int:scope_id>")
 @login_required
 def retrieve_scope_defaults(scope_id):
diff --git a/app/templates/network/edit_network.html b/app/templates/network/edit_network.html
new file mode 100644
index 0000000000000000000000000000000000000000..d06182f2c445ff65bc0d9633ca4841958f9e7641
--- /dev/null
+++ b/app/templates/network/edit_network.html
@@ -0,0 +1,34 @@
+{% extends "network/networks.html" %}
+{% from "_helpers.html" import render_field %}
+
+{% block title %}Edit Network - CSEntry{% endblock %}
+
+{% block networks_nav %}
+    <li class="nav-item">
+      <a class="nav-link" href="{{ url_for('network.view_network', vlan_name=form.vlan_name.data) }}">View network</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link active" href="{{ url_for('network.edit_network', vlan_name=form.vlan_name.data) }}">Edit network</a>
+    </li>
+{% endblock %}
+
+{% block networks_main %}
+  <form id="editNetworkForm" method="POST">
+    {{ form.hidden_tag() }}
+    {{ render_field(form.vlan_name) }}
+    {{ render_field(form.vlan_id) }}
+    {{ render_field(form.description) }}
+    {{ render_field(form.address) }}
+    {{ render_field(form.first_ip) }}
+    {{ render_field(form.last_ip) }}
+    {{ render_field(form.gateway) }}
+    {{ render_field(form.domain_id) }}
+    {{ render_field(form.admin_only) }}
+    {{ render_field(form.sensitive) }}
+    <div class="form-group row">
+      <div class="col-sm-10">
+        <button type="submit" class="btn btn-primary">Submit</button>
+      </div>
+    </div>
+  </form>
+{%- endblock %}
diff --git a/app/templates/network/view_network.html b/app/templates/network/view_network.html
index 31276ac049fdb4b48aef233e03f65ecabb6fd301..b5676f0096b34a0c98d59a8b1f9db25776015532 100644
--- a/app/templates/network/view_network.html
+++ b/app/templates/network/view_network.html
@@ -7,6 +7,9 @@
 <li class="nav-item">
   <a class="nav-link active" href="{{ url_for('network.view_network', vlan_name=network.vlan_name) }}">View network</a>
 </li>
+<li class="nav-item">
+  <a class="nav-link" href="{{ url_for('network.edit_network', vlan_name=network.vlan_name) }}">Edit network</a>
+</li>
 {% endblock %}
 
 {% block networks_main %}
diff --git a/tests/functional/test_web.py b/tests/functional/test_web.py
index 92410ea71ae5aeb3f509423021a9d6648cfb9241..460db6d351bf70f6994683a2e0cccb14cf82bc2c 100644
--- a/tests/functional/test_web.py
+++ b/tests/functional/test_web.py
@@ -750,6 +750,55 @@ def test_create_network_no_vlan(
     assert network.vlan_id is None
 
 
+def test_edit_network(
+    logged_admin_client, domain_factory, network_scope_factory, network_factory
+):
+    domain = domain_factory(name="lab.example.org")
+    scope = network_scope_factory(
+        name="MyNetworks",
+        first_vlan=100,
+        last_vlan=200,
+        supernet="192.168.0.0/16",
+        domain_id=domain.id,
+    )
+    vlan_name = "my-network"
+    network = network_factory(
+        vlan_name=vlan_name,
+        domain=domain,
+        scope=scope,
+        vlan_id=100,
+        address="192.168.0.0/24",
+        first_ip="192.168.0.11",
+        last_ip="192.168.0.249",
+        gateway="192.168.0.254",
+        admin_only=False,
+        sensitive=False,
+    )
+    new_first_ip = "192.168.0.10"
+    form = {
+        "vlan_name": vlan_name,
+        "vlan_id": network.vlan_id,
+        "address": network.address,
+        "first_ip": new_first_ip,
+        "last_ip": network.last_ip,
+        "gateway": network.gateway,
+        "domain_id": network.domain_id,
+        "admin_only": True,
+        "sensitive": True,
+    }
+    response = logged_admin_client.post(
+        f"/network/networks/edit/{vlan_name}", data=form
+    )
+    assert response.status_code == 302
+    # The network was updated
+    network = models.Network.query.filter_by(vlan_name=vlan_name).first()
+    assert network is not None
+    assert network.vlan_name == vlan_name
+    assert network.first_ip == new_first_ip
+    assert network.admin_only is True
+    assert network.sensitive is True
+
+
 def test_create_item_invalid_ics_id(logged_rw_client):
     ics_id = "AAA1100"
     form = {"ics_id": ics_id, "serial_number": "12345"}