Dynamic Routes & Variables
Reviewed & published by Brayan K
A dynamic route is a route with a variable part in its URL, written in angle brackets like /user/<name> — the value in that spot is captured from the URL and passed straight into your view function.
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 capture values from URLs, use converters to control their type, and build flexible pages that respond to many addresses with a single function. Run any example locally with flask run.
What You'll Learn in This Lesson
- How to capture a value from a URL with angle brackets
- How the captured value becomes a function argument
- Using converters like <int:id> and <string:name>
- Handling routes with multiple variables
- Why a type mismatch returns a 404
- Building flexible, reusable view functions
1 Capturing a Value From the URL
A dynamic route has a piece of its path wrapped in angle brackets. The text inside the brackets is a variable name. When a request matches, Flask pulls that part of the URL out and passes it to your view function as an argument with the same name.
In the example below, visiting /user/Ada calls profile(username="Ada"). The same single function handles /user/Grace, /user/Linus, and any other name.
# dynamic.py — capture a value from the URL
from flask import Flask
app = Flask(__name__)
# <username> captures whatever follows /user/ and passes
# it into the function as the 'username' argument.
@app.route("/user/<username>")
def profile(username):
return f"<h1>Profile page for {username}</h1>"
# Simulate a few requests to see the captured value used:
for name in ["Ada", "Grace", "Linus"]:
print(profile(name))
# Run locally with: flask run, then visit /user/Ada
# ✅ Expected output:
# <h1>Profile page for Ada</h1>
# <h1>Profile page for Grace</h1>
# <h1>Profile page for Linus</h1>2 Converters: Controlling the Type
By default a captured value is a string. A converter lets you require a specific type by writing it before the variable name, like <int:post_id>. Flask both checks the value matches and converts it for you — so post_id arrives as a real Python int.
If the URL doesn't match the converter (for example /post/hello when an int is expected), Flask returns a 404 instead of calling your function.
# Using the int converter
from flask import Flask
app = Flask(__name__)
# <int:post_id> only matches whole numbers, and post_id
# arrives as a real int (not a string).
@app.route("/post/<int:post_id>")
def show_post(post_id):
# We can do math because post_id is an integer
return f"<h1>Post #{post_id}</h1><p>Next post: {post_id + 1}</p>"
print(show_post(5)) # -> Post #5, next 6
print(show_post(42)) # -> Post #42, next 43
# /post/hello would NOT match -> Flask returns 404
# ✅ Expected output:
# <h1>Post #5</h1><p>Next post: 6</p>
# <h1>Post #42</h1><p>Next post: 43</p>- <string:x> — text without a slash (the default)
- <int:x> — a whole number, converted to int
- <float:x> — a decimal number, converted to float
- <path:x> — text that may include slashes
3 Multiple Variables in One Route
A route can capture more than one value. Each angle-bracket section becomes one argument, in order. The route below captures a category as text and an item id as an integer.
# Two captured values, two converters
from flask import Flask
app = Flask(__name__)
# /shop/books/3 -> category="books", item_id=3
@app.route("/shop/<category>/<int:item_id>")
def product(category, item_id):
return f"<h1>{category.title()} item #{item_id}</h1>"
print(product("books", 3)) # Books item #3
print(product("toys", 17)) # Toys item #17
# Run locally with flask run, then visit /shop/books/3
# ✅ Expected output:
# <h1>Books item #3</h1>
# <h1>Toys item #17</h1>The order matters: the first bracket maps to the first parameter, the second bracket to the second. The names just need to match between the route and the function signature.
🔬 Anatomy of a Dynamic Route
| Pattern | What it captures |
|---|---|
| /user/<name> | Any text as a string, passed as name |
| /post/<int:id> | A whole number, passed as an int |
| /price/<float:amt> | A decimal, passed as a float |
| /files/<path:p> | Text that may contain slashes |
🎯 Your Turn: Fill in the Blanks
Finish a route that greets a user by name and another that shows a numbered order. Replace each ___.
# 🎯 YOUR TURN: replace each ___ to finish the routes
from flask import Flask
app = Flask(__name__)
# Greet any name from the URL
@app.route("/hello/<___>") # capture a name
def greet(name):
return f"<h1>Hello, {___}!</h1>" # use the captured value
# Show an order by its number (must be an integer)
@app.route("/order/<___:order_id>") # require an integer
def order(order_id):
return f"<h1>Order {order_id}</h1>"
print(greet("Sam"))
print(order(99))Common Errors & Quick Reference
❌ TypeError: profile() missing 1 required positional argument
Your view function takes a parameter but you forgot the matching variable in the route, or the names don't match. The bracket name and the parameter name must be identical.
❌ 404 Not Found for /post/abc
The converter rejected the value. <int:post_id> only matches numbers, so a non-numeric value never reaches your function — that's working as intended.
| Goal | Route pattern |
|---|---|
| Capture text | /user/<name> |
| Capture an integer | /post/<int:id> |
| Capture a decimal | /price/<float:amt> |
| Capture a path | /files/<path:p> |
🏆 Mini Challenge: A Blog URL Scheme
Design routes for a simple blog.
- A route /author/<name> that greets the author
- A route /article/<int:id> that shows an article number
- A route /article/<int:id>/comments/<int:c> for a specific comment
# 🏆 Mini Challenge: a blog URL scheme
from flask import Flask
app = Flask(__name__)
@app.route("/author/<name>")
def author(name):
return f"<h1>Articles by {name}</h1>"
@app.route("/article/<int:id>")
def article(id):
return f"<h1>Article #{id}</h1>"
# TODO: a comment under a specific article
@app.route("/article/<int:id>/comments/<int:c>")
def comment(id, c):
return f"<h1>Comment {c} on article {id}</h1>"
print(author("Ada"))
print(article(7))
print(comment(7, 2))❓ Frequently Asked Questions
Lesson complete — your routes are dynamic now!
You can capture values from URLs, convert them with <int:...> and friends, and handle several variables in one route. One function can now serve thousands of URLs.
Practice quiz
How do you mark a variable part of a URL in a Flask route?
- with curly braces {name}
- with angle brackets <name>
- with a colon :name
- with parentheses (name)
Answer: with angle brackets <name>. Angle brackets like /user/<name> capture that part of the URL.
In @app.route('/user/<username>'), how is the value passed to the view?
- as a function argument named username
- via request.args
- in a global variable
- as request.form['username']
Answer: as a function argument named username. The captured segment is passed as the matching function parameter.
What type does a captured value have by default (no converter)?
- int
- bytes
- string
- list
Answer: string. Without a converter, captured URL values are strings.
Which converter requires a whole number and passes it as an int?
- <num:id>
- <digit:id>
- <integer:id>
- <int:id>
Answer: <int:id>. <int:id> matches whole numbers and converts the value to a Python int.
What happens when /post/abc hits a route declared as /post/<int:id>?
- Flask returns a 404 Not Found
- id becomes the string 'abc'
- Flask raises a 500 error
- id becomes 0
Answer: Flask returns a 404 Not Found. A non-integer does not match the converter, so the route is not selected and Flask returns 404.
Which converter matches text that may contain slashes?
- <slash:p>
- <path:p>
- <any:p>
- <url:p>
Answer: <path:p>. <path:p> accepts segments that include forward slashes.
Which converter is used for a decimal number?
- <decimal:x>
- <double:x>
- <float:x>
- <number:x>
Answer: <float:x>. <float:x> matches a decimal and passes it as a Python float.
Can a single route capture more than one variable?
- Yes — each angle-bracket section is one argument
- No, only one is allowed
- Only with query strings
- Only if both are ints
Answer: Yes — each angle-bracket section is one argument. A route like /<category>/<int:item_id> maps each bracket to a parameter in order.
For /shop/<category>/<int:item_id>, how does the view receive them?
- both as strings
- as request.args
- as two parameters: category (str) and item_id (int)
- as a single dict
Answer: as two parameters: category (str) and item_id (int). Each bracket becomes a parameter; the int converter makes item_id an int.
What must match between the route bracket and the view function?
- the file name
- the HTTP method
- the return type
- the variable/parameter name
Answer: the variable/parameter name. <username> in the route must pair with a username parameter in the function.
Continue this course
- Previous: Your First Route
- Next: HTTP Methods (GET & POST) — Handle GET and POST requests and read submitted data
- Quick reference: Flask cheat sheet › Setup & Routes
Frequently asked questions
What is a dynamic route in Flask?
A dynamic route is a route whose URL contains a variable part written in angle brackets, like /user/<name>. Whatever appears in that part of the URL is passed into your view function as an argument.
What are route converters?
Converters tell Flask what type a URL variable should be, like <int:id> for an integer or <path:subpath> for a value that may contain slashes. They both validate the value and convert it to the right Python type.
What happens if the value doesn't match the converter?
Flask returns a 404 Not Found. For example, /post/abc won't match @app.route('/post/<int:id>') because 'abc' isn't an integer, so the route simply isn't selected.
Can a route have more than one variable?
Yes. A route like /<category>/<int:item_id> has two variables, and your view function just takes two matching parameters. Each angle-bracket section becomes one argument.