diff --git a/src/app/core/settings/pages/space-usage/space-usage.html b/src/app/core/settings/pages/space-usage/space-usage.html
new file mode 100644
index 000000000..ad08d1c8d
--- /dev/null
+++ b/src/app/core/settings/pages/space-usage/space-usage.html
@@ -0,0 +1,46 @@
+<ion-header>
+    <ion-toolbar>
+        <ion-buttons slot="start">
+            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
+        </ion-buttons>
+        <ion-title>{{ 'core.settings.spaceusage' | translate }}</ion-title>
+        <ion-buttons slot="end">
+            <!-- @todo <core-navbar-buttons></core-navbar-buttons>-->
+            <ion-button (click)="showInfo()" [attr.aria-label]="'core.info' | translate">
+                <ion-icon name="fas-info-circle" slot="icon-only"></ion-icon>
+            </ion-button>
+        </ion-buttons>
+    </ion-toolbar>
+</ion-header>
+<ion-content>
+    <ion-refresher [disabled]="!loaded" (ionRefresh)="refreshData($event)" slot="fixed">
+        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
+    </ion-refresher>
+    <core-loading [hideUntil]="loaded">
+        <ion-item *ngFor="let site of sites" [class.core-selected-item]="site.id == currentSiteId">
+            <ion-label class="ion-text-wrap">
+                <h2>
+                    <core-format-text [text]="site.siteName" clean="true" [siteId]="site.id"></core-format-text>
+                </h2>
+                <p class="ion-text-wrap">{{ site.fullName }}</p>
+                <p>{{ site.siteUrl }}</p>
+            </ion-label>
+            <p *ngIf="site.spaceUsage != null" slot="end">
+                {{ site.spaceUsage | coreBytesToSize }}
+            </p>
+            <ion-button fill="clear" color="danger" slot="end" (click)="deleteSiteStorage(site)"
+                [hidden]="site.spaceUsage! + site.cacheEntries! <= 0"
+                [attr.aria-label]="'core.settings.deletesitefilestitle' | translate">
+                <ion-icon name="fas-trash" slot="icon-only"></ion-icon>
+            </ion-button>
+        </ion-item>
+        <ion-item-divider>
+            <ion-label>
+                <h2>{{ 'core.settings.total' | translate }}</h2>
+            </ion-label>
+            <p slot="end" class="ion-margin-end">
+                {{ totals.spaceUsage | coreBytesToSize }}
+            </p>
+        </ion-item-divider>
+    </core-loading>
+</ion-content>
diff --git a/src/app/core/settings/pages/space-usage/space-usage.page.module.ts b/src/app/core/settings/pages/space-usage/space-usage.page.module.ts
new file mode 100644
index 000000000..336081181
--- /dev/null
+++ b/src/app/core/settings/pages/space-usage/space-usage.page.module.ts
@@ -0,0 +1,49 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { TranslateModule } from '@ngx-translate/core';
+import { RouterModule, Routes } from '@angular/router';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from '@ionic/angular';
+
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+import { CorePipesModule } from '@pipes/pipes.module';
+
+import { CoreSettingsSpaceUsagePage } from './space-usage.page';
+
+const routes: Routes = [
+    {
+        path: '',
+        component: CoreSettingsSpaceUsagePage,
+    },
+];
+
+@NgModule({
+    imports: [
+        RouterModule.forChild(routes),
+        CommonModule,
+        IonicModule,
+        TranslateModule.forChild(),
+        CoreComponentsModule,
+        CoreDirectivesModule,
+        CorePipesModule,
+    ],
+    declarations: [
+        CoreSettingsSpaceUsagePage,
+    ],
+    exports: [RouterModule],
+})
+export class CoreSettingsSpaceUsagePageModule {}
diff --git a/src/app/core/settings/pages/space-usage/space-usage.page.ts b/src/app/core/settings/pages/space-usage/space-usage.page.ts
new file mode 100644
index 000000000..8d37f95a5
--- /dev/null
+++ b/src/app/core/settings/pages/space-usage/space-usage.page.ts
@@ -0,0 +1,157 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { IonRefresher } from '@ionic/angular';
+
+import { CoreSiteBasicInfo, CoreSites } from '@services/sites';
+import { CoreDomUtils } from '@services/utils/dom';
+import { Translate } from '@singletons/core.singletons';
+import { CoreEventObserver, CoreEvents, CoreEventSiteUpdatedData } from '@singletons/events';
+
+import { CoreSettingsHelper, CoreSiteSpaceUsage } from '../../services/settings.helper';
+
+/**
+ * Page that displays the space usage settings.
+ */
+@Component({
+    selector: 'page-core-app-settings-space-usage',
+    templateUrl: 'space-usage.html',
+})
+export class CoreSettingsSpaceUsagePage implements OnInit, OnDestroy {
+
+    loaded = false;
+    sites: CoreSiteBasicInfoWithUsage[] = [];
+    currentSiteId = '';
+    totals: CoreSiteSpaceUsage = {
+        cacheEntries: 0,
+        spaceUsage: 0,
+    };
+
+    protected sitesObserver: CoreEventObserver;
+
+    constructor() {
+        this.currentSiteId = CoreSites.instance.getCurrentSiteId();
+
+        this.sitesObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, async (data: CoreEventSiteUpdatedData) => {
+            const site = await CoreSites.instance.getSite(data.siteId);
+
+            const siteEntry = this.sites.find((siteEntry) => siteEntry.id == site.id);
+            if (siteEntry) {
+                const siteInfo = site.getInfo();
+
+                siteEntry.siteName = site.getSiteName();
+
+                if (siteInfo) {
+                    siteEntry.siteUrl = siteInfo.siteurl;
+                    siteEntry.fullName = siteInfo.fullname;
+                }
+            }
+        });
+    }
+
+    /**
+     * View loaded.
+     */
+    ngOnInit(): void {
+        this.loadSiteData().finally(() => {
+            this.loaded = true;
+        });
+    }
+
+    /**
+     * Convenience function to load site data/usage and calculate the totals.
+     *
+     * @return Resolved when done.
+     */
+    protected async loadSiteData(): Promise<void> {
+        // Calculate total usage.
+        let totalSize = 0;
+        let totalEntries = 0;
+
+        this.sites = await CoreSites.instance.getSortedSites();
+
+        const settingsHelper = CoreSettingsHelper.instance;
+
+        // Get space usage.
+        await Promise.all(this.sites.map(async (site) => {
+            const siteInfo = await settingsHelper.getSiteSpaceUsage(site.id);
+
+            site.cacheEntries = siteInfo.cacheEntries;
+            site.spaceUsage = siteInfo.spaceUsage;
+
+            totalSize += site.spaceUsage ||  0;
+            totalEntries += site.cacheEntries || 0;
+        }));
+
+        this.totals.spaceUsage = totalSize;
+        this.totals.cacheEntries = totalEntries;
+    }
+
+    /**
+     * Refresh the data.
+     *
+     * @param event Refresher event.
+     */
+    refreshData(event?: CustomEvent<IonRefresher>): void {
+        this.loadSiteData().finally(() => {
+            event?.detail.complete();
+        });
+    }
+
+    /**
+     * Deletes files of a site and the tables that can be cleared.
+     *
+     * @param siteData Site object with space usage.
+     */
+    async deleteSiteStorage(siteData: CoreSiteBasicInfoWithUsage): Promise<void> {
+        try {
+            const newInfo = await CoreSettingsHelper.instance.deleteSiteStorage(siteData.siteName || '', siteData.id);
+
+            this.totals.spaceUsage -= siteData.spaceUsage! - newInfo.spaceUsage;
+            this.totals.spaceUsage -= siteData.cacheEntries! - newInfo.cacheEntries;
+
+            siteData.spaceUsage = newInfo.spaceUsage;
+            siteData.cacheEntries = newInfo.cacheEntries;
+        } catch {
+            // Ignore cancelled confirmation modal.
+        }
+    }
+
+    /**
+     * Show information about space usage actions.
+     */
+    showInfo(): void {
+        CoreDomUtils.instance.showAlert(
+            Translate.instance.instant('core.help'),
+            Translate.instance.instant('core.settings.spaceusagehelp'),
+        );
+    }
+
+    /**
+     * Page destroyed.
+     */
+    ngOnDestroy(): void {
+        this.sitesObserver?.off();
+    }
+
+}
+
+/**
+ * Basic site info with space usage and cache entries that can be erased.
+ */
+export interface CoreSiteBasicInfoWithUsage extends CoreSiteBasicInfo {
+    cacheEntries?: number; // Number of cached entries that can be cleared.
+    spaceUsage?: number; // Space used in this site.
+}
diff --git a/src/app/core/settings/services/settings.helper.ts b/src/app/core/settings/services/settings.helper.ts
index 840300527..736ac57a2 100644
--- a/src/app/core/settings/services/settings.helper.ts
+++ b/src/app/core/settings/services/settings.helper.ts
@@ -31,8 +31,8 @@ import { makeSingleton, Translate } from '@singletons/core.singletons';
  * Object with space usage and cache entries that can be erased.
  */
 export interface CoreSiteSpaceUsage {
-    cacheEntries?: number; // Number of cached entries that can be cleared.
-    spaceUsage?: number; // Space used in this site (total files + estimate of cache).
+    cacheEntries: number; // Number of cached entries that can be cleared.
+    spaceUsage: number; // Space used in this site (total files + estimate of cache).
 }
 
 /**
diff --git a/src/app/core/settings/settings-routing.module.ts b/src/app/core/settings/settings-routing.module.ts
index aa46c4a83..66bb7de18 100644
--- a/src/app/core/settings/settings-routing.module.ts
+++ b/src/app/core/settings/settings-routing.module.ts
@@ -24,6 +24,12 @@ const routes: Routes = [
         path: 'general',
         loadChildren: () => import('./pages/general/general.page.module').then( m => m.CoreSettingsGeneralPageModule),
     },
+    {
+        path: 'spaceusage',
+        loadChildren: () =>
+            import('@core/settings/pages/space-usage/space-usage.page.module')
+                .then(m => m.CoreSettingsSpaceUsagePageModule),
+    },
     {
         path: '',
         loadChildren: () => import('./pages/app/app.page.module').then( m => m.CoreSettingsAppPageModule),
diff --git a/src/app/pipes/bytes-to-size.pipe.ts b/src/app/pipes/bytes-to-size.pipe.ts
new file mode 100644
index 000000000..97d18678b
--- /dev/null
+++ b/src/app/pipes/bytes-to-size.pipe.ts
@@ -0,0 +1,55 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Pipe, PipeTransform } from '@angular/core';
+
+import { CoreLogger } from '@singletons/logger';
+import { CoreTextUtils } from '@services/utils/text';
+
+/**
+ * Pipe to turn a number in bytes to a human readable size (e.g. 5,25 MB).
+ */
+@Pipe({
+    name: 'coreBytesToSize',
+})
+export class CoreBytesToSizePipe implements PipeTransform {
+
+    protected logger: CoreLogger;
+
+    constructor() {
+        this.logger = CoreLogger.getInstance('CoreBytesToSizePipe');
+    }
+
+    /**
+     * Takes a number and turns it to a human readable size.
+     *
+     * @param value The bytes to convert.
+     * @return Readable bytes.
+     */
+    transform(value: number | string): string {
+        if (typeof value == 'string') {
+            // Convert the value to a number.
+            const numberValue = parseInt(value, 10);
+            if (isNaN(numberValue)) {
+                this.logger.error('Invalid value received', value);
+
+                return value;
+            }
+            value = numberValue;
+        }
+
+        return CoreTextUtils.instance.bytesToSize(value);
+    }
+
+}
diff --git a/src/app/pipes/pipes.module.ts b/src/app/pipes/pipes.module.ts
index 4108c1255..492bf7e08 100644
--- a/src/app/pipes/pipes.module.ts
+++ b/src/app/pipes/pipes.module.ts
@@ -17,6 +17,7 @@ import { CoreCreateLinksPipe } from './create-links.pipe';
 import { CoreFormatDatePipe } from './format-date.pipe';
 import { CoreNoTagsPipe } from './no-tags.pipe';
 import { CoreTimeAgoPipe } from './time-ago.pipe';
+import { CoreBytesToSizePipe } from './bytes-to-size.pipe';
 
 @NgModule({
     declarations: [
@@ -24,6 +25,7 @@ import { CoreTimeAgoPipe } from './time-ago.pipe';
         CoreNoTagsPipe,
         CoreTimeAgoPipe,
         CoreFormatDatePipe,
+        CoreBytesToSizePipe,
     ],
     imports: [],
     exports: [
@@ -31,6 +33,7 @@ import { CoreTimeAgoPipe } from './time-ago.pipe';
         CoreNoTagsPipe,
         CoreTimeAgoPipe,
         CoreFormatDatePipe,
+        CoreBytesToSizePipe,
     ],
 })
 export class CorePipesModule {}
diff --git a/src/app/services/sites.ts b/src/app/services/sites.ts
index 7bcccfc8b..665798c64 100644
--- a/src/app/services/sites.ts
+++ b/src/app/services/sites.ts
@@ -1038,7 +1038,7 @@ export class CoreSitesProvider {
                     id: site.id,
                     siteUrl: site.siteUrl,
                     fullName: siteInfo?.fullname,
-                    siteName: CoreConstants.CONFIG.sitename ?? siteInfo?.sitename,
+                    siteName: CoreConstants.CONFIG.sitename == '' ? siteInfo?.sitename: CoreConstants.CONFIG.sitename,
                     avatar: siteInfo?.userpictureurl,
                     siteHomeId: siteInfo?.siteid || 1,
                 };
@@ -1055,32 +1055,32 @@ export class CoreSitesProvider {
      * @param ids IDs of the sites to get. If not defined, return all sites.
      * @return Promise resolved when the sites are retrieved.
      */
-    getSortedSites(ids?: string[]): Promise<CoreSiteBasicInfo[]> {
-        return this.getSites(ids).then((sites) => {
-            // Sort sites by url and ful lname.
-            sites.sort((a, b) => {
-                // First compare by site url without the protocol.
-                const urlA = a.siteUrl.replace(/^https?:\/\//, '').toLowerCase();
-                const urlB = b.siteUrl.replace(/^https?:\/\//, '').toLowerCase();
-                const compare = urlA.localeCompare(urlB);
+    async getSortedSites(ids?: string[]): Promise<CoreSiteBasicInfo[]> {
+        const sites = await this.getSites(ids);
 
-                if (compare !== 0) {
-                    return compare;
-                }
+        // Sort sites by url and ful lname.
+        sites.sort((a, b) => {
+            // First compare by site url without the protocol.
+            const urlA = a.siteUrl.replace(/^https?:\/\//, '').toLowerCase();
+            const urlB = b.siteUrl.replace(/^https?:\/\//, '').toLowerCase();
+            const compare = urlA.localeCompare(urlB);
 
-                // If site url is the same, use fullname instead.
-                const fullNameA = a.fullName?.toLowerCase().trim();
-                const fullNameB = b.fullName?.toLowerCase().trim();
+            if (compare !== 0) {
+                return compare;
+            }
 
-                if (!fullNameA || !fullNameB) {
-                    return 0;
-                }
+            // If site url is the same, use fullname instead.
+            const fullNameA = a.fullName?.toLowerCase().trim();
+            const fullNameB = b.fullName?.toLowerCase().trim();
 
-                return fullNameA.localeCompare(fullNameB);
-            });
+            if (!fullNameA || !fullNameB) {
+                return 0;
+            }
 
-            return sites;
+            return fullNameA.localeCompare(fullNameB);
         });
+
+        return sites;
     }
 
     /**
diff --git a/src/app/singletons/events.ts b/src/app/singletons/events.ts
index 4bc192fa3..eeb8f554c 100644
--- a/src/app/singletons/events.ts
+++ b/src/app/singletons/events.ts
@@ -16,6 +16,7 @@ import { Params } from '@angular/router';
 import { Subject } from 'rxjs';
 
 import { CoreLogger } from '@singletons/logger';
+import { CoreSiteInfoResponse } from '@classes/site';
 
 /**
  * Observer instance to stop listening to an event.
@@ -192,13 +193,24 @@ export class CoreEvents {
 
 }
 
+/**
+ * Some events contains siteId added by the trigger function. This type is intended to be combined with others.
+ */
+export type CoreEventSiteData = {
+    siteId?: string;
+};
+
+/**
+ * Data passed to SITE_UPDATED event.
+ */
+export type CoreEventSiteUpdatedData = CoreEventSiteData & CoreSiteInfoResponse;
+
 /**
  * Data passed to SESSION_EXPIRED event.
  */
-export type CoreEventSessionExpiredData = {
+export type CoreEventSessionExpiredData = CoreEventSiteData & {
     pageName?: string;
     params?: Params;
-    siteId?: string;
 };
 
 /**
diff --git a/src/theme/app.scss b/src/theme/app.scss
index 3995d3bef..1ad9fce1b 100644
--- a/src/theme/app.scss
+++ b/src/theme/app.scss
@@ -105,6 +105,13 @@ ion-list.list-md {
     font-size: 14px;
 }
 
+// Item styles
+.item.core-selected-item {
+    // TODO: Add safe are to border and RTL
+    border-inline-start: var(--selected-item-border-width) solid var(--selected-item-color);
+    --ion-safe-area-left: calc(-1 * var(--selected-item-border-width));
+}
+
 // Avatar
 // -------------------------
 // Large centered avatar
diff --git a/src/theme/variables.scss b/src/theme/variables.scss
index e7f6acc4d..c9e862cbd 100644
--- a/src/theme/variables.scss
+++ b/src/theme/variables.scss
@@ -131,6 +131,9 @@
         }
     }
 
+    --selected-item-color: var(--custom-selected-item-color, var(--core-color));
+    --selected-item-border-width: var(--custom-selected-item-border-width, 5px);
+
     --drop-shadow: var(--custom-drop-shadow, 0, 0, 0, 0.2);
 
     --core-login-background: var(--custom-login-background, var(--white));