{"id":3777,"date":"2021-04-09T11:10:17","date_gmt":"2021-04-09T05:40:17","guid":{"rendered":"https:\/\/blog.guvi.in\/?p=3777"},"modified":"2026-09-07T10:26:31","modified_gmt":"2026-09-07T04:56:31","slug":"how-to-make-a-basic-html-form-in-flask","status":"publish","type":"post","link":"https:\/\/www.guvi.in\/blog\/how-to-make-a-basic-html-form-in-flask\/","title":{"rendered":"How to Create an HTML Form in Flask (2026 Step-by-Step Guide)"},"content":{"rendered":"\n<p>To create a basic HTML form in Flask, you build the form in an HTML file, then use a Flask route to read and process what the user submits. That&#8217;s the whole idea in one line, but getting it working properly and doing it the right way takes a few more steps.<\/p>\n\n\n\n<p>In this guide, you&#8217;ll set up a simple Flask project, create an HTML form, and handle the submitted data using Python. We&#8217;ll also cover the parts most tutorials skip, like validating input and protecting your form with CSRF security, so what you build here isn&#8217;t just a toy example but something closer to how forms are actually handled in real projects.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>TL;DR Summary<\/strong><\/h2>\n\n\n\n<ul>\n<li>Flask forms work by connecting an HTML file to a Python route, the form sends data, Flask reads it, and does something with it<\/li>\n\n\n\n<li>You&#8217;ll use <code>render_template()<\/code> to show the form and <code>request.form<\/code> to grab what the user typed in<\/li>\n\n\n\n<li>GET and POST aren&#8217;t interchangeable here, POST is what you want when a form is actually submitting data<\/li>\n\n\n\n<li>Skipping server-side validation is one of the most common mistakes, client-side checks alone aren&#8217;t enough<\/li>\n\n\n\n<li>For anything beyond a quick test, Flask-WTF and CSRF protection turn a basic form into something actually safe to use<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<div style=\"background-color: #099f4e; border: 3px solid #110053; border-radius: 12px; padding: 18px 22px; color: #FFFFFF; font-size: 18px; font-family: Montserrat, Helvetica, sans-serif; line-height: 1.6; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); max-width: 750px;\">\n  <strong style=\"font-size: 22px; color: #ffffff;\">\ud83d\udca1 Did You Know?<\/strong> <br \/><br \/>\n  <span>\n    <strong style=\"color: #110053;\">Flask<\/strong> was first released by \n    <strong style=\"color: #110053;\">Armin Ronacher<\/strong> on \n    <strong style=\"color: #110053;\">April 1, 2010<\/strong>, and despite its April Fools&#8217; Day launch, it became one of Python&#8217;s most widely used web frameworks.\n  <\/span>\n<\/div>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What You&#8217;ll Need Before You Start<\/strong><\/h2>\n\n\n\n<p>Before jumping in, make sure you have <a href=\"https:\/\/www.guvi.in\/blog\/what-is-python-used-for\/\" target=\"_blank\" rel=\"noreferrer noopener\">Python<\/a> installed on your system, along with a code editor like VS Code. You&#8217;ll also need Flask itself, which you can install using pip:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pip install Flask<\/code><\/pre>\n\n\n\n<p>A basic understanding of Python and <a href=\"https:\/\/www.guvi.in\/blog\/html-tutorial-guide-for-web-development\/\" target=\"_blank\" rel=\"noreferrer noopener\">HTML<\/a> helps, but you don&#8217;t need to be an expert. If you know how variables and functions work in Python, and how a basic HTML page is structured, you&#8217;re ready to go.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><strong><em>Full stack development starts with small, practical wins like this one, and builds into something much bigger. HCL GUVI&#8217;s <\/em><\/strong><em><a href=\"https:\/\/www.guvi.in\/zen-class\/full-stack-development-course\/?utm_source=blog&amp;utm_medium=hyperlink&amp;utm_campaign=create-an-html-form-in-flask\" target=\"_blank\" rel=\"noreferrer noopener\">Software &amp; AI Engineer Course<\/a><\/em><strong><em> takes you through the full stack, from React and JavaScript on the frontend to Node.js, Express, and MongoDB on the backend, along with APIs, authentication, and real deployment workflows. Enroll today and start building the skills top tech companies are actually hiring for!<\/em><\/strong><\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Setting Up Your Flask Project Structure<\/strong><\/h2>\n\n\n\n<p><a href=\"https:\/\/www.guvi.in\/blog\/what-is-flask-in-python\/\" target=\"_blank\" rel=\"noreferrer noopener\">Flask<\/a> expects a specific folder layout to find your HTML files correctly. Create a project folder, and inside it, set up your files like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>my_flask_form\/\n\u2502\n\u251c\u2500\u2500 app.py\n\u2514\u2500\u2500 templates\/\n    \u2514\u2500\u2500 form.html<\/code><\/pre>\n\n\n\n<p>The <code>templates<\/code> folder isn&#8217;t optional, Flask looks for HTML files there by default when you use <code>render_template()<\/code>. If your HTML file sits outside this folder, Flask won&#8217;t find it, and you&#8217;ll run into an error.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Creating an HTML Form in Flask<\/strong><\/h2>\n\n\n\n<p>Now let&#8217;s build the actual HTML form in Flask. Inside <code>templates\/form.html<\/code>, add the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n    &lt;title&gt;Simple Form&lt;\/title&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n    &lt;h2&gt;Enter Your Details&lt;\/h2&gt;\n    &lt;form action=\"\/submit\" method=\"POST\"&gt;\n        &lt;label for=\"name\"&gt;Name:&lt;\/label&gt;\n        &lt;input type=\"text\" id=\"name\" name=\"name\" required&gt;\n        &lt;br&gt;&lt;br&gt;\n        &lt;label for=\"email\"&gt;Email:&lt;\/label&gt;\n        &lt;input type=\"email\" id=\"email\" name=\"email\" required&gt;\n        &lt;br&gt;&lt;br&gt;\n        &lt;button type=\"submit\"&gt;Submit&lt;\/button&gt;\n    &lt;\/form&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/code><\/pre>\n\n\n\n<p>Here&#8217;s what&#8217;s happening in this code. The <code>&lt;form&gt;<\/code> tag has an <code>action<\/code> attribute set to <code>\/submit<\/code>, which tells the browser where to send the data once the user hits submit. <\/p>\n\n\n\n<p>The <code>method=\"POST\"<\/code> means the data is sent securely in the request body rather than exposed in the <a href=\"https:\/\/en.wikipedia.org\/wiki\/URL\" target=\"_blank\" rel=\"noreferrer noopener\">URL<\/a>. Each <code>&lt;input&gt;<\/code> field has a <code>name<\/code> attribute, this is important, because that&#8217;s exactly how Flask will identify and retrieve each piece of data on the backend.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><strong><em>Want to go beyond a single form? HCL GUVI&#8217;s <\/em><\/strong><em><a href=\"https:\/\/www.guvi.in\/courses\/web-development\/python-flask-framework\/?utm_source=blog&amp;utm_medium=hyperlink&amp;utm_campaign=create-an-html-form-in-flask\" target=\"_blank\" rel=\"noreferrer noopener\">Web Development with Python Flask Course<\/a><\/em><strong><em> covers Flask routing, templating, user authentication, database integration, and building REST APIs, everything needed to build real Flask applications, not just basic examples.<\/em><\/strong><\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Handling Form Data in Flask (GET vs POST)<\/strong><\/h2>\n\n\n\n<p>With the HTML form ready, you now need a Flask route to display it and another to handle what&#8217;s submitted. Open <code>app.py<\/code> and add this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from flask import Flask, render_template, request\n\napp = Flask(__name__)\n\n@app.route('\/')\ndef form():\n    return render_template('form.html')\n\n@app.route('\/submit', methods=&#91;'POST'])\ndef submit():\n    name = request.form&#91;'name']\n    email = request.form&#91;'email']\n    return f\"Thanks, {name}! We received your email: {email}\"\n\nif __name__ == '__main__':\n    app.run(debug=True)\n<\/code><\/pre>\n\n\n\n<p>The first route simply renders the form when someone visits the homepage. The second route, <code>\/submit<\/code>, only accepts <code>POST<\/code> requests, which matches the method we set in the HTML form. Inside this route, <code>request.form['name']<\/code> and <code>request.form['email']<\/code> pull the actual values the user typed in, using the same <code>name<\/code> attributes defined in the HTML.<\/p>\n\n\n\n<p>It&#8217;s worth understanding why POST matters here. GET requests append data to the URL, which isn&#8217;t suitable for anything sensitive, like passwords or personal details. POST keeps that data inside the request body, out of the URL entirely, making it the correct choice for form submissions.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Displaying Submitted Data on Another Page<\/strong><\/h2>\n\n\n\n<p>Right now, the submitted data just shows up as plain text. A cleaner approach is to render it on a separate HTML page. Create a new file, <code>templates\/result.html<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n    &lt;title&gt;Submission Result&lt;\/title&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n    &lt;h2&gt;Thank you, {{ name }}!&lt;\/h2&gt;\n    &lt;p&gt;We've received your email: {{ email }}&lt;\/p&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/code><\/pre>\n\n\n\n<p>Then update the <code>\/submit<\/code> route in <code>app.py<\/code> to render this page instead of returning plain text:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@app.route('\/submit', methods=&#91;'POST'])\ndef submit():\n    name = request.form&#91;'name']\n    email = request.form&#91;'email']\n    return render_template('result.html', name=name, email=email)\n<\/code><\/pre>\n\n\n\n<p><strong>Important Note:<\/strong><\/p>\n\n\n\n<p>Notice the double curly braces in <code>result.html<\/code>, like <code>{{ name }}<\/code>. This is Jinja2 syntax, Flask&#8217;s built-in templating engine, and it&#8217;s how Python variables get inserted directly into your HTML.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Adding Server-Side Validation<\/strong><\/h2>\n\n\n\n<p>Client-side validation, like the <code>required<\/code> attribute in your HTML input, is helpful, but it can be bypassed easily. Anyone can disable JavaScript or send a request directly, skipping your form entirely. That&#8217;s why server-side validation matters.<\/p>\n\n\n\n<p>Here&#8217;s a simple way to add it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@app.route('\/submit', methods=&#91;'POST'])\ndef submit():\n    name = request.form.get('name', '').strip()\n    email = request.form.get('email', '').strip()\n\n    if not name or not email:\n        return \"Name and email are required.\", 400\n\n    return render_template('result.html', name=name, email=email)\n<\/code><\/pre>\n\n\n\n<p>Using <code>.get()<\/code> instead of direct dictionary access avoids errors if a field is missing entirely, and the check right after makes sure empty submissions don&#8217;t slip through. This small addition makes your form noticeably more reliable.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Securing Your HTML Form in Flask with CSRF Protection<\/strong><\/h2>\n\n\n\n<p>This is the part most beginner tutorials skip entirely, but it matters a lot once your form handles real user data. Without protection, your form can be vulnerable to CSRF (Cross-Site Request Forgery) attacks, where a malicious site tricks a user&#8217;s browser into submitting a form on your site without their knowledge.<\/p>\n\n\n\n<p>The standard way to fix this in Flask is using <strong>Flask-WTF<\/strong>, an extension that adds CSRF protection automatically. First, install it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pip install flask-wtf<\/code><\/pre>\n\n\n\n<p>Then update <code>app.py<\/code> to include a secret key, which Flask-WTF needs to generate CSRF tokens:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.config&#91;'SECRET_KEY'] = 'your-secret-key-here'<\/code><\/pre>\n\n\n\n<p>And in your HTML form, add a hidden CSRF token field:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;form action=\"\/submit\" method=\"POST\"&gt;\n    &lt;input type=\"hidden\" name=\"csrf_token\" value=\"{{ csrf_token() }}\"&gt;\n    ...\n&lt;\/form&gt;<\/code><\/pre>\n\n\n\n<p>This token gets validated automatically on submission, and if it&#8217;s missing or doesn&#8217;t match, Flask-WTF blocks the request. It&#8217;s a small addition, but it&#8217;s the difference between a form that works and a form that&#8217;s actually safe to use in production.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Common Mistakes to Avoid<\/strong><\/h2>\n\n\n\n<p>A few mistakes come up repeatedly when people are just starting out with an <strong>HTML form in Flask<\/strong>:<\/p>\n\n\n\n<ol>\n<li><strong>Forgetting the <code>name<\/code> attribute:<\/strong> Without it, <code>request.form<\/code> has no way to identify the field, and you&#8217;ll get a KeyError.<\/li>\n\n\n\n<li><strong>Mismatched form method:<\/strong> If your HTML form uses <code>POST<\/code> but your Flask route only allows <code>GET<\/code>, Flask will return a 405 error.<\/li>\n\n\n\n<li><strong>Placing HTML files outside the templates folder:<\/strong> <code>render_template()<\/code> won&#8217;t find your file, and you&#8217;ll get a TemplateNotFound error.<\/li>\n\n\n\n<li><strong>Skipping server-side validation:<\/strong> Relying only on HTML&#8217;s <code>required<\/code> attribute leaves your form open to bad or missing data.<\/li>\n\n\n\n<li><strong>Ignoring CSRF protection:<\/strong> Fine for practice projects, but a real risk once your form is live and handling actual user input.<\/li>\n<\/ol>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Conclusion<\/strong><\/h2>\n\n\n\n<p>Building an HTML form in Flask starts simple, an HTML file, a route, and a bit of Python to read what&#8217;s submitted. But going from a working example to something production-ready means adding validation and CSRF protection along the way. Once you&#8217;ve built one form this way, the same pattern applies to pretty much any form you&#8217;ll build in Flask going forward, login pages, contact forms, feedback systems, all of it.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>FAQs<\/strong><\/h2>\n\n\n<div id=\"rank-math-faq\" class=\"rank-math-block\">\n<div class=\"rank-math-list \">\n<div id=\"faq-question-1787665431626\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \">1. How do I create a form in HTML Flask?<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Build the form in an HTML file inside the templates folder, then render it using <code>render_template()<\/code> in your Flask route. This is how you create a html form in flask.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1787665433165\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \">2. How to get form data in Flask?<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Use <code>request.form['field_name']<\/code> inside your route to access whatever the user typed into that input field.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1787665434033\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \">3. Is GET or POST used for forms in Flask?<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>POST is used for form submissions since it keeps data in the request body instead of exposing it in the URL.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1787665434887\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \">4. How do I display form data in Flask?<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Pass the submitted data into <code>render_template()<\/code> and use Jinja2&#8217;s <code>{{ }}<\/code> syntax to show it on another page.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1787665436025\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \">5. Do I need Flask-WTF to build an HTML form in Flask?<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>No, it&#8217;s optional for basic forms, but recommended for CSRF protection and validation in production.<\/p>\n\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p> <\/p>\n","protected":false},"excerpt":{"rendered":"<p>To create a basic HTML form in Flask, you build the form in an HTML file, then use a Flask route to read and process what the user submits. That&#8217;s the whole idea in one line, but getting it working properly and doing it the right way takes a few more steps. In this guide, [&hellip;]<\/p>\n","protected":false},"author":64,"featured_media":137463,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[37,718],"tags":[],"views":"24489","authorinfo":{"name":"Abhishek Pati","url":"https:\/\/www.guvi.in\/blog\/author\/abhishek-pati\/"},"thumbnailURL":"https:\/\/www.guvi.in\/blog\/wp-content\/uploads\/2021\/04\/How-to-Create-an-HTML-Form-in-Flask-300x116.png","_links":{"self":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts\/3777"}],"collection":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/users\/64"}],"replies":[{"embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/comments?post=3777"}],"version-history":[{"count":65,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts\/3777\/revisions"}],"predecessor-version":[{"id":137473,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts\/3777\/revisions\/137473"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/media\/137463"}],"wp:attachment":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/media?parent=3777"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/categories?post=3777"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/tags?post=3777"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}