--- lgmc-platform-app-config/lgmc-platform-app-config.ts
+++ lgmc-platform-app-config/lgmc-platform-app-config.ts
@@ -150,12 +150,11 @@ export class LgmcAppConfigManager {
    * @returns moduleConfig
    */
   public getModuleConfig(id: string): LgmcModuleConfig | undefined {
-    const moduleIndex = this.moduleIndexes.get(id);
-    const appConfig = this.getAppConfig();
-    if(moduleIndex >=0 && moduleIndex < appConfig.modules.length) {
-      return appConfig.modules[moduleIndex];
-    }
-    return undefined;
+    const moduleIndex = this.getModuleIndex(id);
+    if(moduleIndex === undefined)
+      return undefined;
+
+    return this.getAppConfig().modules[moduleIndex];
   }
 
   /**
@@ -225,4 +224,13 @@ export class LgmcAppConfigManager {
       this.moduleIndexes.set(modules[i].id, i);
     }
   }
+
+  public getModuleIndex(id: string): number | undefined {
+    const moduleIndex = this.moduleIndexes.get(id);
+    const appConfig = this.getAppConfig();
+    if(moduleIndex >= 0 && moduleIndex < appConfig.modules.length) {
+      return moduleIndex;
+    }
+    return undefined;
+  }
 }
\ No newline at end of file
--- lgmc-platform-app-state/services/lgmc-platform-app-state.service.ts
+++ lgmc-platform-app-state/services/lgmc-platform-app-state.service.ts
@@ -3,8 +3,8 @@ import { registerLocaleData } from "@angular/common";
 import { NavigationEnd, Route, Router } from "@angular/router";
 import { Title } from "@angular/platform-browser";
 
-import {BehaviorSubject} from "rxjs";
-import {filter} from "rxjs/operators";
+import {BehaviorSubject, combineLatest} from "rxjs";
+import {filter, map} from "rxjs/operators";
 
 import {TranslateService} from "@ngx-translate/core";
 
@@ -30,7 +30,8 @@ import {
   LgmcStylesConfig,
   LgmcMenuItemConfig,
   LgmcThemeName,
-  lgmcThemeNames
+  lgmcThemeNames,
+  LgmcConfigTimedLock
 } from "../../lgmc-platform-model/lgmc-platform-model";
 
 
@@ -47,6 +48,8 @@ import { LgmcOnPremisePathToUrlPipe } from '../../lgmc-platform-utils/pipes/lgmc
 import { ApplicationConfigService } from 'src/shared/lgmc-api-artifacts/src/services/application-config.service';
 import { LgmcPlatformDbApiSerqService } from "../../lgmc-platform-db-api/services/lgmc-platform-db-api-serq.service";
 import { Platform } from "@ionic/angular";
+import { lgmcGuiConfigsModuleId } from "../../../lgmc-gui/lgmc-gui-configs/model/lgmc-gui-configs.model";
+import { deepCopy, getChangedValues, mergeArrayIntoObject, serializeUnserialize } from "../../lgmc-platform-utils/utils/lgmc-platform-utils";
 
 /**
  * This class represents Logimic app state. It keeps the state consistent.
@@ -79,7 +82,9 @@ export class LgmcPlatformAppStateService {
   /**
    * App config.
    */
-  public readonly appConfig: BehaviorSubject<LgmcAppConfig> = new BehaviorSubject<LgmcAppConfig>(undefined);
+  public readonly globalAppConfig: BehaviorSubject<LgmcAppConfig> = new BehaviorSubject<LgmcAppConfig>(undefined);
+  public readonly userAppConfig: BehaviorSubject<Partial<LgmcAppConfig>> = new BehaviorSubject<Partial<LgmcAppConfig>>({});
+  public readonly mergedAppConfig: BehaviorSubject<LgmcAppConfig> = new BehaviorSubject<LgmcAppConfig>(undefined);
 
   /**
    * Loading state of the app.
@@ -151,7 +156,7 @@ export class LgmcPlatformAppStateService {
   /**
    * Current navigation enabled flag used e. g. in breadcrumb.
    */
-  public readonly userAvatar: BehaviorSubject<string> = new BehaviorSubject<string>("");
+  public readonly userAvatar: BehaviorSubject<string> = new BehaviorSubject<string>('');
 
   public readonly theme: BehaviorSubject<LgmcThemeName> = new BehaviorSubject<LgmcThemeName>("light");
 
@@ -179,7 +184,9 @@ export class LgmcPlatformAppStateService {
               private deviceApiService: LgmcPlatformDbApiDeviceModelService, private generalApiService: LgmcPlatformDbApiGeneralService,
               private kpiApiService: LgmcPlatformDbApiKpiService, private generalService: GeneralService, private appConfigService: ApplicationConfigService,
               private serqApiService: LgmcPlatformDbApiSerqService,
-              public platform: Platform) {
+              public platform: Platform) 
+  {
+
     this.router.events
       .pipe(filter(event => event instanceof NavigationEnd))
       .subscribe(({url}: NavigationEnd) => {
@@ -206,11 +213,16 @@ export class LgmcPlatformAppStateService {
     this.appConfigManager = appConfigManager;
 
     // process app config
-    this.setAppConfig(appConfigManager.getAppConfig(), true);
+    combineLatest([this.globalAppConfig, this.userAppConfig]).pipe(map(([global, user]) => mergeArrayIntoObject(global, user)))
+      .subscribe(mergedConfig => this.mergedAppConfig.next(mergedConfig));
+    this.setGlobalAppConfig(appConfigManager.getAppConfig());
+    this.initFromCurrentAppConfig(true);
+
+    // hybrid platform
+    this.setHybrid(this.platform.is('hybrid'));
 
     // refresh session
     let user = await this.refreshSession();
-
     // if the session has not been re-established, try to log in using default test login
     if(!user) {
       const testUser = this.getAppConfig().auth.testUser;
@@ -219,46 +231,43 @@ export class LgmcPlatformAppStateService {
       }
     }
 
-    // hybrid platform
-    this.setHybrid(this.platform.is('hybrid'));
-
-    // DB config merged
-    this.mergeDbConfigs(user);
+    // merge DB configs into static config
+    await this.pullDbConfigs();
+    this.initFromCurrentAppConfig();
+    // certain user features could be rewritten by DB configs
+    await this.refreshSession();
 
     // notify that app is loaded
     this.setInitialized(true);
     this.setLoading(false);
-
     return user;
   }
 
   /**
-   * Help method which fetches app and user config from DB and merge the result.
+   * Help method which fetches app and user config from DB and merges the result via set[Global|User]AppConfig()
    *
    * @param user
    */
-  protected async mergeDbConfigs(user: MnUser) {
+  protected async pullDbConfigs(globalOnly: boolean = false) {
     try {
       //app config from database
-      let dbAppConfig = await this.getDbAppConfig(1);
+      let dbGlobalAppConfig = await this.getDbGlobalAppConfig();
 
-      console.log("db app");
-      console.log(dbAppConfig);
-      if(dbAppConfig !== undefined) {
-        this.mergeObjectWithArray(this.getAppConfig(), dbAppConfig);//merge also arrays
+      if(dbGlobalAppConfig !== undefined) {
+        this.setGlobalAppConfig(mergeArrayIntoObject(this.getGlobalAppConfig(), dbGlobalAppConfig));//merge also arrays
       }
     } catch (e) {
       console.error(e);
-      console.warn("cant fetch app config db data");
+      console.warn("cant fetch global app config db data");
     }
 
+    if (globalOnly) return;
+
     try {
       //user app config from database
-      let dbUserAppConfig = await this.getDbUserAppConfig(1, user.id);
-      console.log("db user");
-      console.log(dbUserAppConfig);
-      if(dbUserAppConfig !== undefined) {
-        this.mergeObjectWithArray(this.getAppConfig(), dbUserAppConfig);//merge also arrays*/
+      let dbUserAppConfig = await this.getDbUserAppConfig();
+      if(dbUserAppConfig !== undefined){
+        this.setUserAppConfig(mergeArrayIntoObject(this.getUserAppConfig(), dbUserAppConfig));//merge also arrays
       }
     } catch (e) {
       console.error(e);
@@ -267,92 +276,166 @@ export class LgmcPlatformAppStateService {
   }
 
 
-  public updateAppConfig(newAppConfig: LgmcAppConfig) {
-    //todo
-    console.log("updating");
-    this.appConfig.next(newAppConfig);
-  }
+  /**
+   * Update underlying AppConfig
+   *
+   * @param newGlobalAppConfig New AppConfig to be applied
+   * @param uploadToDb Upload the new AppConfig to database; defaults to true
+   */
+  public async updateGlobalAppConfig(newGlobalAppConfig: LgmcAppConfig, uploadToDb: boolean = true) {
+
+    this.setLoading(true);
 
+    try{
+      // omit metamodel keys
+      const replacer = (key, value) => { return key==='metamodel' ? undefined : value };
+
+      // compare the new config with: static global config
+      const configsDiff = getChangedValues(
+        serializeUnserialize(this.appConfigManager.getAppConfig(), replacer),
+        serializeUnserialize(newGlobalAppConfig, replacer)
+      );
+
+      this.setGlobalAppConfig(newGlobalAppConfig);
+      if(!uploadToDb)
+        return;
+
+      try {
+        await this.appConfigService.putGlobalAppConfig({
+          conname: this.getGlobalAppConfig().api.connName, //it makes sense to not consider user configuration here
+          body: configsDiff,
+          appId: this.getGlobalAppConfig().api.appId
+        }).toPromise();
+      }
+      catch {
+        await this.appConfigService.postGlobalAppConfig({
+          conname: this.getGlobalAppConfig().api.connName, //it makes sense to not consider user configuration here
+          body: configsDiff,
+          appId: this.getGlobalAppConfig().api.appId
+        }).toPromise();
+      }
+    
+    } finally {
+      this.setLoading(false);
+    }
+  }
 
   /**
-   * This function completes mergeDeep function. It merges changes made into arrays containing objects.
-   * Result is saved into appConfig argument
+   * Update underlying user's AppConfig
    *
-   * @param appConfig object that we want obj to mergen into
-   * @param obj object with extra data needing merge into appConfig
+   * @param newMergedAppConfig New AppConfig to be applied
+   * @param uploadToDb Upload the new AppConfig to database; defaults to true
    */
+  public async updateUserAppConfig(newMergedAppConfig: LgmcAppConfig, uploadToDb: boolean = true) {
 
-  public async mergeObjectWithArray (appConfig, obj) {
-
+    this.setLoading(true);
 
-    //loop through object check for same indexes in array
-    for (const property in obj) {
-      if(appConfig.hasOwnProperty(property)) {
-        if(Array.isArray(obj[property])) {
-          appConfig[property] = obj[property];
-        }
-        else if(typeof obj[property] === 'object') {
-          //recursive loop
-          this.mergeObjectWithArray(appConfig[property], obj[property]);
-        }
-        else {
-          //replace the array value
-          appConfig[property] = obj[property];
-        }
+    try{
+      // omit metamodel keys
+      const replacer = (key, value) => { return key==='metamodel' ? undefined : value };
+
+      // compare the new config with: static global config combined with dynamic global config
+      const configsDiff = getChangedValues(
+        serializeUnserialize(this.getAppConfig(false), replacer),
+        serializeUnserialize(newMergedAppConfig, replacer)
+      );
+
+      this.setUserAppConfig(configsDiff);
+      if(!uploadToDb)
+        return;
+
+      try {
+        await this.appConfigService.putUserAppConfig({
+          conname: this.getGlobalAppConfig().api.connName, //it makes sense to not consider user configuration here
+          body: configsDiff,
+          appId: this.getGlobalAppConfig().api.appId,
+          userId: this.getUser().id
+        }).toPromise();
       }
-      else {
-        //add object property into array
-        appConfig[property] = obj[property];
+      catch {
+        await this.appConfigService.postUserAppConfig({
+          conname: this.getGlobalAppConfig().api.connName, //it makes sense to not consider user configuration here
+          body: configsDiff,
+          appId: this.getGlobalAppConfig().api.appId,
+          userId: this.getUser().id
+        }).toPromise();
       }
+    
+    } finally {
+      this.setLoading(false);
     }
+  }
+
+  public async updateModuleConfig(moduleId: string, newModuleConfig: LgmcModuleConfig, globalScope: boolean = false, uploadToDb: boolean = true) {
+    const wasLoading: boolean = this.isLoading();
+    this.setLoading(true);
+
+    const appConfigCopy = this.getAppConfig(!globalScope, true);
+
+    const moduleIndex = this.appConfigManager.getModuleIndex(moduleId);
+    if(moduleIndex === undefined)
+      return;
+    
+    appConfigCopy.modules[moduleIndex] = newModuleConfig;
 
+    await (globalScope ? this.updateGlobalAppConfig(appConfigCopy, uploadToDb) : this.updateUserAppConfig(appConfigCopy, uploadToDb));
+
+    this.setLoading(wasLoading);
   }
 
   /**
-   * This function updates app config and notifies listeners.
+   * This function updates global app config and notifies listeners.
    *
    * @param appConfig
-   * @param init
    */
-  protected setAppConfig(appConfig: LgmcAppConfig, init: boolean = false): void {
+  protected setGlobalAppConfig(appConfig: LgmcAppConfig): void {
+    this.globalAppConfig.next(appConfig);
+    //this.appConfigManager.setAppConfig(this.getGlobalAppConfig());
+  }
 
-    // update manager
-    if (!init) {
-      console.log("calling myself");
-      this.appConfigManager.setAppConfig(appConfig);
-    }
+  /**
+   * This function updates user's app config and notifies listeners.
+   *
+   * @param appConfig
+   */
+  protected setUserAppConfig(appConfig: Partial<LgmcAppConfig>): void {
+    this.userAppConfig.next(appConfig);
+    //this.appConfigManager.setAppConfig(this.getGlobalAppConfig());
+  }
+
+  protected initFromCurrentAppConfig(core: boolean = false): void {
+    if(core) {
+      // init logger
+      this.initLogger(this.getAppConfig().build.logger);
 
-    // init logger
-    this.initLogger(appConfig.build.logger);
+      // init auth service
+      this.initAuthService(this.getAppConfig().auth);
+
+      // init db api service (TODO: remove)
+      this.dbApiService.init(this.getAppConfig().api.connName, this.getAppConfig().api.connNameRah, this.getAppConfig().api.stage, 'lgmc-platform-images');
+      this.deviceApiService.init(this.getAppConfig().api.connName, this.getAppConfig().api.connNameRah, this.getAppConfig().api.stage);
+      this.generalApiService.init(this.getAppConfig().api.connName, this.getAppConfig().api.connNameRah, this.getAppConfig().api.stage);
+      this.kpiApiService.init(this.getAppConfig().api.connName, this.getAppConfig().api.connNameRah, this.getAppConfig().api.stage);
+      this.serqApiService.init(this.getAppConfig().api.connName, this.getAppConfig().api.connNameRah, this.getAppConfig().api.stage);
+      // TODO:...
+    }
 
     // localisation needs to be set in the beginning (for the login form)
-    this.initLocalisation(appConfig.lang);
+    this.initLocalisation(this.getAppConfig().lang);
 
-    // init auth service
-    this.initAuthService(appConfig.auth);
+    // notify listeners
+    this.setUrlConfig();
+    this.staticPathUrl.next(this.getAppConfig().api.urls.reports);
 
     // init styles
     this.syncStoredTheme(); // if there is theme in localStorage, update appState
-    this.initStyles(appConfig.styles);
+    this.initStyles(this.getAppConfig().styles);
 
     // init favicon
-    this.initFavicon(appConfig.images.favicon);
+    this.initFavicon(this.getAppConfig().images.favicon);
 
     // set tab title
-    this.titleService.setTitle(appConfig.labels.tabTitle);
-
-    // init db api service (TODO: remove)
-    this.dbApiService.init(appConfig.api.connName, appConfig.api.connNameRah, appConfig.api.stage, 'lgmc-platform-images');
-    this.deviceApiService.init(appConfig.api.connName, appConfig.api.connNameRah, appConfig.api.stage);
-    this.generalApiService.init(appConfig.api.connName, appConfig.api.connNameRah, appConfig.api.stage);
-    this.kpiApiService.init(appConfig.api.connName, appConfig.api.connNameRah, appConfig.api.stage);
-    this.serqApiService.init(appConfig.api.connName, appConfig.api.connNameRah, appConfig.api.stage);
-    // TODO:...
-
-    // notify listeners
-    this.setUrlConfig();
-    this.staticPathUrl.next(appConfig.api.urls.reports);
-    this.appConfig.next(appConfig);
+    this.titleService.setTitle(this.getAppConfig().labels.tabTitle);
   }
 
   /**
@@ -467,7 +550,7 @@ export class LgmcPlatformAppStateService {
     this.setThemeLocalStorage(theme);
 
     // reload CSS themes and variable overrides
-    const conf = this.appConfig.getValue();
+    const conf = this.getAppConfig();
     if (conf) {
       this.initStyles(conf.styles);
     }
@@ -797,40 +880,39 @@ export class LgmcPlatformAppStateService {
   }
 
   /**
-   * It returns app config from databse.
-   *
+   * Returns app config from databse.
    * @returns appConfig
    */
-  public async getDbAppConfig(appId?: number) {
+  protected async getDbGlobalAppConfig(): Promise<undefined|Partial<LgmcAppConfig>> {
 
-    /*let dbData;
+    let dbData;
     try {
-      const dbAppConfigPromise = await this.appConfigService.getAppConfig({
-        appId: appId
+      const dbAppConfigPromise = await this.appConfigService.getGlobalAppConfig({
+        appId: this.getGlobalAppConfig().api.appId
       }).toPromise();
       [ dbData ] = await Promise.all([
         dbAppConfigPromise
       ]);
     } catch {
       dbData = undefined;
-    }*/
+    }
 
-    // currently disabled
-    return undefined;
+    return dbData;
   }
 
-
   /**
-   * It returns app config from databse.
-   *
+   * Returns user's app config from databse.
    * @returns appConfig
    */
-  public async getDbUserAppConfig(appId?: number, userId?: number) {
+  protected async getDbUserAppConfig(): Promise<undefined|Partial<LgmcAppConfig>> {
+
+    const userId = this.getUser()?.id;
+    if(userId === undefined) return undefined;
 
     let dbData;
     try {
       const dbUserAppConfigPromise = await this.appConfigService.getUserAppConfig({
-        appId: appId,
+        appId: this.getGlobalAppConfig().api.appId,
         userId: userId
       }).toPromise();
       [ dbData ] = await Promise.all([
@@ -839,8 +921,7 @@ export class LgmcPlatformAppStateService {
 
       if(dbData.length > 0) {
         dbData = dbData[0].config;
-      }
-      else if (dbData.length == 0) {
+      } else if (dbData.length == 0) {
         dbData = undefined;
       }
     } catch {
@@ -850,14 +931,22 @@ export class LgmcPlatformAppStateService {
     return dbData;
   }
 
+  public getGlobalAppConfig(): LgmcAppConfig {
+    return this.globalAppConfig.getValue();
+  }
+
+  public getUserAppConfig(): Partial<LgmcAppConfig> {
+    return this.userAppConfig.getValue();
+  }
 
   /**
-   * It returns app config.
+   * Returns app config.
    *
-   * @returns appConfig
+   * @param includeUserConfig Whether the value should consider user's configuration
    */
-  public getAppConfig(): LgmcAppConfig {
-    return this.appConfig.getValue();
+  public getAppConfig(includeUserConfig: boolean = true, asCopy: boolean = false): LgmcAppConfig {
+    const config = includeUserConfig ? this.mergedAppConfig.getValue() : this.globalAppConfig.getValue();
+    return asCopy ? deepCopy(config) : config;
   }
 
   /**
@@ -875,8 +964,90 @@ export class LgmcPlatformAppStateService {
    * @param id
    * @returns moduleConfig
    */
-  public getModuleConfig(id: string): LgmcModuleConfig | undefined {
-    return this.appConfigManager.getModuleConfig(id);
+  public getModuleConfig<T extends LgmcModuleConfig>(id: string, includeUserConfig: boolean = true, asCopy: boolean = false): T | undefined {
+    const moduleIndex = this.appConfigManager.getModuleIndex(id);
+    if(moduleIndex === undefined)
+      return undefined;
+
+    const moduleConfig = this.getAppConfig(includeUserConfig).modules[moduleIndex] as T;
+    return asCopy ? deepCopy(moduleConfig) : moduleConfig;
+  }
+
+  /**
+   * Whether specified module is locked for dynamic configuration with timed lock
+   *
+   * @param moduleId
+   * @returns is module config time locked
+   */
+  public async isModuleConfigTimeLocked(moduleId: string): Promise<boolean> {
+
+    await this.pullDbConfigs(true);
+
+    const moduleIndex = this.appConfigManager.getModuleIndex(moduleId);
+    if(moduleIndex === undefined)
+      return false;
+
+    const moduleConfig = this.getAppConfig().modules[moduleIndex];
+    if(!(moduleConfig.metamodel?.timeLockable ?? false) || moduleConfig.timedLock === undefined)
+      return false;
+
+
+    const guiConfigsModuleIndex = this.appConfigManager.getModuleIndex(lgmcGuiConfigsModuleId);
+    if(guiConfigsModuleIndex === undefined)
+      return false;
+    const guiConfigsModuleConfig = this.getAppConfig().modules[guiConfigsModuleIndex];
+    
+    const timedLockActive = ((new Date()).getTime() - (new Date(moduleConfig.timedLock.timestamp)).getTime())/1000 < guiConfigsModuleConfig.timedLockDuration;
+    return timedLockActive && moduleConfig.timedLock.userId !== this.getUser()?.id;
+  }
+
+  /**
+   * Apply a new timedLock to module's config
+   *
+   * @param moduleId
+   */
+  public async timeLockModuleConfig(moduleId: string): Promise<void> {
+
+    const wasLoading: boolean = this.isLoading();
+    this.setLoading(true);
+
+    const currUserId = this.getUser()?.id;
+    if(currUserId === undefined)
+      return;
+
+    const moduleConfigCopy = this.getModuleConfig(moduleId, false, true);
+    if(!(moduleConfigCopy.metamodel?.timeLockable ?? false))
+      return;
+
+    moduleConfigCopy.timedLock = {
+      userId: currUserId,
+      timestamp: new Date()
+    } as LgmcConfigTimedLock;
+
+    console.log(`${moduleId} new timedLock:`, moduleConfigCopy.timedLock)
+    await this.updateModuleConfig(moduleId, moduleConfigCopy, true, true);
+
+    this.setLoading(wasLoading);
+  }
+
+  public async timeUnlockModuleConfig(moduleId: string): Promise<void> {
+
+    const wasLoading: boolean = this.isLoading();
+    this.setLoading(true);
+
+    const moduleIndex = this.appConfigManager.getModuleIndex(moduleId);
+    if(moduleIndex === undefined)
+      return;
+
+    const moduleConfig = this.getAppConfig().modules[moduleIndex];
+    if(!(moduleConfig.metamodel?.timeLockable ?? false))
+      return;
+
+    const appConfigCopy = deepCopy(this.getAppConfig(false));
+    appConfigCopy.modules[moduleIndex].timedLock = undefined;
+    await this.updateGlobalAppConfig(appConfigCopy);
+
+    this.setLoading(wasLoading);
   }
 
   /**
@@ -905,11 +1076,11 @@ export class LgmcPlatformAppStateService {
    *
    * @param moduleConfig
    */
-  public setModuleConfig(moduleConfig: LgmcModuleConfig): void {
+  /* public setModuleConfig(moduleConfig: LgmcModuleConfig): void {
     this.appConfigManager.setModuleConfig(moduleConfig);
     this.notifyModuleListeners(moduleConfig);
-    this.appConfig.next(this.appConfigManager.getAppConfig());
-  }
+    this.globalAppConfig.next(this.appConfigManager.getAppConfig());
+  } */
 
   /**
    * Help method which notifies listeners.
@@ -1143,7 +1314,7 @@ export class LgmcPlatformAppStateService {
     let image = this.getAppConfig().images.defaultProfile;
     if (user.imgAvatarId) {
       const userAvatar = (await this.generalService.getImagesByIds({
-        conname: this.appConfig.getValue().api.connName,
+        conname: this.getAppConfig().api.connName,
         body: [user.imgAvatarId]
       }).toPromise())[0];
       if (userAvatar) {
--- lgmc-platform-model/lgmc-platform-model.ts
+++ lgmc-platform-model/lgmc-platform-model.ts
@@ -10,10 +10,15 @@ export type LgmcModuleListener = {
   onModuleChanged: (module: LgmcModuleConfig) => void;
 };
 
+export type LgmcConfigurable = {
+  metamodel?: LgmcMetaConfig;
+  timedLock?: LgmcConfigTimedLock;
+};
+
 /**
  * Aplication config model.
  */
-export type LgmcAppConfig = {
+export type LgmcAppConfig = LgmcConfigurable & {
   api: LgmcApiConfig;
   auth: LgmcAuthConfig;
   build: LgmcBuildConfig;
@@ -36,6 +41,7 @@ export type LgmcAppConfig = {
  * API config model.
  */
 export type LgmcApiConfig = {
+  appId: number;
   connName: string;
   connNameRah?: string;
   region?: string;
@@ -260,34 +266,44 @@ export enum LgmcDisplayOn {
   ALL,
   WEB,
   MOBILE,
-}
+};
 
 /**
- * Menu item model.
+ * Specified for each configuration item
+ * This applies to all users if not overriden by specific accessForms!
+ */
+export enum LmgcMetaConfigPermissionsEnum {
+  HIDDEN,   // item is not visible
+  READABLE, // read only access
+  WRITEABLE // full read and write access
+};
+
+/**
+ * In case a node is not a leaf node, this specifies how it will be displayed at the parent level view
+ */
+export enum LgmcMetaConfigDisplayAtParentLevelEnum {
+  TABLE_ROW, // as an item in the table row under the parent's leaves; default value
+  BAR_TAB,   // as tab of the LgmcGTabsAccordion component
+};
+
+/**
+ * Describes the configuration item's metamodel for configuration GUIs
  */
 export type LgmcMetaConfig = {
-  enabled: boolean;
-  icon: string;
   label: string;
-  children: {
+  icon?: string;
+  displayAtParentLevel?: LgmcMetaConfigDisplayAtParentLevelEnum;
+  permissions: LmgcMetaConfigPermissionsEnum;
+  timeLockable?: boolean;
+  validator?: 'string' | 'integer' | ((inputValue: string) => boolean);
+  children?: {
     [child: string]: LgmcMetaConfig;
   };
 };
 
-export type LgmcAppMetaConfig = {
-  api: LgmcMetaConfig;
-  auth: LgmcMetaConfig;
-  build: LgmcMetaConfig;
-  entities: LgmcMetaConfig;
-  images: LgmcMetaConfig;
-  labels: LgmcMetaConfig;
-  lang: LgmcMetaConfig;
-  layout: LgmcMetaConfig;
-  menu: LgmcMetaConfig;
-  modules: LgmcMetaConfig;
-  router: LgmcMetaConfig;
-  styles: LgmcMetaConfig;
-  units: LgmcMetaConfig;
+export type LgmcConfigTimedLock = {
+  userId: number;
+  timestamp: Date;
 };
 
 /**
@@ -298,7 +314,7 @@ export type LgmcModulesConfig = LgmcModuleConfig[];
 /**
  * Module config model.
  */
-export type LgmcModuleConfig = {
+export type LgmcModuleConfig = LgmcConfigurable & {
   id: string;
   enabled?: boolean;
   general?: Record<string, any>;
--- lgmc-platform-utils/services/lgmc-platform-utils.service.ts
+++ lgmc-platform-utils/services/lgmc-platform-utils.service.ts
@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core';
-import { cloneDeep, isEqualDeep, isNumber, isString, mergeDeep, round } from '../utils/lgmc-platform-utils';
+import { serializeUnserialize, isEqualDeep, isNumber, isString, mergeDeep, round } from '../utils/lgmc-platform-utils';
 
 @Injectable({
   providedIn: 'root'
@@ -24,7 +24,7 @@ export class LgmcPlatformUtilsService {
    * @param object
    */
   cloneDeep(object) {
-    return cloneDeep(object);
+    return serializeUnserialize(object);
   }
   
   /**
--- lgmc-platform-utils/utils/lgmc-platform-utils.ts
+++ lgmc-platform-utils/utils/lgmc-platform-utils.ts
@@ -36,14 +36,31 @@ export function mergeDeep(target, ...sources) {
   return mergeDeep(target, ...sources);
 }
 
-export function cloneDeep(object) {
+export function serializeUnserialize<T = any>(object: T, replacer?: (this: any, key: string, value: any) => any): T {
   // implement better way (avoid lodash dependency)
-  return JSON.parse(JSON.stringify(object));
+  return object!==undefined ? JSON.parse(JSON.stringify(object, replacer)) as T : undefined;
+}
+
+/**
+ * source: https://plainenglish.io/blog/deep-clone-an-object-and-preserve-its-type-with-typescript-d488c35e5574
+ */
+export function deepCopy<T = any>(obj: T): T {
+  return Array.isArray(obj)
+  ? obj.map(item => deepCopy(item))
+  : obj instanceof Date
+  ? new Date(obj.getTime())
+  : obj && typeof obj === 'object'
+        ? Object.getOwnPropertyNames(obj).reduce((o, prop) => {
+           Object.defineProperty(o, prop, Object.getOwnPropertyDescriptor(obj, prop)!);
+           o[prop] = deepCopy((obj as { [key: string]: any })[prop]);
+           return o;
+        }, Object.create(Object.getPrototypeOf(obj)))
+  : obj as T;
 }
 
 export function isEqualDeep(object1: any, object2: any) {
   // implement better way (avoid lodash dependency)
-  return JSON.stringify(object1) === JSON.stringify(object2);;
+  return JSON.stringify(object1) === JSON.stringify(object2);
 }
 
 /**
@@ -106,4 +123,85 @@ function getTag(value) {
  */
 function isObjectLike(value) {
   return typeof value === 'object' && value !== null
+}
+
+
+/**
+*  Compares two objects recursively and returns a new object containing
+*  only the properties that have changed.
+*
+*  @param oldObj - The old object to compare.
+*  @param newObj - The new object to compare.
+*  @returns - An object containing only the properties that have changed.
+*/
+export function getChangedValues(oldObj: any, newObj: any): any {
+  let changedValues = {};
+  for (let key in newObj) {
+    if (newObj.hasOwnProperty(key)) {//todo
+      // Check if the value is an array
+      if (Array.isArray(newObj[key])) {
+        // If the value is an array and the old value is not an array, or the lengths are different, mark the value as changed
+        if (!Array.isArray(oldObj[key]) || oldObj[key].length !== newObj[key].length) {
+          changedValues[key] = newObj[key];
+        } else {
+          // If the value is an array and the old value is an array, recursively compare the arrays for changes
+          let arrayChanges = getChangedValues(oldObj[key], newObj[key]);
+          if (Object.keys(arrayChanges).length > 0) {
+            changedValues[key] = arrayChanges;
+          }
+        }
+      } else if (typeof newObj[key] === 'object') {
+        // If the value is an object and the old value is not an object, mark the value as changed
+        if (typeof oldObj[key] !== 'object') {
+          changedValues[key] = newObj[key];
+        } else {
+          // If the value is an object and the old value is an object, recursively compare the objects for changes
+          let nestedChanges = getChangedValues(oldObj[key], newObj[key]);
+          if (Object.keys(nestedChanges).length > 0) {
+            changedValues[key] = nestedChanges;
+          }
+        }
+      } else if (oldObj[key] !== newObj[key]) {
+        // If the value is not an array or object and the value has changed, mark the value as changed
+        changedValues[key] = newObj[key];
+      }
+    }
+  }
+
+  // Return the object containing only the changed values
+  return changedValues;
+}
+
+/**
+ * This function completes mergeDeep function. It merges changes made into arrays containing objects.
+ * Change: this function no longer operates in-place
+ *
+ * @param dest object that we want obj to merge into
+ * @param src object merged into arr
+ */
+export function mergeArrayIntoObject<T = Object>(dest: T, src: any): T {
+
+  const newObj = deepCopy(dest);
+
+  //loop through object check for same indexes in array
+  for (const property in src) {
+
+    if(newObj.hasOwnProperty(property)) {
+
+      if(Array.isArray(src[property]))
+        newObj[property] = src[property];
+      else if(typeof src[property] === 'object')
+        //recursive loop
+        newObj[property] = mergeArrayIntoObject(newObj[property], src[property]);
+      else
+        //replace the array value
+        newObj[property] = src[property];
+
+    } else {
+      //add object property into array
+      newObj[property] = src[property];
+    }
+  }
+
+  return newObj;
 }
\ No newline at end of file
