diff --git a/tasmota/xdrv_98_filesystem.ino b/tasmota/xdrv_98_filesystem.ino
new file mode 100644
index 000000000..5ea685109
--- /dev/null
+++ b/tasmota/xdrv_98_filesystem.ino
@@ -0,0 +1,494 @@
+/*
+ xdrv_98_filesystem.ino - unified file system for Tasmota
+
+ Copyright (C) 2020 Gerhard Mutz and Theo Arends
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+/*
+this driver adds universal file system support for
+ESP8266 (sd card or littlfs on > 1 M devices with special linker file e.g. eagle.flash.4m2m.ld)
+(makes no sense on 1M devices without sd card)
+and
+ESP32 (sd card or fatfile system)
+the sd card chip select is the standard SPI_CS or when not found SDCARD_CS_PIN
+initializes the FS System Pointer ufsp which can be used by all standard file system calls
+the only specific call is ufs_fsinfo() which gets the total size (0) and free size (1)
+a button is created in the setup section to show up the file directory to download and upload files
+subdirectories are supported
+
+console calls :
+
+ufs fs info
+ufstype get filesytem type 0=none 1=SD 2=Flashfile
+ufssize total size in kB
+ufsfree free size in kB
+
+driver enabled by
+
+#define USE_UFILESYS
+
+*/
+
+
+#ifdef USE_UFILESYS
+
+#define XDRV_98 98
+
+#ifdef ESP8266
+#include
+#include
+#include
+#include
+#else
+#include
+#include "FFat.h"
+#include "FS.h"
+#include "SPIFFS.h"
+#endif
+
+#define UFS_FILE_WRITE "w"
+#define UFS_FILE_READ "r"
+
+// global file system pointer
+FS *ufsp;
+char ufs_path[48];
+File ufs_upload_file;
+
+
+#ifndef SDCARD_CS_PIN
+#define SDCARD_CS_PIN 4
+#endif
+
+// 0 = none, 1 = SD, 2 = Flash
+// spiffs should be obsolete
+uint8_t ufs_type;
+#define UFS_TNONE 0
+#define UFS_TSDC 1
+#define UFS_TFAT 2
+#define UFS_TSPIFFS 3
+
+#ifndef UFS_SDCS
+#define UFS_SDCS 4
+#endif
+
+void UFSInit(void) {
+ ufs_type = 0;
+ // check for fs options,
+ // 1. check for SD card
+ // 2. check for littlefs or FAT
+ // 3. check for SPIFFS obsolete
+// if (TasmotaGlobal.spi_enabled) {
+ if (1) {
+ int8_t cs;
+ if (!PinUsed(GPIO_SPI_CS)) {
+ cs = SDCARD_CS_PIN;
+ } else {
+ cs = Pin(GPIO_SPI_CS);
+ }
+
+ if (SD.begin(cs)) {
+#ifdef ESP8266
+ ufsp = (FS*)&SD;
+#else
+ ufsp = &SD;
+#endif
+ ufs_type = 1;
+ return;
+ }
+ }
+
+// if no success with sd card try flash fs
+#ifdef ESP8266
+ ufsp = &LittleFS;
+ if (!fsp->begin()) {
+ return;
+ }
+#else
+ ufsp = &FFat;
+ if (!FFat.begin(true)) {
+ return;
+ }
+#endif
+ ufs_type = 2;
+ return;
+}
+
+uint32_t ufs_fsinfo(uint32_t sel) {
+uint32_t result = 0;
+
+ switch (ufs_type) {
+ case UFS_TSDC:
+#ifdef ESP32
+ if (sel == 0) {
+ result = SD.totalBytes();
+ } else {
+ result = (SD.totalBytes() - SD.usedBytes());
+ }
+#else
+ // currently no support on esp8266
+#endif
+ break;
+ case UFS_TFAT:
+#ifdef ESP8266
+ FSInfo64 fsinfo;
+ ufsp->info64(fsinfo);
+ if (sel == 0) {
+ result = fsinfo.totalBytes;
+ } else {
+ result = (fsinfo.totalBytes - fsinfo.usedBytes);
+ }
+#else
+ if (sel == 0) {
+ result = FFat.totalBytes();
+ } else {
+ result = FFat.freeBytes();
+ }
+#endif
+ break;
+ case UFS_TSPIFFS:
+ break;
+ }
+ return result / 10000;
+}
+
+#if USE_LONG_FILE_NAMES>0
+#undef REJCMPL
+#define REJCMPL 6
+#else
+#undef REJCMPL
+#define REJCMPL 8
+#endif
+
+uint8_t ufs_reject(char *name) {
+
+ char *lcp = strrchr(name,'/');
+ if (lcp) {
+ name = lcp + 1;
+ }
+
+ while (*name=='/') name++;
+ if (*name=='_') return 1;
+ if (*name=='.') return 1;
+
+ if (!strncasecmp(name, "SPOTLI~1", REJCMPL)) return 1;
+ if (!strncasecmp(name, "TRASHE~1", REJCMPL)) return 1;
+ if (!strncasecmp(name, "FSEVEN~1", REJCMPL)) return 1;
+ if (!strncasecmp(name, "SYSTEM~1", REJCMPL)) return 1;
+ if (!strncasecmp(name, "System Volume", 13)) return 1;
+ return 0;
+}
+
+// format number with thousand marker
+void UFS_form1000(uint32_t number, char *dp, char sc) {
+ char str[32];
+ sprintf(str, "%d", number);
+ char *sp = str;
+ uint32_t inum = strlen(sp)/3;
+ uint32_t fnum = strlen(sp)%3;
+ if (!fnum) inum--;
+ for (uint32_t count=0; count<=inum; count++) {
+ if (fnum){
+ memcpy(dp,sp,fnum);
+ dp+=fnum;
+ sp+=fnum;
+ fnum=0;
+ } else {
+ memcpy(dp,sp,3);
+ dp+=3;
+ sp+=3;
+ }
+ if (count!=inum) {
+ *dp++=sc;
+ }
+ }
+ *dp=0;
+}
+
+
+const char kUFSCommands[] PROGMEM = "UFS" "|" // Prefix
+ "|" "TYPE" "|" "SIZE" "|" "FREE";
+
+void (* const kUFSCommand[])(void) PROGMEM = {
+ &UFS_info, &UFS_type, &UFS_size, &UFS_free};
+
+void UFS_info(void) {
+ Response_P(PSTR("{\"UFS\":{\"TYPE\":%d,\"SIZE\":%d,\"FREE\":%d}}"),ufs_type,ufs_fsinfo(0),ufs_fsinfo(1));
+}
+void UFS_type(void) {
+ ResponseCmndNumber(ufs_type);
+}
+void UFS_size(void) {
+ ResponseCmndNumber(ufs_fsinfo(0));
+}
+void UFS_free(void) {
+ ResponseCmndNumber(ufs_fsinfo(1));
+}
+
+const char UFS_WEB_DIR[] PROGMEM =
+ "