Skip to main content

How to Save Contact Messages in JSON Using Flask?

TinyDB

In Part One, we built and styled our contact form using Flask and CKEditor. Now in Part Two, we’ll take it a step further by connecting the form to TinyDB so we can actually store user submissions.

In this tutorial, you’ll learn how to save form data into a JSON database, handle validation properly, and implement a clean redirect after submission. By the end, your contact form won’t just collect messages—it will securely store them like a real web application.

Prerequisite:

This tutorial is part of the Flask CKEditor Project Series.

📚 View the Complete Flask CKEditor Series

⬅ Previous Part                                                                                                      ➡ Next Part   

Preliminary
Before I begin, it is recommended that you activate the virtual environment and install the relevant dependencies.
python -m venv venv
venv\Scripts\activate 
pip install flask tinydb
Then, we need to set up the file and folder structure, as below:

TinyDB

Everything is the same as in part one, the templates folder, except for contact.html, which will now add a new thankyou.html. On the other hand, I need to amend the form.py and add two new routes to streamline the form submission in app.py! 

In fact, TinyDB is a non-schema database storage, and it stores all the input data in JSON format. Therefore, when the submission button is clicked, the data will be automatically stored in the message.json, as shown in the above diagram. 

The benefits are as follows:
  1. TinyDB is a pure Python database that stores data in a simple JSON file.
  2. It feels like working with Python dictionaries and lists.
  3. TinyDB is small and fast for low to medium data volumes.
  4. All data is stored in a readable .json file.
  5. You don’t need to learn SQL queries.
  6. TinyDB works smoothly inside Flask apps without needing external database services like MySQL or PostgreSQL

Step 1: Amend the form.py
from flask_wtf import FlaskForm
from wtforms import StringField, BooleanField, SubmitField
from wtforms.validators import DataRequired
from flask_ckeditor import CKEditorField

class ContactForm(FlaskForm):
    name = StringField('Name:', validators=[DataRequired()])
    email = StringField('Email:', validators=[DataRequired()])
    subscribe = BooleanField("Subscribe to our newsletter?")
    message = CKEditorField('Message:', validators=[DataRequired()])
    submit = SubmitField("Submit ☺️")
In order to ensure the message field is accepted and validated under the WTF. I need to import the CKEditorField, assign it to the message field, and set the validator as data required.


Step 2: Update the submission form
Once I have amended the form.py, I need to update it on contact.html, as below:
<form method="POST" action="{{ url_for('submit') }}">
    {{ form.hidden_tag() }}

    <p>{{ form.name.label }} {{ form.name(size=32) }}</p>
    <p>{{ form.email.label }} {{ form.email(size=32) }}</p>
    <p>{{ form.message.label }} {{ form.message() }}</p>

    {{ ckeditor.load() }}
    {{ ckeditor.config(name='message') }}

    <p>{{ form.subscribe() }} {{ form.subscribe.label }}</p>
    <p class="submit-container">{{ form.submit(class="btn-submit") }}</p>
</form>
The form action refers to the submit() function on the app.py (we will set it up later on), and I am also adding an additional 'form.message()' widget on the contact.html. 

Even though I need to create a rich text editor by creating the ckeditor.load(). However, the 'form.message()' widget originally refers to a textarea by default. Therefore, when I coded a ckeditor.config with name='message', the purpose was to allow the rich text editor to replace the existing textarea widget display. 


Step 3: Set up the submit() function
Now I need to create the Submit() function as follows:
from flask import (Flask, render_template, redirect, 
                   url_for)
from tinydb import TinyDB

# The JSON file output path
db = TinyDB('flask_ckeditor/message.json')

def submit():
    # Create an instance of the ContactForm
    form = ContactForm()
    
    # Check if the form is submitted and passes validation
    
    if form.validate_on_submit():
    # Retrieve data from each form field
        name = form.name.data
        email = form.email.data
        subscribe = form.subscribe.data
        message = form.message.data
        
        # Insert the form data into the database
        db.insert({
            'name': name,
            'email': email,
            'subscribe': subscribe,
            'message': message
        })
        
        # Redirect the user to the thank you page after successful submission
        return redirect(url_for('thankyou'))
   
   # If form is not submitted or validation fails,
   # re-render the contact page and display validation errors
   return render_template('contact.html', form=form)
Let me import the relevant libraries and set the output path of message.json

What I did here:
1) I need to validate all the input fields (data required), such as name, email, subscribe, and message.
2) After validation, I need to add those input fields to the message.json database.
3) If the form is accepted and validated, the app will now redirect to the thankyou() function.
4) Otherwise, it will re-render on the same page.

The formatted message.json is as follows:
Json Formatter



Step 4: Set up the thankyou() function and thankyou.html
This function is to inform the user that the message was added successfully. 
The thankyou() is as follows:
@app.route('/thankyou')
def thankyou()
   # render to thankyou page
    return render_template('thankyou.html')
The thankyou.html is as follows:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Submitted Successfully</title>
  <style>
    h1 {
      colour: red;
      text-align: centre;
      font-family: Arial, sans-serif;
    }
  </style>
</head>
<body>
  <h1>Your message has been sent successfully! 😊</h1>
</body>
</html>
Here is the thankyou.html view:
thankyou.html

Bonus
If you need to view the details of the output on the terminal, simply add this line of code
print(db.all())

The details on the terminal are as follows:
Show a details




Final wrap-up:
We’ve now connected our Flask contact form to TinyDB and can store messages in a JSON file. The form validates input with Flask-WTF and uses CKEditor for rich text messages.
Submissions are saved securely, and users see a thank-you page after sending.
This setup makes your contact form fully functional and ready for small web apps or projects.
      
Published: Mar 2026
Last Updated: Mar 2026

About the Author

Kelvin Loh is a Python developer focused on Flask, desktop applications, and business automation solutions. He shares practical tutorials and real-world coding projects to help developers and small businesses build useful applications.

Comments

Popular Posts

How to Design a Location Tracking Module for Desktop Business Systems Using Python?

This tutorial will show you how to create an interactive map application that allows users to input geographic coordinates and visualize locations on an interactive map using Python's Tkinter GUI framework enhanced with TTKBootstrap styling and the TkinterMapView widget.  Prerequisite: This tutorial is part of the standalone tutorial. 📚 View the  standalone tutorial Preliminary   Before I begin, it is recommended to activate the virtual environment before installing the relevant dependencies. python -m venv venv venv\Scripts\activate pip install ttkbootstrap tkintermapview As usual, we need to import the relevant module and set the root values from tkinter import * import ttkbootstrap as tb import tkintermapview from ttkbootstrap.dialogs import Messagebox root = tb.Window(themename='darkly') root.title('Find My Map') root.geometry("1200x1200") I would like to divide it into three sections.  a) Input Panel Layout,  The headers are mainly a label...

How to Create Flask Forms with CKEditor and Flask-WTF?

In this tutorial, we will be integrating Flask-WTF and Flask-CKEditor into your Flask application! You'll learn how to set up the editor, securely handle formatted HTML content, and create a seamless user experience that enhances any project that requires user-generated content. Let's get started! Prerequisite: This tutorial is part of the Flask CKEditor Project Series. 📚 View the Complete Flask CKEditor Series                                                                                                                                                  ➡ Next Part Preliminary Before I begin, it is recommended to activate the v...

How to Store Application Data with SQLite in Python?

  In the previous tutorial, I used TinyDB as our storage solution to keep application data in a simple JSON-based format. While TinyDB is lightweight and easy to use, many web applications require a more structured and scalable database system. In this tutorial, we will switch to SQLite , a powerful relational database that integrates well with Python and web frameworks. By using SQLite, you will learn how to store, manage, and query data in a more structured way for real-world applications. Prerequisite: This tutorial is part of the Flask CKEditor Project Series. 📚 View the Complete Flask CKEditor Series ⬅ Previous Part                                                                                                         ...