Flask GET & POST Requests

Master handling GET and POST requests in Flask with this hands-on Python web development lesson. Learn the core concepts, step-by-step implementation, and troubleshooting tips.

Focus: handle get and post requests in flask

Sponsored

You've built a few Flask routes that return static text or simple HTML, but every real web app needs to accept input from users—whether it's a search query, a form submission, or a login. The moment you try to handle user input, you run into the core distinction between GET and POST requests. In this lesson, you'll learn how to handle GET and POST requests in Flask, when to use each method, and how to write routes that respond correctly to both. By the end, you'll be able to build interactive forms and API endpoints with confidence.

The problem this lesson solves

Imagine you're building a simple contact form. When a user submits it, you need to send the data to your server, process it, and return a response. But where does the data go? How does Flask know what to do with it? If you don't understand the difference between GET and POST, you'll end up with URLs full of messy query strings or worse—you'll expose sensitive data in the browser's address bar.

Many beginners start by hardcoding URLs and passing data through the URL path, but that breaks down fast. You quickly hit issues like:

  • URL length limits — Browsers and servers have limits on how long a URL can be (often 2048 characters), so you can't send large amounts of data via GET.
  • Data exposure — GET requests put data in the URL, visible in history, logs, and bookmarks. That's bad for passwords, credit cards, or any sensitive input.
  • Broken bookmarks — If a form action is wrong, users can't bookmark the result or share a link to a POST page without resubmitting.
  • No way to distinguish actions — A route that only handles GET can't process form submissions at all.

This lesson is your step-by-step fix: you'll learn how to handle GET and POST requests in Flask so your routes become dynamic, interactive, and secure.

Core concept / mental model

Think of HTTP methods as verbs that describe what you want to do with a resource. The two you'll use most are GET and POST. Here's a simple analogy: GET is like asking a librarian for a book (you just want to read it), while POST is like handing the librarian a filled-out form to add a new book to the catalog (you're sending data to be processed).

In Flask, a route can be configured to accept one or more methods using the methods parameter in the @app.route() decorator. By default, Flask only allows GET. When a request comes in with a method that isn't allowed, Flask returns a 405 Method Not Allowed error.

The key to handling both is inside the route function: you check request.method to decide what to do. request is a global object that Flask gives you, containing all the data sent by the client—headers, form data, query strings, and JSON.

Here's the mental model:

  1. The client (browser or another app) sends an HTTP request with a method (GET, POST, PUT, DELETE, etc.) and a URL.
  2. Flask matches the URL to a route.
  3. If the method is allowed for that route, Flask calls your function.
  4. Inside the function, you inspect request.method and request.args (for GET) or request.form (for POST) to get the data.
  5. You return a response—HTML, JSON, a redirect—whatever fits.

Think of request as the mailbag the postman hands you. GET data arrives as a stack of sticky notes on the front door (the URL), while POST data is a sealed envelope in the bag.

How it works step by step

Setting up a Flask app

Make sure Flask is installed. If not, install it with:

pip install flask

Create a file named app.py with the setup code below. This imports Flask and initializes the app object.

from flask import Flask, request, render_template_string

app = Flask(__name__)

Route with GET only

By default, a route only responds to GET requests. If someone sends a POST to that route, they'll get a 405 error. Test this with the example below.

@app.route('/')
def home():
    return "<h1>Welcome!</h1>"

Route with POST only

To make a route accept POST, set methods=['POST']. If you send a GET request to this route, Flask will return 405.

@app.route('/submit', methods=['POST'])
def submit():
    return "Form submitted!"

Route with both GET and POST

This is the heart of the lesson. Set methods=['GET', 'POST'] and use an if statement inside the function to branch based on request.method.

@app.route('/form', methods=['GET', 'POST'])
def form():
    if request.method == 'POST':
        # Process the form data (e.g., store it, send an email)
        name = request.form.get('name')
        return f"Hello, {name}! Your form was submitted."
    # GET request: serve the HTML form
    return render_template_string('''
        <form method="post">
            <label>Name: <input type="text" name="name"></label>
            <button type="submit">Submit</button>
        </form>
    ''')

Accessing GET data

When a GET request is sent to a route, any query parameters appear in request.args (an immutable dict). For example, visiting /search?q=flask gives you request.args.get('q').

Accessing POST data

For POST requests, data comes in request.form (for form-encoded bodies) or request.json (if the client sent JSON with Content-Type: application/json). Use request.form.get('fieldname') for form data.

Now you have the complete picture: the flow is request → route → method check → data access → response.

Hands-on walkthrough

Let's build a mini contact form with a route that handles both GET and POST, then test it with curl. Create a new file contact.py with the following code.

from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/contact', methods=['GET', 'POST'])
def contact():
    if request.method == 'POST':
        name = request.form.get('name', 'Anonymous')
        message = request.form.get('message', '')
        # In a real app, you'd save this or send an email
        return f"<h2>Thanks, {name}!</h2><p>Your message: '{message}'</p>"
    return render_template_string('''
        <form method="post">
            <label>Name: <input type="text" name="name"></label><br>
            <label>Message: <textarea name="message"></textarea></label><br>
            <button type="submit">Send</button>
        </form>
    ''')

if __name__ == '__main__':
    app.run(debug=True)

Run the app with python contact.py and open http://127.0.0.1:5000/contact in your browser. Fill out the form and submit it—you'll see the POST branch execute.

Test with curl

Open a second terminal and test both methods:

# GET request (will return the form HTML)
curl http://127.0.0.1:5000/contact

# POST request with form data
curl -X POST -d "name=Alice&message=Hello%20Flask" http://127.0.0.1:5000/contact

The second command sends a POST with -X POST and -d to pass form data. You should see the response: Thanks, Alice! Your message: 'Hello Flask'.

Example with GET parameters

Now add a search endpoint to see how GET data works:

@app.route('/search')
def search():
    query = request.args.get('q', '')
    if query:
        return f"Searching for: {query}"
    return "<p>No query provided. Try /search?q=flask</p>"

Restart the app and visit /search?q=python. You'll see Searching for: python. This demonstrates how GET data arrives in the URL.

Pro tip: Always use request.args.get('key', default) or request.form.get('key', default) instead of request.args['key'], because the missing-key version raises a 400 Bad Request error when the key is absent. The .get() method lets you supply a fallback.

Compare options / when to choose what

Method When to use Data location Security Browser behavior
GET Retrieving data (search, filters, pagination) URL query string Not for sensitive data (exposed in history/logs) Can be bookmarked, shared, refreshed
POST Creating/updating resources, forms with sensitive data Request body Better—data not in URL Cannot be bookmarked; browser warns on refresh resubmit
  • Choose GET when the request is idempotent (repeating it gives the same result) and not changing server state. Examples: fetching a blog post, searching products.
  • Choose POST for any action that changes state—submitting a comment, logging in, uploading a file. Even if you could do it with GET, using POST prevents accidental re-submission and keeps data out of the URL.

Other methods like PUT and DELETE exist, but for most beginner projects, GET and POST cover 90% of your needs. When you build a REST API later, you'll learn when to use each of the four main verbs.

Troubleshooting & edge cases

405 Method Not Allowed

If you send a POST to a route that only accepts GET (or vice versa), Flask returns a 405 Method Not Allowed error. Fix by adding methods=['GET', 'POST'] to the route decorator, or using a separate route for each method.

400 Bad Request: KeyError

Using request.form['name'] without checking for existence raises a 400 Bad Request if the key is missing. Always use .get() with a default, as shown earlier.

Form data not showing up

If your form uses method="get", the data goes into request.args, not request.form. Double-check the form tag: use method="post" for POST data. Also ensure the route allows that method.

Sending JSON via POST

If your client sends JSON (e.g., from JavaScript), the data won't be in request.form. Use request.get_json() instead:

data = request.get_json()
name = data.get('name') if data else None

Edge case: missing content type in POST

Some HTTP clients omit the Content-Type header, causing request.form to be empty. Ensure your client sends application/x-www-form-urlencoded for forms or application/json for JSON.

What you learned & what's next

You now know how to handle GET and POST requests in Flask. Specifically, you learned:

  • How to define routes that accept both methods using methods=['GET', 'POST'].
  • How to branch on request.method to serve a form or process its submission in the same route.
  • How to access query parameters via request.args and form data via request.form.
  • When to use GET vs POST, and the trade-offs in security and URL length.
  • How to troubleshoot common errors like 405s and missing form fields.

You also completed a hands-on exercise that runs locally and tested it with curl. This skill is foundational for the next lesson in this track, Handling Form Data with Flask-WTF, where you'll add validation and CSRF protection to your forms.

Next, try building a simple login page that accepts a username and password via POST. You'll be surprised how much you can already build with these three lines!

Practice recap

Now try this mini exercise: create a Flask route /comment that accepts both GET and POST. On GET, serve a simple form with a text field. On POST, read the comment from request.form and return it back, but if the field is empty, return a friendly error instead. Test with curl. This will reinforce your understanding of method branching and safe data access.

Common mistakes

  • Forgetting to add methods=['POST'] to a route that needs to accept form submissions, resulting in a 405 error.
  • Using request.args for POST data — form data lives in request.form and won't appear in the query string.
  • Accessing form fields with request.form['name'] instead of .get(), which raises a 400 error if the key is missing.
  • Putting sensitive data in a GET URL — it gets logged in browser history and server logs, so always use POST for passwords or personal info.
  • Not handling JSON POST bodies — if the client sends JSON, request.form is empty; use request.get_json() instead.

Variations

  1. Use separate routes for GET and POST: @app.route('/submit', methods=['GET']) for the form and @app.route('/submit', methods=['POST']) for processing, with a redirect after POST.
  2. Leverage Flask's render_template with separate HTML files instead of render_template_string for cleaner separation of concerns.
  3. Use Flask-WTF or Django-style class-based views to handle validation and CSRF automatically, but for small apps the plain method check is enough.

Real-world use cases

  • A contact form on a marketing site that receives user messages via POST and displays a thank-you page.
  • A search endpoint on an e-commerce site that reads the query from GET parameters and returns product results.
  • A login or signup API that accepts JSON via POST and returns a token or success message.

Key takeaways

  • Flask routes accept GET by default; you must explicitly allow POST with methods=['GET', 'POST'].
  • Use request.method inside a route to serve different responses for GET vs POST in the same URL.
  • GET data arrives in request.args, POST data in request.form (or request.get_json() for JSON).
  • Always use .get() with a default when reading request data to avoid 400 errors on missing keys.
  • Use GET for reading data and POST for any action that changes server state to keep URLs clean and secure.
  • Testing with curl (curl -X POST -d ...) is a fast way to verify your routes handle the right methods.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.