diff --git a/client/src/app/admin/instance/containers/configure-instance.component.ts b/client/src/app/admin/instance/containers/configure-instance.component.ts
index ace89da19d750cce42ac7d6458b2dd269ce02335..d078026e37d79cc888794dd576fc5b6ecfaf0302 100644
--- a/client/src/app/admin/instance/containers/configure-instance.component.ts
+++ b/client/src/app/admin/instance/containers/configure-instance.component.ts
@@ -13,6 +13,7 @@ import { Store } from '@ngrx/store';
 import * as datasetFamilyActions from 'src/app/metamodel/actions/dataset-family.actions';
 import * as datasetActions from 'src/app/metamodel/actions/dataset.actions';
 import * as datasetGroupActions from 'src/app/metamodel/actions/dataset-group.actions';
+import * as webpageActions from 'src/app/metamodel/actions/webpage.actions';
 
 @Component({
     selector: 'app-configure-instance',
@@ -25,5 +26,6 @@ export class ConfigureInstanceComponent implements OnInit {
         Promise.resolve(null).then(() => this.store.dispatch(datasetFamilyActions.loadDatasetFamilyList()));
         Promise.resolve(null).then(() => this.store.dispatch(datasetActions.loadDatasetList()));
         Promise.resolve(null).then(() => this.store.dispatch(datasetGroupActions.loadDatasetGroupList()));
+        Promise.resolve(null).then(() => this.store.dispatch(webpageActions.loadWebpageList()));
     }
 }
diff --git a/client/src/app/admin/instance/webpages/components/index.ts b/client/src/app/admin/instance/webpages/components/index.ts
index 6de339c71a1248ffa8b768bba4995eab4b3ff143..b624023a22f0ee170d8013948fc87809ff2de4aa 100644
--- a/client/src/app/admin/instance/webpages/components/index.ts
+++ b/client/src/app/admin/instance/webpages/components/index.ts
@@ -7,5 +7,10 @@
  * file that was distributed with this source code.
  */
 
+import { WebpageListMenuComponent } from './webpage-list-menu.component';
+import { WebpageContentComponent } from './webpage-content.component';
+
 export const dummiesComponents = [
+    WebpageListMenuComponent,
+    WebpageContentComponent
 ];
diff --git a/client/src/app/admin/instance/webpages/components/webpage-content.component.html b/client/src/app/admin/instance/webpages/components/webpage-content.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..a7dd4a56980a6549923008e7068272a7c423b1d1
--- /dev/null
+++ b/client/src/app/admin/instance/webpages/components/webpage-content.component.html
@@ -0,0 +1,24 @@
+<form [formGroup]="form" (ngSubmit)="savePage()" novalidate>
+    <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="display">Display</label>
+        <input type="number" class="form-control" id="display" name="display" formControlName="display">
+    </div>
+    <div class="form-group">
+        <label for="title">Title</label>
+        <input type="text" class="form-control" id="title" name="title" formControlName="title">
+    </div>
+    <div class="form-group">
+        <label for="content">Content</label>
+        <editor formControlName="content" [init]="{ plugins: 'lists link image table code help wordcount' }">
+        </editor>
+    </div>
+    <div class="form-group">
+        <button [disabled]="!form.valid" type="submit" class="btn btn-primary">
+            <span class="fa fa-database"></span> Save
+        </button>
+    </div>
+</form>
diff --git a/client/src/app/admin/instance/webpages/components/webpage-content.component.ts b/client/src/app/admin/instance/webpages/components/webpage-content.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..346301666ee582228403147adb4b6535eead4a4d
--- /dev/null
+++ b/client/src/app/admin/instance/webpages/components/webpage-content.component.ts
@@ -0,0 +1,28 @@
+import { Component, Input } from '@angular/core';
+import { FormGroup, FormControl, Validators } from '@angular/forms';
+
+import { Webpage } from 'src/app/metamodel/models';
+
+@Component({
+    selector: 'app-webpage-content',
+    templateUrl: 'webpage-content.component.html'
+})
+export class WebpageContentComponent {
+    @Input() webPage: Webpage;
+
+    public form = new FormGroup({
+        label: new FormControl('', [Validators.required]),
+        display: new FormControl('', [Validators.required]),
+        title: new FormControl('', [Validators.required]),
+        content: new FormControl('', Validators.required)
+    });
+
+    ngOnInit() {
+        this.form.patchValue(this.webPage);
+        this.form.controls.content.markAsPristine();
+    }
+
+    savePage() {
+        console.log(this.form.value);
+    }
+}
\ No newline at end of file
diff --git a/client/src/app/admin/instance/webpages/components/webpage-list-menu.component.html b/client/src/app/admin/instance/webpages/components/webpage-list-menu.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..e9a722017b716234b44c163e454177810037a8b6
--- /dev/null
+++ b/client/src/app/admin/instance/webpages/components/webpage-list-menu.component.html
@@ -0,0 +1,39 @@
+<nav>
+    <div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
+        <a *ngFor="let webpage of webpageList" routerLink="./" [queryParams]="{webpage_selected: webpage.id}" class="nav-link" [ngClass]="{'active': isActive(webpage)}" data-toggle="pill" role="tab">
+            {{ webpage.label }}
+        </a>
+        <a (click)="openModal(template)" class="nav-link text-center pointer" title="Add a new webpage" data-toggle="pill" role="tab">
+            <span class="fas fa-plus"></span>
+        </a>
+    </div>
+</nav>
+
+<ng-template #template>
+    <div class="modal-header">
+        <h4 class="modal-title pull-left"><strong>Add a new webpage</strong></h4>
+    </div>
+    <div class="modal-body">
+        <form [formGroup]="form" (ngSubmit)="submit()" novalidate>
+            <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="display">Display</label>
+                <input type="number" class="form-control" id="display" name="display" formControlName="display">
+            </div>
+            <div class="form-group">
+                <label for="title">Title</label>
+                <input type="text" class="form-control" id="title" name="title" formControlName="title">
+            </div>
+            <div class="form-group">
+                <button [disabled]="!form.valid || form.pristine" type="submit" class="btn btn-primary">
+                    <span class="fa fa-database"></span> Add the webpage
+                </button>
+                &nbsp;
+                <a (click)="modalRef.hide()" type="button" class="btn btn-danger">Cancel</a>
+            </div>
+        </form>
+    </div>
+</ng-template>
diff --git a/client/src/app/admin/instance/webpages/components/webpage-list-menu.component.ts b/client/src/app/admin/instance/webpages/components/webpage-list-menu.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..94abf57aff792e3f72a4c0cad3ca5dab7f9e6c23
--- /dev/null
+++ b/client/src/app/admin/instance/webpages/components/webpage-list-menu.component.ts
@@ -0,0 +1,47 @@
+import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, TemplateRef } from '@angular/core';
+import { FormGroup, FormControl, Validators } from '@angular/forms';
+
+import { BsModalService } from 'ngx-bootstrap/modal';
+import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
+
+import { Webpage } from 'src/app/metamodel/models';
+
+@Component({
+    selector: 'app-webpage-list-menu',
+    templateUrl: 'webpage-list-menu.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class WebpageListMenuComponent {
+    @Input() webpageList: Webpage[];
+    @Input() webpageSelected: Webpage;
+    @Output() addWebpage: EventEmitter<Webpage> = new EventEmitter();
+
+    modalRef: BsModalRef;
+    public form = new FormGroup({
+        label: new FormControl('', [Validators.required]),
+        display: new FormControl('', [Validators.required]),
+        title: new FormControl('', [Validators.required]),
+        content: new FormControl('Add your text here...', [Validators.required])
+    });
+
+    constructor(private modalService: BsModalService) { }
+
+    isActive(webpage: Webpage) {
+        let active = false;
+        if (this.webpageSelected && webpage.id === this.webpageSelected.id) {
+            active = true;
+        }
+        return active;
+    }
+
+    openModal(template: TemplateRef<any>) {
+        this.modalRef = this.modalService.show(template);
+    }
+
+    submit() {
+        this.addWebpage.emit(this.form.value);
+        this.form.reset();
+        this.form.controls.content.setValue('Add your text here...');
+        this.modalRef.hide();
+    }
+}
diff --git a/client/src/app/admin/instance/webpages/containers/webpages-list.component.html b/client/src/app/admin/instance/webpages/containers/webpages-list.component.html
index ca6f8b0ad94bf928b2f07348a98896a6080c1625..174c553c5c6cd7c86c41e63f184dd802b84c0c75 100644
--- a/client/src/app/admin/instance/webpages/containers/webpages-list.component.html
+++ b/client/src/app/admin/instance/webpages/containers/webpages-list.component.html
@@ -15,16 +15,18 @@
         </ol>
     </nav>
 
-    <div class="row">
-        <nav class="col-md-2 col-sm-12 mb-2">
-            <div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
-                <a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">Home</a>
-            </div>
-        </nav>
-        <div class="col-md-10 col-sm-12">
-            <editor [init]="{ plugins: 'lists link image table code help wordcount' }" [(ngModel)]="dataModel">
-            </editor>
-            <button (click)="savePage()" class="btn btn-outline-primary mt-2">Save</button>
+    <app-spinner *ngIf="webpageListIsLoading | async"></app-spinner>
+
+    <div *ngIf="webpageListIsLoaded | async" class="row">
+        <div class="col-md-2 col-sm-12 mb-2">
+            <app-webpage-list-menu 
+                [webpageList]="webpageList | async"
+                [webpageSelected]="webpageSelected | async"
+                (addWebpage)="addWebpage($event)">
+            </app-webpage-list-menu>
+        </div>
+        <div *ngIf="webpageSelected | async" class="col-md-10 col-sm-12">
+            <app-webpage-content [webPage]="webpageSelected | async"></app-webpage-content>
         </div>
     </div>
 </div>
diff --git a/client/src/app/admin/instance/webpages/containers/webpages-list.component.scss b/client/src/app/admin/instance/webpages/containers/webpages-list.component.scss
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/client/src/app/admin/instance/webpages/containers/webpages-list.component.ts b/client/src/app/admin/instance/webpages/containers/webpages-list.component.ts
index 6f5f42621702f768fc92c5e60eb35e2221f5fe8c..102e62b61618a00615572e1bb35e6117870e1078 100644
--- a/client/src/app/admin/instance/webpages/containers/webpages-list.component.ts
+++ b/client/src/app/admin/instance/webpages/containers/webpages-list.component.ts
@@ -8,26 +8,43 @@
  */
 
 import { Component } from '@angular/core';
+
 import { Observable } from 'rxjs';
 import { Store } from '@ngrx/store';
 
+import { Webpage } from 'src/app/metamodel/models';
 import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
+import * as webpageActions from 'src/app/metamodel/actions/webpage.actions';
+import * as webpageSelector from 'src/app/metamodel/selectors/webpage.selector';
 
 @Component({
     selector: 'app-webpages-list',
-    templateUrl: 'webpages-list.component.html',
-    styleUrls: [ 'webpages-list.component.scss' ]
+    templateUrl: 'webpages-list.component.html'
 })
 export class WebpagesListComponent {
     public instanceName: Observable<string>;
-
-    dataModel: string;
+    public webpageListIsLoading: Observable<boolean>;
+    public webpageListIsLoaded: Observable<boolean>;
+    public webpageList: Observable<Webpage[]>;
+    public webpageSelected: Observable<Webpage>;
 
     constructor(private store: Store<{ }>) {
+        this.webpageSelected = store.select(webpageSelector.selectWebpageByQueryParamId);
         this.instanceName = this.store.select(instanceSelector.selectInstanceNameByRoute);
+        this.webpageListIsLoading = store.select(webpageSelector.selectWebpageListIsLoading);
+        this.webpageListIsLoaded = store.select(webpageSelector.selectWebpageListIsLoaded);
+        this.webpageList = store.select(webpageSelector.selectAllWebpages);
+    }
+
+    addWebpage(webpage: Webpage) {
+        this.store.dispatch(webpageActions.addWebpage({ webpage }));
+    }
+
+    editWebpage(webpage: Webpage) {
+        this.store.dispatch(webpageActions.editWebpage({ webpage }));
     }
 
-    savePage() {
-        console.log(this.dataModel);
+    deleteWebpage(webpage: Webpage) {
+        this.store.dispatch(webpageActions.deleteWebpage({ webpage }));
     }
 }
diff --git a/client/src/app/metamodel/actions/webpage.actions.ts b/client/src/app/metamodel/actions/webpage.actions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9e068d01b1e625fb99ae8838d564d75aeabed95e
--- /dev/null
+++ b/client/src/app/metamodel/actions/webpage.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 { Webpage } from '../models';
+
+export const loadWebpageList = createAction('[Metamodel] Load Webpage List');
+export const loadWebpageListSuccess = createAction('[Metamodel] Load Webpage List Success', props<{ webpages: Webpage[] }>());
+export const loadWebpageListFail = createAction('[Metamodel] Load Webpage List Fail');
+export const addWebpage = createAction('[Metamodel] Add Webpage', props<{ webpage: Webpage }>());
+export const addWebpageSuccess = createAction('[Metamodel] Add Webpage Success', props<{ webpage: Webpage }>());
+export const addWebpageFail = createAction('[Metamodel] Add Webpage Fail');
+export const editWebpage = createAction('[Metamodel] Edit Webpage', props<{ webpage: Webpage }>());
+export const editWebpageSuccess = createAction('[Metamodel] Edit Webpage Success', props<{ webpage: Webpage }>());
+export const editWebpageFail = createAction('[Metamodel] Edit Webpage Fail');
+export const deleteWebpage = createAction('[Metamodel] Delete Webpage', props<{ webpage: Webpage }>());
+export const deleteWebpageSuccess = createAction('[Metamodel] Delete Webpage Success', props<{ webpage: Webpage }>());
+export const deleteWebpageFail = createAction('[Metamodel] Delete Webpage Fail');
diff --git a/client/src/app/metamodel/effects/index.ts b/client/src/app/metamodel/effects/index.ts
index 111bd9f732e401b4dc337afedbc0a66adfb68728..536caf45ac449690a9a5df685b522b1739365c99 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 { ImageEffects } from './image.effects';
 import { FileEffects } from './file.effects';
 import { ConeSearchConfigEffects } from './cone-search-config.effects'
+import { WebpageEffects } from './webpage.effects';
 
 export const metamodelEffects = [
     DatabaseEffects,
@@ -34,5 +35,6 @@ export const metamodelEffects = [
     OutputFamilyEffects,
     ImageEffects,
     FileEffects,
-    ConeSearchConfigEffects
+    ConeSearchConfigEffects,
+    WebpageEffects
 ];
diff --git a/client/src/app/metamodel/effects/webpage.effects.ts b/client/src/app/metamodel/effects/webpage.effects.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8294a5072ff963ba2af84e0327d63de8f8c386ac
--- /dev/null
+++ b/client/src/app/metamodel/effects/webpage.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 webpageActions from '../actions/webpage.actions';
+import { WebpageService } from '../services/webpage.service';
+import * as instanceSelector from '../selectors/instance.selector';
+
+/**
+ * @class
+ * @classdesc Webpage effects.
+ */
+@Injectable()
+export class WebpageEffects {
+    /**
+     * Calls action to retrieve webpage list.
+     */
+    loadWebpages$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(webpageActions.loadWebpageList),
+            concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
+            mergeMap(([, instanceName]) => this.webpageService.retrieveWebpageList(instanceName)
+                .pipe(
+                    map(webpages => webpageActions.loadWebpageListSuccess({ webpages })),
+                    catchError(() => of(webpageActions.loadWebpageListFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Calls action to add a webpage.
+     */
+    addWebpage$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(webpageActions.addWebpage),
+            concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
+            mergeMap(([action, instanceName]) => this.webpageService.addWebpage(instanceName, action.webpage)
+                .pipe(
+                    map(webpage => webpageActions.addWebpageSuccess({ webpage })),
+                    catchError(() => of(webpageActions.addWebpageFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Displays add webpage success notification.
+     */
+    addWebpageSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageActions.addWebpageSuccess),
+            tap(() => this.toastr.success('Webpage successfully added', 'The new webpage was added into the database'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Displays add webpage fail notification.
+     */
+    addWebpageFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageActions.addWebpageFail),
+            tap(() => this.toastr.error('Failure to add webpage', 'The new webpage could not be added into the database'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Calls action to modify a webpage.
+     */
+    editWebpage$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(webpageActions.editWebpage),
+            mergeMap(action => this.webpageService.editWebpage(action.webpage)
+                .pipe(
+                    map(webpage => webpageActions.editWebpageSuccess({ webpage })),
+                    catchError(() => of(webpageActions.editWebpageFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Displays edit webpage success notification.
+     */
+    editWebpageSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageActions.editWebpageSuccess),
+            tap(() => this.toastr.success('Webpage successfully edited', 'The existing webpage has been edited into the database'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Displays edit webpage error notification.
+     */
+    editWebpageFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageActions.editWebpageFail),
+            tap(() => this.toastr.error('Failure to edit webpage', 'The existing webpage could not be edited into the database'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Calls action to remove a webpage.
+     */
+    deleteWebpage$ = createEffect((): any =>
+        this.actions$.pipe(
+            ofType(webpageActions.deleteWebpage),
+            mergeMap(action => this.webpageService.deleteWebpage(action.webpage.id)
+                .pipe(
+                    map(() => webpageActions.deleteWebpageSuccess({ webpage: action.webpage })),
+                    catchError(() => of(webpageActions.deleteWebpageFail()))
+                )
+            )
+        )
+    );
+
+    /**
+     * Displays delete webpage success notification.
+     */
+    deleteWebpageSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageActions.deleteWebpageSuccess),
+            tap(() => this.toastr.success('Webpage successfully deleted', 'The existing webpage has been deleted'))
+        ), { dispatch: false }
+    );
+
+    /**
+     * Displays delete webpage error notification.
+     */
+    deleteWebpageFail$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(webpageActions.deleteWebpageFail),
+            tap(() => this.toastr.error('Failure to delete webpage', 'The existing webpage could not be deleted from the database'))
+        ), { dispatch: false }
+    );
+
+    constructor(
+        private actions$: Actions,
+        private webpageService: WebpageService,
+        private toastr: ToastrService,
+        private store: Store<{ }>
+    ) {}
+}
diff --git a/client/src/app/metamodel/metamodel.reducer.ts b/client/src/app/metamodel/metamodel.reducer.ts
index 2cccdfc3787cfaac8e61e759cd38e77e93f7daa0..1549262bba3c7ef0616a66a30fbbf00ca0e2bfa8 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 image from './reducers/image.reducer';
 import * as file from './reducers/file.reducer';
 import * as coneSearchConfig from './reducers/cone-search-config.reducer';
+import * as webpage from './reducers/webpage.reducer';
 
 /**
  * Interface for metamodel state.
@@ -43,6 +44,7 @@ export interface State {
     image: image.State;
     file: file.State;
     coneSearchConfig: coneSearchConfig.State;
+    webpage: webpage.State;
 }
 
 const reducers = {
@@ -58,7 +60,8 @@ const reducers = {
     outputFamily: outputFamily.outputFamilyReducer,
     image: image.imageReducer,
     file: file.fileReducer,
-    coneSearchConfig: coneSearchConfig.coneSearchConfigReducer
+    coneSearchConfig: coneSearchConfig.coneSearchConfigReducer,
+    webpage: webpage.webpageReducer
 };
 
 export const metamodelReducer = combineReducers(reducers);
diff --git a/client/src/app/metamodel/models/index.ts b/client/src/app/metamodel/models/index.ts
index 82f01695472cea1a22424bf1a7465fdcd6f168b5..aea0035164ddbe4aef7ca26ea60985496c9c7d73 100644
--- a/client/src/app/metamodel/models/index.ts
+++ b/client/src/app/metamodel/models/index.ts
@@ -22,4 +22,5 @@ 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
+export * from './file.model';
+export * from './webpage.model';
diff --git a/client/src/app/metamodel/models/webpage.model.ts b/client/src/app/metamodel/models/webpage.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b7a02447bd040df9a9f70c991739c2599390e1b3
--- /dev/null
+++ b/client/src/app/metamodel/models/webpage.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 web page.
+ *
+ * @interface Webpage
+ */
+export interface Webpage {
+    id: number;
+    label: string;
+    display: number;
+    title: string;
+    content: string;
+}
diff --git a/client/src/app/metamodel/reducers/webpage.reducer.ts b/client/src/app/metamodel/reducers/webpage.reducer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cd0a982086bef28ee5cfd7134ff482f5666cd4c2
--- /dev/null
+++ b/client/src/app/metamodel/reducers/webpage.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 { Webpage } from '../models';
+import * as webpageActions from '../actions/webpage.actions';
+
+/**
+ * Interface for webpage state.
+ *
+ * @interface State
+ */
+export interface State extends EntityState<Webpage> {
+    webpageListIsLoading: boolean;
+    webpageListIsLoaded: boolean;
+}
+
+export const adapter: EntityAdapter<Webpage> = createEntityAdapter<Webpage>({
+    selectId: (webpage: Webpage) => webpage.id,
+    sortComparer: (a: Webpage, b: Webpage) => a.display - b.display
+});
+
+export const initialState: State = adapter.getInitialState({
+    webpageListIsLoading: false,
+    webpageListIsLoaded: false
+});
+
+export const webpageReducer = createReducer(
+    initialState,
+    on(webpageActions.loadWebpageList, (state) => {
+        return {
+            ...state,
+            webpageListIsLoading: true
+        }
+    }),
+    on(webpageActions.loadWebpageListSuccess, (state, { webpages }) => {
+        return adapter.setAll(
+            webpages,
+            {
+                ...state,
+                webpageListIsLoading: false,
+                webpageListIsLoaded: true
+            }
+        );
+    }),
+    on(webpageActions.loadWebpageListFail, (state) => {
+        return {
+            ...state,
+            webpageListIsLoading: false
+        }
+    }),
+    on(webpageActions.addWebpageSuccess, (state, { webpage }) => {
+        return adapter.addOne(webpage, state)
+    }),
+    on(webpageActions.editWebpageSuccess, (state, { webpage }) => {
+        return adapter.setOne(webpage, state)
+    }),
+    on(webpageActions.deleteWebpageSuccess, (state, { webpage }) => {
+        return adapter.removeOne(webpage.id, state)
+    })
+);
+
+const {
+    selectIds,
+    selectEntities,
+    selectAll,
+    selectTotal,
+} = adapter.getSelectors();
+
+export const selectWebpageIds = selectIds;
+export const selectWebpageEntities = selectEntities;
+export const selectAllWebpages = selectAll;
+export const selectWebpageTotal = selectTotal;
+
+export const selectWebpageListIsLoading = (state: State) => state.webpageListIsLoading;
+export const selectWebpageListIsLoaded = (state: State) => state.webpageListIsLoaded;
diff --git a/client/src/app/metamodel/selectors/webpage.selector.ts b/client/src/app/metamodel/selectors/webpage.selector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1ca79a3d83fdd8f35058b53c52d084789090b104
--- /dev/null
+++ b/client/src/app/metamodel/selectors/webpage.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 fromWebpage from '../reducers/webpage.reducer';
+
+export const selectWebpageState = createSelector(
+    reducer.getMetamodelState,
+    (state: reducer.State) => state.webpage
+);
+
+export const selectWebpageIds = createSelector(
+    selectWebpageState,
+    fromWebpage.selectWebpageIds
+);
+
+export const selectWebpageEntities = createSelector(
+    selectWebpageState,
+    fromWebpage.selectWebpageEntities
+);
+
+export const selectAllWebpages = createSelector(
+    selectWebpageState,
+    fromWebpage.selectAllWebpages
+);
+
+export const selectWebpageTotal = createSelector(
+    selectWebpageState,
+    fromWebpage.selectWebpageTotal
+);
+
+export const selectWebpageListIsLoading = createSelector(
+    selectWebpageState,
+    fromWebpage.selectWebpageListIsLoading
+);
+
+export const selectWebpageListIsLoaded = createSelector(
+    selectWebpageState,
+    fromWebpage.selectWebpageListIsLoaded
+);
+
+export const selectWebpageByQueryParamId = createSelector(
+    selectWebpageEntities,
+    reducer.selectRouterState,
+    (entities, router) => entities[router.state.queryParams.webpage_selected]
+);
diff --git a/client/src/app/metamodel/services/index.ts b/client/src/app/metamodel/services/index.ts
index c24336c01c0642c22818a75ee76c8be5670a9f0e..3812732f206f17b3323eb8ca8a64e3f402e3acdc 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 { ImageService } from './image.service';
 import { FileService } from './file.service';
 import { ConeSearchConfigService } from './cone-search-config.service';
+import { WebpageService } from './webpage.service';
 
 export const metamodelServices = [
     DatabaseService,
@@ -34,5 +35,6 @@ export const metamodelServices = [
     OutputFamilyService,
     ImageService,
     FileService,
-    ConeSearchConfigService
+    ConeSearchConfigService,
+    WebpageService
 ];
diff --git a/client/src/app/metamodel/services/webpage.service.ts b/client/src/app/metamodel/services/webpage.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d19d75ea6d909db1d976cf983b9b803e200e7b09
--- /dev/null
+++ b/client/src/app/metamodel/services/webpage.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 { Webpage } from '../models';
+import { AppConfigService } from 'src/app/app-config.service';
+
+/**
+ * @class
+ * @classdesc Webpage service.
+ */
+@Injectable()
+export class WebpageService {
+    constructor(private http: HttpClient, private config: AppConfigService) { }
+
+    /**
+     * Retrieves webpages for the given instance.
+     *
+     * @param  {string} instanceName - The instance.
+     *
+     * @return Observable<Webpage[]>
+     */
+    retrieveWebpageList(instanceName: string): Observable<Webpage[]> {
+        return this.http.get<Webpage[]>(`${this.config.apiUrl}/instance/${instanceName}/webpage`);
+    }
+
+    /**
+     * Adds a new webpage for the given instance.
+     *
+     * @param  {string} instanceName - The instance.
+     * @param  {Webpage} newWebpage - The webpage.
+     *
+     * @return Observable<Webpage>
+     */
+    addWebpage(instanceName: string, newWebpage: Webpage): Observable<Webpage> {
+        return this.http.post<Webpage>(`${this.config.apiUrl}/instance/${instanceName}/webpage`, newWebpage);
+    }
+
+    /**
+     * Modifies a webpage.
+     *
+     * @param  {Webpage} webpage - The Webpage.
+     *
+     * @return Observable<Webpage>
+     */
+    editWebpage(webpage: Webpage): Observable<Webpage> {
+        return this.http.put<Webpage>(`${this.config.apiUrl}/webpage/${webpage.id}`, webpage);
+    }
+
+    /**
+     * Removes a webpage.
+     *
+     * @param  {number} webpageId - The webpage ID.
+     *
+     * @return Observable<object>
+     */
+    deleteWebpage(webpageId: number): Observable<object> {
+        return this.http.delete(`${this.config.apiUrl}/webpage/${webpageId}`);
+    }
+}
diff --git a/client/src/styles.scss b/client/src/styles.scss
index 2f276ed780c804c7a33845a14cafaff9d99f1680..d706a62faffc0855002d27a111992b3351d8ab67 100644
--- a/client/src/styles.scss
+++ b/client/src/styles.scss
@@ -79,11 +79,11 @@ main {
 }
 
 /* Angular forms */
-input.ng-valid, select.ng-valid, .ng-select.ng-valid div.ng-select-container, textarea.ng-valid {
+input.ng-valid, select.ng-valid, .ng-select.ng-valid div.ng-select-container, textarea.ng-valid, editor.ng-valid {
     border-left: 5px solid #42A948; /* green */
 }
   
-input.ng-invalid, select.ng-invalid, .ng-select.ng-invalid div.ng-select-container, textarea.ng-invalid {
+input.ng-invalid, select.ng-invalid, .ng-select.ng-invalid div.ng-select-container, textarea.ng-invalid, editor.ng-invalid {
     border-left: 5px solid #a94442; /* red */
 }
 
diff --git a/server/app/dependencies.php b/server/app/dependencies.php
index 994388f37e4bc123ab59b62f8994d076a30dc551..583295365248b049cad9b0980ec3edf99cf5dcf6 100644
--- a/server/app/dependencies.php
+++ b/server/app/dependencies.php
@@ -145,6 +145,14 @@ $container->set('App\Action\DatasetAction', function (ContainerInterface $c) {
     return new App\Action\DatasetAction($c->get('em'));
 });
 
+$container->set('App\Action\WebpageListAction', function (ContainerInterface $c) {
+    return new App\Action\WebpageListAction($c->get('em'));
+});
+
+$container->set('App\Action\WebpageAction', function (ContainerInterface $c) {
+    return new App\Action\WebpageAction($c->get('em'));
+});
+
 $container->set('App\Action\CriteriaFamilyListAction', function (ContainerInterface $c) {
     return new App\Action\CriteriaFamilyListAction($c->get('em'));
 });
diff --git a/server/app/routes.php b/server/app/routes.php
index 2b6715908160e51c1ca4dfbd9c8d8e8ac0b44464..10993e68633d8a2671b146cc54c1c7f9f1b32e0f 100644
--- a/server/app/routes.php
+++ b/server/app/routes.php
@@ -40,9 +40,11 @@ $app->group('', function (RouteCollectorProxy $group) {
     $group->map([OPTIONS, GET], '/instance/{name}/file-explorer[{fpath:.*}]', App\Action\InstanceFileExplorerAction::class);
     $group->map([OPTIONS, GET, POST], '/instance/{name}/dataset-group', App\Action\DatasetGroupListAction::class);
     $group->map([OPTIONS, GET, POST], '/instance/{name}/dataset-family', App\Action\DatasetFamilyListAction::class);
+    $group->map([OPTIONS, GET, POST], '/instance/{name}/webpage', App\Action\WebpageListAction::class);
     $group->map([OPTIONS, GET], '/instance/{name}/dataset', App\Action\DatasetListByInstanceAction::class);
     $group->map([OPTIONS, GET, PUT, DELETE], '/dataset-group/{id}', App\Action\DatasetGroupAction::class);
     $group->map([OPTIONS, GET, PUT, DELETE], '/dataset-family/{id}', App\Action\DatasetFamilyAction::class);
+    $group->map([OPTIONS, GET, PUT, DELETE], '/webpage/{id}', App\Action\WebpageAction::class);
     $group->map([OPTIONS, GET, POST], '/dataset-family/{id}/dataset', App\Action\DatasetListAction::class);
     $group->map([OPTIONS, GET, PUT, DELETE], '/dataset/{name}', App\Action\DatasetAction::class);
     $group->map([OPTIONS, GET], '/dataset/{name}/file-explorer[{fpath:.*}]', App\Action\DatasetFileExplorerAction::class);
diff --git a/server/src/Action/WebpageAction.php b/server/src/Action/WebpageAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..28ea184c4705b361ff66dacc072b80ff29e35468
--- /dev/null
+++ b/server/src/Action/WebpageAction.php
@@ -0,0 +1,106 @@
+<?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\Webpage;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class WebpageAction extends AbstractAction
+{
+    /**
+     * `GET` Returns the webpage found
+     * `PUT` Full update the webpage and returns the new version
+     * `DELETE` Delete the webpage 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 webpage with primary key
+        $webpage = $this->em->find('App\Entity\Webpage', $args['id']);
+
+        // If webpage is not found 404
+        if (is_null($webpage)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Webpage with id ' . $args['id'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($webpage);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+
+            $fields = array('label', 'display', 'title', 'content');
+            foreach ($fields as $a) {
+                if (!array_key_exists($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the webpage'
+                    );
+                }
+            }
+
+            $this->editWebpage($webpage, $parsedBody);
+            $payload = json_encode($webpage);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $id = $webpage->getId();
+            $this->em->remove($webpage);
+            $this->em->flush();
+            $payload = json_encode(array(
+                'message' => 'Webpage with id ' . $id . ' is removed!'
+            ));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Update webpage object with setters
+     *
+     * @param Webpage $webpage    The webpage to update
+     * @param array   $parsedBody Contains the new values ​​of the webpage sent by the user
+     */
+    private function editWebpage(Webpage $webpage, array $parsedBody): void
+    {
+        $webpage->setLabel($parsedBody['label']);
+        $webpage->setDisplay($parsedBody['display']);
+        $webpage->setTitle($parsedBody['title']);
+        $webpage->setContent($parsedBody['content']);
+        $this->em->flush();
+    }
+}
diff --git a/server/src/Action/WebpageListAction.php b/server/src/Action/WebpageListAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d2bca03cd8e138aeba76e997588724da990e9a7
--- /dev/null
+++ b/server/src/Action/WebpageListAction.php
@@ -0,0 +1,105 @@
+<?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\Instance;
+use App\Entity\Webpage;
+
+/**
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class WebpageListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all webpages for a given instance
+     * `POST` Add a new webpage to a given instance
+     *
+     * @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');
+        }
+
+        $instance = $this->em->find('App\Entity\Instance', $args['name']);
+
+        // Returns HTTP 404 if the instance is not found
+        if (is_null($instance)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Instance with name ' . $args['name'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $webpages = $this->em->getRepository('App\Entity\Webpage')->findBy(array('instance' => $instance));
+            $payload = json_encode($webpages);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            // To work this action needs information
+            foreach (array('label', 'display', 'title', 'content') as $a) {
+                if (!array_key_exists($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new webpage'
+                    );
+                }
+            }
+
+            $webpage = $this->postWebpage($parsedBody, $instance);
+            $payload = json_encode($webpage);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Add a new webpage into the metamodel
+     *
+     * @param array    $parsedBody Contains the values ​​of the new webpage sent by the user
+     * @param Instance $instance   The instance for adding the webpage
+     *
+     * @return Webpage
+     */
+    private function postWebpage(array $parsedBody, Instance $instance): Webpage
+    {
+        $webpage = new Webpage($instance);
+        $webpage->setLabel($parsedBody['label']);
+        $webpage->setDisplay($parsedBody['display']);
+        $webpage->setTitle($parsedBody['title']);
+        $webpage->setContent($parsedBody['content']);
+
+        $this->em->persist($webpage);
+        $this->em->flush();
+
+        return $webpage;
+    }
+}
diff --git a/server/src/Entity/Webpage.php b/server/src/Entity/Webpage.php
new file mode 100644
index 0000000000000000000000000000000000000000..026988edc54bf4b58eb64deeed5deac74ad66bed
--- /dev/null
+++ b/server/src/Entity/Webpage.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="webpage")
+ */
+class Webpage 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 int
+     *
+     * @Column(type="integer", name="display", nullable=false)
+     */
+    protected $display;
+
+    /**
+     * @var string
+     *
+     * @Column(type="string", name="title", nullable=false)
+     */
+    protected $title;
+
+    /**
+     * @var string
+     *
+     * @Column(type="string", name="content", nullable=false)
+     */
+    protected $content;
+
+    /**
+     * @var Instance
+     *
+     * @ManyToOne(targetEntity="Instance", inversedBy="datasetFamilies")
+     * @JoinColumn(name="instance_name", referencedColumnName="name", nullable=false, onDelete="CASCADE")
+     */
+    protected $instance;
+
+    public function __construct(Instance $instance)
+    {
+        $this->instance = $instance;
+    }
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function getLabel()
+    {
+        return $this->label;
+    }
+
+    public function setLabel($label)
+    {
+        $this->label = $label;
+    }
+
+    public function getDisplay()
+    {
+        return $this->display;
+    }
+
+    public function setDisplay($display)
+    {
+        $this->display = $display;
+    }
+
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    public function setTitle($title)
+    {
+        $this->title = $title;
+    }
+
+    public function getContent()
+    {
+        return $this->content;
+    }
+
+    public function setContent($content)
+    {
+        $this->content = $content;
+    }
+
+    public function jsonSerialize(): array
+    {
+        return [
+            'id' => $this->getId(),
+            'label' => $this->getLabel(),
+            'display' => $this->getDisplay(),
+            'title' => $this->getTitle(),
+            'content'=> $this->getContent()
+        ];
+    }
+}