EmbedFS is a tiny, read-only virtual filesystem intended for Arduino and ESP32 projects.
It lets you embed files (assets) directly in flash (program memory) as const data and
access them through a filesystem-like API compatible with common Arduino FS libraries
(SD, SPIFFS, LittleFS). Use this when you want to ship static assets with your firmware
and avoid runtime copying or external storage.
- Read-only virtual filesystem implemented as arrays in flash (const data).
- Provides Arduino
File/FSstyle operations (open, read, exists,openNextFile,rewindDirectory, etc.). - Directories can be opened and iterated, so hierarchical layouts work as expected.
- Designed for ESP32 and other Arduino-compatible boards.
- Works with assets generated by the Arduino CLI Wrapper (
assets_embed.h) or any tool that converts a directory into a C header with a file index and byte arrays. - No runtime writes — ideal for firmware-embedded resources.
- Convert your
assets/folder to a C header (for exampleassets_embed.h) using the Arduino CLI Wrapper or another conversion tool. The generated header should expose the embedded data and an index that the library can consume (for example: pointers to file data, lengths, and names). - Include
assets_embed.hin your sketch and initialize EmbedFS with the generated data. - Use the same familiar FS-like calls to open and read files.
The example sketch in examples/BasicTest/ initializes EmbedFS like this:
EmbedFS.begin(assets_file_names, assets_file_data, assets_file_sizes, assets_file_count);So the generated header used by that example exposes these symbols (names and types):
constexpr size_t assets_file_count— number of embedded filesconst char* const assets_file_names[assets_file_count]— array of file paths (C strings)const uint8_t* const assets_file_data[assets_file_count]— array of pointers to file bytes (PROGMEM/const)const size_t assets_file_sizes[assets_file_count]— array of file sizes
The library begin() call in that sketch expects the arrays above. The header generated
by Arduino CLI Wrapper is PROGMEM-friendly (values stored with PROGMEM if available) and
uses C-style arrays so they can be passed directly to begin().
Note: The library is read-only. It is not a replacement for writable filesystems like SD or LittleFS when you need persistence or runtime file updates.
The code below mirrors examples/BasicTest/BasicTest.ino. The generated header (assets_embed.h)
is assumed to provide the arrays described above.
#include <EmbedFS.h>
#include "assets_embed.h"
void setup() {
Serial.begin(115200);
delay(1000);
if (!EmbedFS.begin(assets_file_names, assets_file_data, assets_file_sizes, assets_file_count)) {
Serial.println("EmbedFS mount failed");
return;
}
Serial.println("Embedded files:");
for (size_t i = 0; i < assets_file_count; ++i) {
File file = EmbedFS.open(assets_file_names[i], "r");
if (!file) {
continue;
}
Serial.printf("- path: %s size: %u bytes\n", file.path(), static_cast<unsigned>(file.size()));
while (file.available()) {
Serial.write(file.read());
}
Serial.println();
file.close();
}
Serial.println("Directory listing of /directory:");
File dir = EmbedFS.open("/directory");
if (dir && dir.isDirectory()) {
dir.rewindDirectory();
while (true) {
File child = dir.openNextFile();
if (!child) {
break;
}
Serial.printf("- %s (%s)\n", child.path(), child.isDirectory() ? "dir" : "file");
child.close();
}
dir.close();
}
}
void loop() {
delay(10000);
}BasicTest repeats the dump every 10 seconds so you can watch file contents and /directory
children on the serial monitor.
To be a convenient drop-in for Arduino projects, the EmbedFS library typically offers the following minimal contract:
bool begin(const char* const file_names[], const uint8_t* const file_data[], const size_t file_sizes[], size_t file_count)- Initialize the library with the generated arrays (file names, data pointers, sizes and count).
File open(const char* path, const char* mode = "r")- Open a file in read mode (
"r"). The returnedFilesupportsread(),available(),size(),path()andname(). When the target is a directory,isDirectory()returns true and you can iterate children viaopenNextFile()/rewindDirectory().
- Open a file in read mode (
bool exists(const char* path)- Return true if an embedded file or directory with that path exists.
size_t totalBytes()/size_t usedBytes()- Return the total embedded byte count (identical for read-only storage).
Error modes:
begin()returns false on invalid index pointers or zero count.open()returns a falsyFilewhen the target path is missing or the mode is unsupported.
Design note: keep the API read-only. If you need write support, use SD or LittleFS.
Class shape recommendation (FS-like)
To be familiar to users, EmbedFS mirrors LittleFS and exposes a global instance. The implementation is shaped like this:
class EmbedFSFS : public FS {
public:
bool begin(const char* const file_names[], const uint8_t* const file_data[], const size_t file_sizes[], size_t file_count);
bool begin(bool formatOnFail = false, const char* basePath = "/embedfs", uint8_t maxOpenFiles = 10, const char* partitionLabel = nullptr);
bool exists(const char* path) const;
File open(const char* path, const char* mode = "r") const;
void end();
size_t totalBytes();
size_t usedBytes();
};
extern EmbedFSFS EmbedFS;examples/BasicTest/ calls EmbedFS.begin(assets_file_names, assets_file_data, assets_file_sizes, assets_file_count);,
streams text directly from the returned File, and opens /directory to iterate children with openNextFile().
Preferred: Arduino CLI Wrapper (the project that converts assets/ to assets_embed.h).
It typically produces a header that contains the file data and an index table. The details
depend on the tool, but a minimal layout is:
- an array (or arrays) with file bytes stored in PROGMEM/const
- a struct array with entries: { const char* path; const uint8_t* data; size_t length }
- a symbol with count/size
If you don't use Arduino CLI Wrapper, you can prepare a similar header with a script. Example (Python) to generate a simple header (illustrative only):
#!/usr/bin/env python3
import sys
from pathlib import Path
out = []
files = list(Path('assets').rglob('*'))
for p in files:
if p.is_file():
name = '/' + str(p.relative_to('assets')).replace('\\', '/')
b = p.read_bytes()
arr = ','.join(str(x) for x in b)
out.append(f"// {name}\nstatic const uint8_t data_{len(out)}[] PROGMEM = {{{arr}}};\n")
print('\n'.join(out))You must also produce the index entry mapping paths to data arrays. The exact layout
depends on how EmbedFS::begin() expects the input.
See examples/BasicTest/ in this repository for a minimal Arduino sketch and an
example assets_embed.h produced by the Arduino CLI Wrapper. That example shows the
expected header format and demonstrates directory iteration.
- Read-only: files cannot be modified at runtime.
- Flash-size: embedded assets are stored in program memory; be mindful of flash usage on small MCUs.
- Path normalization: generated index should use a consistent path format (leading
slash vs. not).
EmbedFSshould normalize paths internally. - Binary files: reading binary data works the same as text; take care when printing to Serial (use write rather than print for binary blobs).
- Accessing data stored in flash (PROGMEM) is fast and avoids SD card latency.
- Memory usage: EmbedFS should avoid copying whole files into RAM; provide a File stream abstraction that reads directly from flash.
Contributions are welcome. When opening an issue or PR, please include:
- A short description of the use case.
- Minimal reproduction (board, Arduino core version, a small sketch and assets).
This repository follows the license in the project root (see LICENSE).
- If
begin()fails: check that the generated header symbols are present and the index format matches whatEmbedFSexpects. - If files are not found: verify the path used by the sketch matches the path in the
generated index (leading
/, case sensitivity, directory separators).
EmbedFS provides a small, convenient way to bundle static assets into your firmware and access them via a familiar filesystem-like API. It is especially useful for small web assets, configuration templates, or resources that never change at runtime.