diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts
index a1175088db8d56f6f0a879ef5c2e40b439db917e..9abbd42776480899103ff4b228616cccd1fd7f80 100644
--- a/src/atlasViewer/atlasViewer.component.ts
+++ b/src/atlasViewer/atlasViewer.component.ts
@@ -20,7 +20,7 @@ import { FixedMouseContextualContainerDirective } from "src/util/directives/Fixe
 import { DatabrowserService } from "src/ui/databrowserModule/databrowser.service";
 import { AGREE_COOKIE, AGREE_KG_TOS, SHOW_KG_TOS } from "src/services/state/uiState.store";
 import { TabsetComponent } from "ngx-bootstrap/tabs";
-import { ToastService } from "src/services/toastService.service";
+import { LocalFileService } from "src/services/localFile.service";
 
 /**
  * TODO
@@ -85,6 +85,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
   /* handlers for nglayer */
   /**
    * TODO make untangle nglayernames and its dependency on ng
+   * TODO deprecated
    */
   public ngLayerNames$ : Observable<any>
   public ngLayers : NgLayerInterface[]
@@ -109,9 +110,13 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     private modalService: BsModalService,
     private databrowserService: DatabrowserService,
     private dispatcher$: ActionsSubject,
-    private toastService: ToastService,
-    private rd: Renderer2
+    private rd: Renderer2,
+    public localFileService: LocalFileService
   ) {
+
+    /**
+     * TODO deprecated
+     */
     this.ngLayerNames$ = this.store.pipe(
       select('viewerState'),
       filter(state => isDefined(state) && isDefined(state.templateSelected)),
@@ -284,6 +289,9 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
       })
     )
 
+    /**
+     * TODO deprecated
+     */
     this.subscriptions.push(
       this.ngLayerNames$.pipe(
         concatMap(data => this.constantsService.loadExportNehubaPromise.then(data))
@@ -386,6 +394,10 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
         map(([_flag, onhoverLandmark]) => onhoverLandmark || [])
     )
 
+    /**
+     * TODO clean up code
+     * do not do this imperatively
+     */
     this.closeMenuWithSwipe(this.mobileSideNav)
   }
 
@@ -399,7 +411,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
   /**
    * perhaps move this to constructor?
    */
-  meetsRequirements() {
+  meetsRequirements():boolean {
 
     const canvas = document.createElement('canvas')
     const gl = canvas.getContext('webgl2') as WebGLRenderingContext
@@ -425,6 +437,9 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     return true
   }
 
+  /**
+   * TODO deprecated
+   */
   ngLayersChangeHandler(){
     this.ngLayers = (window['viewer'].layerManager.managedLayers as any[])
       // .filter(obj => obj.sourceUrl && /precomputed|nifti/.test(obj.sourceUrl))
@@ -450,12 +465,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     })
   }
 
-  panelAnimationEnd(){
-
-    if( this.nehubaContainer && this.nehubaContainer.nehubaViewer && this.nehubaContainer.nehubaViewer.nehubaViewer )
-      this.nehubaContainer.nehubaViewer.nehubaViewer.redraw()
-  }
-
   nehubaClickHandler(event:MouseEvent){
     if (!this.rClContextualMenu) return
     this.rClContextualMenu.mousePos = [
@@ -465,17 +474,18 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     this.rClContextualMenu.show()
   }
 
-  toggleSidePanel(panelName:string){
-    this.store.dispatch({
-      type : TOGGLE_SIDE_PANEL,
-      focusedSidePanel :panelName
-    })
-  }
-
   private selectedTemplate: any
   searchRegion(regions:any[]){
     this.rClContextualMenu.hide()
+
+    /**
+     * TODO move this to somewhere that makes sense, not in atlas viewer (? perhaps)
+     */
     this.databrowserService.queryData({ regions, parcellation: this.selectedParcellation, template: this.selectedTemplate })
+
+    /**
+     * TODO clean up code. do not do this imperically 
+     */
     if (this.isMobile) {
       this.store.dispatch({
         type : OPEN_SIDE_PANEL
@@ -492,6 +502,9 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
   @HostBinding('attr.version')
   public _version : string = VERSION
 
+  /**
+   * TODO deprecated
+   */
   changeMenuState({open, close}:{open?:boolean, close?:boolean} = {}) {
     if (open) {
       return this.store.dispatch({
@@ -508,6 +521,26 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     })
   }
 
+  /**
+   * TODO deprecated
+   */
+
+  toggleSidePanel(panelName:string){
+    this.store.dispatch({
+      type : TOGGLE_SIDE_PANEL,
+      focusedSidePanel :panelName
+    })
+  }
+
+  /**
+   * TODO deprecated
+   */
+  panelAnimationEnd(){
+    if( this.nehubaContainer && this.nehubaContainer.nehubaViewer && this.nehubaContainer.nehubaViewer.nehubaViewer ) {
+      this.nehubaContainer.nehubaViewer.nehubaViewer.redraw()
+    }
+  }
+
 
   closeMenuWithSwipe(documentToSwipe: ElementRef) {
     if (documentToSwipe && documentToSwipe.nativeElement) {
diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html
index 429869cf77846acea64457a1b8cdbf52c32b4ad5..d77828f903cc1b0856653f37d146e698c21426e5 100644
--- a/src/atlasViewer/atlasViewer.template.html
+++ b/src/atlasViewer/atlasViewer.template.html
@@ -122,7 +122,7 @@
 
 <!-- atlas template -->
 <ng-template #viewerBody>
-  <div class="atlas-container" helpdirective>
+  <div class="atlas-container" (drag-drop)="localFileService.handleFileDrop($event)" helpdirective>
 
     <ui-nehuba-container (contextmenu)="nehubaClickHandler($event)">
     </ui-nehuba-container>
diff --git a/src/atlasViewer/atlasViewer.urlService.service.ts b/src/atlasViewer/atlasViewer.urlService.service.ts
index 8506cf25f696ab09fbe69dd4647c8d650b5dc6de..118d5123f19854425acaa5a8f65eec612c08a584 100644
--- a/src/atlasViewer/atlasViewer.urlService.service.ts
+++ b/src/atlasViewer/atlasViewer.urlService.service.ts
@@ -229,7 +229,7 @@ export class AtlasViewerURLService{
       const niftiLayers = searchparams.get('niftiLayers')
       if(niftiLayers){
         const layers = niftiLayers.split('__')
-        /*  */
+
         layers.forEach(layer => this.store.dispatch({
           type : ADD_NG_LAYER, 
           layer : {
@@ -326,7 +326,11 @@ export class AtlasViewerURLService{
           return _
         })
       ),
-      this.additionalNgLayers$,
+      this.additionalNgLayers$.pipe(
+        map(layers => layers
+          .map(layer => layer.name)
+          .filter(layername => !/^blob\:/.test(layername)))
+      ),
       this.pluginState$
     ).pipe(
       /* TODO fix encoding of nifti path. if path has double underscore, this encoding will fail */
@@ -337,7 +341,7 @@ export class AtlasViewerURLService{
             ? Array.from(pluginState.initManifests.values()).filter(v => v !== null).join('__') 
             : null,
           niftiLayers : niftiLayers.length > 0
-            ? niftiLayers.map(layer => layer.name).join('__')
+            ? niftiLayers.join('__')
             : null
         }
       })
diff --git a/src/main.module.ts b/src/main.module.ts
index 1fb66d55c46ea77a249b850a7a349d681d4528f2..336718e137af62d3ec860a5c80cc6018876b6810 100644
--- a/src/main.module.ts
+++ b/src/main.module.ts
@@ -42,6 +42,8 @@ import { TransformOnhoverSegmentPipe } from "./atlasViewer/onhoverSegment.pipe";
 import {HttpClientModule} from "@angular/common/http";
 import { EffectsModule } from "@ngrx/effects";
 import { UseEffects } from "./services/effect/effect";
+import { DragDropDirective } from "./util/directives/dragDrop.directive";
+import { LocalFileService } from "./services/localFile.service";
 
 @NgModule({
   imports : [
@@ -91,6 +93,7 @@ import { UseEffects } from "./services/effect/effect";
     PluginFactoryDirective,
     FloatingMouseContextualContainerDirective,
     FixedMouseContextualContainerDirective,
+    DragDropDirective,
 
     /* pipes */
     GetNamesPipe,
@@ -113,6 +116,7 @@ import { UseEffects } from "./services/effect/effect";
     ToastService,
     AtlasWorkerService,
     AuthService,
+    LocalFileService,
     
     /**
      * TODO
diff --git a/src/services/localFile.service.ts b/src/services/localFile.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d38450012e3d7a8e4ea680f483c27c52a4ea4013
--- /dev/null
+++ b/src/services/localFile.service.ts
@@ -0,0 +1,91 @@
+import { Injectable } from "@angular/core";
+import { MatSnackBar } from "@angular/material";
+import { DatabrowserService } from "src/ui/databrowserModule/databrowser.service";
+
+/**
+ * experimental service handling local user files such as nifti and gifti
+ */
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class LocalFileService {
+  public SUPPORTED_EXT = SUPPORTED_EXT
+  private supportedExtSet = new Set(SUPPORTED_EXT)
+
+  constructor(
+    private snackBar: MatSnackBar,
+    private dbService: DatabrowserService  
+  ){
+
+  }
+
+  private niiUrl
+
+  public handleFileDrop(files: File[]){
+    try {
+      this.validateDrop(files)
+      for (const file of files) {
+        const ext = this.getExtension(file.name)
+        switch (ext) {
+          case NII: {
+            this.handleNiiFile(file)
+            break;
+          }
+          default:
+            throw new Error(`File ${file.name} does not have a file handler`)
+        }
+      }
+    } catch (e) {
+      this.snackBar.open(e, `Dismiss`, {
+        duration: 5000
+      })
+      console.error(e)
+    }
+  }
+
+  private getExtension(filename:string) {
+    const match = /(\.\w*?)$/i.exec(filename)
+    return (match && match[1]) || ''
+  }
+
+  private validateDrop(files: File[]){
+    if (files.length !== 1) {
+      throw new Error('Interactive atlas viewer currently only supports drag and drop of one file at a time')
+    }
+    for (const file of files) {
+      const ext = this.getExtension(file.name)
+      if (!this.supportedExtSet.has(ext)) {
+        throw new Error(`File ${file.name}${ext === '' ? ' ' : (' with extension ' + ext)} cannot be loaded. The supported extensions are: ${this.SUPPORTED_EXT.join(', ')}`)
+      }
+    }
+  }
+
+  private handleNiiFile(file: File){
+
+    if (this.niiUrl) {
+      URL.revokeObjectURL(this.niiUrl)
+    }
+    this.niiUrl = URL.createObjectURL(file)
+    this.dbService.showNewNgLayer({
+      url: this.niiUrl
+    })
+
+    this.showLocalWarning()
+  }
+
+  private showLocalWarning() {
+    this.snackBar.open(`Warning: sharing URL will not share the loaded local file`, 'Dismiss', {
+      duration: 5000
+    })
+  }
+}
+
+const NII = `.nii`
+const GII = '.gii'
+
+const SUPPORTED_EXT = [
+  NII,
+  GII
+]
\ No newline at end of file
diff --git a/src/ui/sharedModules/angularMaterial.module.ts b/src/ui/sharedModules/angularMaterial.module.ts
index b63e2dfaa5a37e5caa567efebe4bf1c135e00a4b..707c71a085245706c04898b9b2935b34432b3b0a 100644
--- a/src/ui/sharedModules/angularMaterial.module.ts
+++ b/src/ui/sharedModules/angularMaterial.module.ts
@@ -4,12 +4,13 @@ import {
   MatSidenavModule,
   MatCardModule,
   MatTabsModule,
-  MatTooltipModule
+  MatTooltipModule,
+  MatSnackBarModule
 } from '@angular/material';
 import { NgModule } from '@angular/core';
 
 @NgModule({
-  imports: [MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
-  exports: [MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
+  imports: [MatSnackBarModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
+  exports: [MatSnackBarModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule],
 })
 export class AngularMaterialModule { }
\ No newline at end of file
diff --git a/src/util/directives/dragDrop.directive.ts b/src/util/directives/dragDrop.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..83895d5f0c4b81e472eb3e6c714697037986e109
--- /dev/null
+++ b/src/util/directives/dragDrop.directive.ts
@@ -0,0 +1,89 @@
+import { Directive, Input, Output, EventEmitter, HostListener, ElementRef, OnInit, OnDestroy, HostBinding } from "@angular/core";
+import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from "@angular/material";
+import { Observable, fromEvent, merge, Subscription, of, from } from "rxjs";
+import { map, scan, distinctUntilChanged, debounceTime, tap, switchMap, takeUntil } from "rxjs/operators";
+
+@Directive({
+  selector: '[drag-drop]'
+})
+
+export class DragDropDirective implements OnInit, OnDestroy{
+
+  @Input()
+  snackText: string
+
+  @Output('drag-drop')
+  dragDropOnDrop: EventEmitter<File[]> = new EventEmitter()
+
+  @HostBinding('style.transition')
+  transition = `opacity 300ms ease-in`
+
+  @HostBinding('style.opacity')
+  opacity = null
+
+  public snackbarRef: MatSnackBarRef<SimpleSnackBar>
+
+  private dragover$: Observable<boolean>
+
+  @HostListener('dragover', ['$event'])
+  ondragover(ev:DragEvent){
+    ev.preventDefault()
+  }
+
+  @HostListener('drop', ['$event'])
+  ondrop(ev:DragEvent) {
+    ev.preventDefault()
+    this.reset()
+
+    this.dragDropOnDrop.emit(Array.from(ev.dataTransfer.files))
+  }
+
+  reset(){
+    if (this.snackbarRef) {
+      this.snackbarRef.dismiss()
+    }
+    this.opacity = null
+  }
+
+  private subscriptions: Subscription[] = []
+
+  ngOnInit(){
+    this.subscriptions.push(
+      this.dragover$.pipe(
+        debounceTime(16)
+      ).subscribe(flag => {
+        if (flag) {
+          this.snackbarRef = this.snackBar.open(this.snackText || `Drop file(s) here.`)
+          this.opacity = 0.2
+        } else {
+          this.reset()
+        }
+      })
+    )
+  }
+
+  ngOnDestroy(){
+    while(this.subscriptions.length > 0) {
+      this.subscriptions.pop().unsubscribe()
+    }
+  }
+
+  constructor(private snackBar: MatSnackBar, private el:ElementRef){
+    this.dragover$ = merge(
+      of(null),
+      fromEvent(this.el.nativeElement, 'drop')
+    ).pipe(
+      switchMap(() => merge(
+        fromEvent(this.el.nativeElement, 'dragenter').pipe(
+          map(() => 1)
+        ),
+        fromEvent(this.el.nativeElement, 'dragleave').pipe(
+          map(() => -1)
+        )
+      ).pipe(
+        scan((acc, curr) => acc + curr, 0),
+        map(val => val > 0)
+      ))
+    )
+  }
+}
\ No newline at end of file