diff --git a/README-automountmap.md b/README-automountmap.md
new file mode 100644
index 0000000000000000000000000000000000000000..208dd53deb5e6b7ddba8a42d85204577889c7ce9
--- /dev/null
+++ b/README-automountmap.md
@@ -0,0 +1,96 @@
+Automountmap module
+=====================
+
+Description
+-----------
+
+The automountmap module allows the addition and removal of maps within automount locations.
+
+It is desgined to follow the IPA api as closely as possible while ensuring ease of use.
+
+
+Features
+--------
+* Automount map management
+
+Supported FreeIPA Versions
+--------------------------
+
+FreeIPA versions 4.4.0 and up are supported by the ipaautomountmap module.
+
+Requirements
+------------
+**Controller**
+* Ansible version: 2.8+
+
+**Node**
+* Supported FreeIPA version (see above)
+
+
+Usage
+=====
+
+Example inventory file
+
+```ini
+[ipaserver]
+ipaserver.test.local
+```
+
+Example playbook to ensure presence of an automount map:
+
+```yaml
+---
+- name: Playbook to add an automount map
+  hosts: ipaserver
+  become: no
+
+  tasks:
+  - name: ensure map named auto.DMZ in location DMZ is created
+    ipaautomountmap:
+      ipaadmin_password: SomeADMINpassword
+      name: auto.DMZ
+      location: DMZ
+      desc: "this is a map for servers in the DMZ"
+```
+
+Example playbook to ensure auto.DMZi is absent:
+
+```yaml
+---
+- name: Playbook to remove an automount map
+  hosts: ipaserver
+  become: no
+
+  tasks:
+  - name: ensure map auto.DMZ has been removed
+    ipaautomountmap:
+      ipaadmin_password: SomeADMINpassword
+      name: auto.DMZ
+      location: DMZ
+      state: absent
+```
+
+
+Variables
+=========
+
+Variable | Description | Required
+-------- | ----------- | --------
+`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
+`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`name` \| `mapname` \| `map` \| `automountmapname` | Name of the map to manage | yes
+`location` \| `automountlocation` \| `automountlocationcn` | Location name. | yes
+`desc` \| `description` | Description of the map | yes
+`state` | The state to ensure. It can be one of `present`, or `absent`, default: `present`. | no
+
+
+Notes
+=====
+
+Creation of indirect mount points are not supported.
+
+Authors
+=======
+
+Chris Procter
diff --git a/playbooks/automount/automount-map-absent.yaml b/playbooks/automount/automount-map-absent.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..344fb20556907b4cd2015b82c391bf7ab63c819e
--- /dev/null
+++ b/playbooks/automount/automount-map-absent.yaml
@@ -0,0 +1,12 @@
+---
+- name: Automount map absent example
+  hosts: ipaserver
+  become: no
+
+  tasks:
+  - name: ensure map TestMap is absent
+    ipaautomountmap:
+      ipaadmin_password: SomeADMINpassword
+      name: TestMap
+      location: TestLocation
+      state: absent
diff --git a/playbooks/automount/automount-map-present.yaml b/playbooks/automount/automount-map-present.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..88f4bb7b2fcb5550c762d1ca41a5c9ba7cc76c54
--- /dev/null
+++ b/playbooks/automount/automount-map-present.yaml
@@ -0,0 +1,12 @@
+---
+- name: Automount map present example
+  hosts: ipaserver
+  become: no
+
+  tasks:
+  - name: ensure map TestMap is present
+    ipaautomountmap:
+      ipaadmin_password: SomeADMINpassword
+      name: TestMap
+      location: TestLocation
+      desc: "this is a test map"
diff --git a/plugins/modules/ipaautomountmap.py b/plugins/modules/ipaautomountmap.py
new file mode 100644
index 0000000000000000000000000000000000000000..72084396668be2b9ebe381c4973029846944e520
--- /dev/null
+++ b/plugins/modules/ipaautomountmap.py
@@ -0,0 +1,195 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Authors:
+#   Chris Procter <cprocter@redhat.com>
+#
+# Copyright (C) 2021 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import (absolute_import, division, print_function)
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {
+    "metadata_version": "1.0",
+    "supported_by": "community",
+    "status": ["preview"],
+}
+
+
+DOCUMENTATION = '''
+---
+module: ipaautomountmap
+author: Chris Procter
+short_description: Manage FreeIPA autommount map
+description:
+- Add, delete, and modify an IPA automount map
+options:
+  ipaadmin_principal:
+    description: The admin principal.
+    default: admin
+  ipaadmin_password:
+    description: The admin password.
+    required: false
+  automountlocation:
+    description: automount location map is anchored to
+    choices: ["location", "automountlocationcn"]
+    required: True
+  name:
+    description: automount map to be managed.
+    choices: ["mapname", "map", "automountmapname"]
+    required: True
+  desc:
+    description: description of automount map.
+    choices: ["description"]
+    required: false
+  state:
+    description: State to ensure
+    required: false
+    default: present
+    choices: ["present", "absent"]
+'''
+
+EXAMPLES = '''
+  - name: ensure map named auto.DMZ in location DMZ is present
+    ipaautomountmap:
+      ipaadmin_password: SomeADMINpassword
+      name: auto.DMZ
+      location: DMZ
+      desc: "this is a map for servers in the DMZ"
+
+  - name: remove a map named auto.DMZ in location DMZ if it exists
+    ipaautomountmap:
+      ipaadmin_password: SomeADMINpassword
+      name: auto.DMZ
+      location: DMZ
+      state: absent
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.ansible_freeipa_module import (
+    IPAAnsibleModule, compare_args_ipa
+)
+
+
+class AutomountMap(IPAAnsibleModule):
+
+    def __init__(self, *args, **kwargs):
+        # pylint: disable=super-with-arguments
+        super(AutomountMap, self).__init__(*args, **kwargs)
+        self.commands = []
+
+    def get_automountmap(self, location, name):
+        try:
+            response = self.ipa_command(
+                "automountmap_show",
+                location,
+                {"automountmapname": name, "all": True}
+            )
+        except Exception:  # pylint: disable=broad-except
+            return None
+        else:
+            return response["result"]
+
+    def check_ipa_params(self):
+        invalid = []
+        name = self.params_get("name")
+        state = self.params_get("state")
+        if state == "present":
+            if len(name) != 1:
+                self.fail_json(msg="Exactly one name must be provided \
+                                for state=present.")
+        if state == "absent":
+            if len(name) == 0 :
+                self.fail_json(msg="Argument 'map_type' can not be used with "
+                                   "state 'absent'")
+            invalid = ["desc"]
+
+        self.params_fail_used_invalid(invalid, state)
+
+    def get_args(self, mapname, desc):  # pylint: disable=no-self-use
+        _args = {}
+        if mapname:
+            _args["automountmapname"] = mapname
+        if desc:
+            _args["description"] = desc
+        return _args
+
+    def define_ipa_commands(self):
+        name = self.params_get("name")
+        state = self.params_get("state")
+        location = self.params_get("location")
+        desc = self.params_get("desc")
+
+        for mapname in name:
+            automountmap = self.get_automountmap(location, mapname)
+
+            if state == "present":
+                args = self.get_args(mapname, desc)
+                if automountmap is None:
+                    self.commands.append([location, "automountmap_add", args])
+                else:
+                    if not compare_args_ipa(self, args, automountmap):
+                        self.commands.append(
+                            [location, "automountmap_mod", args]
+                        )
+
+            if state == "absent":
+                if automountmap is not None:
+                    self.commands.append([
+                        location,
+                        "automountmap_del",
+                        {"automountmapname": [mapname]}
+                    ])
+
+
+def main():
+    ipa_module = AutomountMap(
+        argument_spec=dict(
+            state=dict(type='str',
+                       default='present',
+                       choices=['present', 'absent']
+                       ),
+            location=dict(type="str",
+                          aliases=["automountlocation", "automountlocationcn"],
+                          default=None,
+                          required=True
+                          ),
+            name=dict(type="list",
+                      aliases=["mapname", "map", "automountmapname"],
+                      default=None,
+                      required=True
+                      ),
+            desc=dict(type="str",
+                      aliases=["description"],
+                      required=False,
+                      default=None
+                      ),
+        ),
+    )
+    changed = False
+    ipaapi_context = ipa_module.params_get("ipaapi_context")
+    with ipa_module.ipa_connect(context=ipaapi_context):
+        ipa_module.check_ipa_params()
+        ipa_module.define_ipa_commands()
+        changed = ipa_module.execute_ipa_commands(ipa_module.commands)
+    ipa_module.exit_json(changed=changed)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/automount/test_automountmap.yml b/tests/automount/test_automountmap.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cbf5db406f9f8b468e7702b85be20df4f0c6c143
--- /dev/null
+++ b/tests/automount/test_automountmap.yml
@@ -0,0 +1,147 @@
+---
+- name: Test automountmap
+  hosts: ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  # setup environment
+  - name: ensure test maps are absent
+    ipaautomountmap:
+      ipaadmin_password: SomeADMINpassword
+      name:
+        - TestMap01
+        - TestMap02
+      location: TestLocation
+      state: absent
+
+  - name: ensure location TestLocation is absent
+    ipaautomountlocation:
+      ipaadmin_password: SomeADMINpassword
+      name: TestLocation
+      state: absent
+
+  - name: ensure map TestMap is absent
+    ipaautomountmap:
+      ipaadmin_password: SomeADMINpassword
+      name: TestMap
+      location: TestLocation
+      state: absent
+
+  - name: ensure location TestLocation is present
+    ipaautomountlocation:
+      ipaadmin_password: SomeADMINpassword
+      name: TestLocation
+      state: present
+
+  # TESTS
+  - block:
+    - name: ensure map TestMap is present
+      ipaautomountmap:
+        ipaadmin_password: SomeADMINpassword
+        name: TestMap
+        location: TestLocation
+        desc: "this is a test map that should be deleted by the test"
+      register: result
+      failed_when: result.failed or not result.changed
+
+    - name: ensure map TestMap is present again
+      ipaautomountmap:
+        ipaadmin_password: SomeADMINpassword
+        name: TestMap
+        location: TestLocation
+      register: result
+      failed_when: result.failed or result.changed
+
+    - name: ensure map TestMap has a different description
+      ipaautomountmap:
+        ipaadmin_password: SomeADMINpassword
+        name: TestMap
+        location: TestLocation
+        desc: "this is a changed description that should be deleted by the test"
+      register: result
+      failed_when: result.failed or not result.changed
+
+    - name: ensure map TestMap has a different description, again
+      ipaautomountmap:
+        ipaadmin_password: SomeADMINpassword
+        name: TestMap
+        location: TestLocation
+        desc: "this is a changed description that should be deleted by the test"
+      register: result
+      failed_when: result.failed or result.changed
+
+    - name: ensure map TestMap is removed
+      ipaautomountmap:
+        ipaadmin_password: SomeADMINpassword
+        name: TestMap
+        location: TestLocation
+        state: absent
+      register: result
+      failed_when: result.failed or not result.changed
+
+    - name: ensure map TestMap has been removed
+      ipaautomountmap:
+        ipaadmin_password: SomeADMINpassword
+        name: TestMap
+        location: TestLocation
+        state: absent
+      register: result
+      failed_when: result.failed or result.changed
+
+    - name: ensure map TestMap01 is present
+      ipaautomountmap:
+        ipaadmin_password: SomeADMINpassword
+        name: TestMap01
+        location: TestLocation
+        desc: "this is a changed description that should be deleted by the test"
+      register: result
+      failed_when: result.failed or not result.changed
+
+    - name: ensure map TestMap02 is present
+      ipaautomountmap:
+        ipaadmin_password: SomeADMINpassword
+        name: TestMap02
+        location: TestLocation
+        desc: "this is a changed description that should be deleted by the test"
+      register: result
+      failed_when: result.failed or not result.changed
+
+    - name: ensure TestMap01 and TestMap02 are both absent
+      ipaautomountmap:
+        ipaadmin_password: SomeADMINpassword
+        name:
+          - TestMap01
+          - TestMap02
+        location: TestLocation
+        state: absent
+      register: result
+      failed_when: result.failed or not result.changed
+
+    - name: ensure TestMap01 and TestMap02 are both absent again
+      ipaautomountmap:
+        ipaadmin_password: SomeADMINpassword
+        name:
+          - TestMap01
+          - TestMap02
+        location: TestLocation
+        state: absent
+      register: result
+      failed_when: result.failed or result.changed
+
+    # CLEAN UP
+    always:
+    - name: ensure test maps are absent
+      ipaautomountmap:
+        ipaadmin_password: SomeADMINpassword
+        name:
+          - TestMap01
+          - TestMap02
+        location: TestLocation
+        state: absent
+
+    - name: ensure location TestLocation is absent
+      ipaautomountlocation:
+        ipaadmin_password: SomeADMINpassword
+        name: TestLocation
+        state: absent