diff --git a/packages/enketo-express/app/controllers/survey-controller.js b/packages/enketo-express/app/controllers/survey-controller.js
index f0867c3524875db5046b44641493bdbf491bc2c0..b1cd40cbf9458848d17415dced30f8a642f32c0a 100644
--- a/packages/enketo-express/app/controllers/survey-controller.js
+++ b/packages/enketo-express/app/controllers/survey-controller.js
@@ -188,8 +188,8 @@ function edit(req, res, next) {
  */
 function _renderWebform(req, res, next, options) {
     const deviceId =
-            req.signedCookies["__enketo_meta_deviceid"] ||
-            `${req.hostname}:${utils.randomString(16)}`,
+        req.signedCookies["__enketo_meta_deviceid"] ||
+        `${req.hostname}:${utils.randomString(16)}`,
         cookieOptions = {
             signed: true,
             maxAge: 10 * 365 * 24 * 60 * 60 * 1000,
@@ -201,8 +201,10 @@ function _renderWebform(req, res, next, options) {
     options.formId = req?.query?.id;
     options.backendToken = req?.query?.token;
     options.formSpec = req?.query?.formSpec;
+    options.userId = req?.query?.userId;
 
     res.cookie("backendToken", req?.query?.token, cookieOptions);
+    res.cookie("userId", req?.query?.userId, cookieOptions);
     res.cookie("enketoFormId", req?.query?.id, cookieOptions);
     res.cookie("__enketo_meta_deviceid", deviceId, cookieOptions).render(
         "surveys/webform",
diff --git a/packages/enketo-express/app/views/layout.pug b/packages/enketo-express/app/views/layout.pug
index 218ee0cfc23855726a07d0c1a456adb4785bd212..86f4cdf40590d44cbe259759e791fdd806a34224 100644
--- a/packages/enketo-express/app/views/layout.pug
+++ b/packages/enketo-express/app/views/layout.pug
@@ -6,6 +6,7 @@ html(lang=language, dir=dir(language))
     meta(charset="utf-8")
     meta(name="author", content="Martijn van de Rijdt (Enketo LLC)")
     meta(name="formId" content=formId)
+    meta(name="userId" content=userId)
     meta(name="backendToken" content=backendToken)
     meta(name="formSpec" content=formSpec)
     if !desktop
diff --git a/packages/enketo-express/package.json b/packages/enketo-express/package.json
index 86378398d27e79792874f757a5afe08efd561c87..183357321cce60bae3b03b8be7b49c6852653d73 100644
--- a/packages/enketo-express/package.json
+++ b/packages/enketo-express/package.json
@@ -42,9 +42,11 @@
         "basic-auth": "^2.0.1",
         "body-parser": "^1.19.0",
         "bristol": "^0.4.0",
+        "browser-image-compression": "^2.0.2",
         "busboy": "^0.3.1",
         "compression": "^1.7.4",
         "cookie-parser": "^1.4.5",
+        "cron": "^2.2.0",
         "csurf": "^1.11.0",
         "db.js": "^0.15.0",
         "debug": "^4.3.2",
@@ -68,6 +70,7 @@
         "jwt-simple": "^0.5.6",
         "libxslt": "^0.10.1",
         "load-grunt-tasks": "^5.1.0",
+        "localforage": "^1.10.0",
         "lodash": "^4.17.21",
         "morgan": "^1.10.0",
         "node-fetch": "^3.2.10",
diff --git a/packages/enketo-express/public/js/src/module/controller-webform.js b/packages/enketo-express/public/js/src/module/controller-webform.js
index 9bfa32149851b90d05957bde6153eeea5c9d124e..d4f0072d7cfbc9ade3b1650691e33a0bb72dd4bd 100644
--- a/packages/enketo-express/public/js/src/module/controller-webform.js
+++ b/packages/enketo-express/public/js/src/module/controller-webform.js
@@ -26,6 +26,10 @@ const cronInterval = setInterval(async () => {
         })
 }, 5000);
 
+function getMeta(metaName) {
+    return document.querySelector(`meta[name=${metaName}]`).content;
+}
+
 /**
  * @typedef {import('../../../../app/models/survey-model').SurveyObject} Survey
  */
@@ -224,7 +228,6 @@ function _resetForm(survey, options = {}) {
             }
         });
 }
-
 /**
  * Loads a record from storage
  *
@@ -703,6 +706,14 @@ function _setEventHandlers(survey) {
         }
     });
 
+    document.addEventListener(events.Edited().type, async event => {
+        const formController = new FormController({});
+        const userId = getMeta('userId');
+        const keyToStorage = `${userId}_${form.model.rootElement.id}_${new Date().toISOString().split("T")[0]}`;
+        let olderFiles = JSON.parse(localStorage.getItem(keyToStorage)) || {};
+        await formController.broadcastFormDataUpdate(form.getDataStr(), {});
+    });
+
     if (inIframe() && settings.parentWindowOrigin) {
         document.addEventListener(events.SubmissionSuccess().type, postEventAsMessageToParentWindow);
         document.addEventListener(events.Edited().type, postEventAsMessageToParentWindow);
@@ -730,39 +741,89 @@ function _setEventHandlers(survey) {
         });
     }
 
-    let arrayOfFileURLs = []
-    document.addEventListener(events.XFormsValueChanged().type, async () => {
+
+    document.addEventListener(events.XFormsValueChanged().type, async (e) => {
         const formController = new FormController({});
-        const formFiles = await fileManager.getCurrentFiles();
-        if (formFiles) {
-            console.log(arrayOfFileURLs?.length)
-            console.log("formFiles: " + formFiles?.length)
-            if (arrayOfFileURLs?.length <= formFiles?.length && formFiles?.length) {
-                for (let i = 0; i < formFiles.length; i++) {
-                    const file = formFiles[i];
-                    if (typeof file === 'object') {
-                        const fileURL = await formController.uploadFile(file);
-                        if (fileURL)
-                            arrayOfFileURLs.push({ url: fileURL, name: file.name });
+        const userId = getMeta('userId');
+        const keyToStorage = `${userId}_${form.model.rootElement.id}_${new Date().toISOString().split("T")[0]}`;
+        let olderFiles = JSON.parse(localStorage.getItem(keyToStorage)) || {};
+        await formController.broadcastFormDataUpdate(form.getDataStr(), {});
+        let arrayOfFileURLs = {};
+        if (e.target.nodeName === "INPUT" && e.target.accept === "image/*") {
+            const formFiles = await fileManager.getCurrentFiles();
+            if (formFiles) {
+                console.log("formFiles: " + formFiles?.length)
+                if (formFiles?.length) {
+                    for (let i = 0; i < formFiles.length; i++) {
+                        const file = formFiles[i];
+                        const parts = e.target.name.split("/").slice();
+                        const lastPart = parts[parts.length - 1]
+                        // if (typeof file === 'object' && file.name.includes(lastPart)) {
+                        if (typeof file === 'object') {
+                            const fileURL = await formController.uploadFile(file);
+                            if (fileURL) {
+                                console.log({ fileURL });
+                                arrayOfFileURLs[e.target.name] = { url: fileURL, name: file.name, ref: e.target.name };
+                                // Broadcast File Remove
+                                olderFiles = JSON.parse(localStorage.getItem(keyToStorage)) || {};
+                                const arrayOfFileURLsNew = { ...olderFiles, ...arrayOfFileURLs };
+                                for (const [key, value] of Object.entries(olderFiles)) {
+                                    if (arrayOfFileURLs[key] !== undefined && value.url !== '') {
+                                        arrayOfFileURLsNew[key] = value;
+                                    }
+                                }
+                                if (olderFiles && olderFiles !== undefined) {
+                                    for (const [key, value] of Object.entries(olderFiles)) {
+                                        if (arrayOfFileURLsNew[key] === undefined) {
+                                            arrayOfFileURLsNew[key] = { url: '', name: '', ref: '' }
+                                        }
+                                    }
+                                }
+                                localStorage.setItem(keyToStorage, JSON.stringify(arrayOfFileURLsNew));
+                                await formController.broadcastFormDataUpdate(form.getDataStr(), arrayOfFileURLsNew);
+                            }
+                        } else {
+                            if (file.includes("cdn.samagra.io")) {
+                                if (e.target.value === '') {
+                                    olderFiles = JSON.parse(localStorage.getItem(keyToStorage)) || {};
+                                    olderFiles[e.target.name] = { url: '', name: '', ref: '' }
+                                    localStorage.setItem(keyToStorage, JSON.stringify(olderFiles));
+                                    olderFiles = JSON.parse(localStorage.getItem(keyToStorage)) || {};
+                                    await formController.broadcastFormDataUpdate(form.getDataStr(), olderFiles);
+                                } else {
+                                    console.info("File has already been uploaded");
+                                }
+                            } else {
+                                arrayOfFileURLs[e.target.name] = { url: '', name: '', ref: '' }
+                                // Broadcast File Remove
+                                const arrayOfFileURLsNew = { ...arrayOfFileURLs, ...olderFiles };
+                                if (olderFiles && olderFiles !== undefined) {
+                                    for (const [key, value] of Object.entries(olderFiles)) {
+                                        if (arrayOfFileURLs[key] === undefined) {
+                                            arrayOfFileURLsNew[key] = { url: '', name: '', ref: '' }
+                                        }
+                                    }
+                                }
+                                localStorage.setItem(keyToStorage, JSON.stringify(arrayOfFileURLsNew));
+                                olderFiles = JSON.parse(localStorage.getItem(keyToStorage)) || {};
+                                await formController.broadcastFormDataUpdate(form.getDataStr(), arrayOfFileURLsNew);
+                            }
+                        }
                     }
                 }
-            } else {
-                arrayOfFileURLs = arrayOfFileURLs.filter(file => formFiles.find(el => el.name == file.name))
             }
-            // console.log({ arrayOfFileURLs })
-            // this.data = (await fetch('http://localhost:3006/parse', {
-            //     method: "POST",
-            //     body: JSON.stringify({ xml: form.getDataStr() }),
-            //     headers: {
-            //         "Content-type": "application/json; charset=UTF-8"
+            // Broadcast File Remove
+            // const arrayOfFileURLsNew = { ...olderFiles, ...arrayOfFileURLs };
+            // if (olderFiles && olderFiles !== undefined) {
+            //     for (const [key, value] of Object.entries(olderFiles)) {
+            //         if (arrayOfFileURLs[key] === undefined) {
+            //             arrayOfFileURLsNew[key] = { url: '', name: '', ref: '' }
+            //         }
             //     }
-            // }).then(res => res.json())).data;
-            // if (fileURL) {
-            //     const kk = formController.findKey(this.data, file.name, '$t', '');
-            //     this.data = formController.set(this.data, kk.substring(1), fileURL);
             // }
+            // localStorage.setItem(`${form.model}_${new Date().toISOString().split("T")[0]}`, JSON.stringify(arrayOfFileURLsNew));
+            // await formController.broadcastFormDataUpdate(form.getDataStr(), arrayOfFileURLsNew);
         }
-        formController.broadcastFormDataUpdate(form.getDataStr(), arrayOfFileURLs);
     });
     if (settings.offline) {
         document.addEventListener(events.XFormsValueChanged().type, () => {
diff --git a/packages/enketo-express/public/js/src/module/file-manager.js b/packages/enketo-express/public/js/src/module/file-manager.js
index da55d8610d8099a0180a61f383389d93b9d3385d..12f3be97ad18f7c36a778632eea383bb13761ebe 100644
--- a/packages/enketo-express/public/js/src/module/file-manager.js
+++ b/packages/enketo-express/public/js/src/module/file-manager.js
@@ -18,7 +18,7 @@ let instanceAttachments;
  * @return { object } promise boolean or rejection with Error
  */
 function init() {
-    return Promise.resolve( true );
+    return Promise.resolve(true);
 }
 
 /**
@@ -36,7 +36,7 @@ function isWaitingForPermissions() {
  *
  * @param {{filename: string}} attachments - attachments sent with record to be loaded
  */
-function setInstanceAttachments( attachments ) {
+function setInstanceAttachments(attachments) {
     instanceAttachments = attachments;
 }
 /**
@@ -46,57 +46,57 @@ function setInstanceAttachments( attachments ) {
  * @param  {?string|object} subject - File or filename
  * @return { object }         promise url string or rejection with Error
  */
-function getFileUrl( subject ) {
-    return new Promise( ( resolve, reject ) => {
-        if ( !subject ) {
-            resolve( null );
-        } else if ( typeof subject === 'string' ) {
-            if ( subject.startsWith( '/' ) ) {
-                resolve( subject );
-            } else if ( instanceAttachments && ( Object.prototype.hasOwnProperty.call( instanceAttachments, subject ) ) ) {
-                resolve( instanceAttachments[ subject ] );
-            } else if ( !store.available ) {
+function getFileUrl(subject) {
+    return new Promise((resolve, reject) => {
+        if (!subject) {
+            resolve(null);
+        } else if (typeof subject === 'string') {
+            if (subject.startsWith('/')) {
+                resolve(subject);
+            } else if (instanceAttachments && (Object.prototype.hasOwnProperty.call(instanceAttachments, subject))) {
+                resolve(instanceAttachments[subject]);
+            } else if (!store.available) {
                 // e.g. in an online-only edit view
-                reject( new Error( 'store not available' ) );
-            } else if ( URL_RE.test( subject ) ) {
+                reject(new Error('store not available'));
+            } else if (URL_RE.test(subject)) {
                 // Any URL values are default binary values. These should only occur in offline-capable views,
                 // because the form cache module removed the src attributes
                 // (which are /urls/like/this/http:// and are caught above this statement)
-                store.survey.resource.get( settings.enketoId, subject )
-                    .then( file => {
-                        if ( file.item ) {
-                            resolve( URL.createObjectURL( file.item ) );
+                store.survey.resource.get(settings.enketoId, subject)
+                    .then(file => {
+                        if (file.item) {
+                            resolve(URL.createObjectURL(file.item));
                         } else {
-                            reject( new Error( 'File Retrieval Error' ) );
+                            reject(new Error('File Retrieval Error'));
                         }
-                    } )
-                    .catch( reject );
+                    })
+                    .catch(reject);
             } else {
                 // obtain file from storage
-                store.record.file.get( _getInstanceId(), subject )
-                    .then( file => {
-                        if ( file.item ) {
-                            if ( isTooLarge( file.item ) ) {
-                                reject( _getMaxSizeError() );
+                store.record.file.get(_getInstanceId(), subject)
+                    .then(file => {
+                        if (file.item) {
+                            if (isTooLarge(file.item)) {
+                                reject(_getMaxSizeError());
                             } else {
-                                resolve( URL.createObjectURL( file.item ) );
+                                resolve(URL.createObjectURL(file.item));
                             }
                         } else {
-                            reject( new Error( 'File Retrieval Error' ) );
+                            reject(new Error('File Retrieval Error'));
                         }
-                    } )
-                    .catch( reject );
+                    })
+                    .catch(reject);
             }
-        } else if ( typeof subject === 'object' ) {
-            if ( isTooLarge( subject ) ) {
-                reject( _getMaxSizeError() );
+        } else if (typeof subject === 'object') {
+            if (isTooLarge(subject)) {
+                reject(_getMaxSizeError());
             } else {
-                resolve( URL.createObjectURL( subject ) );
+                resolve(URL.createObjectURL(subject));
             }
         } else {
-            reject( new Error( 'Unknown error occurred' ) );
+            reject(new Error('Unknown error occurred'));
         }
-    } );
+    });
 }
 
 /**
@@ -107,15 +107,15 @@ function getFileUrl( subject ) {
  * @param  {?string|object} subject - File or filename in local storage
  * @return { object }         promise url string or rejection with Error
  */
-function getObjectUrl( subject ) {
-    return getFileUrl( subject )
-        .then( url => {
-            if ( /https?:\/\//.test( url ) ) {
-                return connection.getMediaFile( url ).then( obj => URL.createObjectURL( obj.item ) );
+function getObjectUrl(subject) {
+    return getFileUrl(subject)
+        .then(url => {
+            if (/https?:\/\//.test(url)) {
+                return connection.getMediaFile(url).then(obj => URL.createObjectURL(obj.item));
             }
 
             return url;
-        } );
+        });
 }
 
 /**
@@ -124,57 +124,62 @@ function getObjectUrl( subject ) {
  * @return { Promise } A promise that resolves with an array of files
  */
 function getCurrentFiles() {
-    const fileInputs = [ ...document.querySelectorAll( 'form.or input[type="file"], form.or input[type="text"][data-drawing="true"]' ) ];
+    const fileInputs = [...document.querySelectorAll('form.or input[type="file"], form.or input[type="text"][data-drawing="true"]')];
     const fileTasks = [];
 
-    const _processNameAndSize = function( input, file ){
-        if ( file && file.name ) {
+    const _processNameAndSize = function (input, file) {
+        if (file && file.name) {
             // Correct file names by adding a unique-ish postfix
             // First create a clone, because the name property is immutable
             // TODO: in the future, when browser support increase we can invoke
             // the File constructor to do this.
-            const newFilename = getFilename( file, input.dataset.filenamePostfix );
+            const newFilename = getFilename(file, input.dataset.filenamePostfix);
             // If file is resized, get Blob representation of data URI
-            if ( input.dataset.resized && input.dataset.resizedDataURI ) {
-                file = utils.dataUriToBlobSync( input.dataset.resizedDataURI );
+            if (input.dataset.resized && input.dataset.resizedDataURI) {
+                file = utils.dataUriToBlobSync(input.dataset.resizedDataURI);
             }
-            file = new Blob( [ file ], {
+            file = new Blob([file], {
                 type: file.type
-            } );
-            file.name = newFilename;
+            });
+            console.log("test");
+            const parts = newFilename.split(".");
+            const extension = parts[parts.length - 1];
+            const fileName = parts[0];
+            const postfix = input.name.split("/")[input.name.split("/").length - 1];
+            file.name = `${fileName}.${postfix}.${extension}`;
         }
 
         return file;
     };
 
-    fileInputs.forEach( input => {
-        if ( input.type === 'file' ) {
+    fileInputs.forEach(input => {
+        if (input.type === 'file') {
             // first get any files inside file input elements
-            if ( input.files[0] ){
-                fileTasks.push( Promise.resolve( _processNameAndSize( input, input.files[ 0 ] ) ) );
+            if (input.files[0]) {
+                fileTasks.push(Promise.resolve(_processNameAndSize(input, input.files[0])));
             }
-        } else if ( input.value ) {
+        } else if (input.value) {
             // then from canvases
-            const canvas = input.closest( '.question' ).querySelector( '.draw-widget canvas' );
-            if ( canvas && !URL_RE.test( input.value ) ) {
-                fileTasks.push( new Promise( resolve => canvas.toBlob( blob => {
+            const canvas = input.closest('.question').querySelector('.draw-widget canvas');
+            if (canvas && !URL_RE.test(input.value)) {
+                fileTasks.push(new Promise(resolve => canvas.toBlob(blob => {
                     blob.name = input.value;
-                    resolve( _processNameAndSize( input, blob ) );
-                } ) ) );
+                    resolve(_processNameAndSize(input, blob));
+                })));
             }
         }
 
-    } );
+    });
 
-    return Promise.all( fileTasks )
-        .then( files => {
+    return Promise.all(fileTasks)
+        .then(files => {
             // get any file names of files that were loaded as DataURI and have remained unchanged (i.e. loaded from Storage)
             fileInputs
-                .filter( input => input.matches( '[data-loaded-file-name]' ) )
-                .forEach( input => files.push( input.getAttribute( 'data-loaded-file-name' ) ) );
+                .filter(input => input.matches('[data-loaded-file-name]'))
+                .forEach(input => files.push(input.getAttribute('data-loaded-file-name')));
 
             return files;
-        } );
+        });
 }
 
 /**
@@ -183,10 +188,10 @@ function getCurrentFiles() {
  * @param { string } filename - filename
  * @return { Promise } array of files
  */
-function getCurrentFile( filename ) {
+function getCurrentFile(filename) {
     // relies on all file names to be unique (which they are)
     return getCurrentFiles()
-        .then( files => files.find( file => file.name === filename )  );
+        .then(files => files.find(file => file.name === filename));
 }
 
 /**
@@ -204,14 +209,14 @@ function _getInstanceId() {
  * @param  { object }  file - the File
  * @return { boolean } whether file is too large
  */
-function isTooLarge( file ) {
+function isTooLarge(file) {
     return file && file.size > _getMaxSize();
 }
 
 function _getMaxSizeError() {
-    return new Error( t( 'filepicker.toolargeerror', {
+    return new Error(t('filepicker.toolargeerror', {
         maxSize: getMaxSizeReadable()
-    } ) );
+    }));
 }
 
 /**
@@ -224,7 +229,7 @@ function _getMaxSize() {
 }
 
 function getMaxSizeReadable() {
-    return `${Math.round( _getMaxSize() * 100 / ( 1000 * 1000 * 100 ) )}MB`;
+    return `${Math.round(_getMaxSize() * 100 / (1000 * 1000 * 100))}MB`;
 }
 
 export default {
diff --git a/packages/enketo-express/public/js/src/module/form-controller.js b/packages/enketo-express/public/js/src/module/form-controller.js
index abebc75bc0e13ac942930a2c5a3691b115111474..5becf924fddc3a24d98018ac92113773ce582aad 100644
--- a/packages/enketo-express/public/js/src/module/form-controller.js
+++ b/packages/enketo-express/public/js/src/module/form-controller.js
@@ -218,6 +218,18 @@ export class FormController {
     }
 
     async broadcastFormDataUpdate(xml, fileURLs) {
+        console.log("Broadcasting file update")
+        // broadcast form data to parent window
+        window.parent.postMessage(JSON.stringify({
+            formData: xml,
+            formXML: xml,
+            state: this._state,
+            fileURLs: fileURLs
+        }), '*');
+    }
+
+    async broadcastFileRemoveUpdate(xml, fileURLs) {
+        console.log("Broadcasting file update")
         // broadcast form data to parent window
         window.parent.postMessage(JSON.stringify({
             formData: xml,
diff --git a/packages/form-manager/package.json b/packages/form-manager/package.json
index 93a62477c8f287d48021144196595c375fbe781e..14e714e007790a2e7d6fdce2314c08ddf08dd1a0 100644
--- a/packages/form-manager/package.json
+++ b/packages/form-manager/package.json
@@ -26,6 +26,9 @@
     "@nestjs/core": "^8.0.0",
     "@nestjs/platform-express": "^8.0.0",
     "@nestjs/platform-fastify": "^9.2.1",
+    "cache-manager": "^4.1.0",
+    "cache-manager-redis-store": "^3.0.1",
+    "ioredis": "^5.3.1",
     "minio": "^7.0.32",
     "reflect-metadata": "^0.1.13",
     "request": "^2.88.2",
@@ -35,7 +38,8 @@
     "webpack": "^5.74.0",
     "xml2js": "^0.4.23",
     "xml2json": "^0.12.0",
-    "xmldom": "^0.6.0"
+    "xmldom": "^0.6.0",
+    "localforage": "^1.10.0"
   },
   "devDependencies": {
     "@nestjs/cli": "^8.0.0",
diff --git a/packages/form-manager/src/app.controller.ts b/packages/form-manager/src/app.controller.ts
index 3c3975f8422ef7358e65d385ca8e14694a87994c..e378b52b97d319b7264c9f17dfb2d113158f9b28 100644
--- a/packages/form-manager/src/app.controller.ts
+++ b/packages/form-manager/src/app.controller.ts
@@ -1,9 +1,11 @@
 import {
   Body,
+  CACHE_MANAGER,
   Controller,
   Get,
   HttpException,
   HttpStatus,
+  Inject,
   Param,
   Post,
   Query,
@@ -14,6 +16,8 @@ import { FileInterceptor } from '@nestjs/platform-express/multer';
 import { AppService } from './app.service';
 import { v4 as uuidv4 } from 'uuid';
 
+import { Cache } from 'cache-manager';
+
 // eslint-disable-next-line @typescript-eslint/no-var-requires
 const Minio = require('minio');
 
@@ -38,9 +42,10 @@ type PrefillDto = {
 @Controller()
 export class AppController {
   constructor(
+    @Inject(CACHE_MANAGER) private cacheManager: Cache,
     private readonly appService: AppService,
     private configService: ConfigService,
-  ) { }
+  ) {}
 
   getLoginToken = () => {
     try {
@@ -62,7 +67,7 @@ export class AppController {
         json: postData,
       };
 
-      console.log(options)
+      console.log(options);
 
       return new Promise((resolve, reject) => {
         request(options, function (error, response, body) {
@@ -167,23 +172,50 @@ export class AppController {
   }
 
   @Post('prefillXML')
-  prefillXML(
+  async prefillXML(
     @Query('form') form,
     @Query('onFormSuccessData') onFormSuccessData,
     @Body('prefillXML') prefillXML,
-  ): string {
+    @Body('imageUrls') files,
+  ): Promise<string> {
     try {
       if (onFormSuccessData) {
-        return this.appService.prefillFormXML(
+        const prefilledForm = this.appService.prefillFormXML(
           form,
           onFormSuccessData,
           prefillXML,
+          files,
         );
+        const instanceId = uuidv4();
+        await this.cacheManager.set(instanceId, prefilledForm, 86400 * 10);
+        return `${this.configService.get(
+          'SERVER_BASR_URL',
+        )}form/instance/${instanceId}`;
       } else {
-        return "OK";
+        return 'OK';
       }
     } catch (e) {
-      return "OK2";
+      console.error(e);
+      return 'OK2';
+    }
+  }
+
+  @Post('submissionXML')
+  async submissionXML(
+    @Query('form') form,
+    @Body('prefillXML') prefillXML,
+    @Body('imageUrls') files,
+  ): Promise<string> {
+    try {
+      const submissionFormXML = this.appService.submissionFormXML(
+        form,
+        prefillXML,
+        files,
+      );
+      return submissionFormXML;
+    } catch (e) {
+      console.error(e);
+      return 'OK2';
     }
   }
 
@@ -192,6 +224,14 @@ export class AppController {
     return this.appService.getForm(id);
   }
 
+  @Get('form/instance/:instanceId')
+  async getFormWithInstanceID(
+    @Param('instanceId') instanceId,
+  ): Promise<string> {
+    const xml = await this.cacheManager.get(instanceId);
+    return xml;
+  }
+
   @Post('parse')
   parseXML(@Body() xml: any): any {
     // console.log({ xml })
@@ -200,19 +240,27 @@ export class AppController {
   }
 
   @Get('osceForm/:type/:year/:speciality?')
-  getOsceForm(@Param('type') type, @Param('year') year, @Param('speciality') speciality): any {
+  getOsceForm(
+    @Param('type') type,
+    @Param('year') year,
+    @Param('speciality') speciality,
+  ): any {
     return this.appService.getOsceForms(type, year, speciality);
   }
 
   @Get('osceFormTeachers/:type/:year/:speciality?')
-  getOsceFormTeachers(@Param('type') type, @Param('year') year, @Param('speciality') speciality): any {
+  getOsceFormTeachers(
+    @Param('type') type,
+    @Param('year') year,
+    @Param('speciality') speciality,
+  ): any {
     return this.appService.getOsceForms(type, year, speciality, 2);
   }
 
   @Post('form/uploadFile')
   @UseInterceptors(FileInterceptor('file'))
   async uploadFile(@UploadedFile() file: Express.Multer.File) {
-    console.log(file)
+    console.log(file);
     const extension = file.originalname.split('.').pop();
     const fileName = uuidv4() + `.${extension}`;
     const tokenRes = await this.getLoginToken();
@@ -252,7 +300,7 @@ export class AppController {
       'MINIO_BUCKET_ID',
     )}/${fileName}`;
 
-    console.log("Uploaded File:", fileURL);
+    console.log('Uploaded File:', fileURL);
 
     return { fileURL };
   }
diff --git a/packages/form-manager/src/app.module.ts b/packages/form-manager/src/app.module.ts
index 7042665f9f87a252b365be7344890012d961971d..c0c58347ec7cc7ecc2288a6b8157ed8a471c9fcb 100644
--- a/packages/form-manager/src/app.module.ts
+++ b/packages/form-manager/src/app.module.ts
@@ -1,16 +1,34 @@
-import { Module } from '@nestjs/common';
+import { CacheModule, CacheStore, Module } from '@nestjs/common';
 import { AppController } from './app.controller';
 import { AppService } from './app.service';
-import { ConfigModule } from '@nestjs/config';
+import { ConfigModule, ConfigService } from '@nestjs/config';
+import { redisStore } from 'cache-manager-redis-store';
+import type { RedisClientOptions } from 'redis';
 
 @Module({
   imports: [
+    CacheModule.registerAsync({
+      isGlobal: true,
+      useFactory: async (configService: ConfigService) => {
+        const store = await redisStore({
+          socket: {
+            host: '0.0.0.0',
+            port: 6369,
+          },
+        });
+        return {
+          store: store as unknown as CacheStore,
+          ttl: 5,
+        };
+      },
+    }),
     ConfigModule.forRoot({
       isGlobal: true,
       envFilePath: '.development.env',
     }),
+    // CacheModule.register(),
   ],
   controllers: [AppController],
   providers: [AppService],
 })
-export class AppModule { }
+export class AppModule {}
diff --git a/packages/form-manager/src/app.service.ts b/packages/form-manager/src/app.service.ts
index fdf05038f1843e135566b34d97084013e12bb87f..d8cde6f9bb6581db47664903b38fb4c2bcdc2527 100644
--- a/packages/form-manager/src/app.service.ts
+++ b/packages/form-manager/src/app.service.ts
@@ -1,4 +1,4 @@
-import { Injectable } from '@nestjs/common';
+import { ConsoleLogger, Injectable } from '@nestjs/common';
 import { DOMParser } from 'xmldom';
 import * as fs from 'fs';
 import { join } from 'path';
@@ -6,6 +6,7 @@ import { join } from 'path';
 @Injectable()
 export class AppService {
   parser = new DOMParser();
+  pf = '';
 
   getHello(): string {
     return 'Hello World!';
@@ -16,25 +17,34 @@ export class AppService {
     return fs.readFileSync(formFilePath, 'utf8');
   }
 
-  getOsceForms(type?: string, year?: string, speciality?: string, noOfForms?: number) {
+  getOsceForms(
+    type?: string,
+    year?: string,
+    speciality?: string,
+    noOfForms?: number,
+  ) {
     try {
-      if (!type || !year)
-        return "Please provide valid inputs"
+      if (!type || !year) return 'Please provide valid inputs';
 
-      const matchingText = speciality ? `${type}_${year}_${speciality}` : `${type}_${year}`;
+      const matchingText = speciality
+        ? `${type}_${year}_${speciality}`
+        : `${type}_${year}`;
       let matchingFiles = [];
       // const fileNames = fs.readdirSync(`/Users/amitsharma/Projects/workflow/packages/form-manager/src/forms`);
-      const fileNames = fs.readdirSync(__dirname + "/forms");
+      const fileNames = fs.readdirSync(__dirname + '/forms');
 
-      fileNames.forEach(file => { if (file.startsWith(matchingText)) matchingFiles.push(file) })
+      fileNames.forEach((file) => {
+        if (file.startsWith(matchingText)) matchingFiles.push(file);
+      });
 
       if (matchingFiles.length) {
         if (noOfForms) {
           const names = [];
           for (let i = 0; i < noOfForms; i++) {
-            let form = matchingFiles[Math.floor(Math.random() * matchingFiles.length)];
+            let form =
+              matchingFiles[Math.floor(Math.random() * matchingFiles.length)];
             names.push(form);
-            matchingFiles = matchingFiles.filter(el => el != form);
+            matchingFiles = matchingFiles.filter((el) => el != form);
           }
           return names;
         }
@@ -79,7 +89,11 @@ export class AppService {
         const key_arr = key.split('_*_');
         let element = null;
         if (this.isImage(prefillSpec[key])) {
-          const parentEl = this.findElementRecursively(0, key_arr.slice(0, key_arr.length - 1), instance);
+          const parentEl = this.findElementRecursively(
+            0,
+            key_arr.slice(0, key_arr.length - 1),
+            instance,
+          );
           for (let i = 0; i < parentEl.childNodes.length; i++) {
             if (element) break;
             if (parentEl.childNodes[i].tagName == key_arr[key_arr.length - 1]) {
@@ -91,7 +105,6 @@ export class AppService {
               }
             }
           }
-
         }
         if (element) {
           element.textContent = eval(prefillSpec[key]);
@@ -102,26 +115,201 @@ export class AppService {
   }
 
   isImage(filename: string): boolean {
-    if (filename.includes(".png") || filename.includes(".tif") || filename.includes(".tiff") || filename.includes(".jpg") || filename.includes(".jpeg") || filename.includes(".bmp") || filename.includes(".gif") || filename.includes(".eps"))
+    if (
+      filename.includes('.png') ||
+      filename.includes('.tif') ||
+      filename.includes('.tiff') ||
+      filename.includes('.jpg') ||
+      filename.includes('.jpeg') ||
+      filename.includes('.bmp') ||
+      filename.includes('.gif') ||
+      filename.includes('.eps')
+    )
       return true;
     return false;
   }
 
   findElementRecursively(start: number, key_arr: any, instance: any) {
     if (!instance) return null;
-    if (!key_arr[start + 1]) return instance.getElementsByTagName(key_arr[start])?.[0]
-    return this.findElementRecursively(start + 1, key_arr, instance.getElementsByTagName(key_arr[start])?.[0])
+    if (!key_arr[start + 1])
+      return instance.getElementsByTagName(key_arr[start])?.[0];
+    return this.findElementRecursively(
+      start + 1,
+      key_arr,
+      instance.getElementsByTagName(key_arr[start])?.[0],
+    );
   }
 
-  prefillFormXML(form: string, onFormSuccessData: any, prefillSpec: any): string {
+  prefillFormXML(
+    form: string,
+    onFormSuccessData: any,
+    prefillSpec: any,
+    files: any,
+  ): string {
     const formFilePath = join(__dirname, `forms/${form}.xml`);
     const formString = fs.readFileSync(formFilePath, 'utf8');
     const doc = this.parser.parseFromString(formString, 'text/xml');
-    console.log({ prefillSpec })
-    const instance = doc.getElementsByTagName('instance')[0];
-    if (instance) {
-      instance.textContent = prefillSpec;
+    const instanceFromForm = doc.getElementsByTagName('instance')[0];
+    console.log({ form, prefillSpec, files });
+
+    if (prefillSpec !== undefined) {
+      let instanceData = this.parser.parseFromString(prefillSpec, 'text/xml');
+      if (files) {
+        for (const [key, value] of Object.entries(files)) {
+          instanceData = this.setElementByPath(
+            instanceData,
+            key,
+            value,
+          ).cloneNode(true);
+          console.log('instance after 1 cycle', instanceData.toString());
+          // this.walk(instanceData, prefillSpec, key, value);
+          // instanceData = this.parser.parseFromString(this.pf, 'text/xml');
+        }
+      }
+      console.log(instanceData.toString());
+      doc
+        .getElementsByTagName('instance')[0]
+        .replaceChild(instanceData, instanceFromForm);
     }
+
+    console.log(doc.toString().length);
     return doc.toString();
   }
+
+  submissionFormXML(form: string, prefillSpec: any, files: any): string {
+    const formFilePath = join(__dirname, `forms/${form}.xml`);
+    const formString = fs.readFileSync(formFilePath, 'utf8');
+    const doc = this.parser.parseFromString(formString, 'text/xml');
+    const instanceFromForm = doc.getElementsByTagName('instance')[0];
+    console.log({ form, prefillSpec, files });
+
+    if (prefillSpec !== undefined) {
+      let instanceData = this.parser.parseFromString(prefillSpec, 'text/xml');
+      if (files) {
+        for (const [key, value] of Object.entries(files)) {
+          instanceData = this.setElementByPath(
+            instanceData,
+            key,
+            value,
+          ).cloneNode(true);
+          console.log('instance after 1 cycle', instanceData.toString());
+          // this.walk(instanceData, prefillSpec, key, value);
+          // instanceData = this.parser.parseFromString(this.pf, 'text/xml');
+        }
+      }
+      console.log(instanceData.toString());
+      doc
+        .getElementsByTagName('instance')[0]
+        .replaceChild(instanceData, instanceFromForm);
+      return instanceData.toString();
+    } else {
+      return instanceFromForm.toString();
+    }
+  }
+
+  setElementByPath(doc, path, value) {
+    const pathParts = path.split('/');
+    let node = doc;
+    let tree = [];
+    tree.push(node.cloneNode(true));
+    for (let i = 1; i < pathParts.length - 1; i++) {
+      console.log(pathParts[i], '||', node.toString());
+      node = node.getElementsByTagName(pathParts[i + 1])[0];
+      tree.push(node);
+    }
+
+    console.log('root', tree[pathParts.length - 2].toString());
+
+    let originalURLNode = tree[pathParts.length - 2].cloneNode(true);
+    originalURLNode.textContent = value['url'];
+
+    let rootNode = tree[pathParts.length - 2].nextSibling.cloneNode(true);
+    rootNode.textContent = value['url'];
+    tree[pathParts.length - 2] = tree[pathParts.length - 2].nextSibling;
+
+    console.log(
+      'RootNode ****************',
+      '\n',
+      rootNode.toString(),
+      '\n',
+      tree[pathParts.length - 2].toString(),
+      '\n',
+      tree[pathParts.length - 3].toString(),
+      '\n',
+    );
+
+    // parts = 7
+
+    // T[0] = data
+    // T[1] = l1
+    // T[2] = l2
+    // T[3] = l3
+    // T[4] = l4
+    // T[5] = <url78/>
+
+    // rootNode = <url78>url</url78>
+
+    // rootNode = T[4].replaceChild(rootNode, T[5]).cloneNode(true)
+    // rootNode = T[3].replaceChild(rootNode, T[4]).cloneNode(true)
+    // rootNode = T[2].replaceChild(rootNode, T[3]).cloneNode(true)
+    // rootNode = T[1].replaceChild(rootNode, T[2]).cloneNode(true)
+    // rootNode = T[0].replaceChild(rootNode, T[1]).cloneNode(true)
+    for (let j = pathParts.length - 3; j >= 0; j--) {
+      console.log(
+        j,
+        'pathParts',
+        pathParts[j + 2],
+        '\n',
+        'Parent',
+        tree[j].toString(),
+        '\n',
+        'Old Child',
+        tree[j + 1].toString(),
+        '\n',
+        'New Child',
+        rootNode.toString(),
+      );
+      console.log('');
+      // Edge case
+      let oldChild = tree[j].getElementsByTagName(pathParts[j + 2])[0];
+      if (j + 2 === pathParts.length - 1) {
+        tree[j].replaceChild(originalURLNode, oldChild);
+        oldChild = oldChild.nextSibling;
+      }
+      tree[j].replaceChild(rootNode, oldChild);
+      rootNode = tree[j].cloneNode(true);
+    }
+    console.log('After replace T[0]', rootNode.toString());
+    console.log('After replace T[1]', tree[1].toString());
+    // tree[0].replaceChild(rootNode, tree[1]);
+    return rootNode;
+  }
+
+  walk(node, prefillSpec, key, value) {
+    try {
+      var children = node.childNodes;
+      if (children) {
+        for (
+          var i = 0;
+          i < children.length;
+          i++ // Children are siblings to each other
+        )
+          this.walk(children[i], prefillSpec, key, value);
+        const nodeName = key.split('/')[key.split('/').length - 1];
+        if (node.nodeName === nodeName || node.tagName === nodeName) {
+          const xmlString = `<${node.nextSibling.tagName}>${value['url']}</${node.nextSibling.tagName}>`;
+          console.log(`<${node.nextSibling.tagName}/>`, xmlString);
+          prefillSpec = prefillSpec.replace(
+            `<${node.nextSibling.tagName}/>`,
+            xmlString,
+          );
+          this.pf = prefillSpec;
+          return prefillSpec;
+        }
+      }
+    } catch (e) {
+      console.error(e);
+      console.log('Update Done');
+    }
+  }
 }
diff --git a/packages/form-manager/src/workflow.code-workspace b/packages/form-manager/src/workflow.code-workspace
new file mode 100644
index 0000000000000000000000000000000000000000..d9480f3881c2df37f9b49b311ff3624eeb529525
--- /dev/null
+++ b/packages/form-manager/src/workflow.code-workspace
@@ -0,0 +1,11 @@
+{
+	"folders": [
+		{
+			"path": "../../.."
+		},
+		{
+			"path": "../../../../UP-HRH"
+		}
+	],
+	"settings": {}
+}
\ No newline at end of file