405 Method Not Allowed: The method is not allowed for the requested URL in Flask
Add methods=['GET', 'POST'] (or the required HTTP verb) to your @app.route() decorator, as Flask routes only accept GET by default.
Root Cause Analysis
This error occurs when Python tries to route an incoming HTTP request via Flask's URL routing map, but the requested HTTP method (such as POST, PUT, or DELETE) is not listed in the route decorator's allowed methods list.
Flask Routing Rules & Default Verbs
When you define a route using @app.route('/path') without specifying the methods argument, Flask defaults strictly to allowing GET (and automatically handles HEAD and OPTIONS). If a client, HTML <form method="POST">, or JavaScript fetch() sends a POST request to that URL, Flask's URL adapter fails the method check and returns HTTP status code 405 Method Not Allowed with an Allow header indicating the accepted verbs.
Key Scenarios Triggering 405
- HTML Form Submission: An HTML form posting data to a route defined with
@app.route('/login')lackingmethods=['GET', 'POST']. - RESTful API Calls: Sending
PUT,PATCH, orDELETErequests to an endpoint configured only for retrieval. - Trailing Slash Redirects: Submitting a
POSTrequest to/api/datawhen the route is defined as/api/data/. Flask issues a 308/301 redirect to the slashed URL, but some HTTP clients downgrade the follow-up request to aGETor fail method validation. - CORS Preflight Failures: Browsers sending preflight
OPTIONSrequests to endpoints where custom middleware stripped standard method handling.
Reproduction Code (MCVE)
from flask import Flask
app = Flask(__name__)
# By default, routes only accept GET requests
@app.route('/api/submit', methods=['GET'])
def submit():
return {'status': 'ok'}
# In raw URL map routing, matching an unsupported method raises MethodNotAllowed
adapter = app.url_map.bind('localhost')
adapter.match('/api/submit', method='POST')
Solution 1: Explicitly Declare `methods=['GET', 'POST']` in `@app.route`
Add the list of supported HTTP methods to the decorator and branch inside the view function based on request.method.
from flask import Flask, request
app = Flask(__name__)
@app.route('/api/submit', methods=['GET', 'POST'])
def handle_submit():
if request.method == 'POST':
return {'action': 'created', 'data': request.get_json(silent=True) or {}}, 201
return {'action': 'read', 'message': 'Submit form via POST'}
# Validate both GET and POST requests succeed via test client
with app.test_client() as client:
res_get = client.get('/api/submit')
res_post = client.post('/api/submit', json={'name': 'PythonFix'})
print(f'GET status: {res_get.status_code}, POST status: {res_post.status_code}')
assert res_get.status_code == 200 and res_post.status_code == 201
Solution 2: Use Class-Based Views (`MethodView`) for Clean REST Routing
Implement Flask's MethodView to separate HTTP verbs into distinct class methods (get, post, delete) automatically.
from flask import Flask, request
from flask.views import MethodView
app = Flask(__name__)
class ItemAPI(MethodView):
def get(self, item_id):
return {'item_id': item_id, 'name': 'Sample Item'}
def post(self, item_id=None):
return {'status': 'item created'}, 201
# Register class-based view
item_view = ItemAPI.as_view('item_api')
app.add_url_rule('/items/', defaults={'item_id': None}, view_func=item_view, methods=['GET', 'POST'])
app.add_url_rule('/items/<int:item_id>', view_func=item_view, methods=['GET'])
with app.test_client() as client:
post_res = client.post('/items/')
print(f'MethodView POST response: {post_res.status_code}')
assert post_res.status_code == 201
Common Pitfalls & Error Contrasts
A subtle cause of 405 errors is forgetting to define methods on @app.post('/path') vs @app.route('/path'). In Flask 2.0+, helper decorators like @app.post(), @app.get(), @app.put(), and @app.delete() exist as shortcuts. Mixing them up (e.g. decorating with @app.get() and sending a POST) causes 405.
Contrasting 405 with similar HTTP errors:
404 Not Found: The URL endpoint does not match any registered route pattern.405 Method Not Allowed: The URL exists and matched a route, but the requested HTTP verb is forbidden.400 Bad Request: The route accepted the HTTP method, but the payload format (e.g. malformed JSON) was rejected by the application handler.