Defining Routes
A route maps a URL path to one controller method, written as a #[Route] attribute or as a YAML entry.
#[Route(path: '/about', name: 'app_about', methods: ['GET'])]A route is a rule that maps an incoming URL path to one method of one class. That class is a controller: a plain PHP class whose methods answer requests and return a response. The router compares the path of each request against every route and runs the first one that fits. Without routes, nothing decides which of your methods should run.
Declaring a route
You write the rule as a PHP attribute on the method. The attribute class is Symfony\Component\Routing\Attribute\Route and it is imported like any other class. The first argument is the path. The name argument gives the route an identifier used later for building URLs. Name every route.
Shorthand and longhand
#[Route('/about', name: 'app_about')] and #[Route(path: '/about', name: 'app_about')] are the same declaration. The first argument is path passed positionally; everything else is a named argument.
Attributes are not the only format. The same route can live in config/routes.yaml as an entry with path and controller keys. Both formats produce identical routes with identical performance. Attributes keep the rule beside the code it runs; YAML is where imported and generated routes appear, so you will read both.
What trips people up
A path is matched in full, not as a prefix: /about does not match /about/team. Paths are case-sensitive. And attributes are only read from what the application imports: the default import is resource: routing.controllers, which collects #[Route] attributes from every class registered as a service, so a controller outside src/Controller/ still gets its routes. A class that is not a registered service, or one excluded from the import, has no routes at all.
Example
<?php
// src/Controller/PageController.php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class PageController extends AbstractController
{
// Shorthand: the path is the first positional argument.
#[Route('/', name: 'app_home')]
public function home(): Response
{
return new Response('<h1>Home</h1>');
}
// Longhand: the same route with every argument named.
// These two forms are identical to the router.
#[Route(path: '/about', name: 'app_about', methods: ['GET'])]
public function about(): Response
{
return new Response('<h1>About this site</h1>');
}
}
When to use it
- Add a public /about page to an application that currently only serves the homepage.
- Expose a /health endpoint that a load balancer or Kubernetes probe can call.
- Read an unfamiliar project and find which class answers a given URL.
More examples
The same two routes in YAML
The YAML key is the route name, and `controller` is the fully qualified class name plus `::` plus the method. This produces exactly the same two routes as the attributes, so you can move a route between formats without changing behaviour. Reach for YAML when the controller is in a package you do not edit, or when a route is generated by tooling.
# config/routes.yaml
app_home:
path: /
controller: App\Controller\PageController::home
app_about:
path: /about
controller: App\Controller\PageController::about
methods: GET
An invokable controller
A controller does not have to extend `AbstractController`; that base class only adds convenience methods. Any class with a public method returning a `Response` works. `GET /health` returns 200 with the body `{"status":"ok"}`. One route per class keeps a growing application readable.
<?php
// src/Controller/HealthController.php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
// A class with one route often has one method named __invoke.
// The attribute then sits on __invoke and the controller
// reference in YAML would be just the class name.
final class HealthController
{
#[Route('/health', name: 'app_health', methods: ['GET'])]
public function __invoke(): JsonResponse
{
return new JsonResponse(['status' => 'ok']);
}
}
Listing what the router actually has
After writing a route, confirm the router found it. If a route you just wrote is missing from this table, the problem is the declaration or the file location, not the controller body. This is the fastest answer to "why is my new page a 404".
php bin/console debug:router
# -------------- -------- -------- ------ --------
# Name Method Scheme Host Path
# -------------- -------- -------- ------ --------
# app_home ANY ANY ANY /
# app_about GET ANY ANY /about
# app_health GET ANY ANY /health
# -------------- -------- -------- ------ --------
Discussion