URL Building & Redirects with url_for

Reviewed & published by Brayan K

url_for is a Flask function that builds the correct URL for a view by its name, so you never hardcode a path again.

Part of the free Flask course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll generate links from view names, attach query parameters, reference static files, and send the browser elsewhere with redirect.

What You'll Learn in This Lesson

1 Building URLs From View Names

url_for("view_name") asks Flask for the URL registered to a view function and returns it as a string. You pass the function's name (its endpoint), and any route parameters as keyword arguments.

Because url_for needs to know about the request, we run it inside test_request_context() — a helper that fakes a request so the example works with no server running. In a real view, the context already exists.

# app.py — build URLs by view name
from flask import Flask, url_for

app = Flask(__name__)

@app.route("/")
def home():
    return "home page"

@app.route("/user/<username>")
def profile(username):
    return f"Profile of {username}"

# test_request_context() lets url_for run without a live server
with app.test_request_context():
    print(url_for("home"))                       # /
    print(url_for("profile", username="ada"))    # /user/ada
    print(url_for("profile", username="grace"))  # /user/grace

# ✅ Expected output:
# /
# /user/ada
# /user/grace

2 Query Strings, Static Files & Why Not to Hardcode

Any keyword you pass that is not a route parameter becomes a query-string value. So url_for("search", q="flask", page=2) appends ?q=flask&page=2 automatically.

The built-in static endpoint generates URLs for your assets: url_for("static", filename="style.css") returns /static/style.css. If you hardcoded /static/... and later moved the folder or added a CDN prefix, every link would break — url_for stays correct.

# app.py — query strings and static files
from flask import Flask, url_for

app = Flask(__name__)

@app.route("/")
def home():
    return "home"

@app.route("/search")
def search():
    return "results"

@app.route("/user/<username>")
def profile(username):
    return f"Profile: {username}"

with app.test_request_context():
    # Extra keywords that aren't route params become a query string
    print(url_for("search", q="flask", page=2))     # /search?q=flask&page=2

    # Mix a route param with a query param
    print(url_for("profile", username="ada", ref="email"))  # /user/ada?ref=email

    # The built-in 'static' endpoint for CSS/JS/images
    print(url_for("static", filename="css/style.css"))      # /static/css/style.css

# ✅ Expected output:
# /search?q=flask&page=2
# /user/ada?ref=email
# /static/css/style.css

In a Jinja template you'd write the same call: . That keeps your HTML links in sync with your routes.

3 Redirecting With redirect(url_for(...))

redirect(target) tells the browser to go to another URL by sending a 302 response with a Location header. The idiomatic pattern is redirect(url_for("some_view")) so the destination is built from a name, not a hardcoded path.

This is the backbone of the Post/Redirect/Get pattern: after handling a form POST, you redirect to a result page so a browser refresh doesn't resubmit the form.

# app.py — redirect to a named route
from flask import Flask, url_for, redirect

app = Flask(__name__)

@app.route("/user/<username>")
def profile(username):
    return f"Profile of {username}"

@app.route("/old-profile")
def old_profile():
    # Send visitors of the old URL to the new one
    return redirect(url_for("profile", username="ada"))

# Inspect the redirect WITHOUT a browser:
with app.test_client() as client:
    r = client.get("/old-profile")
    print(r.status_code)               # 302
    print(r.headers["Location"])       # /user/ada

    # follow_redirects lands on the final page:
    final = client.get("/old-profile", follow_redirects=True)
    print(final.data.decode())         # Profile of ada

# ✅ Expected output:
# 302
# /user/ada
# Profile of ada

The status code 302 plus the Location: /user/ada header is exactly what a real browser receives — then it automatically requests that new URL.

🔬 url_for Cheat Sheet

You want...Code
A simple route's URLurl_for("home")
A URL with a parameterurl_for("profile", username="ada")
A query stringurl_for("search", q="x")
A static asseturl_for("static", filename="a.css")
To redirectredirect(url_for("home"))

🎯 Your Turn: Fill in the Blanks

Complete the code so it builds a profile URL, a search URL with a query, and a redirect. Replace each ___.

# 🎯 YOUR TURN: replace each ___ to finish the routing
from flask import Flask, url_for, redirect

app = Flask(__name__)

@app.route("/user/<username>")
def profile(username):
    return f"Profile: {username}"

@app.route("/search")
def search():
    return "results"

with app.test_request_context():
    # Build /user/max
    print(url_for("___", username="max"))        # endpoint name
    # Build /search?q=python
    print(url_for("search", ___="python"))       # query param name

# A redirect to the profile page:
@app.route("/me")
def me():
    return ___(url_for("profile", username="max"))   # which function redirects?

with app.test_client() as client:
    print(client.get("/me").headers["Location"])   # /user/max

Common Errors & Quick Reference

❌ BuildError: Could not build url for endpoint

The endpoint name is wrong, or you forgot a required route parameter. Pass the exact view function name and every it declares, e.g. url_for("profile", username="ada").

❌ Working outside of request context

Calling url_for at import time fails because there's no request. Call it inside a view, a template, or a test_request_context() / app_context() block.

TaskCode
URL with paramurl_for("v", id=1)
Query stringurl_for("v", q="x")
Static fileurl_for("static", filename="x")
Redirectredirect(url_for("v"))

🏆 Mini Challenge: A Short-Link Redirector

Build a route that redirects a short code to a full article URL.

# 🏆 Mini Challenge: a short-link redirector
from flask import Flask, url_for, redirect

app = Flask(__name__)

@app.route("/article/<slug>")
def article(slug):
    return f"Reading: {slug}"

@app.route("/go")
def go():
    # Redirect short link /go to the full article page
    return redirect(url_for("article", slug="flask-tips"))

with app.test_client() as client:
    r = client.get("/go")
    print(r.status_code, r.headers["Location"])   # 302 /article/flask-tips
    print(client.get("/go", follow_redirects=True).data.decode())  # Reading: flask-tips

❓ Frequently Asked Questions

Lesson complete — your links will never go stale!

You can build URLs from view names, attach query parameters, reference static files, and redirect cleanly with redirect(url_for(...)).

Practice quiz

What does url_for build a URL from?

  • A hardcoded path string
  • The current request URL
  • A regex pattern
  • A view's endpoint name

Answer: A view's endpoint name. url_for('profile') looks up the route registered for that view's endpoint and returns its path.

What does url_for('profile', username='ada') produce for /user/<username>?

  • /profile?username=ada
  • /user/ada
  • /user/<username>
  • /ada

Answer: /user/ada. The keyword matches the route parameter, filling the dynamic segment to give /user/ada.

How does url_for handle a keyword that is NOT a route parameter?

  • It appends it as a query string
  • It raises an error
  • It ignores it
  • It adds it to the path

Answer: It appends it as a query string. Extra keywords become a query string, so url_for('search', q='flask', page=2) gives /search?q=flask&page=2.

Which endpoint name links static files?

  • files
  • assets
  • static
  • public

Answer: static. url_for('static', filename='css/style.css') uses the built-in 'static' endpoint to return /static/css/style.css.

Which idiomatic call redirects to a named view?

  • go(url_for('home'))
  • redirect('/home')
  • send(url_for('home'))
  • redirect(url_for('home'))

Answer: redirect(url_for('home')). redirect(url_for('view')) builds the destination from a name rather than a hardcoded path.

What HTTP status code does redirect() send by default?

  • 200
  • 302
  • 404
  • 500

Answer: 302. redirect() returns a 302 response with a Location header pointing at the target URL.

Why avoid hardcoding URLs like '/user/ada'?

  • They break when you rename a route or add a prefix
  • They load more slowly
  • They are not valid HTML
  • They disable caching

Answer: They break when you rename a route or add a prefix. Hardcoded strings break when the URL map changes; url_for asks Flask for the current path.

Why must url_for run inside a request or test_request_context()?

  • To compress the URL
  • Because it needs an active request context
  • To enable CSRF
  • To cache the result

Answer: Because it needs an active request context. Calling url_for at import time fails; it needs a view, a template, or a test_request_context() block.

Which header carries the destination of a 302 redirect?

  • Refresh
  • Redirect
  • Location
  • Forward

Answer: Location. A redirect sets the Location header, which the browser then follows to the new URL.

What raises a BuildError from url_for?

  • Passing too many query params
  • Using it inside a template
  • Calling redirect first
  • A wrong endpoint name or a missing route parameter

Answer: A wrong endpoint name or a missing route parameter. BuildError means the endpoint name is wrong or a required <param> was not supplied.

Continue this course

Frequently asked questions

What does url_for do in Flask?

url_for builds a URL for a view by its function name instead of you typing the path. url_for('profile', username='ada') looks up the route registered for the profile view and produces /user/ada. If you ever change the route's path, every url_for call updates automatically.

Why shouldn't I hardcode URLs in Flask?

Hardcoded strings like '/user/ada' break the moment you rename a route, mount the app under a prefix, or add a parameter. url_for asks Flask for the current path, so links and redirects stay correct no matter how the URL map changes.

How do I add query parameters with url_for?

Any keyword argument that is NOT a route parameter becomes a query string. url_for('search', q='flask', page=2) produces /search?q=flask&page=2 because q and page aren't part of the route pattern, so Flask appends them after the ?.

How do I link to static files?

Use url_for('static', filename='css/style.css'), which returns /static/css/style.css. The 'static' endpoint is built in, so this is the recommended way to reference CSS, JavaScript, and images instead of hardcoding /static/ paths.