How to display uploaded image in HTML page using FastAPI & Jinja2?

Preview Uploaded Image in the same HTML page

Here is an example of the suggested solution in the comments section (previously described in this answer), where you can use FileReader.readAsDataURL() to convert the image to a base64 encoded string on client side and display the image on the page, without having FastAPI backend send it back to you, as you need to display the same (unprocessed) image uploaded by the user. For related solutions see here, here, as well as here and here. Also, to use asynchronous writing for writing the image file to the disk, have a look at this answer.

app.py

from fastapi import File, UploadFile, Request, FastAPI
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.post("/upload")
def upload(file: UploadFile = File(...)):
    try:
        contents = file.file.read()
        with open("uploaded_" + file.filename, "wb") as f:
            f.write(contents)
    except Exception:
        return {"message": "There was an error uploading the file"}
    finally:
        file.file.close()
        
    return {"message": f"Successfuly uploaded {file.filename}"}

@app.get("/")
def main(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

templates/index.html

<script type="text/javascript">
    function previewFile() {
        const preview = document.querySelector('img');
        const file = document.querySelector('input[type=file]').files[0];
        const reader = new FileReader();
        reader.addEventListener("load", function() {
            preview.src = reader.result; // show image in <img> tag
            uploadFile(file)
        }, false);
        if (file) {
            reader.readAsDataURL(file);
        }
    }

    function uploadFile(file) {
        var formData = new FormData();
        formData.append('file', file);
        fetch('/upload', {
                method: 'POST',
                body: formData,
            })
            .then(response => {
                console.log(response);
            })
            .catch(error => {
                console.error(error);
            });
    }
</script>
<input type="file" onchange="previewFile()"><br>
<img src="" height="200" alt="Image preview...">

Upload Image with the Click of a Button

In case you need the image to be uploaded to the FastAPI server after the user has clicked on an Upload Image button—instead of being automatically uploaded as in the template above as soon as the user has chosen an image—as well as you would like to display the message from the server, whether the image has been succesfully uploaded or not, you can use the template below instead.

templates/index.html

<script type="text/javascript">
    function previewFile() {
        const preview = document.querySelector('img');
        var file = document.getElementById('fileInput').files[0];
        const reader = new FileReader();
        reader.addEventListener("load", function() {
            preview.src = reader.result; // show image in <img> tag
        }, false);
        if (file) {
            reader.readAsDataURL(file);
        }
    }

    function uploadFile(file) {
        var file = document.getElementById('fileInput').files[0];
        if (file) {
            var formData = new FormData();
            formData.append('file', file);
            fetch('/upload', {
                    method: 'POST',
                    body: formData,
                })
                .then(response => response.json())
                .then(data => {
                    document.getElementById("serverMsg").innerHTML = data.message;
                })
                .catch(error => {
                    console.error(error);
                });
        }
    }
</script>
<input type="file" id="fileInput" onchange="previewFile()"><br>
<input type="button" value="Upload Image" onclick="uploadFile()">
<p id="serverMsg"></p>
<img height="200">

Preview Image in a New Tab

To preview the image in a new tab instead of the same tab, you can use the following. The below will open the image in a new tab (using the method described here) once the user has clicked on the "Upload Image" button. If you need the tab to open when the user has chosen an image instead, then comment out the previewFile() line in the uploadFile() function and uncomment the commented HTML <input type="file"> element, where onchange="previewFile()" is used.

templates/index.html

<script type="text/javascript">
    function previewFile() {
       const preview = document.querySelector('img');
       var file = document.getElementById('fileInput').files[0];
       const reader = new FileReader();
       reader.addEventListener("load", function () {
          displayImgInNewTab(reader.result)
       }, false);
       if (file) {
          reader.readAsDataURL(file);
       }
    }

    function uploadFile() {
       var file = document.getElementById('fileInput').files[0];
       if (file) {
          var formData = new FormData();
          formData.append('file', file);
          fetch('/upload', {
                method: 'POST',
                body: formData,
             })
             .then(response => response.json())
             .then(data => {
                document.getElementById("serverMsg").innerHTML = data.message;
             })
             .catch(error => {
                console.error(error);
             });
          previewFile()
       }
    }

    function displayImgInNewTab(data) {
       var image = new Image();
       image.src = data
       var w = window.open("");
       w.document.write(image.outerHTML);
    }
</script>
<!--<input type="file" id="fileInput" onchange="previewFile()"><br>-->
<input type="file" id="fileInput"><br>
<input type="button" value="Upload Image" onclick="uploadFile()">
<p id="serverMsg"></p>
<img height="200">

Return and Display Uploaded Image in a new Jinja2 Template

If what you want is to display the uploaded image in a new Jinja2 template, you can then convert the image to a base64-encoded string and return it using a TemplateResponse, where you can display it. Working example is given below. Alternatively, you could save the uploaded image under a StaticFiles directory and display it to the user in a new template using the url_for() function (e.g., {{ url_for('static', path="/uploaded_img.png") }}); however—as described in this answer, which demonstrates two further approaches for displaying/downloading a file returned from the server—you need to consider whether you expect your server to serve more than one user, and whether users should be able to view/access other users’ uploaded images or not, as well as you may need to consider generating random names/UUIDs for the filenames (as users may upload images having the same filename), and having a mechanism to delete the images from disk, when not needed anymore (similar to this answer). In that case, the approach demonstrated below might be a better choice for you.

app.py

from fastapi import File, UploadFile, Request, FastAPI
from fastapi.templating import Jinja2Templates
import base64

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/")
def main(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})
  
@app.post("/upload")
def upload(request: Request, file: UploadFile = File(...)):
    try:
        contents = file.file.read()
        with open("uploaded_" + file.filename, "wb") as f:
            f.write(contents)
    except Exception:
        return {"message": "There was an error uploading the file"}
    finally:
        file.file.close()
        
    base64_encoded_image = base64.b64encode(contents).decode("utf-8")

    return templates.TemplateResponse("display.html", {"request": request,  "myImage": base64_encoded_image})

templates/index.html

<html>
   <body>
      <form method="post" action="/upload"  enctype="multipart/form-data">   
         <label for="file">Choose image to upload</label>
         <input type="file" id="files" name="file"><br> 
         <input type="submit" value="Upload">
      </form>
   </body>
</html>

templates/display.html

<html>
   <head>
      <title>Display Uploaded Image</title>
   </head>
   <body>
      <h1>My Image<h1>
      <img src="data:image/jpeg;base64,{{ myImage | safe }}">
   </body>
</html>

An alternative to the above approach would be to use a StaticFiles directory, where you can save the image uploaded by the user, and then return a TemplateResponse, passing the path to that image as one of the key-value pairs in the Jinja2 “context” (e.g., 'imgPath': /static/uploaded_img.png'), which you can use to display the image in the Jinja2Template, e.g., <img src="{{ imgPath }}">. Note: Using this approach, images saved under /static directory would be accessible to anyone using the system. Hence, if this is an issue for your task, it might be better not to follow this approach. Also, using this approach, you might need—depending on the requirements of your project—to set up some process to delete the images after some limited period, in order to prevent running out of disk space. Further approaches for returning a file/image can be seen in this answer and this answer.

Leave a Comment