url_for, redirects and static files

Flask · Essentials · 8 min

Hard-coded URLs break when routes change; url_for keeps links correct everywhere.

url_for(endpoint, **values) builds a URL from an endpoint name (the view function's name by default) and its route variables:

url_for("show_post", post_id=7)        # '/posts/7'
url_for("search", q="flask", page=2)   # '/search?q=flask&page=2'

Arguments that are not route variables become the query string, and values are URL-encoded for you. Pass _external=True for an absolute URL with scheme and host, which you need in emails.

Why bother instead of writing "/posts/7"? You can change a route's path in one place, and every link and redirect follows. Misspelled endpoints fail loudly with BuildError rather than producing broken links.

url_for needs to know the current app, so call it inside a view, a template, or a with app.test_request_context(): block.

Static files go in a static/ folder and are served at /static/... by the dev server under the endpoint static:

<link rel="stylesheet" href="{{ url_for('static', filename='css/site.css') }}">

In production, let the web server (Nginx) or a CDN serve them directly, since that is much faster than going through Python.

Combine redirect with url_for for navigation: return redirect(url_for("login")). After a successful form POST, always redirect (the Post/Redirect/Get pattern) so a browser refresh doesn't resubmit the form.

Example

from flask import Flask, redirect, url_for

app = Flask(__name__)

@app.get("/posts/<int:post_id>")
def show_post(post_id):
    return f"post {post_id}"

@app.get("/latest")
def latest():
    return redirect(url_for("show_post", post_id=7))

with app.test_request_context():
    print(url_for("show_post", post_id=3))                 # /posts/3
    print(url_for("show_post", post_id=3, ref="mail"))     # /posts/3?ref=mail
    print(url_for("show_post", post_id=3, _external=True)) # http://localhost/posts/3
    print(url_for("static", filename="css/site.css"))      # /static/css/site.css

print(app.test_client().get("/latest").headers["Location"])   # /posts/7

Remember

  • url_for(endpoint, **values) builds URLs; extra values become the query string
  • Static files live in static/ and are linked with url_for('static', filename=...)
  • Redirect after a successful POST (Post/Redirect/Get)

Watch out for

  • Hard-coding paths in templates, which silently break when a route changes.

Check yourself

  1. Given @app.get("/u/<name>") def profile(name), what does url_for("profile", name="ann", tab="posts") return?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: flask.palletsprojects.com/en/stable/quickstart/#static-files