$7 ESP32 CAM example expanded

A proper implementation of a ESP32 CAM webserver; based on the Espressif example, but extended for real-world use

Similar projects worth following
This sketch is a extension/expansion/rework of the ‘official’ ESP32 Camera example sketch from Espressif:

It has extra wifi options, LED lamp control, permanent settings between reboots and a whole bunch of other little improvements, while keeping the same look and code structure as the original. The HTML is no longer stored as binary data in the header; but left plain-text for ease of modification.

Hopefully this expanded example is more useful for those users who wish to set up a simple ESP32 based webcam using the cheap(ish) modules freely available online. Especially the AI-THINKER board:

AI-THINKER ESP32-CAM and Other Modules:

I have four AI-THINKER ESP32-CAM boards, so the descriptions below are for that board. But I took care to leave the default definitions and controls for other boards in the example intact. You may need to adjust the programming method to suit the your board, look for examples online.

My Modifications:

Find my version of the code online at Github:

The original example has been extended to allow control of a high power LED FlashLamp, which is present on my modules. It can also blink a status LED to show when it connects to WiFi.

The Board model and WiFi details can be stored in a private config header file to allow easier updates, and some other defaults can be set there too. The lamp and status LED's are optional, and the lamp uses a exponential scale for brightness so that the control has some finess.

The compressed and binary encoded HTML used in the example has been unpacked to raw text, this makes it much easier to access and modify the Javascript and UI elements. Given the relatively small size of the index page there is very little benefit from compressing it.

I have left all the Face Recognition code untouched, it works, and with good lighting and camera position it can work quite well. But you can only use it in low-resolution modes, and it is not something I will be using.

The web UI has had minor changes to add the lamp control (only when enabled), I also made the 'Start Stream' and 'Snapshot' controls more prominent, and added feedback of the camera name + firmware.


  • I only have AI-THINKER modules with OV2640 camera installed; so I have only been able to test with this combination. I have attempted to preserve all the code for other boards and the OV3660 module, and I have merged all changes for the WebUI etc, but I cannot guarantee operation for these.
  • I created a small board with a handy switch for power, a pushbutton for the GPIO0 programming switch, and a socket for the AI-THINKER board. This proved very useful for development work and programming multiple devices.
  • I found some excellent cases on Thingieverse.


  • Improve Wifi, add a captive portal for setup and fallback, better disconnect/reconnect behaviour.
  • The module has a SD/TF card slot; this is currently unused, but I would like to add the ability to store snapshots; recording Video at low resolution may be possible, but the card interface is too slow for HD video as far as I know.
  • Remove face rcognition to save a Mb+ of code space and then implement over the air updates.
  • Combine current split html pages (one per camera type) into one which adapts as needed.

Zip Archive - 1.50 MB - 10/12/2020 at 11:41


gzip - 1.48 MB - 10/12/2020 at 11:41


Zip Archive - 1.46 MB - 09/25/2020 at 21:04


gzip - 1.46 MB - 09/25/2020 at 21:04


  • 1 × ESP32 CAM Lots of models available...
  • 1 × FTDI-Friend to program Or another way of programming the module. If you have a proper dev-module with a USB programming port you do not need this.
  • 1 × Hookup Wires, and possibly a case

  • V3 is nearly here, functionally complete. But facedb work dropped.

    Owen10/13/2020 at 14:33 0 comments

    I've uploaded the V3 release candidate; functionality complete. It may get some fixes, but my next task is to revamp the documentation and do the final release.

    I've decided to not do any development on the faceDB. This sketch is, and remains, a re-working of the Expressif example.

    In the esp example sketch the face recognition support is very basic; it does not use the 'named' face database, just a simple list. It only runs when the stream is running, and only works in low resolution modes. This is fine for an example and demo. But you need to look elsewhere for projects that really make use of the face recognition. One of my document tasks is to add a list of projects exploring the capabilities of these boards in more detail.


    I had to get real about saving the face database, eg it's not going to happen in this project without a proper library to work with. I spent a weekend of idle work on this, and decided I do not want to take the time needed to do it properly.

    The face database is actually a series of direct memory data structures created on the fly by the enrol functions; malloc()'d and then pointed to by a array of pointers. All very confusing, I got lost trying to work this all out in the source. I could relatively easily read the structs, but working out how to import and link everything would have taken too long.

    There is direct support in the esp face library for flashing the database to a memory partition.. which makes for permanent storage, **but** you need to provide a new partition map to the build system. Since I want to keep this project compatible with the ArduinoIDE I do not want to be telling people to hack their board definitions etc.. so this solution is rejected for now.

    - Maybe it could be done as an option for a platformio build. PR's on github are welcome.

  • 3.0 beta2

    Owen10/03/2020 at 20:32 0 comments

    A small update; the viewer handles rotated views in a better (but not perfect) way. I also added an info page with some system and debug output.

    I still need to work on the Face DB save/restore; I have an idea how to do this so it' just a matter of finding time to code & test it.

  • V3.0 Preview has landed​

    Owen09/27/2020 at 14:29 0 comments

    I am proud to announce V3.0 beta 1:


    • Camera Preferences can be saved and will be restored on reboot (they can also be cleared, and the reboot can be triggered from the UI too)
    • Dedicated stream player; served on /view on the same port as the stream itself (81 by default) it has no controls, auto-zooms the stream to the window size, follows the browser rotation setting and can be double-clicked for a full screen view.
    • Simple view for the main window; only exposes the video resolution and lamp settings
    • Tweaks, minor options relating to Filesystem etc.

    Known issues:

    • Rotated views can be misplaced I still need to make the CSS and Javascript play well together. #40
    • Face Recognition Database is not (yet) saved / restored, This is planned for the full v3 release but needs more time #28

    Please try it out, any bugs and issues can be looked for and reported at:

    V3.0 final is on the way, but may have to wait for me to find some free time.

  • Release 2.1: more robust networking

    Owen09/25/2020 at 21:12 0 comments

    I Just uploaded a minor update,: v2.1, this has a network watchdog and re-try mechanism so that the unit will now reconnect after a WiFi hiccup, instead of just disconnecting until rebooted. Otherwise it is identical to 2.0.

  • Small steps: Release 2.0 is out with better networking options

    Owen09/21/2020 at 09:31 0 comments

    I've Just uploaded a quick release, V2.0.

    - More networking options; define port number for both the UI and Stream, this addresses several recent questions. Note: The two ports must differ, default is :80 & :81.

    - I merged in a couple of small bugfixes from the upstream source project, and incorporated a few comments and trivial fixes of my own.

    - Face detection can be enabled by default, the lamp can be pre-set to a value, and the lamp controls can be disabled. Unfortunately the FaceDB does not yet survive reboots .

    - Favicons; nice when bookmarking streams etc.

    Coming soon: V3

    - Simple player view, CSS and html improvements, standalone CSS file.

    - Control-less but rotataing and scalable miniplayer on the stream port.


    I'm working on this in the master branch on GitHub; it's still a work in progress but you can always download the latest stable version via this link :-)
    And please see the GitHub issues list for common questions/troubleshooting.

  • Options, bloody Options

    Owen09/09/2020 at 02:54 0 comments

    New release coming:
    Lots of new options are now in the user config, Wifi IP address overrides, stream and http port settings, lamp defaults, flip and rotation defaults. Plus a bunch of little fixes and enhancements to the UI and backend.

    All these changes are already in the main branch on GitHub, I have one final task to address and then I will do a catchup-release here too

  • New release; options in seperate config, cleanup and in-browser rotation

    Owen08/23/2020 at 20:11 0 comments

    I'm just adding V1.3.

    Nothing major: I have moved all options into the myconfig.h file, so that most users do not need to modify the main .ino file directly.

    I also merged in a in-browser screen rotation mechanism and easier wifi AP setup from @jmfloyd on github.

    I hit some small typo/debug changes too. Nothing dramatic.

  • Continually Integrated

    Owen08/20/2020 at 11:22 0 comments

    Time to pay some attention to this project. It seems to be getting subscribers etc. Hi Guys!

    Step 1 of this is that I now have a Travis CI continuous integration build running on my GitHub repo. So if anyone wants to submit PR's I have a better handle on whether I can just accept them.

    I will triage the existing issues list and maybe incorporate some suggestions.

    The thing I most want to do is get an OTA update system in place. This /requires/ disabling face recognition. For OTA you need sufficient storage to upload the update, but Face Recognition makes this sketch currently take up 83% of the memory. I plan to make it a compile-time option and OFF by default.

  • Remote Access

    Owen11/20/2019 at 14:33 0 comments

    There is something weird happening when I try and proxy through Apache to the cameras (or remote (*) access  I get:

    Header fields are too long for server to interpret

     This is coming from the ESP itself; needs more debug.

    (*) A simple http proxy with basic authentication enabled, it works for some other tools including a ESP32 based CNC controller, but not for this. Strange.

View all 9 project logs

  • 1
    Setup, gather components

    For programming you will need a suitable development environment, I use the Arduino IDE, but this code should work in the Espressif development environment too.

    Follow This Guide to set up the Espressif Arduino core in the IDE.

    The AI-THINKER board requires use of an external 3.3v serial adapter to program; I use a FTDI Friend adapter, for more about this read AdaFruits excellent FTDI Friend guide.

    Be careful not to use a 5v serial adapter since this will damage the ESP32.

  • 2

    Hooking this up is pretty simple, see the diagram below. 


    Connect the RX line from the serial adapter to the TX pin on ESP32

    The adapters TX line goes to the ESP32 RX pin

    The GPIO0 pin of the ESP32 must be held LOW (to ground) when the unit is powered up to allow it to enter it's programming mode. This can be done with simple jumper cable connected at poweron, fitting a switch for this is useful if you will be reprogramming a lot.

    You must supply 5v to the ESP32 in order to power it during programming, the FTDI board can supply this.

  • 3

    Download the code from the Files section here, or clone the GitHub repo.

    In the sketch dont edit the main esp32-cam-webserver.ino sketch file, instead; copy the file myconfig.sample.h to myconfig.h and edit the settings there.

    You can set the camera board type, WiFi settings and some hardware/ui defaults in that file. Everything iscommented, and this file can remain private (it is in the .gitignore list).

View all 5 instructions

Enjoy this project?



Gilles wrote 09/20/2020 at 09:57 point

Bonjour, quelqu'un a réussi à accéder à la vidéo de l'esp32cam depuis internet (pas depuis le réseau local)? 

Comment si Oui? 

En effet j'arrive bien depuis internet à afficher la page web de l'esp32, la capture d'image et les autres installations fonctionnent mais pas la vidéo ... Merci

  Are you sure? yes | no

Owen wrote 09/21/2020 at 10:14 point

"Hello, has anyone managed to access esp32cam video from internet (not from local network)?

How if yes?

Indeed I manage from the internet to display the web page of the esp32, the image capture and the other installations work but not the video ... Thank you."

- translated

I have not done this; and with the current non-ssl server and hacker nature of the code I cant really recommend it to anybody not expert enough to keep it secure.
But.. if you do understand the issues; the first point is that you need to expose both the Main UI port, and the stream port.

Or; just expose the stream port if you do not want to be able to access the UI. This gives you a 'pure', un-rotated and non-controllable video stream.

The best solution would be a proxy giving both encryption and http authentication; this is what I have tried to do but there is a major header length Issue that blocked me; and I lost interest since this is not a priority.

  Are you sure? yes | no

Gilles wrote 08/30/2020 at 18:01 point

Hello, I have the same concerns as Jassim Ali post of 04/18/20

 I can't get the video on esp 32 if I change the port (other than the port 80), I can control the flash, capture images but nothing on the video ...

 Anyone have an idea? 

Thank you

  Are you sure? yes | no

Owen wrote 09/21/2020 at 10:03 point

I just added a note to that question; in summary: v2.0 is out with more flexible networking options.

  Are you sure? yes | no

Arjen wrote 07/23/2020 at 09:40 point

Hello Owen,

Great job with this. Works a lot better than the original post with the Camera example.

I would like these in a 'door opening' event for security.

When a door opens (zwave module used in Domoticz) i want to record 5-10 seconds video and then post this to an email address.

Do you now a way to handle this situation?

Thanks for your time!! and your help in this. It is highly appreciated.

  Are you sure? yes | no

Owen wrote 08/20/2020 at 11:04 point

To do this within the ESP might be possible at low resolution, but you will need to look for people doing ESP32 security cam projects for that

Providing a fixed stream and a trigger, then recording that on something more capable like a Raspberry Pi is the way I'd be approaching it.

 It may be that someone already is doing similar. But it's beyond the scope of what I am trying to achieve here. I know that frameworks for connecting IOT devices into security systems exist, I've seen them in passing while researching other stuff.

  Are you sure? yes | no

Davor Dundovic wrote 08/30/2020 at 00:46 point

Hi Arjen!

I'm doing sort of security cam project.
So, my workflow is this:
(on ESP32-CAM)
- capture single image
- send it over WiFi using MQTT protocol to server (VPS)
(on server, Linux based)
- save image to mariaDB (mysql) database
- periodically run Python3 script that does motion detection using OpenCV
- you can also at this point through OpenCV add overlay data onto images (timestamp, motion status, rectangles that show what area triggered motion detection etc)
- when first motion is detected and some predefined time has passed with no motion after last, expand a bit image interval on both sides and generate ffmpeg concatenate input file
- run ffmpeg to stitch images into video clip, make time sync and save video file
- delete old/processed images

Adding email or other notifications should be trivial, server side, of course.


  Are you sure? yes | no

sanviernes wrote 07/18/2020 at 11:17 point

Great work!,  A simple question:  how to unpack to raw text the original camera_index_ov2640.h?  Thanks in advance

  Are you sure? yes | no

Albert wrote 07/23/2020 at 04:11 point

What do you mean? If you are using the Windows version, you should be able to see the contents of this file with Notepad. 

  Are you sure? yes | no

sanviernes wrote 07/29/2020 at 17:39 point

Oh no, I mean the original bin file from the original post with the Camera example

  Are you sure? yes | no

Owen wrote 08/20/2020 at 12:32 point

Reasonable question, sort of useful for diffs etc I guess.

I uploaded the unpacked originals to a new branch forked from the initial commit:


Unpacking is a bit tricky, there are online tools that can do this, but I brute-forced with a blob of C. I just uploaded that, see the readme on the branch.

  Are you sure? yes | no

Jassim Ali wrote 04/18/2020 at 02:37 point

first I would thank u for this amazing alternative, and I was looking for something like this urgently, I think the face recognition is useless on this cam, and I was looking to make an FPV robot that is connected to the WIFI and be able to access the stream remotely. it will be nice to make a (bare bone) version, that is light and require less computation, since the camera gets rely hot using the standard sketch. BTW, How can I change the defult server port (80) ? I tried so many lines but with no avail. all what I could do is change the lines in app_httpd file to this 

    config.server_port =***;
    config.ctrl_port = ***;

*** = my new port

this changed the streaming server but not the web server.

and If I entered the web server, I can take still pictures but not live stream. 

any suggestions ?


I added lines in the main code before the setup function to creat a static IP as :

IPAddress local_IP(192, 168, 8, 203);
IPAddress gateway(192, 168, 8, 1);

IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(192, 168, 8, 1); 
IPAddress secondaryDNS(192, 168, 8, 1); 

  Are you sure? yes | no

Owen wrote 08/30/2020 at 10:49 point

Sorry for delayed reply; A good question and something I definitely think should be user configurable.

I've added this to my immediate 'wish list':

  Are you sure? yes | no

Owen wrote 09/21/2020 at 10:02 point

I'm adding a reply here since thei is the original question:
The short answer is to upgrade to V2.0 or later; where you can set all the network options including static IP and specific port numbers for both the UI and the Stream.

The more complex answer is that proior to V2.0 the stream port number is calculated by the Javascript (running in the browser) as being 'my port number + 1', so if you force the stream onto another port in the app-server the javascript does not know this and still assumes the html port plus one.

V2.0 solves this by passing the stream port URL to the browser along with the other camera properties; and the javascript uses that value.

  Are you sure? yes | no

bentech4you wrote 04/04/2020 at 22:16 point


i would like to add overlay text(live temperate and humidity) to the video, is that possible?

  Are you sure? yes | no

Owen wrote 04/07/2020 at 21:37 point

Unfortunately this cannot be done directly in the stream output (for the same 'not enough cpu/ram' reasons that stops rotation etc).

But, as with the rotation setting, this could be applied in the browser window via an overlay pretty easily.  I have no plans to do anything about this myself, but if anybody wants to give it a go and submit a PR on GitHub I'd be interested.

  Are you sure? yes | no

al wrote 01/21/2020 at 19:30 point


I tried your sketch on an ESP32-CAM from a Cinese vendor "DIY MORE" which looks identical to the AI-THINKER. I can see the camera dashboard in my browser but the serial monitor shows the following error...

"[E][camera.c:1344] esp_camera_fb_get(): Failed to get the frame on time! Camera capture failed"

Any idea as to what is causing the failure?


  Are you sure? yes | no

Owen wrote 04/07/2020 at 21:33 point

Answer no longer available due to the hackaday UI stupidity.. I mean, who thinks grey labels on black backgrounds enhance usability. Cos I think they make it really easy to hit the wrong button since they all look the same in sunlight.

IIRC the answer was memory. Or lack thereof.

  Are you sure? yes | no

Owen wrote 04/07/2020 at 22:13 point

I have now added a pins definition to the code (in pins.h) for non-psram modules that get this error when run with the standard examples.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates