ESP32 Captive Portal – Fetching HTML using LittleFS


In the Create Captive Portal with ESP32 article, we saw an example of how to create a captive portal with ESP32, using the AsyncWebServer library. While the HTML file was hard-coded into the code in that tutorial, you would ideally want to store the HTML file elsewhere and reduce the bulkiness of your code.

You could utilize the SPIFFS storage of ESP32. For those who aren’t aware of SPIFFS, you can think of it as a mini-SD Card onboard the ESP32. ESP32 comes with a default SPIFFS storage of 1.5 MB. You can increase or decrease this size (see How to set partitions in ESP32). This default 1.5 MB storage is large enough to store sufficiently HTML files, and remove the burden of HTML from the Arduino code.

We have already seen how to utilize this portion of the memory for storing and retrieving our HTML file, using the SPIFFS library, in a previous tutorial: ESP32 Captive Portal – Fetching HTML from SPIFFS. If you’ve already been through that tutorial, you will find that this one is very similar.

In this tutorial, we will see how to fetch the HTML file from the same storage, but we will use the LittleFS library this time. In other words, the physical location where the file is stored remains the same. But the file system (the way the files are stored, organized, and retrieved), changes. LittleFS claims to be about 4 times faster than SPIFFS for read operations, and about 2 times faster for write operations. It will perhaps be inducted in the Arduino core for ESP32 in a future update, and you won’t have to make any additional downloads once that is done. For now, follow the steps below.


It is strongly recommended that you go through the Create Captive Portal with ESP32 tutorial before starting this one. In this tutorial, no further explanation is provided for the concepts already covered there.

Apart from this, you will need to download and install the ESP32 File System Uploader tool for the Arduino IDE and the mklittlefs tool, to upload the HTML file to SPIFFS, using LittleFS. The steps for doing that are given below.

Making Arduino IDE Compatible with LittleFS

In order to make your Arduino IDE compatible with LittleFS, follow the steps below:

Download the updated esp32fs plugin

You can get the plugin compatible with LittleFS, and having SPIFFS and FatFS support here. Download the file, and once you unzip it, you will see that it contains esp32fs.jar file.

Now, locate the tools folder of your Arduino IDE (note that this is tools folder of the IDE, not specific to any board). On Windows, this is typically located in C:\Program Files (x86)\Arduino\tools.

Over there, if you’ve already downloaded the esp32fs plugin in the past, you will see an ESP32FS folder. If it is already present, go to ESP32FS/tool/ and replace the jar file there with the one you just downloaded. If that folder is not present, create it, create the ‘tool’ folder within it, and paste your jar file within it.

Download mklittlefs

In order to make LittleFS file upload work for ESP32, you will need the mklittlefs tool in your ESP32 tools folder (note, we are referring to the board-specific folder here, not the Arduino IDE tools folder).

The mklittlefs tool can be downloaded from here. Download the one compatible to your platform, and unzip it. You will see that it contains the mklittlefs.exe file. You need to copy this file to the tools location, which contains or On my machine (Windows), the path is: C:\Users\Yash\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\tools

When you now restart the Arduino IDE, you should be able to see the ‘ESP32 Sketch Data Upload’ option in the Tools menu.

When you click on that option, you will get a dropdown asking you to pick from LittleFS, SPIFFS, and FatFS file system.

Depending on which file system you choose, the corresponding image will be created in the SPIFFS region of your ESP32 storage. What all will be stored? Everything within the /data folder of your Sketch folder. If you see the GitHub repo of this project, you will see that we have only the HTML file in the data folder for now.

Install the Littlefs_esp32 library

Last but not the least, you will need the LittleFS library. Go to the library manager in the Arduino IDE, and search for littlefs. Install the library by lorol.

With these steps, your Arduino IDE should be compatible to LittleFS. If you face any issues with the installation of the plugin or the mklittlefs tool, you can check out the README of the GitHub repo of the plugin for more detailed steps.

Code Walkthrough

The code can be found on GitHub here:

As mentioned earlier, the HTML file is stored within the data folder, and the image of the contents of the data folder is created using the esp32fs tool.

Click on the tool, select ‘LittleFS’ from the dropdown, and click ‘OK’. You need to select SPIFFS when working with the SPIFFS library. Please note that the Serial Monitor has to be closed for the file upload to proceed.

The file will start getting uploaded and you should see the following in the Output Window:

Once this is done, the HTML file should be present in the SPIFFS storage, using the LittleFS file system. Now, upload the .ino sketch using the normal upload process.

Because the Arduino code is more or less similar to the code in the Create Captive Portal with ESP32 tutorial, we will just look at the differences.

Library Inclusion

We add the LITTLEFS and the FS libraries in addition to the other libraries.

#include "FS.h"
#include <LITTLEFS.h>

listDir() function

While this is not required for the Captive Portal to work, this function has just been added for us to verify that the ‘ESP32 Sketch Data Upload’ tool worked, and that our HTML file is indeed within the SPIFFS.

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
    Serial.printf("Listing directory: %s\r\n", dirname);

    File root =;
        Serial.println("- failed to open directory");
        Serial.println(" - not a directory");

    File file = root.openNextFile();
            Serial.print("  DIR : ");
                listDir(fs,, levels -1);
        } else {
            Serial.print("  FILE: ");
            Serial.print("\tSIZE: ");
        file = root.openNextFile();

This function is invoked in the setup().

//List contents of SPIFFS
listDir(LITTLEFS, "/", 0);

Here, LITTLEFS is the file system, “/” specifies that we want to look at the root directory of SPIFFS, and 0 specifies that we don’t want to go further down the levels (i.e., we don’t want nested listing).

The Serial Monitor output will show this listing.

In case you don’t see the HTML file, make sure you have selected the correct file system (LittleFS), and re-upload the files once again, using the plugin.


This is another step added in the setup. We need to initialize the SPIFFS.

    Serial.println("An Error has occurred while mounting LITTLEFS");

The ‘true’ in the LITTLEFS.begin() specifies that the LITTLEFS should be formatted if the mounting fails. If you’ve worked with the SPIFFS library before, you will notice that the syntax for both the libraries is more or less the same.


This is perhaps the most important change in the code. In the previous captive portal code, we used the following line for displaying our HTML page:

request->send_P(200, "text/html", index_html);

Over here, we use:

request->send(LITTLEFS, "/captive_page.html","text/html", false);

Why this change? If you go to the source code of the AsyncWebServerLibrary, you will see the following options for the send function.

    void send(AsyncWebServerResponse *response);
    void send(int code, const String& contentType=String(), const String& content=String());
    void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
    void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
    void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
    void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
    void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
    void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
    void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);

Since we want to fetch our HTML page from a file system, we are using the highlighted option.

In case you wish to display a dynamic web page, you can add placeholders in the HTML, and add a processor function, as described in Display dynamic webpages with ESP32. With the processor, the syntax will be,

request->send(LITTLEFS, "/captive_page.html","text/html", false, processor);

That’s it. These are the code changes required for making the Captive Portal work with an HTML page stored in SPIFFS, using the LITTLEFS File System. You can connect your mobile or PC to your ESP32, and the captive portal will immediately pop up.

I hope you enjoyed this tutorial. For more tutorials on ESP32, checkout


  1. This is an excellent tutorial, thank you! Do you know how I can link to external files in my index.html (for instance style.css)? Currently the dnsserver intercepts every URL. Perhaps it is possible to include an exception in this function: dnsServer.start(53, “*”, WiFi.softAPIP()); ?

    1. Hey, this is a good question. Here’s how you can do it. First create a style.css in the data folder. Below is an example of a very simple style.css:
      h3 {color: #00f;}

      Next, in the HTML file, add the following line in the head:

      < link rel="stylesheet" href="/style.css" > (without the space after ‘<'). Notice the / here before style.css. Next, in the ino, in the SetupServer function, add this block
      server.on(“/style.css”, HTTP_GET, [](AsyncWebServerRequest * request) {
      request->send(LITTLEFS, “/style.css”, “text/css”);

      This should give you a blue-colored h3 text. I hope this answers your question.

  2. Thank you so much for your amazing work!
    Although the first tutorial “” works with IOS, with this new version using LittleFS the captive portal does not show up on my iphone. Do you know what to do?

      1. Same here, doesn’t open on my phone, nor on my PC, I’ve been going round in circles for hours, then found this comment. I am using an 8266, not sure whether that is the issue?

Leave a comment

Your email address will not be published. Required fields are marked *