diff --git a/client/src/app/admin/instance/dataset/components/file/add-file.component.html b/client/src/app/admin/instance/dataset/components/file/add-file.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..c43bbc8910f7b1a8f0f1411c85f898363b62c3de
--- /dev/null
+++ b/client/src/app/admin/instance/dataset/components/file/add-file.component.html
@@ -0,0 +1,30 @@
+<div class="row mb-1">
+    <div class="col-12">
+        <button title="Add new file" (click)="openModal(template); $event.stopPropagation()" class="btn btn-outline-success">
+            <span class="fas fa-plus"></span> New file
+        </button>
+    </div>
+</div>
+
+<ng-template #template>
+    <div class="modal-header">
+        <h4 class="modal-title pull-left"><strong>Add new file</strong></h4>
+    </div>
+    <div class="modal-body">
+        <app-file-form 
+            [instance]="instance"
+            [dataset]="dataset"
+            [files]="files"
+            [filesIsLoading]="filesIsLoading"
+            [filesIsLoaded]="filesIsLoaded"
+            (loadRootDirectory)="loadRootDirectory.emit($event)"
+            (onSubmit)="add.emit($event); modalRef.hide()"
+            #formNewFile>
+            <button [disabled]="!formNewFile.form.valid || formNewFile.form.pristine" type="submit" class="btn btn-primary">
+                <span class="fa fa-database"></span> Add file
+            </button>
+            &nbsp;
+            <button (click)="modalRef.hide()" type="button" class="btn btn-danger">Cancel</button>
+        </app-file-form>
+    </div>
+</ng-template>
diff --git a/client/src/app/admin/instance/dataset/components/file/add-file.component.ts b/client/src/app/admin/instance/dataset/components/file/add-file.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b89acd24d72abd21c8478e46f907eb24d9e51c96
--- /dev/null
+++ b/client/src/app/admin/instance/dataset/components/file/add-file.component.ts
@@ -0,0 +1,39 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component, ChangeDetectionStrategy, TemplateRef, Input, Output, EventEmitter } from '@angular/core';
+
+import { BsModalService } from 'ngx-bootstrap/modal';
+import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
+
+import { Dataset, File, Instance } from 'src/app/metamodel/models';
+import { FileInfo } from 'src/app/admin/store/models';
+
+@Component({
+    selector: 'app-add-file',
+    templateUrl: 'add-file.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class AddFileComponent {
+    @Input() instance: Instance;
+    @Input() dataset: Dataset;
+    @Input() files: FileInfo[];
+    @Input() filesIsLoading: boolean;
+    @Input() filesIsLoaded: boolean;
+    @Output() loadRootDirectory: EventEmitter<string> = new EventEmitter();
+    @Output() add: EventEmitter<File> = new EventEmitter();
+
+    modalRef: BsModalRef;
+
+    constructor(private modalService: BsModalService) { }
+
+    openModal(template: TemplateRef<any>) {
+        this.modalRef = this.modalService.show(template);
+    }
+}
diff --git a/client/src/app/admin/instance/dataset/components/file/edit-file.component.html b/client/src/app/admin/instance/dataset/components/file/edit-file.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..4323f423cec71dc2c948a0841091326013e22c9c
--- /dev/null
+++ b/client/src/app/admin/instance/dataset/components/file/edit-file.component.html
@@ -0,0 +1,27 @@
+<button title="Edit this file" (click)="openModal(template); $event.stopPropagation()" class="btn btn-outline-primary">
+    <span class="fas fa-edit"></span>
+</button>
+
+<ng-template #template>
+    <div class="modal-header">
+        <h4 class="modal-title pull-left"><strong>Edit file</strong></h4>
+    </div>
+    <div class="modal-body">
+        <app-file-form 
+            [file]="file"
+            [instance]="instance"
+            [dataset]="dataset"
+            [files]="files"
+            [filesIsLoading]="filesIsLoading"
+            [filesIsLoaded]="filesIsLoaded"
+            (loadRootDirectory)="loadRootDirectory.emit($event)"
+            (onSubmit)="edit.emit($event); modalRef.hide()"
+            #formEditFile>
+            <button [disabled]="!formEditFile.form.valid || formEditFile.form.pristine" type="submit" class="btn btn-primary">
+                <span class="fa fa-database"></span> Update file information
+            </button>
+            &nbsp;
+            <button (click)="modalRef.hide()" type="button" class="btn btn-danger">Cancel</button>
+        </app-file-form>
+    </div>
+</ng-template>
diff --git a/client/src/app/admin/instance/dataset/components/file/edit-file.component.ts b/client/src/app/admin/instance/dataset/components/file/edit-file.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..927611edb3bca3785af265ec3cb109b468766d7a
--- /dev/null
+++ b/client/src/app/admin/instance/dataset/components/file/edit-file.component.ts
@@ -0,0 +1,40 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component, Input, ChangeDetectionStrategy, TemplateRef, Output, EventEmitter } from '@angular/core';
+
+import { BsModalService } from 'ngx-bootstrap/modal';
+import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
+
+import { Dataset, File, Instance } from 'src/app/metamodel/models';
+import { FileInfo } from 'src/app/admin/store/models';
+
+@Component({
+    selector: 'app-edit-file',
+    templateUrl: 'edit-file.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class EditFileComponent {
+    @Input() file: File;
+    @Input() instance: Instance;
+    @Input() dataset: Dataset;
+    @Input() files: FileInfo[];
+    @Input() filesIsLoading: boolean;
+    @Input() filesIsLoaded: boolean;
+    @Output() loadRootDirectory: EventEmitter<string> = new EventEmitter();
+    @Output() edit: EventEmitter<File> = new EventEmitter();
+
+    modalRef: BsModalRef;
+
+    constructor(private modalService: BsModalService) { }
+
+    openModal(template: TemplateRef<any>) {
+        this.modalRef = this.modalService.show(template);
+    }
+}
diff --git a/client/src/app/admin/instance/dataset/components/file/file-form.component.html b/client/src/app/admin/instance/dataset/components/file/file-form.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..62e7075142d337137bf8df9a43253dfad1c99712
--- /dev/null
+++ b/client/src/app/admin/instance/dataset/components/file/file-form.component.html
@@ -0,0 +1,28 @@
+<form [formGroup]="form" (ngSubmit)="submit()" novalidate>
+    <app-path-select-form-control
+        [form]="form"
+        [controlName]="'file_path'"
+        [controlLabel]="'File path'"
+        [files]="files"
+        [filesIsLoading]="filesIsLoading"
+        [filesIsLoaded]="filesIsLoaded"
+        [selectType]="'file'"
+        (loadDirectory)="onChangeFileSelect($event)"
+        (select)="onFileSelect($event)">
+    </app-path-select-form-control>
+    <div class="form-group">
+        <label for="label">Label</label>
+        <input type="text" class="form-control" id="label" name="label" formControlName="label">
+    </div>
+    <div class="form-group">
+        <label for="file_size">File size</label>
+        <input type="number" class="form-control" id="file_size" name="file_size" formControlName="file_size">
+    </div>
+    <div class="form-group">
+        <label for="type">Type</label>
+        <input type="text" class="form-control" id="type" name="type" formControlName="type">
+    </div>
+    <div class="form-group">
+        <ng-content></ng-content>
+    </div>
+</form>
\ No newline at end of file
diff --git a/client/src/app/admin/instance/dataset/components/file/file-form.component.ts b/client/src/app/admin/instance/dataset/components/file/file-form.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..04e83901ba876a97ddf191d82c59ac08647c509f
--- /dev/null
+++ b/client/src/app/admin/instance/dataset/components/file/file-form.component.ts
@@ -0,0 +1,61 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core';
+import { FormGroup, FormControl, Validators } from '@angular/forms';
+
+import { File, Dataset, Instance } from 'src/app/metamodel/models';
+import { FileInfo } from 'src/app/admin/store/models';
+
+@Component({
+    selector: 'app-file-form',
+    templateUrl: 'file-form.component.html'
+})
+export class FileFormComponent implements OnInit {
+    @Input() file: File;
+    @Input() instance: Instance;
+    @Input() dataset: Dataset;
+    @Input() files: FileInfo[];
+    @Input() filesIsLoading: boolean;
+    @Input() filesIsLoaded: boolean;
+    @Output() loadRootDirectory: EventEmitter<string> = new EventEmitter();
+    @Output() onSubmit: EventEmitter<File> = new EventEmitter();
+
+    public form = new FormGroup({
+        label: new FormControl('', [Validators.required]),
+        file_path: new FormControl('', [Validators.required]),
+        file_size: new FormControl('', [Validators.required]),
+        type: new FormControl('', [Validators.required])
+    });
+
+    ngOnInit() {
+        if (this.file) {
+            this.form.patchValue(this.file);
+        }
+    }
+
+    onChangeFileSelect(path: string) {
+        this.loadRootDirectory.emit(`${this.instance.data_path}${this.dataset.data_path}${path}`);
+    }
+
+    onFileSelect(fileInfo: FileInfo) {
+        this.form.controls.file_size.setValue(fileInfo.size);
+    }
+
+    submit() {
+        if (this.file) {
+            this.onSubmit.emit({
+                ...this.file,
+                ...this.form.getRawValue()
+            });
+        } else {
+            this.onSubmit.emit(this.form.getRawValue());
+        }
+    }
+}
diff --git a/client/src/app/admin/instance/dataset/components/file/file-list.component.html b/client/src/app/admin/instance/dataset/components/file/file-list.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..c4d3ed92d36c9c27a261f0159f2aafe28389529a
--- /dev/null
+++ b/client/src/app/admin/instance/dataset/components/file/file-list.component.html
@@ -0,0 +1,59 @@
+<app-add-file 
+    [instance]="instance"
+    [dataset]="dataset"
+    [files]="files"
+    [filesIsLoading]="filesIsLoading"
+    [filesIsLoaded]="filesIsLoaded"
+    (loadRootDirectory)="loadRootDirectory.emit($event)"
+    (add)="add.emit($event)">
+</app-add-file>
+
+<div class="row" *ngIf="fileList.length === 0">
+    <div class="col-12 lead text-center font-weight-bold">
+        No files available...
+    </div>
+</div>
+
+<div *ngIf="fileList.length > 0" class="table-responsive">
+    <table class="table table-striped" aria-describedby="Image list">
+        <thead>
+            <tr>
+                <th scope="col">ID</th>
+                <th scope="col">Label</th>
+                <th scope="col">File path</th>
+                <th scope="col">File size</th>
+                <th scope="col">Type</th>
+                <th scope="col">Edit</th>
+                <th scope="col">Delete</th>
+            </tr>
+        </thead>
+        <tbody>
+            <tr *ngFor="let file of fileList">
+                <td class="align-middle">{{ file.id }}</td>
+                <td class="align-middle">{{ file.label }}</td>
+                <td class="align-middle">{{ file.file_path }}</td>
+                <td class="align-middle">{{ file.file_size | formatFileSize:false }}</td>
+                <td class="align-middle">{{ file.type }}</td>
+                <td class="align-middle">
+                    <app-edit-file
+                        [file]="file"
+                        [instance]="instance"
+                        [dataset]="dataset"
+                        [files]="files"
+                        [filesIsLoading]="filesIsLoading"
+                        [filesIsLoaded]="filesIsLoaded"
+                        (loadRootDirectory)="loadRootDirectory.emit($event)"
+                        (edit)="edit.emit($event)">
+                    </app-edit-file>
+                </td>
+                <td class="align-middle">
+                    <app-delete-btn
+                        [type]="'file'"
+                        [label]="file.label"
+                        (confirm)="delete.emit(file)">
+                    </app-delete-btn>
+                </td>
+            </tr>
+        </tbody>
+    </table>
+</div>
\ No newline at end of file
diff --git a/client/src/app/admin/instance/dataset/components/file/file-list.component.ts b/client/src/app/admin/instance/dataset/components/file/file-list.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5d4ec5402c2be5c28179c07ef57b001c3e6a03d3
--- /dev/null
+++ b/client/src/app/admin/instance/dataset/components/file/file-list.component.ts
@@ -0,0 +1,22 @@
+import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
+
+import { Dataset, File, Instance } from 'src/app/metamodel/models';
+import { FileInfo } from 'src/app/admin/store/models';
+
+@Component({
+    selector: 'app-file-list',
+    templateUrl: 'file-list.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class FileListComponent {
+    @Input() fileList: File[];
+    @Input() instance: Instance;
+    @Input() dataset: Dataset;
+    @Input() files: FileInfo[];
+    @Input() filesIsLoading: boolean;
+    @Input() filesIsLoaded: boolean;
+    @Output() loadRootDirectory: EventEmitter<string> = new EventEmitter();
+    @Output() add: EventEmitter<File> = new EventEmitter();
+    @Output() edit: EventEmitter<File> = new EventEmitter();
+    @Output() delete: EventEmitter<File> = new EventEmitter();
+}
diff --git a/client/src/app/admin/instance/dataset/components/file/index.ts b/client/src/app/admin/instance/dataset/components/file/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2c48a5fc6b29a5548f6a4f83b8e193116a63be58
--- /dev/null
+++ b/client/src/app/admin/instance/dataset/components/file/index.ts
@@ -0,0 +1,11 @@
+import { FileListComponent } from './file-list.component';
+import { FileFormComponent } from './file-form.component';
+import { AddFileComponent } from './add-file.component';
+import { EditFileComponent } from './edit-file.component';
+
+export const fileComponents = [
+    FileListComponent,
+    FileFormComponent,
+    AddFileComponent,
+    EditFileComponent
+];
diff --git a/client/src/app/admin/instance/dataset/components/index.ts b/client/src/app/admin/instance/dataset/components/index.ts
index 539033b11b2a14f59a55282faf5c3774763736e6..b28b04af274518cf75b5d4a0ffaf4e3e91654f87 100644
--- a/client/src/app/admin/instance/dataset/components/index.ts
+++ b/client/src/app/admin/instance/dataset/components/index.ts
@@ -12,6 +12,7 @@ import { criteriaFamilyComponents } from './criteria-family';
 import { datasetComponents } from './dataset';
 import { datasetFamilyComponents } from './dataset-family';
 import { imageComponents } from './image';
+import { fileComponents } from './file';
 import { coneSearchConfigComponents } from './cone-search-config';
 import { outputCategoryComponents } from './output-category';
 import { outputFamilyComponents } from './output-family';
@@ -23,6 +24,7 @@ export const dummiesComponents = [
     datasetComponents,
     datasetFamilyComponents,
     imageComponents,
+    fileComponents,
     coneSearchConfigComponents,
     outputCategoryComponents,
     outputFamilyComponents,
diff --git a/client/src/app/admin/instance/dataset/containers/configure-dataset.component.html b/client/src/app/admin/instance/dataset/containers/configure-dataset.component.html
index e584c3b1a23ff22c8aa5668d83e2538910621e51..a556cfeec2fc0a658f1450919f778ed597696ed9 100644
--- a/client/src/app/admin/instance/dataset/containers/configure-dataset.component.html
+++ b/client/src/app/admin/instance/dataset/containers/configure-dataset.component.html
@@ -71,6 +71,9 @@
                         <li class="nav-item">
                             <a class="nav-link" routerLink="./" [queryParams]="{tab_selected: 'images'}" [ngClass]="{'active': (tabSelected | async) === 'images'}">Images</a>
                         </li>
+                        <li class="nav-item">
+                            <a class="nav-link" routerLink="./" [queryParams]="{tab_selected: 'files'}" [ngClass]="{'active': (tabSelected | async) === 'files'}">Files</a>
+                        </li>
                         <li class="nav-item">
                             <a class="nav-link" routerLink="./" [queryParams]="{tab_selected: 'cone-search'}" [ngClass]="{'active': (tabSelected | async) === 'cone-search'}">Cone-search</a>
                         </li>
@@ -178,6 +181,19 @@
                         (edit)="editImage($event)"
                         (delete)="deleteImage($event)">
                     </app-image-list>
+                    <app-file-list
+                        *ngSwitchCase="'files'"
+                        [fileList]="fileList | async"
+                        [instance]="instance | async"
+                        [dataset]="dataset | async"
+                        [files]="files | async"
+                        [filesIsLoading]="filesIsLoading | async"
+                        [filesIsLoaded]="filesIsLoaded | async"
+                        (loadRootDirectory)="loadRootDirectory($event)"
+                        (add)="addFile($event)"
+                        (edit)="editFile($event)"
+                        (delete)="deleteFile($event)">
+                    </app-file-list>
                     <app-cone-search-config
                         *ngSwitchCase="'cone-search'"
                         [dataset]="dataset | async"
diff --git a/client/src/app/admin/instance/dataset/containers/configure-dataset.component.ts b/client/src/app/admin/instance/dataset/containers/configure-dataset.component.ts
index 0b6e800da65824a360252e07282a87388b32fa7e..25e9fe5ffc246d11c9fc5cf5cd123d32a5461342 100644
--- a/client/src/app/admin/instance/dataset/containers/configure-dataset.component.ts
+++ b/client/src/app/admin/instance/dataset/containers/configure-dataset.component.ts
@@ -14,7 +14,7 @@ import { Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
 import { Store } from '@ngrx/store';
 
-import { SelectOption, Instance, Dataset, Attribute, CriteriaFamily, OutputCategory, OutputFamily, Image, ConeSearchConfig } from 'src/app/metamodel/models';
+import { SelectOption, Instance, Dataset, Attribute, CriteriaFamily, OutputCategory, OutputFamily, Image, File, ConeSearchConfig } from 'src/app/metamodel/models';
 import { Column, FileInfo, FitsImageLimits } from 'src/app/admin/store/models';
 import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
 import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector';
@@ -33,6 +33,8 @@ import * as attributeDistinctActions from 'src/app/admin/store/actions/attribute
 import * as attributeDistinctSelector from 'src/app/admin/store/selectors/attribute-distinct.selector';
 import * as imageActions from 'src/app/metamodel/actions/image.actions';
 import * as imageSelector from 'src/app/metamodel/selectors/image.selector';
+import * as fileActions from 'src/app/metamodel/actions/file.actions';
+import * as fileSelector from 'src/app/metamodel/selectors/file.selector';
 import * as adminFileExplorerActions from 'src/app/admin/store/actions/admin-file-explorer.actions';
 import * as adminFileExplorerSelector from 'src/app/admin/store/selectors/admin-file-explorer.selector';
 import * as fitsImageActions from 'src/app/admin/store/actions/fits-image.actions';
@@ -75,6 +77,9 @@ export class ConfigureDatasetComponent implements OnInit {
     public imageList: Observable<Image[]>;
     public imageListIsLoading: Observable<boolean>;
     public imageListIsLoaded: Observable<boolean>;
+    public fileList: Observable<File[]>;
+    public fileListIsLoading: Observable<boolean>;
+    public fileListIsLoaded: Observable<boolean>;
     public files: Observable<FileInfo[]>;
     public filesIsLoading: Observable<boolean>;
     public filesIsLoaded: Observable<boolean>;
@@ -114,6 +119,9 @@ export class ConfigureDatasetComponent implements OnInit {
         this.imageList = store.select(imageSelector.selectAllImages);
         this.imageListIsLoading = store.select(imageSelector.selectImageListIsLoading);
         this.imageListIsLoaded = store.select(imageSelector.selectImageListIsLoaded);
+        this.fileList = store.select(fileSelector.selectAllFiles);
+        this.fileListIsLoading = store.select(fileSelector.selectFileListIsLoading);
+        this.fileListIsLoaded = store.select(fileSelector.selectFileListIsLoaded);
         this.files = store.select(adminFileExplorerSelector.selectFiles);
         this.filesIsLoading = store.select(adminFileExplorerSelector.selectFilesIsLoading);
         this.filesIsLoaded = store.select(adminFileExplorerSelector.selectFilesIsLoaded);
@@ -217,6 +225,18 @@ export class ConfigureDatasetComponent implements OnInit {
         this.store.dispatch(imageActions.deleteImage({ image }));
     }
 
+    addFile(file: File) {
+        this.store.dispatch(fileActions.addFile({ file }));
+    }
+
+    editFile(file: File) {
+        this.store.dispatch(fileActions.editFile({ file }));
+    }
+
+    deleteFile(file: File) {
+        this.store.dispatch(fileActions.deleteFile({ file }));
+    }
+
     addConeSearchConfig(coneSearchConfig: ConeSearchConfig) {
         this.store.dispatch(coneSearchConfigActions.addConeSearchConfig({ coneSearchConfig }));
     }
diff --git a/client/src/app/metamodel/actions/file.actions.ts b/client/src/app/metamodel/actions/file.actions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3c8b0b0bfc4f6dae6b0589a8b3293ec6f2d6372e
--- /dev/null
+++ b/client/src/app/metamodel/actions/file.actions.ts
@@ -0,0 +1,25 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { createAction, props } from '@ngrx/store';
+ 
+import { File } from '../models';
+
+export const loadFileList = createAction('[Metamodel] Load File List');
+export const loadFileListSuccess = createAction('[Metamodel] Load File List Success', props<{ files: File[] }>());
+export const loadFileListFail = createAction('[Metamodel] Load File List Fail');
+export const addFile = createAction('[Metamodel] Add File', props<{ file: File }>());
+export const addFileSuccess = createAction('[Metamodel] Add File Success', props<{ file: File }>());
+export const addFileFail = createAction('[Metamodel] Add File Fail');
+export const editFile = createAction('[Metamodel] Edit File', props<{ file: File }>());
+export const editFileSuccess = createAction('[Metamodel] Edit File Success', props<{ file: File }>());
+export const editFileFail = createAction('[Metamodel] Edit File Fail');
+export const deleteFile = createAction('[Metamodel] Delete File', props<{ file: File }>());
+export const deleteFileSuccess = createAction('[Metamodel] Delete File Success', props<{ file: File }>());
+export const deleteFileFail = createAction('[Metamodel] Delete File Fail');
\ No newline at end of file
diff --git a/client/src/app/metamodel/effects/file.effects.ts b/client/src/app/metamodel/effects/file.effects.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1923f3af2b5d07eb604aae3f1d78f61422f84df5
--- /dev/null
+++ b/client/src/app/metamodel/effects/file.effects.ts
@@ -0,0 +1,156 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Injectable } from '@angular/core';
+
+import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects';
+import { Store } from '@ngrx/store';
+import { of } from 'rxjs';
+import { map, tap, mergeMap, catchError } from 'rxjs/operators';
+import { ToastrService } from 'ngx-toastr';
+
+import * as fileActions from '../actions/file.actions';
+import { FileService } from '../services/file.service';
+import * as datasetSelector from '../selectors/dataset.selector';
+
+/**
+ * @class
+ * @classdesc File effects.
+ */
+@Injectable()
+export class FileEffects {
+    /**
+     * Calls action to retrieve file list.
+     */
+    loadFiles$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(fileActions.loadFileList),
+            concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
+            mergeMap(([, datasetName]) => this.fileService.retrieveFileList(datasetName)
+                .pipe(
+                    map(files => fileActions.loadFileListSuccess({ files })),
+                    catchError(() => of(fileActions.loadFileListFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Calls action to add an file.
+     */
+    addFile$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(fileActions.addFile),
+            concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
+            mergeMap(([action, datasetName]) => this.fileService.addFile(datasetName, action.file)
+                .pipe(
+                    map(file => fileActions.addFileSuccess({ file })),
+                    catchError(() => of(fileActions.addFileFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Displays add file success notification.
+     */
+    addFileSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(fileActions.addFileSuccess),
+            tap(() => this.toastr.success('File successfully added', 'The new file was added into the database'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Displays add file error notification.
+     */
+    addFileFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(fileActions.addFileFail),
+            tap(() => this.toastr.error('Failure to add file', 'The new file could not be added into the database'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Calls action to modify an file.
+     */
+    editFile$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(fileActions.editFile),
+            mergeMap(action => this.fileService.editFile(action.file)
+                .pipe(
+                    map(file => fileActions.editFileSuccess({ file })),
+                    catchError(() => of(fileActions.editFileFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Displays edit file success notification.
+     */
+    editFileSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(fileActions.editFileSuccess),
+            tap(() => this.toastr.success('File successfully edited', 'The existing file has been edited into the database'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Displays edit file error notification.
+     */
+    editFileFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(fileActions.editFileFail),
+            tap(() => this.toastr.error('Failure to edit file', 'The existing file could not be edited into the database'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Calls action to remove an file.
+     */
+    deleteFile$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(fileActions.deleteFile),
+            mergeMap(action => this.fileService.deleteFile(action.file.id)
+                .pipe(
+                    map(() => fileActions.deleteFileSuccess({ file: action.file })),
+                    catchError(() => of(fileActions.deleteFileFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Displays delete file success notification.
+     */
+    deleteFileSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(fileActions.deleteFileSuccess),
+            tap(() => this.toastr.success('File successfully deleted', 'The existing file has been deleted'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Displays delete file error notification.
+     */
+    deleteFileFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(fileActions.deleteFileFail),
+            tap(() => this.toastr.error('Failure to delete file', 'The existing file could not be deleted from the database'))
+        ), { dispatch: false }
+    );
+
+    constructor(
+        private actions$: Actions,
+        private fileService: FileService,
+        private toastr: ToastrService,
+        private store: Store<{ }>
+    ) {}
+}
diff --git a/client/src/app/metamodel/effects/index.ts b/client/src/app/metamodel/effects/index.ts
index 61eaad29e53e1ab5b57e15f5a93f911a56b6103a..64961cd4777e5230daa9cf0592578673aa4632d9 100644
--- a/client/src/app/metamodel/effects/index.ts
+++ b/client/src/app/metamodel/effects/index.ts
@@ -20,6 +20,7 @@ import { OutputFamilyEffects } from './output-family.effects';
 import { SelectEffects } from './select.effects';
 import { SelectOptionEffects } from './select-option.effects';
 import { ImageEffects } from './image.effects';
+import { FileEffects } from './file.effects';
 import { ConeSearchConfigEffects } from './cone-search-config.effects'
 
 export const metamodelEffects = [
@@ -36,5 +37,6 @@ export const metamodelEffects = [
     SelectEffects,
     SelectOptionEffects,
     ImageEffects,
+    FileEffects,
     ConeSearchConfigEffects
 ];
diff --git a/client/src/app/metamodel/metamodel.reducer.ts b/client/src/app/metamodel/metamodel.reducer.ts
index 3ea7e83aecb421c5cef5191a31750e5e83810634..8bbd0bc2c3cfd6709c92fc65c513a057dd117206 100644
--- a/client/src/app/metamodel/metamodel.reducer.ts
+++ b/client/src/app/metamodel/metamodel.reducer.ts
@@ -23,6 +23,7 @@ import * as outputFamily from './reducers/output-family.reducer';
 import * as select from './reducers/select.reducer';
 import * as selectOption from './reducers/select-option.reducer';
 import * as image from './reducers/image.reducer';
+import * as file from './reducers/file.reducer';
 import * as coneSearchConfig from './reducers/cone-search-config.reducer';
 
 /**
@@ -44,6 +45,7 @@ export interface State {
     select: select.State;
     selectOption: selectOption.State;
     image: image.State;
+    file: file.State;
     coneSearchConfig: coneSearchConfig.State;
 }
 
@@ -61,6 +63,7 @@ const reducers = {
     select: select.selectReducer,
     selectOption: selectOption.selectOptionReducer,
     image: image.imageReducer,
+    file: file.fileReducer,
     coneSearchConfig: coneSearchConfig.coneSearchConfigReducer
 };
 
diff --git a/client/src/app/metamodel/models/file.model.ts b/client/src/app/metamodel/models/file.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..805d4cf17737b2956540930bac7ee468ddaf4a9f
--- /dev/null
+++ b/client/src/app/metamodel/models/file.model.ts
@@ -0,0 +1,21 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface for file
+ *
+ * @interface File
+ */
+export interface File {
+    id: number;
+    label: string;
+    file_path: string;
+    file_size: number;
+    type: string;
+}
diff --git a/client/src/app/metamodel/models/index.ts b/client/src/app/metamodel/models/index.ts
index fc907f1cfc10aceec322ce3cd9ae85b520357b23..e0f6870def5b2349a3d201e7e714c8cdef12f93d 100644
--- a/client/src/app/metamodel/models/index.ts
+++ b/client/src/app/metamodel/models/index.ts
@@ -24,3 +24,4 @@ export * from './image.model';
 export * from './renderers';
 export * from './detail-renderers';
 export * from './cone-search-config.model';
+export * from './file.model';
\ No newline at end of file
diff --git a/client/src/app/metamodel/reducers/file.reducer.ts b/client/src/app/metamodel/reducers/file.reducer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fd7c428c4e1f5fb8bcd1dcb57128385e8938ae1f
--- /dev/null
+++ b/client/src/app/metamodel/reducers/file.reducer.ts
@@ -0,0 +1,84 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { createReducer, on } from '@ngrx/store';
+import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
+
+import { File } from '../models';
+import * as fileActions from '../actions/file.actions';
+
+/**
+ * Interface for file state.
+ *
+ * @interface State
+ */
+export interface State extends EntityState<File> {
+    fileListIsLoading: boolean;
+    fileListIsLoaded: boolean;
+}
+
+export const adapter: EntityAdapter<File> = createEntityAdapter<File>({
+    selectId: (file: File) => file.id,
+    sortComparer: (a: File, b: File) => a.id - b.id
+});
+
+export const initialState: State = adapter.getInitialState({
+    fileListIsLoading: false,
+    fileListIsLoaded: false
+});
+
+export const fileReducer = createReducer(
+    initialState,
+    on(fileActions.loadFileList, (state) => {
+        return {
+            ...state,
+            fileListIsLoading: true
+        }
+    }),
+    on(fileActions.loadFileListSuccess, (state, { files }) => {
+        return adapter.setAll(
+            files,
+            {
+                ...state,
+                fileListIsLoading: false,
+                fileListIsLoaded: true
+            }
+        );
+    }),
+    on(fileActions.loadFileListFail, (state) => {
+        return {
+            ...state,
+            fileListIsLoading: false
+        }
+    }),
+    on(fileActions.addFileSuccess, (state, { file }) => {
+        return adapter.addOne(file, state)
+    }),
+    on(fileActions.editFileSuccess, (state, { file }) => {
+        return adapter.setOne(file, state)
+    }),
+    on(fileActions.deleteFileSuccess, (state, { file }) => {
+        return adapter.removeOne(file.id, state)
+    })
+);
+
+const {
+    selectIds,
+    selectEntities,
+    selectAll,
+    selectTotal,
+} = adapter.getSelectors();
+
+export const selectFileIds = selectIds;
+export const selectFileEntities = selectEntities;
+export const selectAllFiles = selectAll;
+export const selectFileTotal = selectTotal;
+
+export const selectFileListIsLoading = (state: State) => state.fileListIsLoading;
+export const selectFileListIsLoaded = (state: State) => state.fileListIsLoaded;
diff --git a/client/src/app/metamodel/selectors/file.selector.ts b/client/src/app/metamodel/selectors/file.selector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..647e397bc7fff929946a1779aec414f1644efeb1
--- /dev/null
+++ b/client/src/app/metamodel/selectors/file.selector.ts
@@ -0,0 +1,54 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { createSelector } from '@ngrx/store';
+
+import * as reducer from '../metamodel.reducer';
+import * as fromFile from '../reducers/file.reducer';
+
+export const selectFileState = createSelector(
+    reducer.getMetamodelState,
+    (state: reducer.State) => state.file
+);
+
+export const selectFileIds = createSelector(
+    selectFileState,
+    fromFile.selectFileIds
+);
+
+export const selectFileEntities = createSelector(
+    selectFileState,
+    fromFile.selectFileEntities
+);
+
+export const selectAllFiles = createSelector(
+    selectFileState,
+    fromFile.selectAllFiles
+);
+
+export const selectFileTotal = createSelector(
+    selectFileState,
+    fromFile.selectFileTotal
+);
+
+export const selectFileListIsLoading = createSelector(
+    selectFileState,
+    fromFile.selectFileListIsLoading
+);
+
+export const selectFileListIsLoaded = createSelector(
+    selectFileState,
+    fromFile.selectFileListIsLoaded
+);
+
+export const selectFileByRouteId = createSelector(
+    selectFileEntities,
+    reducer.selectRouterState,
+    (entities, router) => entities[router.state.params.id]
+);
\ No newline at end of file
diff --git a/client/src/app/metamodel/services/file.service.ts b/client/src/app/metamodel/services/file.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..49536895e652a95de6deb13c2b053cc89921f2bc
--- /dev/null
+++ b/client/src/app/metamodel/services/file.service.ts
@@ -0,0 +1,70 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+import { Observable } from 'rxjs';
+
+import { File } from '../models';
+import { AppConfigService } from 'src/app/app-config.service';
+
+/**
+ * @class
+ * @classdesc File service.
+ */
+@Injectable()
+export class FileService {
+    constructor(private http: HttpClient, private config: AppConfigService) { }
+
+    /**
+     * Retrieves file list for the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     *
+     * @return Observable<File[]>
+     */
+    retrieveFileList(datasetName: string): Observable<File[]> {
+        return this.http.get<File[]>(`${this.config.apiUrl}/dataset/${datasetName}/file`);
+    }
+
+    /**
+     * Adds a new file for the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     * @param  {File} file - The file.
+     *
+     * @return Observable<File>
+     */
+    addFile(datasetName: string, newFile: File): Observable<File> {
+        return this.http.post<File>(`${this.config.apiUrl}/dataset/${datasetName}/file`, newFile);
+    }
+
+    /**
+     * Modifies an file.
+     *
+     * @param  {File} file - The file.
+     *
+     * @return Observable<File>
+     */
+    editFile(file: File): Observable<File> {
+        return this.http.put<File>(`${this.config.apiUrl}/file/${file.id}`, file);
+    }
+
+    /**
+     * Removes an file.
+     *
+     * @param  {number} fileId - The file ID.
+     *
+     * @return Observable<object>
+     */
+    deleteFile(fileId: number): Observable<object> {
+        return this.http.delete(`${this.config.apiUrl}/file/${fileId}`);
+    }
+}
diff --git a/client/src/app/metamodel/services/index.ts b/client/src/app/metamodel/services/index.ts
index 3556bae114714e3bb59f1827406b2d6f6cc6114e..09bc41998f8e4b0fc94a201eaba90073e0402587 100644
--- a/client/src/app/metamodel/services/index.ts
+++ b/client/src/app/metamodel/services/index.ts
@@ -20,6 +20,7 @@ import { OutputFamilyService } from './output-family.service';
 import { SelectService } from './select.service';
 import { SelectOptionService } from './select-option.service';
 import { ImageService } from './image.service';
+import { FileService } from './file.service';
 import { ConeSearchConfigService } from './cone-search-config.service';
 
 export const metamodelServices = [
@@ -36,5 +37,6 @@ export const metamodelServices = [
     SelectService,
     SelectOptionService,
     ImageService,
+    FileService,
     ConeSearchConfigService
 ];
diff --git a/server/app/dependencies.php b/server/app/dependencies.php
index f210549049573958cede080d8ffb581d3e325e36..2ae9343cd21f981912d86842963e3b064278d3ee 100644
--- a/server/app/dependencies.php
+++ b/server/app/dependencies.php
@@ -209,6 +209,14 @@ $container->set('App\Action\ImageAction', function (ContainerInterface $c) {
     return new App\Action\ImageAction($c->get('em'));
 });
 
+$container->set('App\Action\FileListAction', function (ContainerInterface $c) {
+    return new App\Action\FileListAction($c->get('em'));
+});
+
+$container->set('App\Action\FileAction', function (ContainerInterface $c) {
+    return new App\Action\FileAction($c->get('em'));
+});
+
 $container->set('App\Action\ConeSearchConfigAction', function (ContainerInterface $c) {
     return new App\Action\ConeSearchConfigAction($c->get('em'));
 });
diff --git a/server/app/routes.php b/server/app/routes.php
index 928ca2f72b35c9183f4260c5e05847f5407c4c37..2b6715908160e51c1ca4dfbd9c8d8e8ac0b44464 100644
--- a/server/app/routes.php
+++ b/server/app/routes.php
@@ -67,6 +67,8 @@ $app->group('', function (RouteCollectorProxy $group) {
     );
     $group->map([OPTIONS, GET, POST], '/dataset/{name}/image', App\Action\ImageListAction::class);
     $group->map([OPTIONS, GET, PUT, DELETE], '/image/{id}', App\Action\ImageAction::class);
+    $group->map([OPTIONS, GET, POST], '/dataset/{name}/file', App\Action\FileListAction::class);
+    $group->map([OPTIONS, GET, PUT, DELETE], '/file/{id}', App\Action\FileAction::class);
     $group->map([OPTIONS, GET, POST, PUT], '/dataset/{name}/cone-search-config', App\Action\ConeSearchConfigAction::class);
 })->add(new App\Middleware\RouteGuardMiddleware(
     boolval($container->get(SETTINGS)['token']['enabled']), 
diff --git a/server/doctrine-proxy/__CG__AppEntityFile.php b/server/doctrine-proxy/__CG__AppEntityFile.php
new file mode 100644
index 0000000000000000000000000000000000000000..0ec08809bcadb2ff86544ab143e459cff4f3373f
--- /dev/null
+++ b/server/doctrine-proxy/__CG__AppEntityFile.php
@@ -0,0 +1,294 @@
+<?php
+
+namespace DoctrineProxies\__CG__\App\Entity;
+
+
+/**
+ * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR
+ */
+class File extends \App\Entity\File implements \Doctrine\ORM\Proxy\Proxy
+{
+    /**
+     * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with
+     *      three parameters, being respectively the proxy object to be initialized, the method that triggered the
+     *      initialization process and an array of ordered parameters that were passed to that method.
+     *
+     * @see \Doctrine\Common\Proxy\Proxy::__setInitializer
+     */
+    public $__initializer__;
+
+    /**
+     * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object
+     *
+     * @see \Doctrine\Common\Proxy\Proxy::__setCloner
+     */
+    public $__cloner__;
+
+    /**
+     * @var boolean flag indicating if this object was already initialized
+     *
+     * @see \Doctrine\Persistence\Proxy::__isInitialized
+     */
+    public $__isInitialized__ = false;
+
+    /**
+     * @var array<string, null> properties to be lazy loaded, indexed by property name
+     */
+    public static $lazyPropertiesNames = array (
+);
+
+    /**
+     * @var array<string, mixed> default values of properties to be lazy loaded, with keys being the property names
+     *
+     * @see \Doctrine\Common\Proxy\Proxy::__getLazyProperties
+     */
+    public static $lazyPropertiesDefaults = array (
+);
+
+
+
+    public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null)
+    {
+
+        $this->__initializer__ = $initializer;
+        $this->__cloner__      = $cloner;
+    }
+
+
+
+
+
+
+
+    /**
+     * 
+     * @return array
+     */
+    public function __sleep()
+    {
+        if ($this->__isInitialized__) {
+            return ['__isInitialized__', 'id', 'label', 'filePath', 'fileSize', 'type', 'dataset'];
+        }
+
+        return ['__isInitialized__', 'id', 'label', 'filePath', 'fileSize', 'type', 'dataset'];
+    }
+
+    /**
+     * 
+     */
+    public function __wakeup()
+    {
+        if ( ! $this->__isInitialized__) {
+            $this->__initializer__ = function (File $proxy) {
+                $proxy->__setInitializer(null);
+                $proxy->__setCloner(null);
+
+                $existingProperties = get_object_vars($proxy);
+
+                foreach ($proxy::$lazyPropertiesDefaults as $property => $defaultValue) {
+                    if ( ! array_key_exists($property, $existingProperties)) {
+                        $proxy->$property = $defaultValue;
+                    }
+                }
+            };
+
+        }
+    }
+
+    /**
+     * 
+     */
+    public function __clone()
+    {
+        $this->__cloner__ && $this->__cloner__->__invoke($this, '__clone', []);
+    }
+
+    /**
+     * Forces initialization of the proxy
+     */
+    public function __load()
+    {
+        $this->__initializer__ && $this->__initializer__->__invoke($this, '__load', []);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __isInitialized()
+    {
+        return $this->__isInitialized__;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __setInitialized($initialized)
+    {
+        $this->__isInitialized__ = $initialized;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __setInitializer(\Closure $initializer = null)
+    {
+        $this->__initializer__ = $initializer;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __getInitializer()
+    {
+        return $this->__initializer__;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     */
+    public function __setCloner(\Closure $cloner = null)
+    {
+        $this->__cloner__ = $cloner;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific cloning logic
+     */
+    public function __getCloner()
+    {
+        return $this->__cloner__;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @internal generated method: use only when explicitly handling proxy specific loading logic
+     * @deprecated no longer in use - generated code now relies on internal components rather than generated public API
+     * @static
+     */
+    public function __getLazyProperties()
+    {
+        return self::$lazyPropertiesDefaults;
+    }
+
+    
+    /**
+     * {@inheritDoc}
+     */
+    public function getId()
+    {
+        if ($this->__isInitialized__ === false) {
+            return (int)  parent::getId();
+        }
+
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getId', []);
+
+        return parent::getId();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getLabel()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getLabel', []);
+
+        return parent::getLabel();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setLabel($label)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setLabel', [$label]);
+
+        return parent::setLabel($label);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getFilePath()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getFilePath', []);
+
+        return parent::getFilePath();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setFilePath($filePath)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setFilePath', [$filePath]);
+
+        return parent::setFilePath($filePath);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getFileSize()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getFileSize', []);
+
+        return parent::getFileSize();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setFileSize($fileSize)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setFileSize', [$fileSize]);
+
+        return parent::setFileSize($fileSize);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getType()
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getType', []);
+
+        return parent::getType();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setType($type)
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'setType', [$type]);
+
+        return parent::setType($type);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function jsonSerialize(): array
+    {
+
+        $this->__initializer__ && $this->__initializer__->__invoke($this, 'jsonSerialize', []);
+
+        return parent::jsonSerialize();
+    }
+
+}
diff --git a/server/src/Action/FileAction.php b/server/src/Action/FileAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..1396179471eff58a8c9e36ba28d7c7459cc9ba95
--- /dev/null
+++ b/server/src/Action/FileAction.php
@@ -0,0 +1,112 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\File;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class FileAction extends AbstractAction
+{
+    /**
+     * `GET` Returns the file found
+     * `PUT` Full update the file and returns the new version
+     * `DELETE` Delete the file found and return a confirmation message
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(
+        ServerRequestInterface $request,
+        ResponseInterface $response,
+        array $args
+    ): ResponseInterface {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
+        }
+
+        // Search the correct file with primary key
+        $file = $this->em->find('App\Entity\File', $args['id']);
+
+        // If file is not found 404
+        if (is_null($file)) {
+            throw new HttpNotFoundException(
+                $request,
+                'File with id ' . $args['id'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($file);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+
+            $fields = array(
+                'label',
+                'file_path',
+                'file_size',
+                'type'
+            );
+
+            foreach ($fields as $a) {
+                if (!array_key_exists($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the file'
+                    );
+                }
+            }
+
+            $this->editFile($file, $parsedBody);
+            $payload = json_encode($file);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $id = $file->getId();
+            $this->em->remove($file);
+            $this->em->flush();
+            $payload = json_encode(array(
+                'message' => 'File with id ' . $id . ' is removed!'
+            ));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Update file object with setters
+     *
+     * @param File     $file       The file to update
+     * @param string[] $parsedBody Contains the new values ​​of the file sent by the user
+     */
+    private function editFile(File $file, array $parsedBody): void
+    {
+        $file->setLabel($parsedBody['label']);
+        $file->setFilePath($parsedBody['file_path']);
+        $file->setFileSize($parsedBody['file_size']);
+        $file->setType($parsedBody['type']);
+        $this->em->flush();
+    }
+}
diff --git a/server/src/Action/FileListAction.php b/server/src/Action/FileListAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..d9d7955011ac98626039b796fd39fc1808a96ee6
--- /dev/null
+++ b/server/src/Action/FileListAction.php
@@ -0,0 +1,113 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Dataset;
+use App\Entity\File;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class FileListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all files for a given dataset
+     * `POST` Add a new file to a given dataset
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(
+        ServerRequestInterface $request,
+        ResponseInterface $response,
+        array $args
+    ): ResponseInterface {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+        }
+
+        $dataset = $this->em->find('App\Entity\Dataset', $args['name']);
+
+        // Returns HTTP 404 if the dataset is not found
+        if (is_null($dataset)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset with name ' . $args['name'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $files = $this->em->getRepository('App\Entity\File')->findBy(
+                array('dataset' => $dataset),
+                array('id' => 'ASC')
+            );
+            $payload = json_encode($files);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            $fields = array(
+                'label',
+                'file_path',
+                'file_size',
+                'type'
+            );
+
+            // To work this action needs information
+            foreach ($fields as $a) {
+                if (!array_key_exists($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new file'
+                    );
+                }
+            }
+
+            $file = $this->postFile($parsedBody, $dataset);
+            $payload = json_encode($file);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * @param array    $parsedBody  Contains the values ​​of the new file sent by the user
+     * @param Dataset  $dataset     Dataset for adding the file
+     *
+     * @return File
+     */
+    private function postFile(array $parsedBody, Dataset $dataset): File
+    {
+        $file = new File($dataset);
+        $file->setLabel($parsedBody['label']);
+        $file->setFilePath($parsedBody['file_path']);
+        $file->setFileSize($parsedBody['file_size']);
+        $file->setType($parsedBody['type']);
+
+        $this->em->persist($file);
+        $this->em->flush();
+
+        return $file;
+    }
+}
diff --git a/server/src/Entity/File.php b/server/src/Entity/File.php
new file mode 100644
index 0000000000000000000000000000000000000000..dddd8113e6e973c6ccc8522948f548a54d9f9191
--- /dev/null
+++ b/server/src/Entity/File.php
@@ -0,0 +1,129 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Entity;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Entity
+ *
+ * @Entity
+ * @Table(name="file")
+ */
+class File implements \JsonSerializable
+{
+    /**
+     * @var int
+     *
+     * @Id
+     * @Column(type="integer", nullable=false)
+     * @GeneratedValue
+     */
+    protected $id;
+
+    /**
+     * @var string
+     *
+     * @Column(type="string", name="label", nullable=false)
+     */
+    protected $label;
+
+    /**
+     * @var string
+     *
+     * @Column(type="string", name="file_path", nullable=false)
+     */
+    protected $filePath;
+
+    /**
+     * @var integer
+     *
+     * @Column(type="integer", name="file_size", nullable=false)
+     */
+    protected $fileSize;
+
+    /**
+     * @var string
+     *
+     * @Column(type="string", name="type", nullable=false)
+     */
+    protected $type;
+
+    /**
+     * @var Dataset
+     *
+     * @ManyToOne(targetEntity="Dataset")
+     * @JoinColumn(name="dataset_name", referencedColumnName="name", nullable=false)
+     */
+    protected $dataset;
+
+    public function __construct(Dataset $dataset)
+    {
+        $this->dataset = $dataset;
+    }
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function getLabel()
+    {
+        return $this->label;
+    }
+
+    public function setLabel($label)
+    {
+        $this->label = $label;
+    }
+
+    public function getFilePath()
+    {
+        return $this->filePath;
+    }
+
+    public function setFilePath($filePath)
+    {
+        $this->filePath = $filePath;
+    }
+
+    public function getFileSize()
+    {
+        return $this->fileSize;
+    }
+
+    public function setFileSize($fileSize)
+    {
+        $this->fileSize = $fileSize;
+    }
+
+    public function getType()
+    {
+        return $this->type;
+    }
+
+    public function setType($type)
+    {
+        $this->type = $type;
+    }
+
+    public function jsonSerialize(): array
+    {
+        return [
+            'id' => $this->getId(),
+            'label' => $this->getLabel(),
+            'file_path' => $this->getFilePath(),
+            'file_size' => $this->getFileSize(),
+            'type' => $this->getType()
+        ];
+    }
+}