Skip to main content

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

Flask-CKEditor and Flask-WTF Forms

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 virtual environment before installing the relevant dependencies.
python -m venv venv
venv\Scripts\activate 
pip install flask flask-wtf flask-ckeditor
The next step is to set up an app structure; they are app.py, form.py, and the templates folder. Within the templates folder is contact.html. The app structure is described in the following diagram:

Contact form

Step 1: Create the basic minimum file configuration
Let us set up the app.py by importing the necessary module and configuration.
from flask import Flask, render_template

app = Flask(__name__)
app.config['SECRET_KEY'] = 'mysecretkey'

@app.route('/')
def index():
    return render_template('contact.html')

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8000, debug=True)
Meanwhile, the following is the minimum setup for contact.html. I have added a header and subheader at the top of the page.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Contact Form</title>
</head>
      <body>  
       <h1>Keep In Touch 🤙</h1>
       <h5>We welcome your feedback and suggestions. We'll reply to you in 3 business days.</h5>
      </body>
</html>

Step 2: Set up the form widgets
Let us define what the widgets are in the form.py. For the name and email fields, I will use StringField and always validate them. For the subscription and button, I use BooleanField and SubmitField.
from flask_wtf import FlaskForm
from wtforms import StringField, BooleanField, SubmitField
from wtforms.validators import DataRequired

class ContactForm(FlaskForm):
      name = StringField('Name:', validators=[DataRequired()])
      email = StringField('Email:', validators=[DataRequired()])
      subscribe = BooleanField("Subscribe to our newsletter?")
      submit = SubmitField("Submit ☺️")
Then, we need to import the ContactForm from form.py into app.py and perform all the necessary setup in the index function.
from flask import Flask, render_template
from form import ContactForm

app = Flask(__name__)
app.config['SECRET_KEY'] = 'mysecretkey'

@app.route('/')
def index():
    # Create an instance of the ContactForm
    form = ContactForm()
    # The form object is sent to the template so it can be displayed and used
    return render_template('contact.html', form=form)

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8000, debug=True)
Now, we can set up a form to contain the widget in the contact.html.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Contact Form</title>
</head>
      <body>  
       <h1>Keep In Touch 🤙</h1>
       <h5>We welcome your feedback and suggestions. We'll reply to you in 3 business days.</h5>
       
       <form method="post" action="">
          {{ form.hidden_tag() }}
          <p>
              {{ form.name.label }}
              {{ form.name(size=32) }}<br>
          </p>
          <p>
              {{ form.email.label }}
              {{ form.email(size=32) }}<br>
          </p>
          <p>
              {{ form.subscribe() }} {{ form.subscribe.label }}
          </p>
          <p class="submit-container">
              {{ form.submit(class="btn-submit") }}
          </p>
      </form>
      </body>
</html>
Finally, let us check out how it looks.
Raw contact form

Step 3: Initialise and configure the Flask-CKEditor
First, let's import the module and initialise it in the app.py
from flask import Flask, render_template
from form import ContactForm
from flask_ckeditor import CKEditor

app = Flask(__name__)
# Initialize CKEditor and attach it to the Flask application
ckeditor = CKEditor(app)

app.config['SECRET_KEY'] = 'mysecretkey'
# Specify the CKEditor package type to load
app.config['CKEDITOR_PKG_TYPE'] = 'full-all'

@app.route('/')
def index():
    # Create an instance of the ContactForm
    form = ContactForm()
    # The form object is sent to the template so it can be displayed and used
    return render_template('contact.html', form=form)

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8000, debug=True)
Then, I have added the CKEditor widget in between the email and subscription widgets on the contact.html.
<p>
    {{ ckeditor.load() }}
    {{ ckeditor.config() }}
    <label>Comment:</label>
    {{ ckeditor.create() }}
</p>
The web page will display as the below view.
Unstyling contact form

Step 4: Style the contact.html 
To gain a better outlook, as shown in the cover diagram above, I have styled the contact.html file with CSS as follows:
<style>
    body {
        background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
        padding: 20px;
        font-family: 'Roboto', sans-serif;
        justify-content: center;
        align-items: center;
        margin: 50px 100px 50px 100px;
        border-radius: 5px;
    }

    h1 {
        color: #2c4d3d;
        text-align: center;
    }

    h5 {
        font-size: 18px;
        color: #333333;
        text-align: center;
        font-style: italic;
    }

    label {
        font-weight: bold;
        margin-top: 10px;
        padding-right: 50px;
    }

    input[type="text"],
    input[type="email"] {
        border: 4px solid #2c4d3d;
        border-radius: 5px;
        padding: 5px;
    }

    .btn-submit {
        justify-content: center;
        width: 200px;
        background-color: #2c4d3d;
        color: white;
        border: none;
        padding: 10px 20px;
        border-radius: 5px;
        font-size: 16px;
        cursor: pointer;
    }

    .btn-submit:hover {
        background-color: #21b348;
        color: black;
    }

    p.submit-container {
        text-align: center;
    }

    .cke {
        border: 4px solid #2c4d3d !important;
        border-radius: 5px;
    }
</style>
To run the Flask app, in the terminal, type the command below:
python app.py
The following information will be shown, and just click the link provided: http://127.0.0.1:8000
URL address
Final wrap-up:
Currently, this implementation supports only CKEditor 4.22.1 (Full). Support for newer versions is not yet available, but future updates are expected as the ecosystem evolves. Stay tuned for improvements and enhancements ahead.

Published: Feb 2026
Last Updated: Feb 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 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                                                                                                         ...