diff --git a/package.json b/package.json
index c32b06f78a57865f044bc4a5e55f2aaeed1e09fa..9e45f61968a122d765cd8011217e730271c227d1 100644
--- a/package.json
+++ b/package.json
@@ -80,7 +80,7 @@
     "@ngrx/effects": "^9.1.1",
     "@ngrx/store": "^9.1.1",
     "@types/node": "12.12.39",
-    "export-nehuba": "0.0.4",
+    "export-nehuba": "0.0.6",
     "hbp-connectivity-component": "^0.3.18",
     "zone.js": "^0.10.2"
   }
diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts
index 5d2652a506fa056e1fd4bbc828a4bec891920b99..d77129d69ba46c90ac2eee782cac01fb1948112a 100644
--- a/src/atlasViewer/atlasViewer.apiService.service.ts
+++ b/src/atlasViewer/atlasViewer.apiService.service.ts
@@ -16,6 +16,7 @@ import {
 import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util";
 import { FRAGMENT_EMIT_RED } from "src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component";
 import { IPluginManifest, PluginServices } from "src/plugin";
+import { ILoadMesh } from 'src/messaging/types'
 
 declare let window
 
@@ -34,14 +35,6 @@ interface IGetUserSelectRegionPr{
 
 export const CANCELLABLE_DIALOG = 'CANCELLABLE_DIALOG'
 
-export interface ILoadMesh {
-  type: 'VTK'
-  id: string
-  url: string
-  customFragmentColor?: string
-}
-export const LOAD_MESH_TOKEN = new InjectionToken<(loadMeshParam: ILoadMesh) => void>('LOAD_MESH_TOKEN')
-
 @Injectable({
   providedIn : 'root'
 })
diff --git a/src/main.module.ts b/src/main.module.ts
index cc8b838adf510afd39302d2275cf4fd1fca61cb9..f4a8fcaa08008d4069692958c6c32adf8735037d 100644
--- a/src/main.module.ts
+++ b/src/main.module.ts
@@ -14,8 +14,9 @@ import { GetNamesPipe } from "./util/pipes/getNames.pipe";
 
 import { HttpClientModule } from "@angular/common/http";
 import { EffectsModule } from "@ngrx/effects";
-import { AtlasViewerAPIServices, CANCELLABLE_DIALOG, API_SERVICE_SET_VIEWER_HANDLE_TOKEN, setViewerHandleFactory, LOAD_MESH_TOKEN, ILoadMesh } from "./atlasViewer/atlasViewer.apiService.service";
+import { AtlasViewerAPIServices, CANCELLABLE_DIALOG, API_SERVICE_SET_VIEWER_HANDLE_TOKEN, setViewerHandleFactory } from "./atlasViewer/atlasViewer.apiService.service";
 import { AtlasWorkerService } from "./atlasViewer/atlasViewer.workerService.service";
+import { LOAD_MESH_TOKEN, ILoadMesh, WINDOW_MESSAGING_HANDLER_TOKEN } from 'src/messaging/types'
 
 import { ConfirmDialogComponent } from "./components/confirmDialog/confirmDialog.component";
 import { DialogComponent } from "./components/dialog/dialog.component";
@@ -60,6 +61,7 @@ import { CookieModule } from './ui/cookieAgreement/module';
 import { KgTosModule } from './ui/kgtos/module';
 import { MouseoverModule } from './mouseoverModule/mouseover.module';
 import { AtlasViewerRouterModule } from './routerModule';
+import { MessagingGlue } from './messagingGlue';
 
 export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
   return function(state, action) {
@@ -248,6 +250,10 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
       provide: VIEWERMODULE_DARKTHEME,
       useFactory: (pureConstantService: PureContantService) => pureConstantService.darktheme$,
       deps: [ PureContantService ]
+    },
+    {
+      provide: WINDOW_MESSAGING_HANDLER_TOKEN,
+      useClass: MessagingGlue
     }
   ],
   bootstrap : [
diff --git a/src/messaging/module.spec.ts b/src/messaging/module.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0718eb33d55cec48f03ce267fcd76be3e214373f
--- /dev/null
+++ b/src/messaging/module.spec.ts
@@ -0,0 +1,54 @@
+import { CommonModule } from "@angular/common"
+import { Component } from "@angular/core"
+import { TestBed } from "@angular/core/testing"
+import { provideMockStore } from "@ngrx/store/testing"
+import { MesssagingModule } from "./module"
+import { IAV_POSTMESSAGE_NAMESPACE } from './service'
+
+@Component({
+  template: ''
+})
+class DummyCmp{}
+
+describe('> module.ts', () => {
+  describe('> MesssagingModule', () => {
+    beforeEach(() => {
+      TestBed.configureTestingModule({
+        imports: [
+          CommonModule,
+          MesssagingModule
+        ],
+        declarations: [
+          DummyCmp
+        ],
+        providers: [
+          provideMockStore()
+        ]
+      }).compileComponents()
+    })
+
+    describe('> service is init', () => {
+      let spy: jasmine.Spy
+      beforeEach(() => {
+      })
+
+      // TODO need to test that module result in service instantiation
+      it('> pong is heard', () => {
+        // const fixture = TestBed.createComponent(DummyCmp)
+
+        // spy = jasmine.createSpy()
+        // window.addEventListener('message', ev => {
+        //   console.log('message', ev.data)
+        //   if (ev.data.result === 'pong') {
+        //     spy()
+        //   }
+        // })
+        // window.postMessage({
+        //   method: `${IAV_POSTMESSAGE_NAMESPACE}ping`,
+        //   id: '123'
+        // }, '*' )
+        // expect(spy).toHaveBeenCalled()
+      })
+    })
+  })
+})
diff --git a/src/messaging/module.ts b/src/messaging/module.ts
index f3e496eea20bbd34cb270640d839c948f17ec6d7..e5ae4af65ba02762978da3f3129a883fff4d107b 100644
--- a/src/messaging/module.ts
+++ b/src/messaging/module.ts
@@ -1,174 +1,22 @@
-import { Inject, NgModule, Optional } from "@angular/core";
-import { MatDialog } from "@angular/material/dialog";
-import { MatSnackBar } from "@angular/material/snack-bar";
-import { AtlasViewerAPIServices } from "src/atlasViewer/atlasViewer.apiService.service";
-import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
-import { LOAD_MESH_TOKEN, ILoadMesh } from "src/atlasViewer/atlasViewer.apiService.service";
+import { NgModule } from "@angular/core";
 import { ComponentsModule } from "src/components";
-import { ConfirmDialogComponent } from "src/components/confirmDialog/confirmDialog.component";
+
 import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module";
-import { getRandomHex } from 'common/util'
+import { MessagingService } from "./service";
 
-const IAV_POSTMESSAGE_NAMESPACE = `ebrains:iav:`
 
 @NgModule({
   imports: [
     AngularMaterialModule,
     ComponentsModule,
+  ],
+  providers: [
+    MessagingService,
   ]
 })
 
 export class MesssagingModule{
-
-  private whiteListedOrigins = new Set()
-  private pendingRequests: Map<string, Promise<boolean>> = new Map()
-  private windowName: string
-
-  constructor(
-    private dialog: MatDialog,
-    private snackbar: MatSnackBar,
-    private worker: AtlasWorkerService,
-    @Optional() private apiService: AtlasViewerAPIServices,
-    @Optional() @Inject(LOAD_MESH_TOKEN) private loadMesh: (loadMeshParam: ILoadMesh) => void
-  ){
-
-    if (window.opener){
-      this.windowName = window.name
-      window.opener.postMessage({
-        id: getRandomHex(),
-        method: `${IAV_POSTMESSAGE_NAMESPACE}onload`,
-        param: {
-          'window.name': this.windowName
-        }
-      }, '*')
-
-      window.addEventListener('beforeunload', () => {
-        window.opener.postMessage({
-          id: getRandomHex(),
-          method: `${IAV_POSTMESSAGE_NAMESPACE}beforeunload`,
-          param: {
-            'window.name': this.windowName
-          }
-        }, '*')
-      })
-    }
-
-    window.addEventListener('message', async ({ data, origin, source }) => {
-      const { method, id, param } = data
-      const src = source as Window
-      if (!method) return
-      if (method.indexOf(IAV_POSTMESSAGE_NAMESPACE) !== 0) return
-      const strippedMethod = method.replace(IAV_POSTMESSAGE_NAMESPACE, '')
-
-      /**
-       * if ping method, respond pong method
-       */
-      if (strippedMethod === 'ping') {
-        src.postMessage({
-          id,
-          result: 'pong',
-          jsonrpc: '2.0'
-        }, origin)
-        return
-      }
-
-      /**
-       * otherwise, check permission
-       */
-
-      try {
-        const allow = await this.checkOrigin({ origin })
-        if (!allow) {
-          src.postMessage({
-            jsonrpc: '2.0',
-            id,
-            error: {
-              code: 403,
-              message: 'User declined'
-            }
-          }, origin)
-          return
-        }
-        const result = await this.processMessage({ method: strippedMethod, param })
-
-        src.postMessage({
-          jsonrpc: '2.0',
-          id,
-          result
-        }, origin)
-
-      } catch (e) {
-
-        src.postMessage({
-          jsonrpc: '2.0',
-          id,
-          error: e.code
-            ? e
-            : { code: 500, message: e.toString() }
-        }, origin)
-      }
-
-    })
-  }
-
-  async processMessage({ method, param }){
-
-    if (method === 'dummyMethod') {
-      return 'OK'
-    }
-
-    if (method === 'viewerHandle:add3DLandmarks') {
-      this.apiService.interactiveViewer.viewerHandle.add3DLandmarks(param)
-      return 'OK'
-    }
-
-    if (method === 'viewerHandle:remove3DLandmarks') {
-      this.apiService.interactiveViewer.viewerHandle.remove3DLandmarks(param)
-      return 'OK'
-    }
-
-    if (method === '_tmp:plotly') {
-      const isLoadingSnack = this.snackbar.open(`Loading plotly mesh ...`)
-      const resp = await this.worker.sendMessage({
-        method: `PROCESS_PLOTLY`,
-        param
-      })
-      isLoadingSnack?.dismiss()
-      const meshId = 'bobby'
-      if (this.loadMesh) {
-        const { objectUrl, customFragmentColor } = resp.result || {}
-        this.loadMesh({
-          type: 'VTK',
-          id: meshId,
-          url: objectUrl,
-          customFragmentColor
-        })
-      } else {
-        this.snackbar.open(`Error: loadMesh method not injected.`)
-      }
-      return 'OK'
-    }
-
-    throw ({ code: 404, message: 'Method not found' })
-  }
-
-  async checkOrigin({ origin }){
-    if (this.whiteListedOrigins.has(origin)) return true
-    if (this.pendingRequests.has(origin)) return this.pendingRequests.get(origin)
-    const responsePromise = this.dialog.open(
-      ConfirmDialogComponent,
-      {
-        data: {
-          title: `Cross tab messaging`,
-          message: `${origin} would like to send data to interactive atlas viewer`,
-          okBtnText: `Allow`
-        }
-      }
-    ).afterClosed().toPromise()
-    this.pendingRequests.set(origin, responsePromise)
-    const response = await responsePromise
-    this.pendingRequests.delete(origin)
-    if (response) this.whiteListedOrigins.add(origin)
-    return response
+  // need to inject service
+  constructor(_mS: MessagingService){
   }
 }
diff --git a/src/messaging/native/index.ts b/src/messaging/native/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..394f5418658eb6ac1ed44621ccf1d9c024f24d33
--- /dev/null
+++ b/src/messaging/native/index.ts
@@ -0,0 +1,27 @@
+import { Observable, Subject } from "rxjs"
+import { IMessagingActions, IMessagingActionTmpl } from "../types"
+
+export const TYPE = 'iav.unload'
+
+type TUnload = {
+  ['@id']: string
+}
+
+export const processJsonLd = (json: TUnload): Observable<IMessagingActions<keyof IMessagingActionTmpl>> => {
+  const sub = new Subject<IMessagingActions<keyof IMessagingActionTmpl>>()
+  const _main = (() => {
+    if (!json['@id']) {
+      return sub.error(`@id must be defined to `)
+    }
+    sub.next({
+      type: 'unloadResource',
+      payload: {
+        "@id": json['@id']
+      }
+    })
+    sub.complete()
+
+  })
+  setTimeout(_main);
+  return sub
+}
diff --git a/src/messaging/nmvSwc/index.ts b/src/messaging/nmvSwc/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7080d4193d3b2abb09b87fbc91f264731c91727a
--- /dev/null
+++ b/src/messaging/nmvSwc/index.ts
@@ -0,0 +1,110 @@
+import { Observable, Subject } from "rxjs"
+import { getUuid } from "src/util/fn"
+import { IMessagingActions, IMessagingActionTmpl } from "../types"
+
+export const TYPE = 'bas.datasource'
+
+const waitFor = (condition: (...arg: any[]) => boolean) => new Promise((rs, rj) => {
+  const intervalRef = setInterval(() => {
+    if (condition()) {
+      clearInterval(intervalRef)
+      rs()
+    }
+  }, 1000)
+})
+
+const NM_IDS = {
+  AMBA_V3: 'hbp:Allen_Mouse_CCF_v3(nm)',
+  WAXHOLM_V1_01: 'hbp:WHS_SD_Rat_v1.01(nm)',
+  BIG_BRAIN: 'hbp:BigBrain_r2015(nm)',
+  COLIN: 'hbp:Colin27_r2008(nm)',
+  MNI152_2009C_ASYM: 'hbp:ICBM_Asym_r2009c(nm)',
+}
+
+const IAV_IDS = {
+  AMBA_V3: 'minds/core/referencespace/v1.0.0/265d32a0-3d84-40a5-926f-bf89f68212b9',
+  WAXHOLM_V1_01: 'minds/core/referencespace/v1.0.0/d5717c4a-0fa1-46e6-918c-b8003069ade8',
+  BIG_BRAIN: 'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588',
+  COLIN: 'minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992',
+  MNI152_2009C_ASYM: 'minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2',
+}
+
+const translateSpace = (spaceId: string) => {
+  for (const key in NM_IDS){
+    if (NM_IDS[key] === spaceId) return IAV_IDS[key]
+  }
+  return null
+}
+
+export const processJsonLd = (json: { [key: string]: any }): Observable<IMessagingActions<keyof IMessagingActionTmpl>> => {
+  const subject = new Subject<IMessagingActions<keyof IMessagingActionTmpl>>()
+  const main = (async () => {
+
+    const {
+      encoding,
+      mediaType,
+      data: rawData,
+      transformations
+    } = json
+
+    if (mediaType.indexOf('model/swc') < 0) return subject.error(`mediaType of ${mediaType} cannot be parsed. Not 'model/swc'`)
+
+    if (!Array.isArray(transformations)) {
+      return subject.error(`transformations must be an array!`)
+    }
+
+    if (!transformations[0]) {
+      return subject.error(`transformations[0] must be defined`)
+    }
+    const { toSpace } = transformations[0]
+    
+    const iavSpace = translateSpace(toSpace)
+    if (!iavSpace) {
+      return subject.error(`toSpace with id ${toSpace} cannot be found.`)
+    }
+    subject.next({
+      type: 'loadTemplate',
+      payload: {
+        ['@id']: iavSpace
+      }
+    })
+
+    await waitFor(() => !!(window as any).export_nehuba)
+
+    const b64Encoded = encoding.indexOf('base64') >= 0
+    const isGzipped = encoding.indexOf('gzip') >= 0
+    let data = rawData
+    if (b64Encoded) {
+      data = atob(data)
+    }
+    if (isGzipped) {
+      data = (window as any).export_nehuba.pako.inflate(data)
+    }
+    let output = ``
+    for (let i = 0; i < data.length; i++) {
+      output += String.fromCharCode(data[i])
+    }
+
+    const encoder = new TextEncoder()
+    const tmpUrl = URL.createObjectURL(
+      new Blob([ encoder.encode(output) ], { type: 'application/octet-stream' })
+    )
+    const uuid = getUuid()
+    const payload: IMessagingActionTmpl['loadResource'] = {
+      '@id': uuid,
+      "@type" : 'swc',
+      unload: () => {
+        URL.revokeObjectURL(tmpUrl)
+      },
+      url: tmpUrl
+    }
+    subject.next({
+      type: 'loadResource',
+      payload
+    })
+    
+    subject.complete()
+  })
+  setTimeout(main);
+  return subject
+}
diff --git a/src/messaging/service.spec.ts b/src/messaging/service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fcacc5aaac382ed90d8a9ba1c50ccc1f4601c673
--- /dev/null
+++ b/src/messaging/service.spec.ts
@@ -0,0 +1,128 @@
+import { TestBed } from "@angular/core/testing"
+import { MockStore, provideMockStore } from "@ngrx/store/testing"
+import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"
+import { viewerStateFetchedAtlasesSelector } from "src/services/state/viewerState/selectors"
+import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"
+import { getUuid } from "src/util/fn"
+import { IAV_POSTMESSAGE_NAMESPACE, MessagingService } from "./service"
+import { IWindowMessaging, WINDOW_MESSAGING_HANDLER_TOKEN } from "./types"
+
+describe('> service.ts', () => {
+  describe('> MessagingService', () => {
+    const windowMessagehandler = {
+      loadResource: jasmine.createSpy(),
+      loadTempladById: jasmine.createSpy(),
+      unloadResource: jasmine.createSpy()
+    }
+    afterEach(() => {
+      windowMessagehandler.loadResource.calls.reset()
+      windowMessagehandler.unloadResource.calls.reset()
+      windowMessagehandler.loadTempladById.calls.reset()
+    })
+    beforeEach(() => {
+
+      TestBed.configureTestingModule({
+        imports: [
+          AngularMaterialModule,
+        ],
+        providers: [
+          provideMockStore(),
+          AtlasWorkerService,
+          {
+            provide: WINDOW_MESSAGING_HANDLER_TOKEN,
+            useValue: windowMessagehandler
+          }
+        ]
+      })
+
+      const mockStore = TestBed.inject(MockStore)
+      mockStore.overrideSelector(viewerStateFetchedAtlasesSelector, [])
+    })
+
+    it('> can be inst', () => {
+      const s = TestBed.inject(MessagingService)
+      expect(s).toBeTruthy()
+    })
+
+    describe('> on construct', () => {
+      describe('> if window.opener', () => {
+        describe('> is defined', () => {
+          let openerProxy = {
+            postMessage: jasmine.createSpy()
+          }
+          const randomWindowName = getUuid()
+          beforeEach(() => {
+            spyOnProperty(window, 'opener').and.returnValue(openerProxy)
+            spyOnProperty(window, 'name').and.returnValue(randomWindowName)
+            TestBed.inject(MessagingService)
+          })
+          afterEach(() => {
+            openerProxy.postMessage.calls.reset()
+          })
+          it('> postMessage is called on window.opener', () => {
+            expect(openerProxy.postMessage).toHaveBeenCalledTimes(1)
+          })
+          describe('> args are as expected', () => {
+            let args: any[]
+            beforeEach(() => {
+              args = openerProxy.postMessage.calls.allArgs()[0]
+            })
+            it('> method === {namespace}onload', () => {
+              expect(args[0]['method']).toEqual(`${IAV_POSTMESSAGE_NAMESPACE}onload`)
+            })
+            it('> param[window.name] is windowname', () => {
+              expect(args[0]['param']['window.name']).toEqual(randomWindowName)
+            })
+          })
+
+          describe('> beforeunload', () => {
+            beforeEach(() => {
+              // onload messages are called before unload
+              openerProxy.postMessage.calls.reset()
+
+              // https://github.com/karma-runner/karma/issues/1062#issuecomment-42421624
+              window.onbeforeunload = null
+              window.dispatchEvent(new Event('beforeunload'))
+            })
+            it('> sends beforeunload event', () => {
+              expect(openerProxy.postMessage).toHaveBeenCalled()
+            })
+            it('> method is {namespace}beforeunload', () => {
+              const args = openerProxy.postMessage.calls.allArgs()[0]
+              expect(args[0]['method']).toEqual(`${IAV_POSTMESSAGE_NAMESPACE}beforeunload`)
+            })
+          })
+        })
+      })
+    
+      describe('> listen to message', () => {
+        beforeEach(() => {
+
+        })
+        describe('> ping', () => {
+          it('> pong')
+        })
+
+        describe('> check permission', () => {
+          it('> if succeeds')
+          it('> if fails')
+        })
+
+        it('> if throws')
+      })
+    
+      describe('> #processMessage', () => {
+        describe('> method === _tmp:plotly ', () => {
+
+        })
+        describe('> managedMethods', () => {
+
+        })
+      })
+
+      describe('> #processJsonld', () => {
+        
+      })
+    })
+  })
+})
diff --git a/src/messaging/service.ts b/src/messaging/service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bca0845834341cb2506a8fdb968708fd98ac4b41
--- /dev/null
+++ b/src/messaging/service.ts
@@ -0,0 +1,238 @@
+import { Inject, Injectable, Optional } from "@angular/core";
+import { Observable } from "rxjs";
+import { MatDialog } from "@angular/material/dialog";
+import { MatSnackBar } from "@angular/material/snack-bar";
+
+import { getUuid } from "src/util/fn";
+import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
+import { ConfirmDialogComponent } from "src/components/confirmDialog/confirmDialog.component";
+
+import { IMessagingActions, IMessagingActionTmpl, ILoadMesh, LOAD_MESH_TOKEN, WINDOW_MESSAGING_HANDLER_TOKEN, IWindowMessaging } from './types'
+import { TYPE as NMV_TYPE, processJsonLd as nmvProcess } from './nmvSwc/index'
+import { TYPE as NATIVE_TYPE, processJsonLd as nativeProcess } from './native'
+
+export const IAV_POSTMESSAGE_NAMESPACE = `ebrains:iav:`
+
+export const MANAGED_METHODS = [
+  'openminds:nmv:loadSwc',
+  'openminds:nmv:unloadSwc'
+]
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class MessagingService {
+
+  private whiteListedOrigins = new Set()
+  private pendingRequests: Map<string, Promise<boolean>> = new Map()
+  private windowName: string
+
+  private typeRegister: Map<string, (arg: any) => Observable<IMessagingActions<keyof IMessagingActionTmpl>>> = new Map()
+  
+  constructor(
+    private dialog: MatDialog,
+    private snackbar: MatSnackBar,
+    private worker: AtlasWorkerService,
+    @Optional() @Inject(WINDOW_MESSAGING_HANDLER_TOKEN) private messagingHandler: IWindowMessaging,
+    @Optional() @Inject(LOAD_MESH_TOKEN) private loadMesh: (loadMeshParam: ILoadMesh) => void,
+  ){
+    
+    if (window.opener){
+      this.windowName = window.name
+      window.opener.postMessage({
+        id: getUuid(),
+        method: `${IAV_POSTMESSAGE_NAMESPACE}onload`,
+        param: {
+          'window.name': this.windowName
+        }
+      }, '*')
+
+      window.addEventListener('beforeunload', () => {
+        window.opener.postMessage({
+          id: getUuid(),
+          method: `${IAV_POSTMESSAGE_NAMESPACE}beforeunload`,
+          param: {
+            'window.name': this.windowName
+          }
+        }, '*')
+      })
+    }
+
+    window.addEventListener('message', async ({ data, origin, source }) => {
+      const { method, id, param } = data
+      const src = source as Window
+      if (!method) return
+      if (method.indexOf(IAV_POSTMESSAGE_NAMESPACE) !== 0) return
+      const strippedMethod = method.replace(IAV_POSTMESSAGE_NAMESPACE, '')
+
+      /**
+       * if ping method, respond pong method
+       */
+      if (strippedMethod === 'ping') {
+        src.postMessage({
+          id,
+          result: 'pong',
+          jsonrpc: '2.0'
+        }, origin)
+        return
+      }
+
+      /**
+       * otherwise, check permission
+       */
+
+      try {
+        const allow = await this.checkOrigin({ origin })
+        if (!allow) {
+          src.postMessage({
+            jsonrpc: '2.0',
+            id,
+            error: {
+              code: 403,
+              message: 'User declined'
+            }
+          }, origin)
+          return
+        }
+        const result = await this.processMessage({ method: strippedMethod, param })
+
+        src.postMessage({
+          jsonrpc: '2.0',
+          id,
+          result
+        }, origin)
+
+      } catch (e) {
+
+        src.postMessage({
+          jsonrpc: '2.0',
+          id,
+          error: e.code
+            ? e
+            : { code: 500, message: e.toString() }
+        }, origin)
+      }
+
+    })
+
+    this.typeRegister.set(
+      NMV_TYPE,
+      nmvProcess
+    )
+    this.typeRegister.set(
+      NATIVE_TYPE,
+      nativeProcess
+    )
+
+  }
+
+  processJsonld(jsonLd: any){
+    const { ['@type']: type } = jsonLd
+    const fn = this.typeRegister.get(type)
+    // TODO tidy this return value up
+    let returnValue: any
+    return new Promise((rs, rj) => {
+
+      const sub = fn(jsonLd)
+      sub.subscribe(
+        ev => {
+          if (ev.type === 'loadTemplate') {
+            const payload = ev.payload as IMessagingActionTmpl['loadTemplate']
+            
+            this.messagingHandler.loadTempladById(payload)
+          }
+
+          if (ev.type === 'loadResource') {
+            const payload = ev.payload as IMessagingActionTmpl['loadResource']
+            returnValue = {
+              ['@id']: payload["@id"],
+              ['@type']: NATIVE_TYPE
+            }
+            this.messagingHandler.loadResource(payload)
+          }
+
+          if (ev.type === 'unloadResource') {
+            const payload = ev.payload as IMessagingActionTmpl['unloadResource']
+            this.messagingHandler.unloadResource(payload)
+          }
+        },
+        rj,
+        () => {
+          rs(returnValue || {})
+        }
+      )
+    })
+  }
+
+  async processMessage({ method, param }){
+
+    // TODO combine api service and messaging service into one
+    // and implement it properly
+
+    // if (method === 'viewerHandle:add3DLandmarks') {
+    //   this.apiService.interactiveViewer.viewerHandle.add3DLandmarks(param)
+    //   return 'OK'
+    // }
+
+    // if (method === 'viewerHandle:remove3DLandmarks') {
+    //   this.apiService.interactiveViewer.viewerHandle.remove3DLandmarks(param)
+    //   return 'OK'
+    // }
+
+    /**
+     * TODO use loadResource in the future
+     */
+    if (method === '_tmp:plotly') {
+      const isLoadingSnack = this.snackbar.open(`Loading plotly mesh ...`)
+      const resp = await this.worker.sendMessage({
+        method: `PROCESS_PLOTLY`,
+        param
+      })
+      isLoadingSnack?.dismiss()
+      const meshId = 'bobby'
+      if (this.loadMesh) {
+        const { objectUrl, customFragmentColor } = resp.result || {}
+        this.loadMesh({
+          type: 'VTK',
+          id: meshId,
+          url: objectUrl,
+          customFragmentColor
+        })
+      } else {
+        this.snackbar.open(`Error: loadMesh method not injected.`)
+      }
+      return 'OK'
+    }
+
+    if (MANAGED_METHODS.indexOf(method) >= 0) {
+      try {
+        return await this.processJsonld(param)
+      } catch (e) {
+        throw ({ code: 401, message: e })
+      }
+    }
+
+    throw ({ code: 404, message: 'Method not found' })
+  }
+
+  async checkOrigin({ origin }){
+    if (this.whiteListedOrigins.has(origin)) return true
+    if (this.pendingRequests.has(origin)) return this.pendingRequests.get(origin)
+    const responsePromise = this.dialog.open(
+      ConfirmDialogComponent,
+      {
+        data: {
+          title: `Cross tab messaging`,
+          message: `${origin} would like to send data to interactive atlas viewer`,
+          okBtnText: `Allow`
+        }
+      }
+    ).afterClosed().toPromise()
+    this.pendingRequests.set(origin, responsePromise)
+    const response = await responsePromise
+    this.pendingRequests.delete(origin)
+    if (response) this.whiteListedOrigins.add(origin)
+    return response
+  }
+}
diff --git a/src/messaging/types.ts b/src/messaging/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7f0fb1391fa404e053f38e823cccef62214dfc71
--- /dev/null
+++ b/src/messaging/types.ts
@@ -0,0 +1,52 @@
+import { InjectionToken } from "@angular/core";
+
+interface ILoadTemplateByIdPayload {
+  ['@id']: string
+}
+
+interface IResourceType {
+  swc: string
+}
+
+interface ILoadResource {
+  ['@id']: string
+  ['@type']: keyof IResourceType
+  url: string
+  unload: () => void
+}
+
+interface IUnloadResource {
+  ['@id']: string
+}
+
+interface ISetResp {
+  [key: string]: any
+}
+
+export interface IMessagingActionTmpl {
+  setResponse: ISetResp
+  loadTemplate: ILoadTemplateByIdPayload
+  loadResource: ILoadResource
+  unloadResource: IUnloadResource
+}
+
+export interface IMessagingActions<TAction extends keyof IMessagingActionTmpl> {
+  type: TAction
+  payload: IMessagingActionTmpl[TAction]
+}
+
+export interface ILoadMesh {
+  type: 'VTK'
+  id: string
+  url: string
+  customFragmentColor?: string
+}
+export const LOAD_MESH_TOKEN = new InjectionToken<(loadMeshParam: ILoadMesh) => void>('LOAD_MESH_TOKEN')
+
+export interface IWindowMessaging {
+  loadTempladById(payload: IMessagingActionTmpl['loadTemplate']): void
+  loadResource(payload: IMessagingActionTmpl['loadResource']): void
+  unloadResource(payload: IMessagingActionTmpl['unloadResource']): void
+}
+
+export const WINDOW_MESSAGING_HANDLER_TOKEN = new InjectionToken<IWindowMessaging>('WINDOW_MESSAGING_HANDLER_TOKEN')
diff --git a/src/messagingGlue.ts b/src/messagingGlue.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f4e6c9087a7a1ca451147e5dfcf0ffb20b846f51
--- /dev/null
+++ b/src/messagingGlue.ts
@@ -0,0 +1,109 @@
+import { OnDestroy } from "@angular/core";
+import { select, Store } from "@ngrx/store";
+import { IMessagingActionTmpl, IWindowMessaging } from "./messaging/types";
+import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer } from "./services/state/ngViewerState/actions";
+import { viewerStateSelectAtlas } from "./services/state/viewerState/actions";
+import { viewerStateFetchedAtlasesSelector } from "./services/state/viewerState/selectors";
+import { generalActionError } from "./services/stateStore.helper";
+
+export class MessagingGlue implements IWindowMessaging, OnDestroy {
+
+  private onDestroyCb: (() => void)[] = []
+  private tmplSpIdToAtlasId = new Map<string, string>()
+  private mapIdUnload = new Map<string, () => void>()
+
+  ngOnDestroy(){
+    while(this.onDestroyCb.length > 0) this.onDestroyCb.pop()()
+  }
+
+  constructor(private store: Store<any>){
+
+    const sub = this.store.pipe(
+      select(viewerStateFetchedAtlasesSelector)
+    ).subscribe((atlases: any[]) => {
+      for (const atlas of atlases) {
+        const { ['@id']: atlasId, templateSpaces } = atlas
+        for (const tmpl of templateSpaces) {
+          const { ['@id']: tmplId } = tmpl
+          this.tmplSpIdToAtlasId.set(tmplId, atlasId)
+        }
+      }
+    })
+
+    this.onDestroyCb.push(() => sub.unsubscribe())
+  }
+
+  /**
+   * it is important to not use select temlate by id. always go from the highest hierarchy,
+   * and enforce single direction flow when possible
+   */
+  loadTempladById( payload: IMessagingActionTmpl['loadTemplate'] ){
+    const atlasId = this.tmplSpIdToAtlasId.get(payload['@id'])
+    if (!atlasId) {
+      return this.store.dispatch(
+        generalActionError({
+          message: `atlas id with the corresponding templateId ${payload['@id']} not found.`
+        })
+      )
+    }
+    this.store.dispatch(
+      viewerStateSelectAtlas({
+        atlas: {
+          ['@id']: atlasId,
+          template: {
+            ['@id']: payload['@id']
+          }
+        }
+      })
+    )
+  }
+
+  loadResource(payload: IMessagingActionTmpl['loadResource']){
+    const {
+      unload,
+      url,
+      ["@type"]: type,
+      ["@id"]: swcLayerUuid
+    } = payload
+
+    if (type === 'swc') {
+
+      const layer = {
+        name: swcLayerUuid,
+        id: swcLayerUuid,
+        source: `swc://${url}`,
+        mixability: 'mixable',
+        type: "segmentation",
+        "segments": [
+          "1"
+        ],
+      }
+
+      this.store.dispatch(
+        ngViewerActionAddNgLayer({
+          layer
+        })
+      )
+
+      this.mapIdUnload.set(swcLayerUuid, () => {
+        this.store.dispatch(
+          ngViewerActionRemoveNgLayer({
+            layer: {
+              name: swcLayerUuid
+            }
+          })
+        )
+        unload()
+      })
+    }
+  }
+  unloadResource(payload: IMessagingActionTmpl['unloadResource']) {
+    const { ["@id"]: id } = payload
+    const cb = this.mapIdUnload.get(id)
+    if (!cb) {
+      throw new Error(`Unload resource id ${id} does not exist.`)
+    }
+    cb()
+    this.mapIdUnload.delete(id)
+  }
+}
diff --git a/src/services/state/viewerState/actions.ts b/src/services/state/viewerState/actions.ts
index 68787f5b45cb6053bb99557bd1b34f42c004db62..4dd72c7aa8268cb5b18b71b8ed8d5c5c17f235cd 100644
--- a/src/services/state/viewerState/actions.ts
+++ b/src/services/state/viewerState/actions.ts
@@ -46,7 +46,14 @@ export const viewerStateSetFetchedAtlases = createAction(
 
 export const viewerStateSelectAtlas = createAction(
   `[viewerState] selectAtlas`,
-  props<{ atlas: { ['@id']: string } }>()
+  props<{
+    atlas: {
+      ['@id']: string
+      template?: {
+        ['@id']: string
+      }
+    }
+  }>()
 )
 
 export const viewerStateHelperSelectParcellationWithId = createAction(
diff --git a/src/state/effects/viewerState.useEffect.spec.ts b/src/state/effects/viewerState.useEffect.spec.ts
index 8952ae3ae0d94c022e40b1f7fa21374fca0f5203..b6c0340d170d1494e97ad45de6f08e8fd9d0cfaa 100644
--- a/src/state/effects/viewerState.useEffect.spec.ts
+++ b/src/state/effects/viewerState.useEffect.spec.ts
@@ -386,7 +386,7 @@ describe('> viewerState.useEffect.ts', () => {
         )
       })
     
-      describe('> if atlas found, will try to find id of first template', () => {
+      describe('> if atlas found', () => {
         const mockParc1 = {
           ['@id']: 'parc-1',
           availableIn: [{
@@ -407,70 +407,152 @@ describe('> viewerState.useEffect.ts', () => {
           ['@id']: 'test-1',
           availableIn: [ mockParc1 ]
         }
-        it('> if fails, will return general error', () => {
-
-          mockStore.overrideSelector(viewerStateFetchedTemplatesSelector, [
-            mockTmplSpc1
-          ])
-          mockStore.overrideSelector(viewerStateFetchedAtlasesSelector, [{
-            ['@id']: 'foo-bar',
-            templateSpaces: [ mockTmplSpc ]
-          }])
-          actions$ = hot('a', {
-            a: viewerStateSelectAtlas({
-              atlas: {
+
+        describe('> if template key val is not provided', () => {
+          describe('> will try to find the id of the first tmpl', () => {
+
+            it('> if fails, will return general error', () => {
+
+              mockStore.overrideSelector(viewerStateFetchedTemplatesSelector, [
+                mockTmplSpc1
+              ])
+              mockStore.overrideSelector(viewerStateFetchedAtlasesSelector, [{
                 ['@id']: 'foo-bar',
-              }
+                templateSpaces: [ mockTmplSpc ]
+              }])
+              actions$ = hot('a', {
+                a: viewerStateSelectAtlas({
+                  atlas: {
+                    ['@id']: 'foo-bar',
+                  }
+                })
+              })
+              
+              const viewerSTateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect)
+              expect(
+                viewerSTateCtrlEffect.onSelectAtlasSelectTmplParc$
+              ).toBeObservable(
+                hot('a', {
+                  a: generalActionError({
+                    message: CONST.TEMPLATE_NOT_FOUND
+                  })
+                })
+              )
             })
-          })
           
-          const viewerSTateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect)
-          expect(
-            viewerSTateCtrlEffect.onSelectAtlasSelectTmplParc$
-          ).toBeObservable(
-            hot('a', {
-              a: generalActionError({
-                message: CONST.TEMPLATE_NOT_FOUND
+            it('> if succeeds, will dispatch new viewer', () => {
+              const completeMocktmpl = {
+                ...mockTmplSpc1,
+                parcellations: [ mockParc1 ]
+              }
+              mockStore.overrideSelector(viewerStateFetchedTemplatesSelector, [
+                completeMocktmpl
+              ])
+              mockStore.overrideSelector(viewerStateFetchedAtlasesSelector, [{
+                ['@id']: 'foo-bar',
+                templateSpaces: [ mockTmplSpc1 ]
+              }])
+              actions$ = hot('a', {
+                a: viewerStateSelectAtlas({
+                  atlas: {
+                    ['@id']: 'foo-bar',
+                  }
+                })
               })
+              
+              const viewerSTateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect)
+              expect(
+                viewerSTateCtrlEffect.onSelectAtlasSelectTmplParc$
+              ).toBeObservable(
+                hot('a', {
+                  a: viewerStateNewViewer({
+                    selectTemplate: completeMocktmpl,
+                    selectParcellation: mockParc1
+                  })
+                })
+              )
             })
-          )
-        })
       
-        it('> if succeeds, will dispatch new viewer', () => {
-          const completeMocktmpl = {
+          })
+        })
+
+        describe('> if template key val is provided', () => {
+
+          const completeMockTmpl = {
+            ...mockTmplSpc,
+            parcellations: [ mockParc0 ]
+          }
+          const completeMocktmpl1 = {
             ...mockTmplSpc1,
             parcellations: [ mockParc1 ]
           }
-          mockStore.overrideSelector(viewerStateFetchedTemplatesSelector, [
-            completeMocktmpl
-          ])
-          mockStore.overrideSelector(viewerStateFetchedAtlasesSelector, [{
-            ['@id']: 'foo-bar',
-            templateSpaces: [ mockTmplSpc1 ]
-          }])
-          actions$ = hot('a', {
-            a: viewerStateSelectAtlas({
-              atlas: {
-                ['@id']: 'foo-bar',
-              }
+          beforeEach(() => {
+
+            mockStore.overrideSelector(viewerStateFetchedTemplatesSelector, [
+              completeMockTmpl,
+              completeMocktmpl1,
+            ])
+            mockStore.overrideSelector(viewerStateFetchedAtlasesSelector, [{
+              ['@id']: 'foo-bar',
+              templateSpaces: [ mockTmplSpc, mockTmplSpc1 ]
+            }])
+          })
+          it('> will select template.@id', () => {
+
+            actions$ = hot('a', {
+              a: viewerStateSelectAtlas({
+                atlas: {
+                  ['@id']: 'foo-bar',
+                  template: {
+                    ['@id']: mockTmplSpc1['@id']
+                  }
+                }
+              })
             })
+            
+            const viewerSTateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect)
+            expect(
+              viewerSTateCtrlEffect.onSelectAtlasSelectTmplParc$
+            ).toBeObservable(
+              hot('a', {
+                a: viewerStateNewViewer({
+                  selectTemplate: completeMocktmpl1,
+                  selectParcellation: mockParc1
+                })
+              })
+            )
+
           })
           
-          const viewerSTateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect)
-          expect(
-            viewerSTateCtrlEffect.onSelectAtlasSelectTmplParc$
-          ).toBeObservable(
-            hot('a', {
-              a: viewerStateNewViewer({
-                selectTemplate: completeMocktmpl,
-                selectParcellation: mockParc1
+          it('> if template.@id is not defined, will fallback to first template', () => {
+
+            actions$ = hot('a', {
+              a: viewerStateSelectAtlas({
+                atlas: {
+                  ['@id']: 'foo-bar',
+                  template: {
+                    
+                  } as any
+                }
               })
             })
-          )
+
+            const viewerSTateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect)
+            expect(
+              viewerSTateCtrlEffect.onSelectAtlasSelectTmplParc$
+            ).toBeObservable(
+              hot('a', {
+                a: viewerStateNewViewer({
+                  selectTemplate: completeMockTmpl,
+                  selectParcellation: mockParc0
+                })
+              })
+            )
+
+          })
         })
       })
     })
-  
   })
 
   describe('> cvtNehubaConfigToNavigationObj', () => {
diff --git a/src/state/effects/viewerState.useEffect.ts b/src/state/effects/viewerState.useEffect.ts
index a26d43e2b76e9cc8256805f702e3a266041bb650..f865242c591578d6b8734b3bf28cf66abe954183 100644
--- a/src/state/effects/viewerState.useEffect.ts
+++ b/src/state/effects/viewerState.useEffect.ts
@@ -136,7 +136,8 @@ export class ViewerStateControllerUseEffect implements OnDestroy {
     ),
     map(([action, fetchedTemplates, fetchedAtlases ])=> {
 
-      const atlas = fetchedAtlases.find(a => a['@id'] === (action as any).atlas['@id'])
+      const { atlas: atlasObj } = action as any
+      const atlas = fetchedAtlases.find(a => a['@id'] === atlasObj['@id'])
       if (!atlas) {
         return generalActionError({
           message: CONST.ATLAS_NOT_FOUND
@@ -145,7 +146,12 @@ export class ViewerStateControllerUseEffect implements OnDestroy {
       /**
        * selecting atlas means selecting the first available templateSpace
        */
-      const templateTobeSelected = atlas.templateSpaces[0]
+      const targetTmplSpcId = atlasObj['template']?.['@id']
+      const templateTobeSelected = (
+        targetTmplSpcId
+        && atlas.templateSpaces.find(t => t['@id'] === targetTmplSpcId)
+      ) || atlas.templateSpaces[0]
+      
       const templateSpaceId = templateTobeSelected['@id']
       
       const parcellationId = (