ESP8266 HTTP server: Serving HTML, Javascript and CSS

The objective of this post is to explain how to serve some HTML, Javascript and CSS in a ESP8266 HTTP webserver.


The objective of this post is to explain how to serve some HTML, Javascript and CSS in a ESP8266 HTTP webserver.

It’s important to take in consideration that HTML, Javascript and CSS are languages that are executed / rendered in the client side [1]. In this example, since we are going to do HTTP requests from a web browser, it will be its responsibility to render the HTML and CSS and to execute the Javascript we will be serving.

This means that the ESP8266 doesn’t need to know how to interpret HTML, CSS or Javascript. So, as we will see bellow, that code will be stored in strings that will be returned in the HTTP responses, pretty much the same way as we would do to return any other kind of content.

In this simple example, we will create an intro page in HTML, a page with Javascript code that will trigger an alert window when a button is clicked, and a page with a button styled with CSS.


The Arduino HTTP server code

In order to create our webserver, we will use the exact same approach explained in the previous post, which explains all the details of setting the server.

So, the most important part that we need to remember is that that we need to specify the handling function for each URL of our server. Recovering the code from the last post, we need to declare  a global variable of the class ESP8266WebServer and pass as argument of the constructor the port where the server will be listening for incoming HTTP requests.

ESP8266WebServer server(80);

Then, for each URL, we specify the handling function that will be executed when a request is sent to that path. To do so, we call the On method on the server object.

For this tutorial, let’s assume that we declare the handling function inline, in order to make the code more compact.

For the sake of simplicity, we will assume that the HTML, Javascript and CSS code we are serving will be static. This means that we can declare it in a string and just return it when a HTTP request is performed on the desired URL.

Remembering again the code from the last post, we call the send method on the server object. As arguments, we pass it the return code for the request, the type of return content and the content, from left to right.

So first, we want to return the 200 HTTP code (OK code). Then, we need to make sure that we specify the return content as text/html, so the browser will know that it should be interpreted and rendered as such. The content type will always be this because for the case we are sending CSS or Javascript code, it will be embedded in HTML code.

Finally, we pass the string with the HTML/Javascript/CSS code. Check the generic function for this bellow.

server.on(“/anyPathWeWant”, [](){      //Define the handling function for the path

server.send(200, “text/html”, stringContainingCode); //Send the code in the response



The HTML code

In our use case, we will assume that the root URL (“/”) will show a HTML message that will have a description message and 2 links. Each of those links will redirect the user to the URL that is serving the Javascript or the CSS code. You can check the code to be used bellow.

const char * htmlMessage = ” <!DOCTYPE html> “

“<html> “

“<body> “

“<p>This is a HTML only intro page. Please select a button bellow.</p>”

“<a href=\”/javascript\”>Javascript code</a>”


“<a href=\”/cssButton\”>CSS code</a>”

“</body> “

“</html> “;

Explaining the details of the structure of HTML is beyond the scope of this post and only a brief analysis of what each element of the code does will be conducted.

The <!DOCTYPE html> tag tells the browser what version of HTML the document is written so that the browser knows what to expect [2]. The <html> element represents the root of an HTML document. All other elements must be descendants of this element [3]. The <body> tag specifies where the body of the document starts [4].

The <p> tag specifies a paragraph and thus we use it to indicate our description message.

The <a> tag allows us to create a hyperlink by specifying the URL of a page and the message of the hyperlink. So, in the href attribute we specify the URL we want the user to be redirected to, and between the two tags we specify the message of the hyperlink.

In this case, we are specifying a path in the current address, so we don’t need to put the IP and the port. So, when a user clicks the hyperlink, the browser knows it will need to redirect the user to http://IP:port/{href value}.

As we will see later, the paths we are specifying in the href will correspond to the paths where the ESP8266 will be programmed to return the corresponding code.

the <br> tag is just a simple line break between the hyperlinks.

One thing that is very important is that we are specifying the HTML code inside a string, and thus some details need to be taken in consideration.

First, in order to leave the code structured, we should split the declaration of the string in multiple lines. To do so, as explained here, we need to put the double quotes in each line so the compiler will know that it should be considered as a single string. So, the outermost quotes of the previous code are not from the HTML but from the declaration of a string in multiple lines.

Finally, every quote that is part of the HTML code (for example, the quotes around the URLs in the <a> tag) needs to be escaped with the \ character. So, the \ characters we see before the quotes are also not part of the HTML code but rather the escaping mechanism.

Those considerations will be the same for the rest of the code described bellow.


The Javascript Code

For the Javascript code we follow just the same approach of declaring it in a string, as seen bellow.

Important: WordPress doesn’t let me add a script block, probably due to security reasons. Please remove the REMOVE THIS bellow and just leave the <> and the script keyword inside.

const char * javascriptCode = ” <!DOCTYPE html> “

“<html> “

“<body> “

“<p>Click the button to get a message from the ESP8266:</p> “

“<button onclick=\”buttonFunction()\”>Message</button> “

“<REMOVE THIS script>”

“function buttonFunction() { “

” alert(\”Hello from the ESP8266!\”); “

“} “


“</body> “

“</html> “;

Since most of the tags were explained in the previous section, we will just focus on the new ones.

The <button> tag allows for the definition of a clickable button. One of its functionalities allows us to specify a function that is executed when the button is clicked (onclick event). In this case, we are assigning to that event the execution of a Javascript function called “buttonFunction”.

We then use the <script> tag that allows us to define Javascript code. In this case, it’s there that we specify the code of the mentioned buttonFunction. The code specified corresponds to an alert window that will output the “Hello from ESP8266” message.

The CSS code

Following the previously mentioned approach, we also declare the CSS as a string, as seen bellow.

const char * cssButton =”<!DOCTYPE html>”




“.button {“

“background-color: #990033;”
“border: none;”
“color: white;”
“padding: 7px 15px;”
“text-align: center;”
“cursor: pointer;”





“<input type=\”button\” class=\”button\” value=\”Input Button\”>”



In this case, we have the <head> which specifies where the definitions of the header elements starts, pretty much as the <body> tag does for the body elements.

Then, we have the <style> element, which is where we specify how HTML elements should render in a browser  [5]. In this case, we are specifying the style of a button by changing some of its default attributes, such as the background color or the type of cursor that appears when the mouse is over it. CSS allows for a lot of different customization that falls outside the scope of this post. You can check here for some example attributes to manipulate the appearance of a button.

Finally, we have the <input> tag, which specifies an input field where the user can enter data [6]. In this specific case, we defined that our input will be a button. This button will have the properties specified on the CSS of the header.

Just as a note, we didn’t associate any functionality to the button for the code to stay compact, so clicking on it will do nothing. The objective was just to demonstrate the difference of styling, in comparison to the button specified for the javascript section, which has the default styling.


The final code

The full Arduino code needed to start the server is shown bellow. Only the declaration of the strings that contain the HTML, Javascript and CSS code is omitted to maintain this post small. Don’t forget to declare them as global variables so they are accessible in the handling functions.

Note that the paths we defined for the CSS and Javascript code are the ones we specified in the URLs of the <a> tags we defined in the HTML code for the intro page. So, when the user clicks the hyperlinks, he will be redirected to existing paths and thus the ESP8266 will return the correct content.

#include <ESP8266WiFi.h>            
#include <ESP8266WebServer.h>

ESP8266WebServer server(80);    //Webserver Object

void setup() {

Serial.begin(115200);                                             //Open Serial connection

WiFi.begin(“ssid”, “password”);                          //Connect to the WiFi network

while (WiFi.status() != WL_CONNECTED) {    //Wait for connection

Serial.println(“Waiting to connect…”);


Serial.print(“IP address: “);
Serial.println(WiFi.localIP());  //Print the local IP

server.on(“/”, []() {                     //Define the handling function for root path (HTML message)

server.send(200, “text/html”, htmlMessage);


server.on(“/javascript”, []() { //Define the handling function for the javascript path

server.send(200, “text/html”, javascriptCode);


server.on(“/cssButton”, []() { //Define the handling function for the CSS path

server.send(200, “text/html”, cssButton);


server.begin(); //Start the server

Serial.println(“Server listening”);


void loop() {

server.handleClient(); //Handling of incoming requests


Important: When directly copying the code from the blog, a stray error may occur when trying to compile it on Arduino. In my case, this occurs because the editor assumes the wrong type of quotes. The easiest way to fix this, given the number of existing quotes, is to do a find and replace and put the correct quotes.

Running the code

To try the code, just upload it to the ESP8266 and after the “Server listening” message is sent to the serial port, just copy the IP printed before and access the server in a browser, on the following url, changing the {IP} for your IP:


You can see bellow the expected output in the serial console, with the IP to use. Note that the IP you get will most probably be different, depending on what your router as assigned to the ESP8266 when registering in the wireless network.


Figure 1 – Output of the serial console with the IP of the webserver.

So, when we access the URL in a browser (in my case, we get to the HTML intro page, as seen bellow. Then, we can click in one of the hyperlinks to go to the page with Javascript or CSS.


Figure 2 – HTML intro page.

In figure 3, we can see the page that has the Javascript code. If we click the button, we get an alert box with the string we defined before.


Figure 3 – Page with Javascript alert box.

In the other page, we have the button styled with some CSS, as can be seen bellow.


Figure 4 – Page with button styled with CSS.

Final notes

As seen through this post, the ESP8266 offers all the tools to set a simple HTTP webserver and start serving some HTML, Javascript and CSS in a very easy way. Nevertheless, we need to take in consideration that we are working with a microcontroller with considerably limited resources.

Thus, we should not expect to implement a full website with lots of complex functionality and styling. On top of that, even for simpler examples as we have shown here, we should also not expect to be able to serve hundreds of requests per second.

Personally, I see this kind of functionality to implement simple configuration or debugging interface for devices. For example, if we implement a temperature server, we can create a simple webpage to configure how many measurements per second we will do, amongst other configurations, and serve that webpage on the ESP8266, removing any external dependencies. Naturally, this would be more suitable for technical users, since developing a good looking webpage for a end user of a commercial product wold need a lot of styling and functionality which, as we said, is not practical for this device.

Related posts










Technical details

ESP8266 libraries: v2.2.0


41 Replies to “ESP8266 HTTP server: Serving HTML, Javascript and CSS”

  1. thank you very much for those explanations.
    I need to send html code with parameters
    (Example: ?var1=text1&var2=text2)
    is it possible to do it using the send command, with something like that :
    server.send(200, “text/html”, cssButton + “?var1=text1&var2=text2”);

    Liked by 2 people

  2. Thanks for your feedback 🙂

    Regarding to your question, you need to clarify something to me first. You want to pass those parameters to the ESP8266 or that the ESP8266 returns them alongside the remaining HTML?

    I’m asking this because typically those query parameters are used by the client (for example, the browser) to pass extra information to the server (in our case, the ESP8266).

    In the code you propose, the ESP8266 is returning the HTML code with something appended to the end, which is not a good practice. If you want a client to get information from the ESP8266 (for example, measurements of sensors) a good practice would be having a dedicated URL to return that content in a standard format (for example, JSON).

    If you want the ESP8266 to return those parameters in a ready to use format (for example, to render them immediately in the browser without further processing), then you should embed the values of those parameters on the HTML, rather than appending them in the end.

    Liked by 1 person

    1. Hi, I haven’t yet tried to send images. Besides that you will have file system size limitations, so it won’t be easy, Probably the best approach is to host the image in other place and reference it on your HTML code so it is retrieved on the client side.


    2. this works like a charm for me…

      ESP8266WebServer server ( 80 );
      – – – – –
      server.on ( “/foto1”, foto1);
      – – – – – – –

      void foto1(){
      Serial.println(“streaming una foto con filestream”);
      File file =“/foto1.jpg”, “r”);
      server.streamFile(file, “image/jpg”);

      Liked by 1 person

    1. Hi! Theoretically you can serve it from the ESP32, but it will depend on the size of both libraries. If you can do that, it would be much easier if you fetched the libraries from an external location ( a cdn for example):

      That way, you would just serve the remaining HTML / javascript from your application.

      I haven’t yet worked with the flash of the ESP32 so unfortunately I can’t be of much help in that part.

      But from what I’ve been seeing, you may be able to take advantage of the file system. At least in the ESP32 MicroPython, it is very easy to put and retrieve data from the file system, which is precisely what you need.

      Best regards,
      Nuno Santos


      1. Thanks for reply. In my application I will not access the internet, so the library must be on esp32. The wifi wil be a P2P connection. I bought the ESp32 , I am just waiting arrive to start the project.


  3. Am new to your tutorials and have learned a lot on 8266 web server set up and use from the previous tutorials. Was going good on this one and looking forward to experimenting and learning from the actual code which can’t find. Please advise where can find this and excuse my blindness if link is somewhere obvious that I’ve overlooked. Thank you and regards.

    Liked by 1 person

  4. On further reviewing of tutorial now realize code shown is complete with inclusion of the html and css string declarations. Should now be able to run, understand and learn what want from it.

    Liked by 1 person

  5. Could you please help me i’m using ESP8266 wemos d1 > works with Arduino ide i have made an html page to show some sensor values but i want to show external link to my own blog it shows the highlighted link but when clicked it adds that request after the ip or ESP itself and if clicked multiple times it adds the request multiple times e.g. I Want it to refer to .com i’m a noob please help

    Liked by 1 person

  6. Hi! You’re welcome 🙂

    I’m trying to refactor some of my older posts because at the time I wasn’t using the awesome source code tags of wordpress (for reference I will leave the link below) and thus the quotes were getting messed.

    So most likely you were running in a “stray error” or something similar when compiling the code right?

    I will try to change it as soon as I have some spare time, but this one will be a tough one because wordpress unformat all the code as soon as I re-edit the post 😦 .

    Best regards,
    Nuno Santos


    1. Hi!

      Thanks for the feedback 🙂

      I haven’t yet tested that, but one simple way may be reading from the serial connection and putting the data in a global string variable and use it on the route that is serving the HTML.

      Naturally this is a very simplified way of doing it since it pretty much depends on what you want to achieve.

      For instance, if you are are going to receive the whole HTML from serial, then you can store it in a string and directly serve it.

      If you are going to receive for example numeric data only from serial and serve it in your route with some HTML to present it in a nice format, then you may store that numeric data in an array and then dynamically build the final HTML page on the route.

      Best regards,
      Nuno Santos


  7. Nuno,

    Great document, it has been my guide!

    My esp8266 sketch sets up as an ap server. It monitors temp from a max6675 probe. The iPhone client connects to the server and I send the temp via html to the web client phone.

    I want to update my sketch to allow the client to enter a temperature alarm threshold. When the temp exceeds the threshold I want the esp8266 to store a small mp3 file in spiff and play it as an alarm to the iPhone browser client. I don’t see any audio examples of how to store audio files or play them. Can you help me?


    Liked by 1 person

    1. Hi Jim,

      Thank you very much for your feedback 🙂

      It seems a very interesting project that you are doing! Also challenging due to all the parts involved.

      Unfortunately I’ve never worked with audio on the ESP, I cannot be of much help.

      I’ve found this library though that may help getting you started:

      Most likely you will have to adapt a lot of stuff for your application, but from the description it seems to be using SPIFFS to store the audio files.

      Note however that the ESP8266 has some resources limitation, I’m not sure if it will handle all the load of having the server plus the audio.

      If you run into resources limitation, maybe a good option is to move to the ESP32, which has plenty of power 🙂

      Let us know if you succeed, I’ve never played with audio but I’m interested in knowing if it is feasible on the ESPs.

      Best regards,
      Nuno Santos


  8. Hola buenas tardes,
    estuve mirando este tutorial y me pareció buenísimo porque de hecho acabo de hacer una página web donde tengo por separado cada uno de los archivos pero me gustaria saber si puedo insertarlos como variables char* string, tal como el tutorial o hay otra manera que se pueda hacer con ‘client.println(“”)’.
    Agradezco tu ayuda,
    Sebastian Baquero

    Liked by 1 person

    1. Hi!

      I’ve not been using this library in a while, but if it is still working as before you should be able to. I’m using const * char just because the content is indeed constant and will not be changed.

      Note however that this tutorial is for an older library of the ESP8266. Currently there is a better library which allow you to set up an asynchronous HTTP web server, which means you don’t need to be polling the server on the main loop function, like we do here.

      Best regards,
      Nuno Santos


  9. Hi Nuno .. thank you so much for your awesome tutorials. Well done rock!
    I wonder if you could please explain something I am battling with.
    I have a web page containing a table with buttons to add or delete rows etc.
    I would like to be able to read the table data (into an VAR/array or directly from a file in SPIFFS)
    Do you know how to share the VAR/array so that I can access it from both the webpage (HTML/javascript/jquery) and the serial console (esp arduino side) ?
    Are variable shared and accessible between the two ? For example if I have String WORDS=ABC; in my arduino code then will that data be accessible from the webpage (HTML side) ?
    I would really appreciate if you cuold clarify this and maybye post an example of if you know of a URL somewhere as a reference ..I have been looking for a few weeks now to understand this.

    Liked by 1 person

    1. Hi!

      Thank you very much for the feedback, I’m very happy to know you are finding the tutorials useful 🙂

      For the ESP, the HTML code is nothing more than a simple string, so you cannot “share” a variable between the Arduino code and the HTML code.

      What you can do, since for the ESP your html is just a string, is to build the html dynamically based on the values that you have for the table data in your ESP.

      For instance, a very rough example. Imagine that you have a string in your arduino code that you want to return as a paragraph in your HTML code. You code do something like (pseudo code):

      String myStr =” Awesome string to return to the client”;

      String HTML = “<p>” + myStr + “</p>”

      And then return the HTML string in your route handling function.

      Note that this is feasible for simple stuff, but if you want to replace a lot of things in your HTML, the code can quickly become a mess.

      So what many web frameworks have is what is called a template engine, which allow you to define your HTML dynamically without having to be building the strings piece by piece.

      In the case of the ESP, the new HTTP async libraries have a template engine:

      I’ve never tested it so I cannot confirm if it works well, but it is my recommendation to take a look if you want to implement a more scalable solution.

      Hope this helps 🙂

      Best regards,
      Nuno Santos


  10. thank you for these tutorials you are a heaven send for anything esp8266webserver! Great explanations and examples. I really appreciate what you have done to help out the esp community,

    Liked by 1 person

    1. Hi!

      Thank you very much for the feedback, I’m very happy to know you are finding these tutorials useful 🙂

      I will try to keep posting more content and hopefully bring more people to work with the ESP, these are really great microcontrollers 🙂

      Best regards,
      Nuno Santos


    1. Hi!

      If you need to use external JS files without having an internet connection, you will need to serve them by the ESP32.

      Depending on the size of the file, it may not be feasible due to resource constrains on the ESP32.

      I’m guessing that jQuery may be a little bit to big to fit in the ESP32, but I’ve never looked into its size.

      If it is a file small enough to fit in the ESP32, then you can create a route just to serve those JS file.

      Hope this helps.

      Best regards,
      Nuno Santos


      1. Hi!

        Thanks for trying to share the code 🙂

        I’m not sure if comments allow to add code blocks, due to security reasons.

        At the time I’ve written this post (it was a long time ago 🙂 ) I wasn’t aware of the existence of the code tags, which is why the code was just pasted as normal text.

        In new posts I’m now making use of code tags.

        As for the older posts, I’m trying to update the more problematic ones (such as this) to use code tags.

        But unfortunately I have very few time for blogging and thus I’m taking more time to get back to the older stuff :/

        Sorry for the troubles, I’ll put this one on the top of my list.

        Best regards,
        Nuno Santos


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s