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

85 thoughts on “ESP8266 HTTP server: Serving HTML, Javascript and CSS”

  1. Hi, i’m having issues using the Esp8266 with the and libraries. Do i have to flash the wifi module firmware to use this libraries.

    1. Hi!

      Which issues are you experiencing?

      You need to compile the code shown on the post and upload it to your ESP8266 using the Arduino core 🙂

      Best regards,
      Nuno Santos

  2. Hi, i’m having issues using the Esp8266 with the and libraries. Do i have to flash the wifi module firmware to use this libraries.

    1. Hi!
      Which issues are you experiencing?
      You need to compile the code shown on the post and upload it to your ESP8266 using the Arduino core 🙂
      Best regards,
      Nuno Santos

  3. I’m working with ESP8266 but with client.println() method, eberything works just fine until it has to “read” the script tag where I have functions for calculating the current hour and date. I had seen that someone asked you about this before but your answer did’t solve my problem 🙁 !!! Please if you have any idea of what is happening or how should I fix this please reply this comment or contact me:

  4. Hi, I would like to use the javascript code to send a something out the serial port of my ESP8266 when a button is pressed. Can I do this, without page reloading?

  5. Is there a way to separate the files into CSS JS HTML files instead of all one inline block? It makes editing the html during dev quite tiresome, especially adding various escape chars etc when making the output html string.

Leave a Reply