Building a Web File Manager on STM32, ESP32, and Embedded Linux with Mongoose Wizard
Embedded systems often require a simple and reliable way to interact with the device's internal storage. Whether for diagnostics, logging, or configuration, a web interface that enables file upload, download, and deletion can dramatically improve development efficiency and end-user experience. In this article, we demonstrate how to build a web file manager using Mongoose Web Server and the Mongoose Wizard across three platforms: Embedded Linux, ESP32, and STM32.
Why a Web File Manager?
A web file manager lets users interact with files stored on an embedded device through a web browser. With drag-and-drop uploads and clickable file links, it becomes easy to handle firmware logs, configuration files, or even firmware updates without needing serial tools or command-line access.
Step 1: Create the File Manager Dashboard
We start by designing a web-based UI using the Mongoose Wizard:
- Select the target platform (Linux/Mac/Windows)
- Choose “Blank Dashboard”
- Add a list view to show uploaded files (name, size, link, delete button)
- Add a file upload button with automatic API binding
The Wizard automatically sets up HTTP endpoints for uploading (/api/upload/FNAME
), listing (/api/files
), and deleting files (/api/delete
), allowing full file lifecycle management.
Step 2: Implement Custom Handlers
The wizard generates placeholder handlers in mongoose_glue.c
. We replace them with custom implementations in main.c
:
my_file_open_upload
andmy_file_serve_upload
handle upload/downloadmy_reply_files
returns a JSON array of uploaded filesmy_action_start_delete
deletes files synchronously
The default upload directory (/tmp
) is replaced with a configurable upload
directory. The mg_fs_ls
API is used to iterate through files and stat them for size metadata.
Here is the source code for the reference:
size_t print_files(void (*out)(char, void *), void *ptr, va_list *ap) {
char buf[128] = {}, full[256];
size_t len = 0;
while (mg_fs_ls(s_fs, s_dir, buf, sizeof(buf))) {
MG_INFO(("FILE: %s", buf));
size_t size = 0;
mg_snprintf(full, sizeof(full), "%s/%s", s_dir, buf);
s_fs->st(full, &size, NULL);
if (len > 0) len += mg_xprintf(out, ptr, ",");
len += mg_xprintf(out, ptr, "{%m:%m,%m:%lu}", //
MG_ESC("name"), MG_ESC(buf), //
MG_ESC("size"), (long) size);
}
(void) ap;
return len;
}
void my_reply_files(struct mg_connection *c, struct mg_http_message *hm) {
const char *headers =
"Cache-Control: no-cache\r\n"
"Content-Type: application/json\r\n";
(void) hm;
mg_http_reply(c, 200, headers, "[%M]\n", print_files);
}
bool my_check_delete(void) {
return false;
}
void my_start_delete(struct mg_str params) {
struct mg_str tok = mg_json_get_tok(params, "$.name");
if (tok.len > 2 && tok.buf[0] == '"') {
char path[128];
mg_snprintf(path, sizeof(path), "%s/%.*s", s_dir, tok.len - 2, tok.buf + 1);
MG_DEBUG(("PATH [%s]", path));
s_fs->rm(path);
}
}
void *my_file_open_upload(char *file_name, size_t file_size) {
char path[128];
mg_snprintf(path, sizeof(path), "%s/%s", s_dir, file_name);
if (file_size > 1024 * 1024) MG_INFO(("%lu is kinda big", file_size));
return mg_fs_open(s_fs, path, MG_FS_WRITE);
}
void my_file_serve_upload(struct mg_connection *c, struct mg_http_message *hm,
char *file_name) {
struct mg_http_serve_opts opts = {};
char path[128];
opts.fs = s_fs;
mg_snprintf(path, sizeof(path), "%s/%s", s_dir, file_name);
mg_http_serve_file(c, hm, path, &opts);
}
The initialisation snippet:
s_fs->mkd(s_dir);
mongoose_set_http_handlers("files", my_reply_files);
mongoose_set_http_handlers("upload", my_file_open_upload,
my_file_serve_upload);
mongoose_set_http_handlers("delete", my_check_delete, my_start_delete);
Step 3: Porting to ESP32
For ESP32 file upload, we target the ESP32-C6 board using ESP-IDF. After generating the ESP32 project with the Wizard:
- Replace the backend logic with the same handlers used on Linux
- Use
/spiffs
as the upload directory (ESP32’s SPIFFS filesystem is POSIX-compliant) - Set Wi-Fi credentials in
wifi.h
- Build with Docker and flash the firmware
- Access the ESP32's IP address in a browser to test ESP32 file upload functionality
This allows full ESP32 file upload support with browsing and deletion directly via the web UI.
Step 4: Porting to STM32 with CubeIDE and FreeRTOS
For STM32 file upload, we use the Nucleo-H563ZI development board. The board lacks external storage, but its internal flash can be used to implement a POSIX-style stm32 filesystem using LittleFS.

Steps:
- Generate the CubeIDE project with FreeRTOS enabled
- Enable POSIX FS and directory listing in
mongoose_config.h
- Copy
dirent.h
and requiredlittlefs
files (lfs.c, lfs.h, retarget_lfs.c) - Modify
syscalls.c
to mark functions like_open
,_read
, and_write
as weak to resolve linker conflicts with newlib - Configure the filesystem to use the final 128KB of internal flash
After flashing, open the board’s IP in a browser and test STM32 file upload. The stm32 filesystem supports full read/write/delete capabilities through the dashboard UI.
Final Result
The result is a cross-platform web file manager that:
- Works on Embedded Linux, ESP32, and STM32
- Supports ESP32 file upload and STM32 file upload
- Uses a consistent HTTP API and UI layout across all platforms
- Leverages Mongoose's built-in web server and filesystem abstraction
Whether you’re building a device dashboard, an OTA update panel, or just need to move files to/from your hardware, this setup delivers fast, reliable, and repeatable functionality.
Conclusion
Implementing a web file manager for embedded systems has never been easier. With Mongoose Wizard and POSIX-compatible filesystems, full-featured ESP32 file upload and STM32 file upload support can be achieved in just an hour. Integrate it once and reuse across your product lines.
For full code examples and board-specific tips, visit https://mongoose.ws.