ESP32 Web Server using LittleFS Filesystem (serve files from filesystem)

In this tutorial, you’ll learn how to build a web server with the ESP32 that serves HTML and CSS files stored in the LittleFS filesystem. Instead of embedding the HTML and CSS directly in the Arduino sketch, we’ll store them as separate files, making the code cleaner and easier to manage.

ESP32 Web Server using LittleFS Filesystem (serve files from the filesystem)

The web server we’ll build shows how to control the ESP32 outputs and how to display sensor readings. As an example, we’ll control an LED and display sensor readings from a BME280 sensor.

You can use the concepts learned in this tutorial to control any output or display sensor readings from other sensors.

Table of Contents

In this tutorial, we’ll cover the following topics:

Introducing LittleFS

LittleFS is a lightweight filesystem created for microcontrollers. It lets you access the flash memory as you do in a standard file system on your computer, but it’s simpler and more limited. You can read, write, close, and delete files. Using LittleFS with the ESP32 boards is useful to:

  • Create configuration files with settings;
  • Save data permanently, even after a restart or power loss;
  • Create files to store small amounts of data instead of using a microSD card;
  • Save HTML, CSS, and JavaScript files to build a web server interface (like we’ll do in this tutorial);
  • Store images, figures, and icons for your web pages or display modules;
  • Log sensor readings or application data locally;
  • And much more, depending on your project needs.

It’s especially helpful in projects where you need to manage data directly on the ESP32 without relying on external storage devices.

Introducing ESP32 Web Servers

In simple terms, a web server is a “computer” that delivers web pages to users. It stores a website’s files, including HTML documents and related assets like images, CSS style sheets, fonts, and other resources.

When a user makes a request—usually by entering a URL or clicking a link—the web server responds by sending the requested files to the user’s web browser using the HTTP (Hypertext Transfer Protocol). HTTP is the foundation of data communication on the web, defining how messages are formatted and transmitted between clients (like your browser) and servers.

ESP32 Request Response

In this example, the ESP32 acts as a web server. It stores the HTML and CSS files needed to build the web page on the LittleFS filesystem. When you access the ESP32 IP address in your browser, an HTTP request is sent to the ESP32. The ESP32 then responds with the HTML and CSS files, which the browser uses to render the page. These files can also include dynamic content, such as sensor readings and the current state of outputs, which are updated each time a new request is made.

ESP32 Client Server Communication explained

If you’re new to ESP32 web servers, we recommend reading the following guide:

Project Overview

Before going straight to the project, it’s important to outline what our web server will do, so that it’s easier to understand. The following picture shows the interface we’ll build with the files from the ESP32 filesystem.

ESP32 Web Server with Files from LittleFS - Control Outputs and Monitor Sensors
  • The web server controls an LED connected to the ESP32 GPIO 2. This is the ESP32 on-board LED. You can control any other GPIO;
  • The web server page shows two buttons: ON and OFF, to turn GPIO 2 on and off;
  • The web server page also shows the current GPIO state;
  • You’ll also use a BME280 sensor to display sensor readings (temperature, humidity, and pressure).

The following figure shows a simplified diagram to demonstrate how everything works.

ESP32 LittleFS Web Server Project Overview

Prerequisites

Before proceeding with this project, make sure you check all the following prerequisites.

1. Install ESP32 Board in Arduino IDE 2

We’ll program the ESP8266 using Arduino IDE, so you must have the ESP32 boards installed. Follow the next tutorial if you haven’t already:

2. Install the ESP32 Filesystem Uploader Plugin

To upload files to the ESP32 LittleFS Filesystem, you need to install the Filesystem Uploader Plugin. Install it in your Arduino IDE 2:

3. Installing Libraries

Async Web Server Libraries

We’ll build the web server using the following libraries:

You can install these libraries in the Arduino Library Manager. Open the Library Manager by clicking the Library icon on the left sidebar.

Search for ESPAsyncWebServer and install the ESPAsyncWebServer by ESP32Async.

Installing ESPAsyncWebServer ESP32 Arduino IDE

Then, install the AsyncTCP library. Search for AsyncTCP and install the AsyncTCP by ESP32Async.

Installing AsyncTCP ESP32 Arduino IDE

Installing BME280 libraries

In this tutorial, we’ll display readings from a BME280 sensor (Guide with the ESP32). You need to install the following libraries:

You can install these libraries through the Arduino IDE Libraries Manager. Go to Sketch Include Libraries > Manage Libraries. Then, search for the libraries’ names to install them.

Parts Required

To proceed with this project, you need the following parts:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Wiring the Circuit

Connect all the components by following this diagram:

ESP32 with LED and BME280 Circuit Diagram

Recommended reading: ESP32 Pinout Reference: Which GPIO pins should you use?

BME280ESP32
Vin3V3
GNDGND
SCLGPIO 22
SDAGPIO 21

Organizing Your Files

To build the web server you need three different files. The Arduino sketch, the HTML file, and the CSS file. The HTML and CSS files should be saved inside a folder called data inside the Arduino sketch folder, as shown below.

ESP32 LittleFS Web Server Folder Organization

You can download all project files:

Then, you need to upload the filesystem image before uploading the code.

Creating the HTML File

Create an index.html file with the following content:

<!DOCTYPE html>
<!-- 
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-web-server-littlefs/ 
-->
<html>
<head>
  <meta charset="UTF-8">
  <title>ESP32 LittleFS Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
  <h1>ESP32 LittleFS Web Server</h1>

  <p>💡 GPIO state<strong> %STATE%</strong></p>

  <p>
    <a href="/on"><button class="button">ON</button></a>
    <a href="/off"><button class="button button2">OFF</button></a>
  </p>

  <p>
    <span class="sensor-labels">🌡️ Temperature</span>
    <span id="temperature">%TEMPERATURE%</span>
    <sup class="units">&deg;C</sup>
  </p>

  <p>
    <span class="sensor-labels">💧 Humidity</span>
    <span id="humidity">%HUMIDITY%</span>
    <sup class="units">&#37;</sup>
  </p>

  <p>
    <span class="sensor-labels">↕️ Pressure</span>
    <span id="pressure">%PRESSURE%</span>
    <sup class="units">hPa</sup>
  </p>
</body>

<script>
  const updateSensorValue = (id, endpoint) => {
    fetch(endpoint)
      .then(response => {
        if (!response.ok) {
          throw new Error(`Failed to fetch ${endpoint}`);
        }
        return response.text();
      })
      .then(data => {
        document.getElementById(id).textContent = data;
      })
      .catch(error => {
        console.error(error);
        document.getElementById(id).textContent = 'Error';
      });
  };

  // Update all sensors every 10 seconds
  setInterval(() => {
    updateSensorValue('temperature', '/temperature');
    updateSensorValue('humidity', '/humidity');
    updateSensorValue('pressure', '/pressure');
  }, 10000);
  
</script>
</html>

View raw code

Head of the HTML File

In the <head> section of the HTML file, we should add the metadata. The following line specifies the encoding for the document. UTF-8 is the most popular and supports all characters from all languages, including emojis.

<meta charset="UTF-8">

The <title> element defines the title of the webpage that appears in the browser tab.

<title>ESP32 LittleFS Web Server</title>

We use the following line to make the web page responsive on all devices.

<meta name="viewport" content="width=device-width, initial-scale=1">

We won’t use a favicon for our web page. So, to prevent requests for the favicon, we can do as follows:

<link rel="icon" href="data:,">

Because we’re using CSS and HTML in different files, we need to reference the CSS file in the HTML text.

<link rel="stylesheet" type="text/css" href="style.css">

The <link> tag tells the HTML file that you’re using an external style sheet to format how the page looks. The rel attribute specifies the nature of the external file, in this case that it is a stylesheet—the CSS file—that will be used to alter the appearance of the page.

The type attribute is set to “text/css” to indicate that you’re using a CSS file for the styles. The href attribute indicates the file location; since both the CSS and HTML files will be in the same folder, you just need to reference the filename: style.css.

Body of the HTML File

In the following line, we write the first heading of our web page. In this case, we have “ESP32 LittleFS Web Server”. You can change the heading to any text:

<h1>ESP32 LittleFS Web Server</h1>

Then, add a paragraph with the text “GPIO state ” followed by the GPIO state. Because the GPIO state changes accordingly to the state of the GPIO, we can add a placeholder that will then be replaced with whatever value we set on the Arduino sketch.

To add a placeholder use % signs. To create a placeholder for the state, you can use %STATE%, for example.

<p>💡 GPIO state<strong> %STATE%</strong></p>

You attribute a value to the STATE placeholder in the Arduino sketch—we’ll see how that works later.

Then, create an ON and an OFF buttons. When you click the on button, we redirect the web page to to root URL followed by /on url. When you click the off button, you are redirected to the /off url.

<a href="/on"><button class="button">ON</button></a>
<a href="/off"><button class="button button2">OFF</button></a>

Finally, create three paragraphs to display the temperature, humidity, and pressure.

<p>
  <a href="/on"><button class="button">ON</button></a>
  <a href="/off"><button class="button button2">OFF</button></a>
</p>

<p>
  <span class="sensor-labels">🌡️ Temperature</span>
  <span id="temperature">%TEMPERATURE%</span>
  <sup class="units">&deg;C</sup>
</p>

<p>
  <span class="sensor-labels">💧 Humidity</span>
  <span id="humidity">%HUMIDITY%</span>
  <sup class="units">&#37;</sup>
</p>

<p>
  <span class="sensor-labels">↕️ Pressure</span>
  <span id="pressure">%PRESSURE%</span>
  <sup class="units">hPa</sup>
</p>

We use the %TEMPERATURE%, %HUMIDITY% and %PRESSURE% placeholders. These will then be replaced by the actual sensor readings in the Arduino sketch.

Automatic Updates

We also add a bit of JavaScript to our HTML file that is responsible for updating the readings every ten seconds without the need to refresh the web page. It should go between <script></script> tags.

Basically, we fetch sensor data from the server (ESP32) and update the content of the corresponding HTML elements on the page.

We create a function called updateSensorValue that takes two parameters: id (the id of the HTML element on the page), and the endpoint (URL from which to fetch sensor data—for example, to get temperature values, we make a request on the /temperature URL).

const updateSensorValue = (id, endpoint) => {
  fetch(endpoint)
    .then(response => {
      if (!response.ok) {
        throw new Error(`Failed to fetch ${endpoint}`);
      }
      return response.text();
    })
    .then(data => {
      document.getElementById(id).textContent = data;
    })
    .catch(error => {
      console.error(error);
      document.getElementById(id).textContent = 'Error';
    });
};

The fetch(endpoint) command function sends a request to the server (the ESP32).

 fetch(endpoint)

If the server responds successfully, the function retrieves the response data (in text format) and updates the text content of the HTML element with the given id.

  }
  return response.text();
})
  .then(data => {
     document.getElementById(id).textContent = data;
   })

If something goes wrong (like the server is down or the request fails), it shows Error in the HTML element and logs the error to the console.

.catch(error => {
  console.error(error);
  document.getElementById(id).textContent = 'Error';
});

Updating Sensor Readings

The setInterval() function is used to call the updateSensorValue() function every 10 seconds. It fetches the data for temperature, humidity, and pressure and places the readings on the right HTML elements.

 // Update all sensors every 10 seconds
 setInterval(() => {
   updateSensorValue('temperature', '/temperature');
   updateSensorValue('humidity', '/humidity');
   updateSensorValue('pressure', '/pressure');
 }, 10000);

Creating the CSS File

Create the style.css file with the following content:

/***
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-web-server-littlefs/ 
***/

html {
  font-family: Arial;
  display: inline-block;
  margin: 0 auto;
  text-align: center;
  background-color: #f4f4f4;
  color: #333;
}

h1 {
  color: #0F3376;
  padding: 2vh 1rem;
  font-size: 2rem;
  margin-bottom: 1rem;
}

p {
  font-size: 1.5rem;
  margin: 1rem auto;
  max-width: 600px;
}

.button {
  display: inline-block;
  background-color: #00A676;
  border: none;
  border-radius: 6px;
  color: white;
  padding: 14px 32px;
  text-decoration: none;
  font-size: 1.2rem;
  margin: 6px 10px;
  cursor: pointer;
  transition: background-color 0.3s ease, transform 0.1s ease;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
  width: 160px;
}

.button:hover {
  background-color: #0077a8;
  transform: translateY(-1px);
}

.button2 {
  background-color: #f44336;
}

.button2:hover {
  background-color: #d9362c;
}

.units {
  font-size: 1.2rem;
  color: #555;
  margin-left: 4px;
}

.sensor-labels {
  font-size: 1.5rem;
  font-weight: bold;
  vertical-align: middle;
  padding-bottom: 15px;
}

View raw code

This is just a basic CSS file to set the font size, style, and color of the buttons and align the page. We won’t explain how CSS works. A good place to learn about CSS is the W3Schools website.

ESP32 Web Server with Files from LittleFS (Arduino Sketch)

ESP32 LittleFS Web Server Control Outputs and Monitor Sensors

Copy the following code to the Arduino IDE or download all project files here. Then, you need to type your network credentials (SSID and password) to connect the ESP32 to your local network.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-web-server-littlefs/ 
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/

// Import required libraries
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <FS.h>
#include <LittleFS.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Set LED GPIO
const int ledPin = 2;
// Stores LED state
String ledState;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

String getTemperature() {
  float temperature = bme.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float t = dht.readTemperature(true);
  Serial.println(temperature);
  return String(temperature);
}
  
String getHumidity() {
  float humidity = bme.readHumidity();
  Serial.println(humidity);
  return String(humidity);
}

String getPressure() {
  float pressure = bme.readPressure()/ 100.0F;
  Serial.println(pressure);
  return String(pressure);
}

// Replaces placeholder with LED state value
String processor(const String& var){
  Serial.println(var);
  if(var == "STATE"){
    if(digitalRead(ledPin)){
      ledState = "ON";
    }
    else{
      ledState = "OFF";
    }
    Serial.println(ledState);
    return ledState;
  }
  else if (var == "TEMPERATURE"){
    return getTemperature();
  }
  else if (var == "HUMIDITY"){
    return getHumidity();
  }
  else if (var == "PRESSURE"){
    return getPressure();
  }
  return String();
}
 
void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);

  // Initialize the sensor
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }

  // Initialize LittleFS
  if(!LittleFS.begin()){
    Serial.println("An Error has occurred while mounting LittleFS");
    return;
  }

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP32 Local IP Address
  Serial.println(WiFi.localIP());

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(LittleFS, "/index.html", String(), false, processor);
  });
  
  // Route to load style.css file
  server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(LittleFS, "/style.css", "text/css");
  });

  // Route to set GPIO to HIGH
  server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request){
    digitalWrite(ledPin, HIGH);    
    request->send(LittleFS, "/index.html", String(), false, processor);
  });
  
  // Route to set GPIO to LOW
  server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request){
    digitalWrite(ledPin, LOW);    
    request->send(LittleFS, "/index.html", String(), false, processor);
  });

  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/plain", getTemperature().c_str());
  });
  
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/plain", getHumidity().c_str());
  });
  
  server.on("/pressure", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/plain", getPressure().c_str());
  });

  // Start server
  server.begin();
}
 
void loop(){
  
}

View raw code

How the code works

Continue reading to learn how the code works, or skip to the next section.

First, include the necessary libraries:

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <FS.h>
#include <LittleFS.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

You need to type your network credentials in the following variables:

const char* ssid = "REPLACE_WITH_YOUR_SSID"; 
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Create an instance that refers to the BME280 sensor called bme:

Adafruit_BME280 bme; // I2C

Next, create a variable that refers to GPIO 2 called ledPin, and a String variable to hold the led state: ledState.

const int ledPin = 2;
String ledState;

Create an AsynWebServer object called server that is listening on port 80.

AsyncWebServer server(80);

Get Sensor Readings

We create three functions to return the sensor readings as strings: the getTemperature(), getHumidity() and getPressure() functions.

Here’s how the getTemperature() function looks like (the other functions are similar).

String getTemperature() {
  float temperature = bme.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float temperature = 1.8 * bme.readTemperature() + 32;
  Serial.println(temperature);
  return String(temperature);
}

If you want to display temperature in Fahrenheit degrees, you just need to uncomment the corresponding line in the getTemperature() function:

float temperature = 1.8 * bme.readTemperature() + 32;

To learn more about interfacing the BME280 sensor with the ESP32, you can read the following tutorial:

processor()

The processor() function attributes a value to the placeholders we’ve created on the HTML file. It accepts as argument the placeholder and should return a String that will replace the placeholder. The processor() function should have the following structure:

String processor(const String& var){
  Serial.println(var);
  if(var == "STATE"){
    if(digitalRead(ledPin)){
      ledState = "ON";
    }
    else{
      ledState = "OFF";
    }
    Serial.print(ledState);
    return ledState;
  }
  else if (var == "TEMPERATURE"){
    return getTemperature();
  }
  else if (var == "HUMIDITY"){
    return getHumidity();
  }
  else if (var == "PRESSURE"){
    return getPressure();
  }
}

This function first checks if the placeholder is the STATE we’ve created on the HTML file.

if(var == "STATE"){

If it is, then, according to the LED state, we set the ledState variable to either ON or OFF.

if(digitalRead(ledPin)){
  ledState = "ON";
}
else{
  ledState = "OFF";
}

Finally, we return the ledState variable. This replaces the STATE placeholder with the ledState string value.

return ledState;

If it finds the %TEMPERATURE% placeholder, we return the temperature by calling the getTemperature() function created previously.

else if (var == "TEMPERATURE"){
  return getTemperature();
}

The same happens for the %HUMIDITY% and %PRESSURE% placeholders by calling the corresponding functions:

else if (var == "TEMPERATURE"){
  return getTemperature();
}
else if (var == "HUMIDITY"){
  return getHumidity();
}
else if (var == "PRESSURE"){
  return getPressure();
}  

setup()

In the setup(), start by initializing the Serial Monitor and setting the GPIO as an output.

Serial.begin(115200);
pinMode(ledPin, OUTPUT);

Initialize the BME280 sensor:

if (!bme.begin(0x76)) {
  Serial.println("Could not find a valid BME280 sensor, check wiring!");
  while (1);
}

Initialize LittleFS:

// Initialize LittleFS
if(!LittleFS.begin()){
  Serial.println("An Error has occurred while mounting LittleFS");
  return;
}

Wi-Fi connection

Connect to Wi-Fi and print the ESP32 IP address:

WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Connecting to WiFi..");
}
Serial.println(WiFi.localIP());

Async Web Server

The ESPAsyncWebServer library allows us to configure the routes where the server will be listening for incoming HTTP requests and execute functions when a request is received on that route. For that, use the on method on the server object as follows:

// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(LittleFS, "/index.html", String(), false, processor);
});

When the server receives a request on the root / URL, it will send the index.html file to the client. The first argument is where it should look for the files—in this case, the files are saved in LittleFS.

The second argument is the file path in the LittleFS to be served when the root / is requested.

String() is the MIME type. Passing an empty string means it will automatically detect the content type.

The false argument disables caching, and finally, the last argument of the send() function is the processor. This is a callback function lets you dynamically insert values into the HTML using template placeholders. We use this so that we can replace the placeholder with the value we want – in this case the ledState, and also send updated values when we refresh the web page.

Because we’ve referenced the CSS file in the HTML file, the client will make a request for the CSS file. When that happens, the CSS file is sent to the client:

server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(LittleFS, "/style.css","text/css");
});

You also need to define what happens on the /on and /off routes. When a request is made on those routes, the LED is either turned on or off, and the ESP32 serves the HTML file.

server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request){
  digitalWrite(ledPin, HIGH);
  request->send(LittleFS, "/index.html", String(),false, processor);
});
server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request){
  digitalWrite(ledPin, LOW);
  request->send(LittleFS, "/index.html", String(),false, processor);
});

In the HTML file, we’ve written a JavaScript code that requests the temperature, humidity and pressure on the /temperature, /humidity, /pressure routes, respectively, every 10 seconds. So, we also need to handle what happens when we receive a request on those routes.

We simply need to send the updated sensor readings. The updated sensor readings are returned by the getTemperature(), getHumidity(), and getPressure() functions we’ve created previously.

The readings are plain text, and should be sent as a char, so we use the c_str() method.

server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(200, "text/plain", getTemperature().c_str());
});
  
server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(200, "text/plain", getHumidity().c_str());
});
  
server.on("/pressure", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(200, "text/plain", getPressure().c_str());
});

In the end, we use the begin() method on the server object, so that the server starts listening for incoming clients.

server.begin();

Because this is an asynchronous web server, you can define all the requests in the setup(). Then, you can add other code to the loop() while the server is listening for incoming clients.

Upload the Code and Files (Filesystem Image)

Now, you need to upload the files and code to your board. Follow the next instructions

1) Save your Arduino sketch.

2) Go to Sketch > Show Sketch folder. The folder where your sketch is saved should open.

ESP32 LittleFS Web Server Show Sketch Folder

3) Inside that folder, create a folder called data.

ESP32 LittleFS Web Server Create Data Folder

4) Save the HTML and CSS files inside that data folder.

ESP32 LittleFS Web Server Folder Organization

5) Make sure you have the right board (Tools Board) and COM port selected (Tools Port).

6) Depending on the ESP32 board selected, you may need to select the desired flash size (some boards don’t have that option, don’t worry). In the Arduino IDE, in Tools > Flash size, select the desired flash size (for example, 4MB SPIFFS).

7) Then, upload the files to the ESP32 board. Press [Ctrl] + [Shift] + [P] on Windows or [] + [Shift] + [P] on MacOS to open the command palette. Search for the Upload LittleFS to Pico/ESP8266/ESP32 command and click on it.

Upload LittleFS to Pico/ESP8266/ESP32

Important: ensure the Serial Monitor is closed. Otherwise, the upload will fail.

After a few seconds, you should get the message “Completed upload. “. The files were successfully uploaded to the ESP32 filesystem.

LittleFS upload success

8) Now, upload the code to the ESP32 by clicking on the upload button.

Arduino IDE 2 Upload Button

Demonstration

After uploading the code, open the Serial Monitor at a baud rate of 115200.

Open Arduino IDE 2 Serial Monitor baud rate of 115200

Press the ESP32 on-board “RST” button. It should print the ESP32 IP address.

ESP32 LittleFS Web Server - Get IP Address on Serial Monitor

Open a web browser on your local network and type the ESP32 IP address. You should get access to the web page. You can control the onboard LED by clicking on the ON and OFF buttons, and you can check the latest sensor readings. The readings are updated every 10 seconds.

ESP32 LittleFS Web Server Demonstration

You can access the web server on your computer or smartphone as long as they are on the same network as the ESP32.

ESP32 Web Server with Files from LittleFS - Control Outputs and Monitor Sensors

Congratulations! You’ve successfully built a web server with files saved on LittleFS filesystem.

Troubleshooting – This page can’t be found

If you get this error, it probably means you didn’t upload the files to the ESP32 LittleFS filesystem or they are in the wrong location.

Go back to this section and make sure you follow all the steps.

Wrapping Up

In this tutorial, you learned how to build an ESP32 web server that serves the HTML and CSS from the LittleFS filesystem. Saving those files on the filesystem is more practical than embedding them in the Arduino sketch.

What you learned here can be applied to other web servers that use the ESPAsyncTCP library. Don’t forget you need to create a folder called data inside your sketch folder. Then, inside that folder, you should save the files you want to upload to the filesystem. Then, don’t forget to upload the filesystem image using the LittleFS uploader plugin.

If you’re new to the ESP32 and web servers, we recommend the following eBooks:

Here are other tutorials you may like:



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Recommended Resources

Build a Home Automation System from Scratch » With Raspberry Pi, ESP8266, Arduino, and Node-RED.

Home Automation using ESP8266 eBook and video course » Build IoT and home automation projects.

Arduino Step-by-Step Projects » Build 25 Arduino projects with our course, even with no prior experience!

What to Read Next…


Enjoyed this project? Stay updated by subscribing our newsletter!

1 thought on “ESP32 Web Server using LittleFS Filesystem (serve files from filesystem)”

  1. Good addition to web server ebook; using LoRa for remote control, I hav a project where a web request arrives, a LoRa packet is sent, LoRa receiver receives packet, turns on a Wyze Cam3 live videofeed battery, starts a ticker two minute timer, Two minute Ticker timer expirs, turns off, videofeed battery, detaches Ticker interrupt, and goes to deep sleep! Waits for next web request.

    Project file: https://github.com/Tech500/EoRa-PI-Foundation

    Reply

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.