diff --git a/app/models.py b/app/models.py
index c234a4b6c8b9c0c1407f31f90f5ce6fca275860b..b757935c767fdc688addb470d5470062ba90e7bc 100644
--- a/app/models.py
+++ b/app/models.py
@@ -775,12 +775,35 @@ ansible_groups_hosts_table = db.Table(
 )
 
 
+class AnsibleGroupType(Enum):
+    STATIC = "STATIC"
+    NETWORK_SCOPE = "NETWORK_SCOPE"
+    NETWORK = "NETWORK"
+    DEVICE_TYPE = "DEVICE_TYPE"
+
+    def __str__(self):
+        return self.name
+
+    @classmethod
+    def choices(cls):
+        return [(item, item.name) for item in AnsibleGroupType]
+
+    @classmethod
+    def coerce(cls, value):
+        return value if type(value) == AnsibleGroupType else AnsibleGroupType[value]
+
+
 class AnsibleGroup(CreatedMixin, db.Model):
     __tablename__ = "ansible_group"
     # Define id here so that it can be used in the primary and secondary join
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(CIText, nullable=False, unique=True)
     vars = db.Column(postgresql.JSONB)
+    type = db.Column(
+        db.Enum(AnsibleGroupType, name="ansible_group_type"),
+        default=AnsibleGroupType.STATIC,
+        nullable=False,
+    )
 
     children = db.relationship(
         "AnsibleGroup",
@@ -793,12 +816,53 @@ class AnsibleGroup(CreatedMixin, db.Model):
     def __str__(self):
         return str(self.name)
 
+    @property
+    def is_dynamic(self):
+        return self.type != AnsibleGroupType.STATIC
+
+    @property
+    def hosts(self):
+        if self.type == AnsibleGroupType.STATIC:
+            return self._hosts
+        if self.type == AnsibleGroupType.NETWORK_SCOPE:
+            return (
+                Host.query.join(Host.interfaces)
+                .join(Interface.network)
+                .join(Network.scope)
+                .filter(NetworkScope.name == self.name)
+                .order_by(Host.name)
+                .all()
+            )
+        if self.type == AnsibleGroupType.NETWORK:
+            return (
+                Host.query.join(Host.interfaces)
+                .join(Interface.network)
+                .filter(Network.vlan_name == self.name)
+                .order_by(Host.name)
+                .all()
+            )
+        if self.type == AnsibleGroupType.DEVICE_TYPE:
+            return (
+                Host.query.join(Host.device_type)
+                .filter(DeviceType.name == self.name)
+                .order_by(Host.name)
+                .all()
+            )
+
+    @hosts.setter
+    def hosts(self, value):
+        # For dynamic group type, _hosts can only be set to []
+        if self.is_dynamic and value:
+            raise AttributeError("can't set dynamic hosts")
+        self._hosts = value
+
     def to_dict(self):
         d = super().to_dict()
         d.update(
             {
                 "name": self.name,
                 "vars": self.vars,
+                "type": self.type.name,
                 "hosts": [str(host) for host in self.hosts],
                 "children": [str(child) for child in self.children],
             }
@@ -820,7 +884,7 @@ class Host(CreatedMixin, db.Model):
         "AnsibleGroup",
         secondary=ansible_groups_hosts_table,
         lazy=True,
-        backref=db.backref("hosts"),
+        backref=db.backref("_hosts"),
     )
 
     def __init__(self, **kwargs):
diff --git a/app/network/forms.py b/app/network/forms.py
index 208b05d707fb418134c658cd7cd0cb9d51fc8b7f..d0fe5c212a59104ad6f0ca9998436304e3d71962 100644
--- a/app/network/forms.py
+++ b/app/network/forms.py
@@ -151,8 +151,11 @@ class HostForm(CSEntryForm):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.device_type_id.choices = utils.get_model_choices(models.DeviceType)
+        ansible_group_query = models.AnsibleGroup.query.filter(
+            models.AnsibleGroup.type == models.AnsibleGroupType.STATIC
+        )
         self.ansible_groups.choices = utils.get_model_choices(
-            models.AnsibleGroup, attr="name"
+            models.AnsibleGroup, attr="name", query=ansible_group_query
         )
 
 
@@ -241,6 +244,12 @@ class AnsibleGroupForm(CSEntryForm):
         "Ansible vars",
         description="Enter variables in YAML format. See https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html",
     )
+    type = SelectField(
+        "Type",
+        choices=models.AnsibleGroupType.choices(),
+        default=models.AnsibleGroupType.STATIC,
+        coerce=models.AnsibleGroupType.coerce,
+    )
     hosts = SelectMultipleField("Hosts", coerce=utils.coerce_to_str_or_none)
 
     def __init__(self, *args, **kwargs):
diff --git a/app/network/views.py b/app/network/views.py
index 7b4f0c6495ba5a9aa4748715556db3a9e69373f7..5932fa7c24b0aa6f207c8ec754ac7fe0763e6179 100644
--- a/app/network/views.py
+++ b/app/network/views.py
@@ -349,6 +349,7 @@ def edit_ansible_group(name):
     if form.validate_on_submit():
         group.name = form.name.data
         group.vars = form.vars.data or None
+        group.type = form.type.data
         group.hosts = [models.Host.query.get(id_) for id_ in form.hosts.data]
         current_app.logger.debug(f"Trying to update: {group!r}")
         try:
@@ -370,7 +371,10 @@ def create_ansible_group():
     if form.validate_on_submit():
         hosts = [models.Host.query.get(id_) for id_ in form.hosts.data]
         group = models.AnsibleGroup(
-            name=form.name.data, vars=form.vars.data or None, hosts=hosts
+            name=form.name.data,
+            vars=form.vars.data or None,
+            type=form.type.data,
+            hosts=hosts,
         )
         current_app.logger.debug(f"Trying to create: {group!r}")
         db.session.add(group)
diff --git a/app/static/js/groups.js b/app/static/js/groups.js
index 3695cf4f7f2d86219ca72b905286c17f74826982..ec1bbf3721b36f7383507f88320e0d5c0a90fa79 100644
--- a/app/static/js/groups.js
+++ b/app/static/js/groups.js
@@ -1,5 +1,26 @@
 $(document).ready(function() {
 
+  function group_type_update() {
+    if( $("#type option:selected").text() == "STATIC" ) {
+      $("#hosts").prop("disabled", false);
+      $("#hosts").selectpicker('refresh');
+    } else {
+      $("#hosts").selectpicker('deselectAll');
+      $("#hosts").prop("disabled", true);
+      $("#hosts").selectpicker('refresh');
+    }
+  }
+
+  // Disable or enable hosts on group type update
+  $("#type").on('change', function() {
+    group_type_update();
+  });
+
+  // Disable or enable hosts on page load depending on group type
+  if( $("#groupForm").length || $("#editGroupForm").length ) {
+    group_type_update();
+  }
+
   if( $("#groupForm").length || $("#editGroupForm").length ) {
     var groupVarsEditor = CodeMirror.fromTextArea(vars, {
         lineNumbers: true,
@@ -36,6 +57,7 @@ $(document).ready(function() {
           return '<pre style="white-space: pre-wrap">' + JSON.stringify(data, null, 2) + '</pre>';
         }
       },
+      { data: 'type' },
       { data: 'hosts' }
     ]
   });
diff --git a/app/templates/network/create_group.html b/app/templates/network/create_group.html
index 41e6f644fd7328f14eff97beb1f259e4b682b621..89161907e0bc0ea6aa67ea17c9eb9a38bb0fbf3c 100644
--- a/app/templates/network/create_group.html
+++ b/app/templates/network/create_group.html
@@ -8,6 +8,7 @@
     {{ form.hidden_tag() }}
     {{ render_field(form.name, class_="text-lowercase") }}
     {{ render_field(form.vars) }}
+    {{ render_field(form.type) }}
     {{ render_field(form.hosts, class_="selectpicker") }}
     <div class="form-group row">
       <div class="col-sm-10">
diff --git a/app/templates/network/edit_group.html b/app/templates/network/edit_group.html
index 155d1c0c23feecb3ce3f24b02b6ea7f6ca2a3502..f19aaca4d2cca315c035d2e3b47a40e40f7e86ac 100644
--- a/app/templates/network/edit_group.html
+++ b/app/templates/network/edit_group.html
@@ -17,6 +17,7 @@
     {{ form.hidden_tag() }}
     {{ render_field(form.name, class_="text-lowercase") }}
     {{ render_field(form.vars) }}
+    {{ render_field(form.type) }}
     {{ render_field(form.hosts, class_="selectpicker") }}
     <div class="form-group row">
       <div class="col-sm-10">
diff --git a/app/templates/network/groups.html b/app/templates/network/groups.html
index 4792deda0c73015a9049e7c78ec483512f85d3cb..af1f517ecfd4c87c02917447bad2418005a3fb78 100644
--- a/app/templates/network/groups.html
+++ b/app/templates/network/groups.html
@@ -23,6 +23,7 @@
       <tr>
         <th>Name</th>
         <th>Vars</th>
+        <th>Type</th>
         <th>Hosts</th>
       </tr>
     </thead>
diff --git a/app/templates/network/view_group.html b/app/templates/network/view_group.html
index 71c4cf740b6b62e9bbf215f05b930e24961f105b..457879ef5f891af1d52c764b23822a27750fde7b 100644
--- a/app/templates/network/view_group.html
+++ b/app/templates/network/view_group.html
@@ -20,6 +20,8 @@
         <dd class="col-sm-9">{{ group.name }}</dd>
         <dt class="col-sm-3">Variables</dt>
         <dd class="col-sm-9"><pre>{{ group.vars | toyaml }}</pre></dd>
+        <dt class="col-sm-3">Type</dt>
+        <dd class="col-sm-9">{{ group.type.name }}</dd>
         <dt class="col-sm-3">hosts</dt>
         <dd class="col-sm-9">{{ link_to_hosts(group.hosts) }}</dd>
         <dt class="col-sm-3">Created by</dt>
diff --git a/migrations/versions/5698c505d70e_add_ansible_group_type.py b/migrations/versions/5698c505d70e_add_ansible_group_type.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a218deb041ed2bfa16322cfae02db64fad7f519
--- /dev/null
+++ b/migrations/versions/5698c505d70e_add_ansible_group_type.py
@@ -0,0 +1,49 @@
+"""Add Ansible group type
+
+Revision ID: 5698c505d70e
+Revises: 78283a288a05
+Create Date: 2018-07-31 19:57:14.175703
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import postgresql
+
+
+# revision identifiers, used by Alembic.
+revision = "5698c505d70e"
+down_revision = "78283a288a05"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    ansible_group_type = postgresql.ENUM(
+        "STATIC", "NETWORK_SCOPE", "NETWORK", "DEVICE_TYPE", name="ansible_group_type"
+    )
+    ansible_group_type.create(op.get_bind())
+    # WARNING! If the database is not empty, we can't set type to nullable=False before adding it to existing rows.
+    op.add_column(
+        "ansible_group",
+        sa.Column(
+            "type",
+            sa.Enum(
+                "STATIC",
+                "NETWORK_SCOPE",
+                "NETWORK",
+                "DEVICE_TYPE",
+                name="ansible_group_type",
+            ),
+            nullable=True,
+        ),
+    )
+    # Set type to STATIC for existing rows
+    ansible_group = sa.sql.table("ansible_group", sa.sql.column("type"))
+    op.execute(ansible_group.update().values(type="STATIC"))
+    # Add the nullable=False constraint
+    op.alter_column("ansible_group", "type", nullable=False)
+
+
+def downgrade():
+    op.drop_column("ansible_group", "type")
+    op.execute("DROP TYPE ansible_group_type")
diff --git a/tests/functional/test_api.py b/tests/functional/test_api.py
index 677fc357bc4ab587356d40d4bd55f7e9a7139518..f47b8f357f9416e21c08a52015b49d18afdac057 100644
--- a/tests/functional/test_api.py
+++ b/tests/functional/test_api.py
@@ -1163,6 +1163,7 @@ def test_create_ansible_group(client, admin_token):
         "id",
         "name",
         "vars",
+        "type",
         "hosts",
         "children",
         "created_at",
diff --git a/tests/functional/test_models.py b/tests/functional/test_models.py
index 2a40e1fbd3647f51d5ab0464bb245acfc73b099d..96aa143c2759dac25255fe949dbcddbedda1f804 100644
--- a/tests/functional/test_models.py
+++ b/tests/functional/test_models.py
@@ -12,6 +12,7 @@ This module defines models tests.
 import ipaddress
 import pytest
 from wtforms import ValidationError
+from app import models
 
 
 def test_user_groups(user_factory):
@@ -179,6 +180,16 @@ def test_ansible_groups(ansible_group_factory, host_factory):
     assert group2.hosts == [host1]
 
 
+def test_ansible_group_is_dynamic(ansible_group_factory):
+    group1 = ansible_group_factory()
+    assert group1.type == models.AnsibleGroupType.STATIC
+    assert not group1.is_dynamic
+    group2 = ansible_group_factory(type=models.AnsibleGroupType.NETWORK_SCOPE)
+    assert group2.is_dynamic
+    group3 = ansible_group_factory(type=models.AnsibleGroupType.NETWORK)
+    assert group3.is_dynamic
+
+
 def test_ansible_groups_children(ansible_group_factory, host_factory):
     group1 = ansible_group_factory()
     group2 = ansible_group_factory()
@@ -197,3 +208,82 @@ def test_host_model(model_factory, item_factory, host_factory):
     model1 = model_factory(name="EX3400")
     item_factory(model=model1, host_id=host1.id)
     assert host1.model == "EX3400"
+
+
+def test_ansible_dynamic_device_type_group(
+    ansible_group_factory, device_type_factory, host_factory
+):
+    device_type1 = device_type_factory(name="type1")
+    device_type2 = device_type_factory(name="type2")
+    host1_t1 = host_factory(name="host1", device_type=device_type1)
+    host2_t1 = host_factory(name="host2", device_type=device_type1)
+    host1_t2 = host_factory(device_type=device_type2)
+    group_t1 = ansible_group_factory(
+        name="type1", type=models.AnsibleGroupType.DEVICE_TYPE
+    )
+    group_t2 = ansible_group_factory(
+        name="type2", type=models.AnsibleGroupType.DEVICE_TYPE
+    )
+    group_t3 = ansible_group_factory(
+        name="unknown", type=models.AnsibleGroupType.DEVICE_TYPE
+    )
+    assert group_t1.hosts == [host1_t1, host2_t1]
+    assert group_t2.hosts == [host1_t2]
+    assert group_t3.hosts == []
+
+
+def test_ansible_dynamic_network_group(
+    ansible_group_factory, network_factory, interface_factory, host_factory
+):
+    network1 = network_factory(vlan_name="network1")
+    network2 = network_factory(vlan_name="network2")
+    interface1_n1 = interface_factory(network=network1)
+    interface2_n1 = interface_factory(network=network1)
+    interface1_n2 = interface_factory(network=network2)
+    host1_n1 = host_factory(name="host1", interfaces=[interface1_n1])
+    host2_n1 = host_factory(name="host2", interfaces=[interface2_n1])
+    host1_n2 = host_factory(interfaces=[interface1_n2])
+    group_n1 = ansible_group_factory(
+        name="network1", type=models.AnsibleGroupType.NETWORK
+    )
+    group_n2 = ansible_group_factory(
+        name="network2", type=models.AnsibleGroupType.NETWORK
+    )
+    group_n3 = ansible_group_factory(
+        name="unknown", type=models.AnsibleGroupType.NETWORK
+    )
+    assert group_n1.hosts == [host1_n1, host2_n1]
+    assert group_n2.hosts == [host1_n2]
+    assert group_n3.hosts == []
+
+
+def test_ansible_dynamic_network_scope_group(
+    ansible_group_factory,
+    network_scope_factory,
+    network_factory,
+    interface_factory,
+    host_factory,
+):
+    scope1 = network_scope_factory(name="scope1")
+    scope2 = network_scope_factory(name="scope2")
+    network1_s1 = network_factory(scope=scope1)
+    network2_s1 = network_factory(scope=scope1)
+    network1_s2 = network_factory(scope=scope2)
+    interface1_s1 = interface_factory(network=network1_s1)
+    interface2_s1 = interface_factory(network=network2_s1)
+    interface1_s2 = interface_factory(network=network1_s2)
+    host1_s1 = host_factory(name="host1", interfaces=[interface1_s1])
+    host2_s1 = host_factory(name="host2", interfaces=[interface2_s1])
+    host1_s2 = host_factory(interfaces=[interface1_s2])
+    group_s1 = ansible_group_factory(
+        name="scope1", type=models.AnsibleGroupType.NETWORK_SCOPE
+    )
+    group_s2 = ansible_group_factory(
+        name="scope2", type=models.AnsibleGroupType.NETWORK_SCOPE
+    )
+    group_s3 = ansible_group_factory(
+        name="unknown", type=models.AnsibleGroupType.NETWORK_SCOPE
+    )
+    assert group_s1.hosts == [host1_s1, host2_s1]
+    assert group_s2.hosts == [host1_s2]
+    assert group_s3.hosts == []