diff --git a/docs/device-agent/deploy.md b/docs/device-agent/deploy.md
index d5757d1673..e63bee5e73 100644
--- a/docs/device-agent/deploy.md
+++ b/docs/device-agent/deploy.md
@@ -91,6 +91,13 @@ in the Developer Mode options panel.
 You will be prompted to give the snapshot a name and description. See [Snapshots](../user/snapshots.md) for more information
 about working with snapshots.
 
+**Auto Device Snapshots**
+
+For devices that are assigned to an application, the platform will automatically create a snapshot of the device
+when it detects flows modified. This snapshot will be created with the name "Auto Snapshot - yyyy-mm-dd hh:mm-ss".
+Only the last 10 auto snapshots are kept, others are deleted on a first in first out basis.
+
+
 ### Important Notes
 
 * Remote access to the editor requires Device Agent v0.8.0 or later.
@@ -109,3 +116,6 @@ about working with snapshots.
 * The device will not receive any updates from the platform while in Developer Mode.
 * The device must be online and connected to the platform to enable "Editor Access".
 * To minimise server and device resources, it is recommended to disable "Editor Access" when not actively developing flows on a device.
+* Auto snapshots were introduced in FlowFuse V2.1.
+* Auto snapshots are only supported for devices assigned to an application.
+* If an auto snapshot is set as the target snapshot for a device or assigned to a pipeline stage, it will not be auto cleaned up meaning it is possible to have more than 10 auto snapshots.
diff --git a/docs/user/snapshots.md b/docs/user/snapshots.md
index 6d81f39aa0..ce5ed1b894 100644
--- a/docs/user/snapshots.md
+++ b/docs/user/snapshots.md
@@ -60,7 +60,7 @@ To set the **Device Target** of an application owned device:
 1. Go to the devices's page and select the **Snapshots** tab.
 2. In the list of snapshots available, a "Deploy Snapshot" button will be displayed
    for each snapshot as you hover over it.
-3. You will be asked to confirm - click **Set Target** to continue.
+3. You will be asked to confirm - click the **Confirm** button to set it as the target snapshot.
 
 This will cause the snapshot to be pushed out to the device the
 next time it checks in.
diff --git a/forge/auditLog/device.js b/forge/auditLog/device.js
index 455633a403..874e66f1d6 100644
--- a/forge/auditLog/device.js
+++ b/forge/auditLog/device.js
@@ -45,6 +45,11 @@ module.exports = {
                 async disabled (actionedBy, error, device) {
                     await log('device.remote-access.disabled', actionedBy, device?.id, generateBody({ error, device }))
                 }
+            },
+            settings: {
+                async updated (actionedBy, error, device, updates) {
+                    await log('device.settings.updated', actionedBy, device?.id, generateBody({ error, device, updates }))
+                }
             }
         }
 
diff --git a/forge/db/controllers/ProjectSnapshot.js b/forge/db/controllers/ProjectSnapshot.js
index 974b8daa14..4caa707310 100644
--- a/forge/db/controllers/ProjectSnapshot.js
+++ b/forge/db/controllers/ProjectSnapshot.js
@@ -1,3 +1,181 @@
+const { Op } = require('sequelize')
+const DEVICE_AUTO_SNAPSHOT_LIMIT = 10
+const DEVICE_AUTO_SNAPSHOT_PREFIX = 'Auto Snapshot' // Any changes to the format should be reflected in frontend/src/pages/device/Snapshots/index.vue
+
+const deviceAutoSnapshotUtils = {
+    deployTypeEnum: {
+        full: 'Full',
+        flows: 'Modified Flows',
+        nodes: 'Modified Nodes'
+    },
+    nameRegex: new RegExp(`^${DEVICE_AUTO_SNAPSHOT_PREFIX} - \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$`), // e.g "Auto Snapshot - 2023-02-01 12:34:56"
+    prefix: DEVICE_AUTO_SNAPSHOT_PREFIX,
+    autoBackupLimit: DEVICE_AUTO_SNAPSHOT_LIMIT,
+    generateName: () => `${DEVICE_AUTO_SNAPSHOT_PREFIX} - ${new Date().toLocaleString('sv-SE')}`, // "base - YYYY-MM-DD HH:MM:SS"
+    generateDescription: (deploymentType = '') => {
+        const deployInfo = deviceAutoSnapshotUtils.deployTypeEnum[deploymentType]
+            ? `${deviceAutoSnapshotUtils.deployTypeEnum[deploymentType]} deployment`
+            : 'deployment'
+        return `Device ${deviceAutoSnapshotUtils.prefix} taken following a ${deployInfo}`
+    },
+    isAutoSnapshot: function (snapshot) {
+        return deviceAutoSnapshotUtils.nameRegex.test(snapshot.name)
+    },
+    /**
+     * Get all auto snapshots for a device
+     *
+     * NOTE: If a `limit` of 10 is provided and some of the snapshots are in use, the actual number of snapshots returned may be less than 10
+     * @param {Object} app - the forge application object
+     * @param {Object} device - a device (model) instance
+     * @param {boolean} [excludeInUse=true] - whether to exclude snapshots that are currently in use by a device, device group or pipeline stage device group
+     * @param {number} [limit=0] - the maximum number of snapshots to query in the database (0 means no limit)
+     */
+    getAutoSnapshots: async function (app, device, excludeInUse = true, limit = 0) {
+        // TODO: the snapshots table should really have a an indexed `type` column to distinguish between auto and manual snapshots
+        // for now, as per MVP, we'll use the name pattern to identify auto snapshots
+
+        // Get snapshots
+        const possibleAutoSnapshots = await app.db.models.ProjectSnapshot.findAll({
+            where: {
+                DeviceId: device.id,
+                // name: { [Op.regexp]: deviceAutoSnapshotUtils.nameRegex } // regex is not supported by sqlite!
+                name: { [Op.like]: `${DEVICE_AUTO_SNAPSHOT_PREFIX} - %-%-% %:%:%` }
+            },
+            order: [['id', 'ASC']]
+        })
+
+        // Filter out any snapshots that don't match the regex
+        const autoSnapshots = possibleAutoSnapshots.filter(deviceAutoSnapshotUtils.isAutoSnapshot)
+
+        // if caller _wants_ all, including those "in use", we can just return here
+        if (!excludeInUse) {
+            return autoSnapshots
+        }
+        // utility function to remove items from an array
+        const removeFromArray = (baseList, removeList) => baseList.filter((item) => !removeList.includes(item))
+
+        // candidates for are those that are not in use
+        let candidateIds = autoSnapshots.map((snapshot) => snapshot.id)
+
+        // since we're excluding "in use" snapshots, we need to check the following tables:
+        // * device
+        // * device groups
+        // * pipeline stage device group
+        // If any of these snapshots are set as active/target, remove them from the candidates list
+
+        // Check `Devices` table
+        const query = {
+            where: {
+                [Op.or]: [
+                    { targetSnapshotId: { [Op.in]: candidateIds } },
+                    { activeSnapshotId: { [Op.in]: candidateIds } }
+                ]
+            }
+        }
+        if (typeof limit === 'number' && limit > 0) {
+            query.limit = limit
+        }
+        const snapshotsInUseInDevices = await app.db.models.Device.findAll(query)
+        const inUseAsTarget = snapshotsInUseInDevices.map((device) => device.targetSnapshotId)
+        const inUseAsActive = snapshotsInUseInDevices.map((device) => device.activeSnapshotId)
+        candidateIds = removeFromArray(candidateIds, inUseAsTarget)
+        candidateIds = removeFromArray(candidateIds, inUseAsActive)
+
+        // Check `DeviceGroups` table
+        if (app.db.models.DeviceGroup) {
+            const snapshotsInUseInDeviceGroups = await app.db.models.DeviceGroup.findAll({
+                where: {
+                    targetSnapshotId: { [Op.in]: candidateIds }
+                }
+            })
+            const inGroupAsTarget = snapshotsInUseInDeviceGroups.map((group) => group.targetSnapshotId)
+            candidateIds = removeFromArray(candidateIds, inGroupAsTarget)
+        }
+
+        // Check `PipelineStageDeviceGroups` table
+        const isLicensed = app.license.active()
+        if (isLicensed && app.db.models.PipelineStageDeviceGroup) {
+            const snapshotsInUseInPipelineStage = await app.db.models.PipelineStageDeviceGroup.findAll({
+                where: {
+                    targetSnapshotId: { [Op.in]: candidateIds }
+                }
+            })
+            const inPipelineStageAsTarget = snapshotsInUseInPipelineStage.map((stage) => stage.targetSnapshotId)
+            candidateIds = removeFromArray(candidateIds, inPipelineStageAsTarget)
+        }
+
+        return autoSnapshots.filter((snapshot) => candidateIds.includes(snapshot.id))
+    },
+    cleanupAutoSnapshots: async function (app, device, limit = DEVICE_AUTO_SNAPSHOT_LIMIT) {
+        // get all auto snapshots for the device (where not in use)
+        const snapshots = await app.db.controllers.ProjectSnapshot.getDeviceAutoSnapshots(device, true, 0)
+        if (snapshots.length > limit) {
+            const toDelete = snapshots.slice(0, snapshots.length - limit).map((snapshot) => snapshot.id)
+            await app.db.models.ProjectSnapshot.destroy({ where: { id: { [Op.in]: toDelete } } })
+        }
+    },
+    doAutoSnapshot: async function (app, device, deploymentType, { clean = true, setAsTarget = false } = {}, meta) {
+        // eslint-disable-next-line no-useless-catch
+        try {
+            // if not permitted, throw an error
+            if (!device) {
+                throw new Error('Device is required')
+            }
+            if (!app.config.features.enabled('deviceAutoSnapshot')) {
+                throw new Error('Device auto snapshot feature is not available')
+            }
+            if (!(await device.getSetting('autoSnapshot'))) {
+                throw new Error('Device auto snapshot is not enabled')
+            }
+            const teamType = await device.Team.getTeamType()
+            const deviceAutoSnapshotEnabledForTeam = teamType.getFeatureProperty('deviceAutoSnapshot', false)
+            if (!deviceAutoSnapshotEnabledForTeam) {
+                throw new Error('Device auto snapshot is not enabled for the team')
+            }
+
+            const saneSnapshotOptions = {
+                name: deviceAutoSnapshotUtils.generateName(),
+                description: deviceAutoSnapshotUtils.generateDescription(deploymentType),
+                setAsTarget
+            }
+
+            // things to do & consider:
+            // 1. create a snapshot from the device
+            // 2. log the snapshot creation in audit log
+            // 3. delete older auto snapshots if the limit is reached (10)
+            //    do NOT delete any snapshots that are currently in use by an target (instance/device/device group)
+            const user = meta?.user || { id: null } // if no user is available, use `null` (system user)
+
+            // 1. create a snapshot from the device
+            const snapShot = await app.db.controllers.ProjectSnapshot.createDeviceSnapshot(
+                device.Application,
+                device,
+                user,
+                saneSnapshotOptions
+            )
+            snapShot.User = user
+
+            // 2. log the snapshot creation in audit log
+            // TODO: device snapshot: implement audit log
+            // await deviceAuditLogger.device.snapshot.created(request.session.User, null, request.device, snapShot)
+
+            // 3. clean up older auto snapshots
+            if (clean === true) {
+                await app.db.controllers.ProjectSnapshot.cleanupDeviceAutoSnapshots(device)
+            }
+
+            return snapShot
+        } catch (error) {
+            // TODO: device snapshot:  implement audit log
+            // await deviceAuditLogger.device.snapshot.created(request.session.User, error, request.device, null)
+            throw error
+        }
+    }
+}
+
+// freeze the object to prevent accidental changes
+Object.freeze(deviceAutoSnapshotUtils)
+
 module.exports = {
     /**
      * Creates a snapshot of the current state of a project.
@@ -168,5 +346,12 @@ module.exports = {
         result.flows.credentials = app.db.controllers.Project.exportCredentials(credentials || {}, keyToDecrypt, options.credentialSecret)
 
         return result
-    }
+    },
+
+    getDeviceAutoSnapshotDescription: deviceAutoSnapshotUtils.generateDescription,
+    getDeviceAutoSnapshotName: deviceAutoSnapshotUtils.generateName,
+    getDeviceAutoSnapshots: deviceAutoSnapshotUtils.getAutoSnapshots,
+    isDeviceAutoSnapshot: deviceAutoSnapshotUtils.isAutoSnapshot,
+    cleanupDeviceAutoSnapshots: deviceAutoSnapshotUtils.cleanupAutoSnapshots,
+    doDeviceAutoSnapshot: deviceAutoSnapshotUtils.doAutoSnapshot
 }
diff --git a/forge/db/models/Device.js b/forge/db/models/Device.js
index 372204ac89..5dd147fd71 100644
--- a/forge/db/models/Device.js
+++ b/forge/db/models/Device.js
@@ -10,7 +10,12 @@ const Controllers = require('../controllers')
 const { buildPaginationSearchClause } = require('../utils')
 
 const ALLOWED_SETTINGS = {
-    env: 1
+    env: 1,
+    autoSnapshot: 1
+}
+
+const DEFAULT_SETTINGS = {
+    autoSnapshot: true
 }
 
 module.exports = {
@@ -74,7 +79,7 @@ module.exports = {
                     await app.auditLog.Platform.platform.license.overage('system', null, devices)
                 }
             },
-            beforeSave: async (device, options) => {
+            afterSave: async (device, options) => {
                 // since `id`, `name` and `type` are added as FF_DEVICE_xx env vars, we
                 // should update the settings checksum if they are modified
                 if (device.changed('name') || device.changed('type') || device.changed('id')) {
@@ -131,6 +136,7 @@ module.exports = {
                 },
                 async updateSettingsHash (settings) {
                     const _settings = settings || await this.getAllSettings()
+                    delete _settings.autoSnapshot // autoSnapshot is not part of the settings hash
                     this.settingsHash = hashSettings(_settings)
                 },
                 async getAllSettings () {
@@ -140,6 +146,9 @@ module.exports = {
                         result[setting.key] = setting.value
                     })
                     result.env = Controllers.Device.insertPlatformSpecificEnvVars(this, result.env) // add platform specific device env vars
+                    if (!Object.prototype.hasOwnProperty.call(result, 'autoSnapshot')) {
+                        result.autoSnapshot = DEFAULT_SETTINGS.autoSnapshot
+                    }
                     return result
                 },
                 async updateSettings (obj) {
@@ -161,7 +170,7 @@ module.exports = {
                         if (key === 'env' && value && Array.isArray(value)) {
                             value = Controllers.Device.removePlatformSpecificEnvVars(value) // remove platform specific values
                         }
-                        const result = await M.ProjectSettings.upsert({ DeviceId: this.id, key, value })
+                        const result = await M.DeviceSettings.upsert({ DeviceId: this.id, key, value })
                         await this.updateSettingsHash()
                         await this.save()
                         return result
@@ -177,7 +186,7 @@ module.exports = {
                         }
                         return result.value
                     }
-                    return undefined
+                    return DEFAULT_SETTINGS[key]
                 },
                 async getLatestSnapshot () {
                     const snapshots = await this.getProjectSnapshots({
diff --git a/forge/ee/lib/index.js b/forge/ee/lib/index.js
index bcd7bd8f69..f6d4ee5c98 100644
--- a/forge/ee/lib/index.js
+++ b/forge/ee/lib/index.js
@@ -25,4 +25,7 @@ module.exports = fp(async function (app, opts) {
 
     // Set the Custom Catalogs Flag
     app.config.features.register('customCatalogs', true, true)
+
+    // Set the Device Auto Snapshot Feature Flag
+    app.config.features.register('deviceAutoSnapshot', true, true)
 }, { name: 'app.ee.lib' })
diff --git a/forge/routes/api/device.js b/forge/routes/api/device.js
index b702e4d52d..d6a08f4db4 100644
--- a/forge/routes/api/device.js
+++ b/forge/routes/api/device.js
@@ -4,6 +4,7 @@ const { Roles } = require('../../lib/roles')
 
 const DeviceLive = require('./deviceLive')
 const DeviceSnapshots = require('./deviceSnapshots.js')
+const hasProperty = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key)
 
 /**
  * Project Device api routes
@@ -552,7 +553,7 @@ module.exports = async function (app) {
     })
 
     app.put('/:deviceId/settings', {
-        preHandler: app.needsPermission('device:edit-env'),
+        preHandler: app.needsPermission('device:edit-env'), // members only
         schema: {
             summary: 'Update a devices settings',
             tags: ['Devices'],
@@ -565,7 +566,8 @@ module.exports = async function (app) {
             body: {
                 type: 'object',
                 properties: {
-                    env: { type: 'array', items: { type: 'object', additionalProperties: true } }
+                    env: { type: 'array', items: { type: 'object', additionalProperties: true } },
+                    autoSnapshot: { type: 'boolean' }
                 }
             },
             response: {
@@ -578,15 +580,51 @@ module.exports = async function (app) {
             }
         }
     }, async (request, reply) => {
+        const updates = new app.auditLog.formatters.UpdatesCollection()
+        const currentSettings = await request.device.getAllSettings()
+        // remove any extra properties from env to ensure they match the format of the body data
+        // and prevent updates from being logged for unchanged values
+        currentSettings.env = (currentSettings.env || []).map(e => ({ name: e.name, value: e.value }))
+        const captureUpdates = (key) => {
+            if (key === 'env') {
+                // transform the env array to a map for better logging format
+                const currentEnv = currentSettings.env.reduce((acc, e) => {
+                    acc[e.name] = e.value
+                    return acc
+                }, {})
+                const newEnv = request.body.env.reduce((acc, e) => {
+                    acc[e.name] = e.value
+                    return acc
+                }, {})
+                updates.pushDifferences({ env: currentEnv }, { env: newEnv })
+            } else {
+                updates.pushDifferences({ [key]: currentSettings[key] }, { [key]: request.body[key] })
+            }
+        }
         if (request.teamMembership?.role === Roles.Owner) {
+            // owner is permitted to update all settings
             await request.device.updateSettings(request.body)
+            const keys = Object.keys(request.body)
+            // capture key/val updates sent in body
+            keys.forEach(key => captureUpdates(key, currentSettings[key], request.body[key]))
         } else {
-            const bodySettingsEnvOnly = {
-                env: request.body.env
+            // members are only permitted to update the env and autoSnapshot settings
+            const settings = {}
+            if (hasProperty(request.body, 'env')) {
+                settings.env = request.body.env
+                captureUpdates('env', currentSettings.env, request.body.env)
             }
-            await request.device.updateSettings(bodySettingsEnvOnly)
+            if (hasProperty(request.body, 'autoSnapshot')) {
+                settings.autoSnapshot = request.body.autoSnapshot
+                captureUpdates('autoSnapshot', currentSettings.autoSnapshot, request.body.autoSnapshot)
+            }
+            await request.device.updateSettings(settings)
         }
         await app.db.controllers.Device.sendDeviceUpdateCommand(request.device)
+        // Log the updates
+        if (updates.length > 0) {
+            await app.auditLog.Device.device.settings.updated(request.session.User.id, null, request.device, updates)
+        }
         reply.send({ status: 'okay' })
     })
 
@@ -605,7 +643,8 @@ module.exports = async function (app) {
                 200: {
                     type: 'object',
                     properties: {
-                        env: { type: 'array', items: { type: 'object', additionalProperties: true } }
+                        env: { type: 'array', items: { type: 'object', additionalProperties: true } },
+                        autoSnapshot: { type: 'boolean' }
                     }
                 },
                 '4xx': {
@@ -619,7 +658,8 @@ module.exports = async function (app) {
             reply.send(settings)
         } else {
             reply.send({
-                env: settings?.env
+                env: settings?.env,
+                autoSnapshot: settings?.autoSnapshot
             })
         }
     })
diff --git a/forge/routes/logging/index.js b/forge/routes/logging/index.js
index 0b7ec4791f..ca3816eabc 100644
--- a/forge/routes/logging/index.js
+++ b/forge/routes/logging/index.js
@@ -14,6 +14,8 @@ module.exports = async function (app) {
     const projectAuditLogger = getProjectLogger(app)
     /** @type {import('../../db/controllers/AuditLog')} */
     const auditLogController = app.db.controllers.AuditLog
+    /** @type {import('../../db/controllers/ProjectSnapshot')} */
+    const snapshotController = app.db.controllers.ProjectSnapshot
 
     app.addHook('preHandler', app.verifySession)
 
@@ -124,5 +126,36 @@ module.exports = async function (app) {
         }
 
         response.status(200).send()
+
+        // For application owned devices, perform an auto snapshot
+        if (request.device.isApplicationOwned) {
+            if (event === 'flows.set' && ['full', 'flows', 'nodes'].includes(auditEvent.type)) {
+                if (!app.config.features.enabled('deviceAutoSnapshot')) {
+                    return // device auto snapshot feature is not available
+                }
+
+                const teamType = await request.device.Team.getTeamType()
+                const deviceAutoSnapshotEnabledForTeam = teamType.getFeatureProperty('deviceAutoSnapshot', false)
+                if (!deviceAutoSnapshotEnabledForTeam) {
+                    return // not enabled for team
+                }
+                const deviceAutoSnapshotEnabledForDevice = await request.device.getSetting('autoSnapshot')
+                if (deviceAutoSnapshotEnabledForDevice === true) {
+                    setImmediate(async () => {
+                        // when after the response is sent & IO is done, perform the snapshot
+                        try {
+                            const meta = { user: request.session.User }
+                            const options = { clean: true, setAsTarget: false }
+                            const snapshot = await snapshotController.doDeviceAutoSnapshot(request.device, auditEvent.type, options, meta)
+                            if (!snapshot) {
+                                throw new Error('Auto snapshot was not successful')
+                            }
+                        } catch (error) {
+                            console.warn('Error occurred during auto snapshot', error)
+                        }
+                    })
+                }
+            }
+        }
     })
 }
diff --git a/forge/routes/ui/avatar.js b/forge/routes/ui/avatar.js
index 428aa1f317..9cf789a1c1 100644
--- a/forge/routes/ui/avatar.js
+++ b/forge/routes/ui/avatar.js
@@ -1,6 +1,17 @@
 module.exports = async function (app) {
     app.get('/:id', async (request, reply) => {
         const identifier = request.params.id
+        if (identifier === 'camera.svg') {
+            const result =
+            `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
+                <path stroke-linecap="round" stroke-linejoin="round" d="M6.827 6.175A2.31 2.31 0 0 1 5.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 0 0-1.134-.175 2.31 2.31 0 0 1-1.64-1.055l-.822-1.316a2.192 2.192 0 0 0-1.736-1.039 48.774 48.774 0 0 0-5.232 0 2.192 2.192 0 0 0-1.736 1.039l-.821 1.316Z" />
+                <path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0ZM18.75 10.5h.008v.008h-.008V10.5Z" />
+            </svg>`
+            reply.header('Content-Type', 'image/svg+xml')
+            reply.send(result)
+            return
+        }
+
         const key = Buffer.from(identifier, 'base64').toString()
         const supportedCharacters = Object.keys(font).join('')
         const rx = new RegExp('[^' + supportedCharacters + ']', 'g')
diff --git a/frontend/src/components/audit-log/AuditEntryIcon.vue b/frontend/src/components/audit-log/AuditEntryIcon.vue
index 33c0435391..b01c375803 100644
--- a/frontend/src/components/audit-log/AuditEntryIcon.vue
+++ b/frontend/src/components/audit-log/AuditEntryIcon.vue
@@ -117,6 +117,7 @@ const iconMap = {
         'platform.settings.update',
         'team.settings.updated',
         'project.settings.updated',
+        'device.settings.updated',
         'team.type.changed'
     ],
     'user-profile': [
diff --git a/frontend/src/components/audit-log/AuditEntryVerbose.vue b/frontend/src/components/audit-log/AuditEntryVerbose.vue
index d91d355f84..9a71cc0554 100644
--- a/frontend/src/components/audit-log/AuditEntryVerbose.vue
+++ b/frontend/src/components/audit-log/AuditEntryVerbose.vue
@@ -365,6 +365,11 @@
         <span v-if="!error && entry.body?.device && entry.body.snapshot">Snapshot '{{ entry.body.snapshot?.name }}' has been set as the target for Application owned device '{{ entry.body.device.name }}'.</span>
         <span v-else-if="!error">Device data not found in audit entry.</span>
     </template>
+    <template v-else-if="entry.event === 'device.settings.updated'">
+        <label>{{ AuditEvents[entry.event] }}</label>
+        <span v-if="!error && entry.body?.device">Device '{{ entry.body.device?.name }}' has had the following changes made to its settings: <AuditEntryUpdates :updates="entry.body.updates" /></span>
+        <span v-else-if="!error">Instance data not found in audit entry.</span>
+    </template>
 
     <!-- Application Device Group Events -->
     <template v-else-if="entry.event === 'application.deviceGroup.updated'">
diff --git a/frontend/src/data/audit-events.json b/frontend/src/data/audit-events.json
index 1a3b5b3343..0d6cae890f 100644
--- a/frontend/src/data/audit-events.json
+++ b/frontend/src/data/audit-events.json
@@ -142,6 +142,7 @@
         "device.remote-access.disabled": "Remote Access Disabled",
         "device.remote-access.enabled": "Remote Access Enabled",
         "device.start-failed": "Device Start Failed",
+        "device.settings.updated": "Device Settings Updated",
         "flows.reloaded": "Flows Reloaded",
         "flows.set": "Flow Deployed",
         "library.set": "Saved to Library",
diff --git a/frontend/src/pages/admin/TeamTypes/dialogs/TeamTypeEditDialog.vue b/frontend/src/pages/admin/TeamTypes/dialogs/TeamTypeEditDialog.vue
index f1eda7fb34..c361f68913 100644
--- a/frontend/src/pages/admin/TeamTypes/dialogs/TeamTypeEditDialog.vue
+++ b/frontend/src/pages/admin/TeamTypes/dialogs/TeamTypeEditDialog.vue
@@ -79,7 +79,7 @@
                     <FormRow v-model="input.properties.features.customCatalogs" type="checkbox">Custom NPM Catalogs</FormRow>
                     <FormRow v-model="input.properties.features.deviceGroups" type="checkbox">Device Groups</FormRow>
                     <FormRow v-model="input.properties.features.emailAlerts" type="checkbox">Email Alerts</FormRow>
-                    <div />
+                    <FormRow v-model="input.properties.features.deviceAutoSnapshot" type="checkbox">Device Auto Snapshot</FormRow>
                     <FormRow v-model="input.properties.features.fileStorageLimit">Persistent File storage limit (Mb)</FormRow>
                     <FormRow v-model="input.properties.features.contextLimit">Persistent Context storage limit (Mb)</FormRow>
                 </div>
diff --git a/frontend/src/pages/device/DeveloperMode/index.vue b/frontend/src/pages/device/DeveloperMode/index.vue
index 929f499ffb..1c23294dec 100644
--- a/frontend/src/pages/device/DeveloperMode/index.vue
+++ b/frontend/src/pages/device/DeveloperMode/index.vue
@@ -21,6 +21,7 @@
                                     :disabled="!editorCanBeEnabled || closingTunnel || !editorEnabled"
                                     kind="primary"
                                     size="small"
+                                    class="w-20 whitespace-nowrap"
                                     @click="closeTunnel"
                                 >
                                     <span v-if="closingTunnel">Disabling...</span>
@@ -31,6 +32,7 @@
                                     :disabled="!editorCanBeEnabled || openingTunnel || editorEnabled"
                                     kind="danger"
                                     size="small"
+                                    class="w-20 whitespace-nowrap"
                                     @click="openTunnel"
                                 >
                                     <span v-if="openingTunnel">Enabling...</span>
@@ -40,6 +42,42 @@
                         </div>
                     </template>
                 </InfoCardRow>
+                <InfoCardRow v-if="autoSnapshotFeatureEnabled && deviceIsApplicationOwned" property="Auto Snapshot:">
+                    <template #value>
+                        <div class="flex gap-9 items-center">
+                            <div class="font-medium forge-badge" :class="'forge-status-' + (autoSnapshotEnabled ? 'running' : 'stopped')">
+                                <span v-if="autoSnapshotEnabled">enabled</span>
+                                <span v-else>disabled</span>
+                            </div>
+                            <div class="space-x-2 flex align-center">
+                                <ff-button
+                                    v-if="autoSnapshotEnabled"
+                                    v-ff-tooltip:bottom="'Automatically take a snapshot of the<br>device after every flow deployment.<br>Only the last 10 snapshots are kept'"
+                                    :disabled="savingAutoSnapshotSetting || !autoSnapshotEnabled"
+                                    kind="primary"
+                                    size="small"
+                                    class="w-20 whitespace-nowrap"
+                                    @click="toggleAutoSnapshotSetting"
+                                >
+                                    <span v-if="savingAutoSnapshotSetting">Saving...</span>
+                                    <span v-else>Disable</span>
+                                </ff-button>
+                                <ff-button
+                                    v-if="!autoSnapshotEnabled"
+                                    v-ff-tooltip:bottom="'Automatically take a snapshot of the<br>device after every flow deployment.<br>Only the last 10 snapshots are kept'"
+                                    :disabled="savingAutoSnapshotSetting || autoSnapshotEnabled"
+                                    kind="danger"
+                                    size="small"
+                                    class="w-20 whitespace-nowrap"
+                                    @click="toggleAutoSnapshotSetting"
+                                >
+                                    <span v-if="savingAutoSnapshotSetting">Saving...</span>
+                                    <span v-else>Enable</span>
+                                </ff-button>
+                            </div>
+                        </div>
+                    </template>
+                </InfoCardRow>
                 <InfoCardRow property="Device Flows:">
                     <template #value>
                         <div class="flex items-center">
@@ -68,6 +106,8 @@ import { BeakerIcon } from '@heroicons/vue/outline'
 import semver from 'semver'
 import { mapState } from 'vuex'
 
+import deviceApi from '../../../api/devices.js'
+
 // components
 import InfoCard from '../../../components/InfoCard.vue'
 import InfoCardRow from '../../../components/InfoCardRow.vue'
@@ -100,11 +140,13 @@ export default {
     data () {
         return {
             agentSupportsDeviceAccess: false,
-            busy: false
+            busy: false,
+            savingAutoSnapshotSetting: false,
+            autoSnapshotEnabled: false
         }
     },
     computed: {
-        ...mapState('account', ['features']),
+        ...mapState('account', ['team', 'teamMembership', 'features']),
         developerMode: function () {
             return this.device?.mode === 'developer'
         },
@@ -122,6 +164,18 @@ export default {
         },
         createSnapshotDisabled () {
             return this.device.ownerType !== 'application' && this.device.ownerType !== 'instance'
+        },
+        autoSnapshotFeatureEnabledForTeam () {
+            return !!this.team.type.properties.features?.deviceAutoSnapshot
+        },
+        autoSnapshotFeatureEnabledForPlatform () {
+            return this.features.deviceAutoSnapshot
+        },
+        autoSnapshotFeatureEnabled () {
+            return this.autoSnapshotFeatureEnabledForTeam && this.autoSnapshotFeatureEnabledForPlatform
+        },
+        deviceIsApplicationOwned () {
+            return this.device.ownerType === 'application'
         }
     },
     watch: {
@@ -137,6 +191,7 @@ export default {
         if (this.device && this.device.mode !== 'developer') {
             this.redirect()
         }
+        this.getSettings()
     },
     methods: {
         redirect () {
@@ -173,6 +228,21 @@ export default {
             }
             alerts.emit('Failed to create snapshot from the device.', 'warning')
             this.busy = false
+        },
+        async toggleAutoSnapshotSetting () {
+            try {
+                this.savingAutoSnapshotSetting = true
+                await deviceApi.updateSettings(this.device.id, { autoSnapshot: !this.autoSnapshotEnabled })
+                this.autoSnapshotEnabled = !this.autoSnapshotEnabled
+            } finally {
+                this.savingAutoSnapshotSetting = false
+            }
+        },
+        async getSettings () {
+            if (this.device) {
+                const settings = await deviceApi.getSettings(this.device.id)
+                this.autoSnapshotEnabled = settings.autoSnapshot
+            }
         }
     }
 }
diff --git a/frontend/src/pages/device/Snapshots/index.vue b/frontend/src/pages/device/Snapshots/index.vue
index 8ffa68588c..09a14ff8bd 100644
--- a/frontend/src/pages/device/Snapshots/index.vue
+++ b/frontend/src/pages/device/Snapshots/index.vue
@@ -198,6 +198,19 @@ export default {
                     }
                 })
                 this.snapshots = [...data.snapshots]
+                // For any snapshots that have no user and match the autoSnapshot name format
+                // we mimic a user so that the table can display the device name and a suitable image
+                // NOTE: Any changes to the below regex should be reflected in forge/db/controllers/ProjectSnapshot.js
+                const autoSnapshotRegex = /^Auto Snapshot - \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/ // e.g "Auto Snapshot - 2023-02-01 12:34:56"
+                this.snapshots.forEach(snapshot => {
+                    if (!snapshot.user && autoSnapshotRegex.test(snapshot.name)) {
+                        snapshot.user = {
+                            name: this.device.name,
+                            username: 'Auto Snapshot',
+                            avatar: '../../avatar/camera.svg'
+                        }
+                    }
+                })
                 this.loading = false
             }
         },
diff --git a/test/unit/forge/auditLog/device_spec.js b/test/unit/forge/auditLog/device_spec.js
index ed7ca8aeeb..768097fcf8 100644
--- a/test/unit/forge/auditLog/device_spec.js
+++ b/test/unit/forge/auditLog/device_spec.js
@@ -3,7 +3,7 @@ const should = require('should') // eslint-disable-line
 const FF_UTIL = require('flowforge-test-utils')
 
 // Declare a dummy getLoggers function for type hint only
-/** @type {import('../../../../forge/auditLog/application').getLoggers} */
+/** @type {import('../../../../forge/auditLog/device').getLoggers} */
 const getLoggers = (app) => { return {} }
 
 describe('Audit Log > Device', async function () {
@@ -124,4 +124,19 @@ describe('Audit Log > Device', async function () {
         logEntry.body.application.should.only.have.keys('id', 'name')
         logEntry.body.device.should.only.have.keys('id', 'name')
     })
+
+    it('Provides a logger for changing a settings of a device', async function () {
+        await logger.device.settings.updated(ACTIONED_BY, null, DEVICE, [{ key: 'name', old: 'old', new: 'new' }])
+        // check log stored
+        const logEntry = await getLog()
+        logEntry.should.have.property('event', 'device.settings.updated')
+        logEntry.should.have.property('scope', { id: DEVICE.hashid, type: 'device' })
+        logEntry.should.have.property('trigger', { id: ACTIONED_BY.hashid, type: 'user', name: ACTIONED_BY.username })
+        logEntry.should.have.property('body')
+        logEntry.body.should.only.have.keys('device', 'updates')
+        logEntry.body.device.should.only.have.keys('id', 'name')
+        logEntry.body.device.id.should.equal(DEVICE.hashid)
+        logEntry.body.updates.should.have.length(1)
+        logEntry.body.updates[0].should.eql({ key: 'name', old: 'old', new: 'new' })
+    })
 })
diff --git a/test/unit/forge/configuration/http_security_spec.js b/test/unit/forge/configuration/http_security_spec.js
index 88e8d49b42..e0c6de5f47 100644
--- a/test/unit/forge/configuration/http_security_spec.js
+++ b/test/unit/forge/configuration/http_security_spec.js
@@ -2,8 +2,8 @@ const should = require('should') // eslint-disable-line
 
 const FF_UTIL = require('flowforge-test-utils')
 
-describe('Check HTTP Security Headers set', async () => {
-    describe('CSP Headers', async () => {
+describe('Check HTTP Security Headers set', () => {
+    describe('CSP Headers', () => {
         let app
 
         afterEach(async function () {
@@ -12,6 +12,7 @@ describe('Check HTTP Security Headers set', async () => {
 
         it('CSP Report only should be disabled', async function () {
             const config = {
+                housekeeper: false,
                 content_security_policy: {
                     enabled: false
                 }
@@ -30,6 +31,7 @@ describe('Check HTTP Security Headers set', async () => {
 
         it('CSP Report only should be enabled', async function () {
             const config = {
+                housekeeper: false,
                 content_security_policy: {
                     enabled: true,
                     report_only: true,
@@ -51,6 +53,7 @@ describe('Check HTTP Security Headers set', async () => {
 
         it('CSP should be enabled', async function () {
             const config = {
+                housekeeper: false,
                 content_security_policy: {
                     enabled: true
                 }
@@ -70,6 +73,7 @@ describe('Check HTTP Security Headers set', async () => {
 
         it('CSP should be enabled, custom directives', async function () {
             const config = {
+                housekeeper: false,
                 content_security_policy: {
                     enabled: true,
                     directives: {
@@ -91,6 +95,7 @@ describe('Check HTTP Security Headers set', async () => {
 
         it('CSP should be enabled with plausible', async function () {
             const config = {
+                housekeeper: false,
                 telemetry: {
                     frontend: {
                         plausible: {
@@ -116,6 +121,7 @@ describe('Check HTTP Security Headers set', async () => {
 
         it('CSP should be enabled with posthog', async function () {
             const config = {
+                housekeeper: false,
                 telemetry: {
                     frontend: {
                         posthog: {
@@ -141,6 +147,7 @@ describe('Check HTTP Security Headers set', async () => {
 
         it('CSP should be enabled with hubspot', async function () {
             const config = {
+                housekeeper: false,
                 support: {
                     enabled: true,
                     frontend: {
@@ -167,6 +174,7 @@ describe('Check HTTP Security Headers set', async () => {
 
         it('CSP should be enabled with hubspot and posthog', async function () {
             const config = {
+                housekeeper: false,
                 support: {
                     enabled: true,
                     frontend: {
@@ -199,6 +207,7 @@ describe('Check HTTP Security Headers set', async () => {
         })
         it('CSP should be enabled with hubspot and posthog empty directive', async function () {
             const config = {
+                housekeeper: false,
                 support: {
                     enabled: true,
                     frontend: {
@@ -233,6 +242,7 @@ describe('Check HTTP Security Headers set', async () => {
         })
         it('CSP with sentry.io', async function () {
             const config = {
+                housekeeper: false,
                 telemetry: {
                     frontend: {
                         sentry: 'foo'
@@ -264,6 +274,7 @@ describe('Check HTTP Security Headers set', async () => {
 
         it('HTST not set', async function () {
             const config = {
+                housekeeper: false,
                 base_url: 'http://localhost:9999'
             }
             app = await FF_UTIL.setupApp(config)
diff --git a/test/unit/forge/db/controllers/ProjectSnapshot_spec.js b/test/unit/forge/db/controllers/ProjectSnapshot_spec.js
index 1d13dbbc52..2accdae53b 100644
--- a/test/unit/forge/db/controllers/ProjectSnapshot_spec.js
+++ b/test/unit/forge/db/controllers/ProjectSnapshot_spec.js
@@ -108,25 +108,7 @@ describe('ProjectSnapshot controller', function () {
     //     })
     // })
     describe('createSnapshot (device)', function () {
-        after(async function () {
-            // un-stub app.comms.devices.sendCommandAwaitReply
-            if (app.comms.devices.sendCommandAwaitReply.restore) {
-                app.comms.devices.sendCommandAwaitReply.restore()
-            }
-            await app.close()
-        })
-
-        it('creates a snapshot of a device owned by an application', async function () {
-            const user = await app.db.models.User.byUsername('alice')
-            const options = {
-                name: 'snapshot1',
-                description: 'a snapshot'
-            }
-            const application = app.TestObjects.application1
-            const team = app.TestObjects.team1
-            const device = await factory.createDevice({ name: 'device-1' }, team, null, application)
-            // get db Device with all associations
-            const dbDevice = await app.db.models.Device.byId(device.id)
+        before(async function () {
             // mock app.comms.devices.sendCommandAwaitReply(device_Team_hashid, device_hashid, ...) so that it returns a valid config
             sinon.stub(app.comms.devices, 'sendCommandAwaitReply').resolves({
                 flows: [{ id: '123', type: 'newNode' }],
@@ -141,6 +123,25 @@ describe('ProjectSnapshot controller', function () {
                     }
                 }
             })
+        })
+        afterEach(async function () {
+            app.comms.devices.sendCommandAwaitReply.resetHistory()
+        })
+        after(async function () {
+            app.comms.devices.sendCommandAwaitReply.restore()
+        })
+        it('creates a snapshot of a device owned by an application', async function () {
+            const user = await app.db.models.User.byUsername('alice')
+            const options = {
+                name: 'snapshot1',
+                description: 'a snapshot'
+            }
+            const application = app.TestObjects.application1
+            const team = app.TestObjects.team1
+            const device = await factory.createDevice({ name: 'device-1' }, team, null, application)
+            // get db Device with all associations
+            const dbDevice = await app.db.models.Device.byId(device.id)
+
             const snapshot = await app.db.controllers.ProjectSnapshot.createDeviceSnapshot(application, dbDevice, user, options)
             snapshot.should.have.property('name', 'snapshot1')
             snapshot.should.have.property('description', 'a snapshot')
@@ -153,5 +154,117 @@ describe('ProjectSnapshot controller', function () {
             snapshot.flows.flows.should.have.length(1)
             snapshot.flows.flows[0].should.have.property('id', '123')
         })
+
+        describe('auto snapshots', function () {
+            it('throws an error when deviceAutoSnapshot feature is not enabled', async function () {
+                const meta = { user: { id: null } } // simulate node-red situation (i.e. user is null)
+                const options = { setAsTarget: false }
+                const auditEventType = 'full' // simulate node-red audit event
+                await app.db.controllers.ProjectSnapshot.doDeviceAutoSnapshot({}, auditEventType, options, meta).should.be.rejectedWith('Device auto snapshot feature is not available')
+            })
+            it('throws an error when team type feature flag deviceAutoSnapshot is not enabled', async function () {
+                app.config.features.register('deviceAutoSnapshot', true, true)
+                const application = app.TestObjects.application1
+                const team = app.TestObjects.team1
+                const device = await factory.createDevice({ name: 'device' }, team, null, application)
+                const deviceWithTeam = await app.db.models.Device.byId(device.id, { include: app.db.models.Team })
+                const meta = { user: { id: null } } // simulate node-red situation (i.e. user is null)
+                const options = { setAsTarget: false }
+                const auditEventType = 'full' // simulate node-red audit event
+                await app.db.controllers.ProjectSnapshot.doDeviceAutoSnapshot(deviceWithTeam, auditEventType, options, meta).should.be.rejectedWith('Device auto snapshot is not enabled for the team')
+            })
+            it('throws an error when device setting autoSnapshot is not enabled', async function () {
+                app.config.features.register('deviceAutoSnapshot', true, true)
+                const application = app.TestObjects.application1
+                const team = app.TestObjects.team1
+                const device = await factory.createDevice({ name: 'device' }, team, null, application)
+                const deviceWithTeam = await app.db.models.Device.byId(device.id, { include: app.db.models.Team })
+                await deviceWithTeam.updateSettings({ autoSnapshot: false })
+                const meta = { user: { id: null } } // simulate node-red situation (i.e. user is null)
+                const options = { setAsTarget: false }
+                const auditEventType = 'full' // simulate node-red audit event
+                await app.db.controllers.ProjectSnapshot.doDeviceAutoSnapshot(deviceWithTeam, auditEventType, options, meta).should.be.rejectedWith('Device auto snapshot is not enabled')
+            })
+
+            describe('with deviceAutoSnapshot feature enabled', function () {
+                before(async function () {
+                    app.config.features.register('deviceAutoSnapshot', true, true)
+                    // Enable deviceAutoSnapshot feature for default team type
+                    const defaultTeamType = await app.db.models.TeamType.findOne({ where: { name: 'starter' } })
+                    const defaultTeamTypeProperties = defaultTeamType.properties
+                    defaultTeamTypeProperties.features.deviceAutoSnapshot = true
+                    defaultTeamType.properties = defaultTeamTypeProperties
+                    await defaultTeamType.save()
+
+                    // create a device
+                    const application = app.TestObjects.application1
+                    const team = app.TestObjects.team1
+                    const device1 = await factory.createDevice({ name: 'device 1' }, team, null, application)
+                    const device2 = await factory.createDevice({ name: 'device 2' }, team, null, application)
+                    app.TestObjects.device1 = await app.db.models.Device.byId(device1.id, { include: app.db.models.Team })
+                    app.TestObjects.device2 = await app.db.models.Device.byId(device2.id, { include: app.db.models.Team })
+                })
+
+                after(async function () {
+                    // delete devices we created in before()
+                    await app.TestObjects.device1.destroy()
+                    await app.TestObjects.device2.destroy()
+                })
+
+                it('creates an autoSnapshot for a device following a \'full\' deploy', async function () {
+                    const device = app.TestObjects.device1
+                    const meta = { user: { id: null } } // simulate node-red situation (i.e. user is null)
+                    const options = { clean: true, setAsTarget: false }
+                    const auditEventType = 'full' // simulate node-red audit event
+                    const snapshot = await app.db.controllers.ProjectSnapshot.doDeviceAutoSnapshot(device, auditEventType, options, meta)
+                    should(snapshot).be.an.Object()
+                    snapshot.should.have.a.property('id')
+                    snapshot.should.have.a.property('name')
+                    snapshot.name.should.match(/Auto Snapshot - \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)
+                    snapshot.should.have.a.property('description')
+                    snapshot.description.should.match(/Device Auto Snapshot taken following a Full deployment/)
+                })
+
+                it('only keeps 10 autoSnapshots for a device', async function () {
+                    const device = app.TestObjects.device1
+                    const meta = { user: { id: null } } // simulate node-red situation (i.e. user is null)
+                    const options = undefined // use fn defined default options this time
+                    // perform 12 autoSnapshots
+                    for (let i = 1; i <= 12; i++) {
+                        const ss = await app.db.controllers.ProjectSnapshot.doDeviceAutoSnapshot(device, 'full', options, meta)
+                        await ss.update({ description: `Auto Snapshot - ${i}` }) // update description to make it clear the round-robin cleanup is working
+                    }
+                    const snapshots = await app.db.models.ProjectSnapshot.findAll({ where: { DeviceId: device.id } })
+                    // even though 12 snapshots were created in total, only 10 are kept
+                    snapshots.should.have.length(10)
+                    snapshots[0].description.should.equal('Auto Snapshot - 3') // note ss 1 & 2 were auto cleaned up
+                    snapshots[9].description.should.equal('Auto Snapshot - 12')
+                })
+
+                it('keeps 11 autoSnapshots for a device if one of them is assigned as target snapshot to another device', async function () {
+                    const device = app.TestObjects.device1
+                    const device2 = app.TestObjects.device2
+                    // create a snapshot and set it as target for device2
+                    const snapshot1 = await app.db.controllers.ProjectSnapshot.doDeviceAutoSnapshot(device, 'flows', { setAsTarget: true }, { user: { id: null } })
+                    await snapshot1.update({ description: 'Auto Snapshot - 1' }) // update description to make it clear the round-robin cleanup is working
+                    await device2.update({ targetSnapshotId: snapshot1.id })
+
+                    // create snapshots
+                    const meta = { user: { id: null } } // simulate node-red situation (i.e. user is null)
+                    const options = { clean: true, setAsTarget: false }
+                    for (let i = 2; i <= 13; i++) {
+                        const ss = await app.db.controllers.ProjectSnapshot.doDeviceAutoSnapshot(device, 'nodes', options, meta)
+                        await ss.update({ description: `Auto Snapshot - ${i}` }) // update description to make it clear the round-robin cleanup is working
+                    }
+                    const snapshots = await app.db.models.ProjectSnapshot.findAll({ where: { DeviceId: device.id }, order: [['id', 'ASC']] })
+
+                    // even though 13 snapshots were created in total, only 10+1 (1 is in use) are kept
+                    snapshots.should.have.length(11)
+                    snapshots[0].description.should.equal('Auto Snapshot - 1') // ss 1 is in use & therefore not cleaned up
+                    snapshots[1].description.should.equal('Auto Snapshot - 4') // ss 2 & 3 were auto cleaned up
+                    snapshots[10].description.should.equal('Auto Snapshot - 13') // this was the last one created
+                })
+            })
+        })
     })
 })
diff --git a/test/unit/forge/db/models/Device_spec.js b/test/unit/forge/db/models/Device_spec.js
index a520765b28..b7df583f96 100644
--- a/test/unit/forge/db/models/Device_spec.js
+++ b/test/unit/forge/db/models/Device_spec.js
@@ -70,7 +70,6 @@ describe('Device model', function () {
         })
         it('is updated when the device env vars are changed', async function () {
             const device = await app.db.models.Device.create({ name: 'D1', type: 'PI', credentialSecret: '' })
-            await device.save()
             const initialSettingsHash = device.settingsHash
             const initialSettings = await device.getAllSettings()
             initialSettings.should.have.a.property('env').and.be.an.Array()
@@ -82,5 +81,15 @@ describe('Device model', function () {
             should(settings).be.an.Object().and.have.a.property('env').of.Array()
             settings.env.length.should.equal(initialEnvCount + 1) // count should be +1
         })
+        it('is not updated when the device option autoSnapshot is changed', async function () {
+            const device = await app.db.models.Device.create({ name: 'D1', type: 'PI', credentialSecret: '' })
+            const initialSettingsHash = device.settingsHash
+            const initialSettings = await device.getAllSettings()
+            initialSettings.should.have.a.property('autoSnapshot', true) // should be true by default
+            await device.updateSettings({ autoSnapshot: false })
+            device.settingsHash.should.equal(initialSettingsHash)
+            const settings = await device.getAllSettings()
+            should(settings).be.an.Object().and.have.a.property('autoSnapshot', false)
+        })
     })
 })
diff --git a/test/unit/forge/routes/logging/index_spec.js b/test/unit/forge/routes/logging/index_spec.js
index 21d129fd30..28d1e551d8 100644
--- a/test/unit/forge/routes/logging/index_spec.js
+++ b/test/unit/forge/routes/logging/index_spec.js
@@ -229,5 +229,80 @@ describe('Logging API', function () {
         it.skip('Adds module to instance settings for modules.install event', async function () {
             // future
         })
+        describe('When an audit event for flows.set is received', function () {
+            async function injectFlowSetEvent (app, device, token, deployType) {
+                const url = `/logging/device/${device.hashid}/audit`
+                const response = await app.inject({
+                    method: 'POST',
+                    url,
+                    headers: {
+                        authorization: `Bearer ${token}`
+                    },
+                    payload: { event: 'flows.set', type: deployType }
+                })
+                return response
+            }
+            before(async function () {
+                app.config.features.register('deviceAutoSnapshot', true, true)
+                // Enable deviceAutoSnapshot feature for default team type
+                const defaultTeamType = await app.db.models.TeamType.findOne({ where: { name: 'starter' } })
+                const defaultTeamTypeProperties = defaultTeamType.properties
+                defaultTeamTypeProperties.features.deviceAutoSnapshot = true
+                defaultTeamType.properties = defaultTeamTypeProperties
+                await defaultTeamType.save()
+
+                // stub sendCommandAwaitReply to fake the device response
+                /** @type {DeviceCommsHandler} */
+                const commsHandler = app.comms.devices
+                sinon.stub(commsHandler, 'sendCommandAwaitReply').resolves({})
+
+                // stub ProjectSnapshot controller `doDeviceAutoSnapshot`
+                sinon.stub(app.db.controllers.ProjectSnapshot, 'doDeviceAutoSnapshot').resolves({})
+
+                // spy app.config.features.enabled function
+                sinon.spy(app.config.features, 'enabled')
+            })
+            afterEach(async function () {
+                app.comms.devices.sendCommandAwaitReply.reset()
+                app.db.controllers.ProjectSnapshot.doDeviceAutoSnapshot.reset()
+                app.config.features.enabled.resetHistory()
+            })
+            after(async function () {
+                app.config.features.register('deviceAutoSnapshot', false, false)
+                app.comms.devices.sendCommandAwaitReply.restore()
+                app.db.controllers.ProjectSnapshot.doDeviceAutoSnapshot.restore()
+                app.config.features.enabled.restore()
+            })
+            it('Generates a snapshot for device when deploy type === full', async function () {
+                app.config.features.enabled.resetHistory()
+                const response = await injectFlowSetEvent(app, TestObjects.device1, TestObjects.tokens.device1, 'full')
+                response.should.have.property('statusCode', 200)
+                // wait a moment for the (stubbed) methods to be called asynchronously
+                // then check if the `doDeviceAutoSnapshot` method was called
+                // with the expected arguments
+                await new Promise(resolve => setTimeout(resolve, 25))
+                app.config.features.enabled.called.should.be.true() // the API calls `app.config.features.enabled` to check if the feature is enabled
+                app.db.controllers.ProjectSnapshot.doDeviceAutoSnapshot.called.should.be.true()
+                const args = app.db.controllers.ProjectSnapshot.doDeviceAutoSnapshot.lastCall.args
+                args.should.have.length(4)
+                should(args[0]).be.an.Object().and.have.property('id', TestObjects.device1.id)
+                args[1].should.equal('full')
+                should(args[2]).be.an.Object().and.deepEqual({ clean: true, setAsTarget: false })
+                should(args[3]).be.an.Object()
+                args[3].should.have.property('user')
+            })
+            it('Does not generates a snapshot for device if the deploy type is not one of full, flows or nodes', async function () {
+                app.config.features.enabled.resetHistory()
+                const response = await injectFlowSetEvent(app, TestObjects.device1, TestObjects.tokens.device1, 'bad-deploy-type')
+                // response should be 200 even if the deploy type is not valid
+                // that is because the audit event was processed successfully.
+                // The auto snapshot feature is simply spawned and not awaited.
+                response.should.have.property('statusCode', 200) // 200 is expected (the audit event was processed successfully)
+                // wait a moment for the (stubbed) methods to be (not) called
+                await new Promise(resolve => setTimeout(resolve, 25))
+                app.config.features.enabled.called.should.be.false() // should not have reached the feature.enabled check in the API call
+                app.db.controllers.ProjectSnapshot.doDeviceAutoSnapshot.called.should.be.false()
+            })
+        })
     })
 })