# How Flask Invokes View Functions Defined with @app.route: The Complete Dispatch Mechanism

> Discover how Flask invokes view functions defined with app route. Learn about the dispatch_request method and endpoint lookup for callable execution.

- Repository: [Pallets/flask](https://github.com/pallets/flask)
- Tags: internals
- Published: 2026-02-16

---

**When you decorate a function with `@app.route`, Flask registers it as a view function and later invokes it during request handling through the `dispatch_request` method, which looks up the endpoint in `self.view_functions` and calls the registered callable with extracted URL arguments.**

The `@app.route` decorator is the primary interface for defining URL patterns in Flask applications. While developers use it daily to map endpoints to Python functions, the internal mechanism that translates an HTTP request into an actual function call involves several sophisticated components within the `pallets/flask` repository. Understanding how Flask invokes these view functions helps developers debug routing issues and optimize application performance.

## How @app.route Registers Your Function

Before Flask can invoke your view function, it must first store a reference to it within the application instance.

### The Decorator Implementation in scaffold.py

The `@app.route` decorator is implemented in [`src/flask/sansio/scaffold.py`](https://github.com/pallets/flask/blob/main/src/flask/sansio/scaffold.py). Rather than immediately executing your function, the decorator captures it and registers it with the application's URL map through the `add_url_rule` method.

```python

# src/flask/sansio/scaffold.py

def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
    ...
    def decorator(f: T_route) -> T_route:
        endpoint = options.pop("endpoint", None)
        self.add_url_rule(rule, endpoint, f, **options)   # registers the view

        return f
    return decorator

```

**Source:** [scaffold.py L36‑L65](https://github.com/pallets/flask/blob/main/src/flask/sansio/scaffold.py#L36-L65)

During registration, Flask stores the view function in a dictionary called `view_functions`, keyed by the endpoint name (which defaults to the function name). This mapping is crucial for the dispatch phase.

## The Request Dispatch Pipeline

When an HTTP request arrives, Flask follows a precise sequence to match the URL and invoke the correct view function.

### URL Matching with Werkzeug

Flask relies on Werkzeug's routing system to match incoming URLs. When a request arrives, Flask builds a `MapAdapter` from `self.url_map`. The adapter matches the request path to a `Rule` object and attaches it to the request object as `request.url_rule`. Simultaneously, any dynamic URL parameters are extracted and stored in `request.view_args`.

### Invoking the View Function in app.py

The actual invocation occurs in [`src/flask/app.py`](https://github.com/pallets/flask/blob/main/src/flask/app.py) within the `dispatch_request` method. This method retrieves the endpoint name from the matched rule, looks up the corresponding view function in `self.view_functions`, and executes it with the parsed URL arguments.

```python

# src/flask/app.py

def dispatch_request(self, ctx: AppContext) -> ft.ResponseReturnValue:
    req = ctx.request
    if req.routing_exception is not None:
        self.raise_routing_exception(req)
    rule: Rule = req.url_rule                     # matched rule

    view_args = req.view_args                     # URL variables

    # Invoke the view function stored in self.view_functions[endpoint]

    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)

```

**Source:** [app.py L65‑L90](https://github.com/pallets/flask/blob/main/src/flask/app.py#L65-L90)

The `ensure_sync` method handles both synchronous and asynchronous view functions. If your view is declared with `async def`, `ensure_sync` wraps it to ensure it can be called within the synchronous WSGI context, though Flask 2.0+ properly supports async views through this mechanism.

## Complete Execution Flow Example

Consider a practical example to illustrate the complete invocation chain:

```python

# myapp.py

from flask import Flask
app = Flask(__name__)

@app.route("/hello/<name>")
def greet(name):
    return f"Hello, {name}!"

```

When a client requests `/hello/Flask`, the following occurs:

1. **Registration Phase**: The `@app.route` decorator calls `add_url_rule` in [`src/flask/sansio/scaffold.py`](https://github.com/pallets/flask/blob/main/src/flask/sansio/scaffold.py), storing `greet` in `app.view_functions` under the endpoint `"greet"`.

2. **Matching Phase**: Werkzeug's router matches `/hello/Flask` to the rule `/hello/<name>`, setting `request.url_rule` to the matched rule and `request.view_args` to `{"name": "Flask"}`.

3. **Dispatch Phase**: `dispatch_request` in [`src/flask/app.py`](https://github.com/pallets/flask/blob/main/src/flask/app.py) retrieves `rule.endpoint` (which is `"greet"`), looks up `app.view_functions["greet"]`, and invokes `greet(name="Flask")` via `ensure_sync`.

4. **Response Phase**: The return value from `greet` is passed to `make_response`, converting the string into a proper HTTP response object.

## Summary

- **`@app.route`** in [`src/flask/sansio/scaffold.py`](https://github.com/pallets/flask/blob/main/src/flask/sansio/scaffold.py) registers view functions by calling `add_url_rule`, which stores them in the `view_functions` dictionary.
- **URL matching** occurs through Werkzeug's `MapAdapter`, which attaches the matched `Rule` to `request.url_rule` and extracts parameters into `request.view_args`.
- **Function invocation** happens in [`src/flask/app.py`](https://github.com/pallets/flask/blob/main/src/flask/app.py) within `dispatch_request`, which looks up the endpoint in `view_functions` and calls the registered function with `**view_args`.
- **Async support** is handled transparently via `ensure_sync`, allowing both synchronous and asynchronous view functions to work within Flask's dispatch mechanism.

## Frequently Asked Questions

### What is the difference between @app.route and add_url_rule?

`@app.route` is a convenience decorator that internally calls `add_url_rule`. While `@app.route` is the standard way to register views in application code, `add_url_rule` is the underlying method in [`src/flask/sansio/scaffold.py`](https://github.com/pallets/flask/blob/main/src/flask/sansio/scaffold.py) that actually creates the `Rule` object and populates the `view_functions` dictionary. You might use `add_url_rule` directly when registering views dynamically or when working with class-based views.

### How does Flask handle async view functions?

Flask supports async view functions through the `ensure_sync` method in [`src/flask/app.py`](https://github.com/pallets/flask/blob/main/src/flask/app.py). When `dispatch_request` retrieves the view function, it passes it through `ensure_sync`, which detects if the function is a coroutine (defined with `async def`). If so, it wraps the function to ensure it can be executed within Flask's synchronous WSGI context while still allowing async/await syntax in your view code.

### Where does Flask store the mapping between URLs and view functions?

Flask maintains this mapping in two places. The URL patterns themselves are stored in `self.url_map`, which is a Werkzeug `Map` object containing `Rule` instances. The actual callable view functions are stored in the `view_functions` dictionary on the Flask application instance, where keys are endpoint names (strings) and values are the function objects. This separation allows Flask to match URLs to endpoints first, then look up the actual code to execute.

### Can I invoke a view function manually without going through the HTTP request cycle?

While technically possible by calling `app.view_functions['endpoint_name'](**kwargs)`, this is not recommended for production code as it bypasses Flask's request context setup, error handling, and response processing. For testing purposes, use Flask's test client (`app.test_client().get('/path')`) which properly simulates the full request cycle including `dispatch_request`, or use `app.test_request_context()` to manually push a context before calling the view.