diff --git a/src/ch/ch_conf.h b/src/ch/ch_conf.h
index 579eca894e..4b4c3345b6 100644
--- a/src/ch/ch_conf.h
+++ b/src/ch/ch_conf.h
@@ -81,6 +81,17 @@ struct _virCHDriver
     ebtablesContext *ebtables;
 };
 
+#define CH_SAVE_MAGIC "libvirt-xml\n \0 \r"
+#define CH_SAVE_XML "libvirt-save.xml"
+
+typedef struct _CHSaveXMLHeader CHSaveXMLHeader;
+struct _CHSaveXMLHeader {
+    char magic[sizeof(CH_SAVE_MAGIC)-1];
+    uint32_t xmlLen;
+    /* 20 bytes used, pad up to 64 bytes */
+    uint32_t unused[11];
+};
+
 virCaps *virCHDriverCapsInit(void);
 virCaps *virCHDriverGetCapabilities(virCHDriver *driver,
                                       bool refresh);
diff --git a/src/ch/ch_driver.c b/src/ch/ch_driver.c
index ae550802f5..8a00d035c9 100644
--- a/src/ch/ch_driver.c
+++ b/src/ch/ch_driver.c
@@ -19,6 +19,7 @@
  */
 
 #include <config.h>
+#include <fcntl.h>
 
 #include "ch_capabilities.h"
 #include "ch_conf.h"
@@ -34,6 +35,7 @@
 #include "virerror.h"
 #include "virlog.h"
 #include "virobject.h"
+#include "virfile.h"
 #include "virtypedparam.h"
 #include "virutil.h"
 #include "viruuid.h"
@@ -621,6 +623,146 @@ chDomainDestroy(virDomainPtr dom)
     return chDomainDestroyFlags(dom, 0);
 }
 
+/**
+ * chDoDomainSave:
+ * @driver: pointer to driver structure
+ * @vm: pointer to virtual machine structure. Must be locked before invocation.
+ * @to_dir: directory path (CH needs directory input) to save the domain
+ * @managed: whether the VM is managed or not
+ *
+ * Checks if the domain is running or paused, then suspends it and saves it
+ * using CH's vmm.snapshot API. CH creates multiple files for config, memory,
+ * device state into @to_dir.
+ *
+ * Returns 0 on success or -1 in case of error
+ */
+static int
+chDoDomainSave(virCHDriver *driver,
+               virDomainObj *vm,
+               const char *to_dir,
+               bool managed)
+{
+    g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(driver);
+    virCHDomainObjPrivate *priv = vm->privateData;
+    CHSaveXMLHeader hdr;
+    g_autofree char *to = NULL;
+    g_autofree char *xml = NULL;
+    uint32_t xml_len;
+    VIR_AUTOCLOSE fd = -1;
+    int ret = -1;
+
+    virDomainState domainState = virDomainObjGetState(vm, NULL);
+    if (domainState == VIR_DOMAIN_RUNNING) {
+        if (virCHMonitorSuspendVM(priv->monitor) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("failed to suspend domain before saving"));
+            goto end;
+        }
+        virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_SAVE);
+    } else if (domainState != VIR_DOMAIN_PAUSED) {
+        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                       _("only can save running/paused domain"));
+        goto end;
+    }
+
+    if (virDirCreate(to_dir, 0770, cfg->user, cfg->group,
+                     VIR_DIR_CREATE_ALLOW_EXIST) < 0) {
+        virReportSystemError(errno, _("Failed to create SAVE dir %1$s"), to_dir);
+        goto end;
+    }
+
+    to = g_strdup_printf("%s/%s", to_dir, CH_SAVE_XML);
+    if ((fd = virFileOpenAs(to, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR,
+                            cfg->user, cfg->group, 0)) < 0) {
+        virReportSystemError(-fd,
+                             _("Failed to create/open domain save xml file '%1$s'"),
+                             to);
+        goto end;
+    }
+
+    if ((xml = virDomainDefFormat(vm->def, driver->xmlopt, 0)) == NULL)
+        goto end;
+    xml_len = strlen(xml) + 1;
+
+    memset(&hdr, 0, sizeof(hdr));
+    memcpy(hdr.magic, CH_SAVE_MAGIC, sizeof(hdr.magic));
+    hdr.xmlLen = xml_len;
+
+    if (safewrite(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+        virReportSystemError(errno, "%s", _("Failed to write file header"));
+        goto end;
+    }
+
+    if (safewrite(fd, xml, xml_len) != xml_len) {
+        virReportSystemError(errno, "%s", _("Failed to write xml definition"));
+        goto end;
+    }
+
+    if (virCHMonitorSaveVM(priv->monitor, to_dir) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to save domain"));
+        goto end;
+    }
+
+    if (virCHProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SAVED) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("Failed to shutoff after domain save"));
+        goto end;
+    }
+
+    vm->hasManagedSave = managed;
+    ret = 0;
+
+ end:
+    return ret;
+}
+
+static int
+chDomainSaveFlags(virDomainPtr dom, const char *to, const char *dxml, unsigned int flags)
+{
+    virCHDriver *driver = dom->conn->privateData;
+    virDomainObj *vm = NULL;
+    int ret = -1;
+
+    virCheckFlags(0, -1);
+    if (dxml) {
+        virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
+                       _("xml modification unsupported"));
+        return -1;
+    }
+
+    if (!(vm = virCHDomainObjFromDomain(dom)))
+        goto cleanup;
+
+    if (virDomainSaveFlagsEnsureACL(dom->conn, vm->def) < 0)
+        goto cleanup;
+
+    if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0)
+        goto cleanup;
+
+    if (virDomainObjCheckActive(vm) < 0)
+        goto endjob;
+
+    if (chDoDomainSave(driver, vm, to, false) < 0)
+        goto endjob;
+
+    /* Remove if VM is not persistent */
+    virCHDomainRemoveInactive(driver, vm);
+    ret = 0;
+
+ endjob:
+    virDomainObjEndJob(vm);
+
+ cleanup:
+    virDomainObjEndAPI(&vm);
+    return ret;
+}
+
+static int
+chDomainSave(virDomainPtr dom, const char *to)
+{
+    return chDomainSaveFlags(dom, to, NULL, 0);
+}
+
 static virDomainPtr chDomainLookupByID(virConnectPtr conn,
                                        int id)
 {
@@ -1742,6 +1884,8 @@ static virHypervisorDriver chHypervisorDriver = {
     .nodeGetCPUMap = chNodeGetCPUMap,                       /* 8.0.0 */
     .domainSetNumaParameters = chDomainSetNumaParameters,   /* 8.1.0 */
     .domainGetNumaParameters = chDomainGetNumaParameters,   /* 8.1.0 */
+    .domainSave = chDomainSave,                             /* 10.2.0 */
+    .domainSaveFlags = chDomainSaveFlags,                   /* 10.2.0 */
 };
 
 static virConnectDriver chConnectDriver = {