# URL builder

> Name routes and build their URLs from the name, with parameters, wildcards and query strings escaped for you.

This guide covers building URLs for routes in Tinka. You will learn how to:

- Name routes
- Build a route's URL with its parameters
- Add a query string
- Know how values are escaped
- Catch links to routes that don't exist
- Sign URLs so they can't be changed, and make them expire

## Overview

Links and redirects that repeat a route's path break when the path changes, and a path assembled by hand goes wrong in ways tests rarely catch: a title with a `/` or `?` in it ends up in the wrong place. **Named routes** solve both. You name a route once, where you register it, and build its URL from the name everywhere else, with every value escaped for its place.

## Name a route

`As` names the route it's called on. Names are unique, and dots group related routes by convention:

```go title="main.go"
	app.Get("/posts", listPosts).As("posts.index")
	app.Get("/posts/{id}", showPost).As("posts.show")
	app.Route("/teams/{team}", func(r *tinka.Router) {
		r.Get("/members/{id}", showMember).As("teams.members.show")
	})
	app.Get("/docs/{path...}", showDoc).As("docs.show")
```

A route inside `Route` keeps its full path, so its URL includes the prefix.

## Build a URL

`URL` takes a route's name and one value for each of its parameters, in order, and returns the path. Strings, integers and any `fmt.Stringer` work as values:

```go
	app.URL("posts.index")                     // /posts
	app.URL("posts.show", 7)                   // /posts/7
	app.URL("teams.members.show", "go", 42)    // /teams/go/members/42
```

A handler that redirects after creating a post builds the new post's URL the same way:

```go title="posts.go"
func (h Posts) Create(c *tinka.Ctx) (tinka.Response, error) {
	post, err := h.Service.Create(c, "Second post")
	if err != nil {
		return nil, err
	}
	return tinka.Redirect(h.App.URL("posts.show", post.ID)), nil
}
```

`URL` is a method of every `*tinka.Router`, so code that registers routes inside a group can build URLs too.

## Add a query string

Pass a `url.Values` after the parameters, and it becomes the query string:

```go
	app.URL("posts.index", url.Values{"page": {"2"}, "tag": {"go"}}) // /posts?page=2&tag=go
```

## How values are escaped

Each value is escaped for its place, so it always reaches the route as the same value. A `{name}` parameter is one path segment, so a `/` in the value is escaped too. A `{name...}` wildcard keeps the slashes of its value and escapes each segment:

```go
	app.URL("posts.show", "a/b?c")              // /posts/a%2Fb%3Fc
	app.URL("docs.show", "guides/basics/routing") // /docs/guides/basics/routing
	app.URL("docs.show", "release notes/2026")    // /docs/release%20notes/2026
```

## Catch broken links

`URL` panics when no route has the name, or when the number of values doesn't match the route's parameters. A link to a page that doesn't exist is a bug, and the panic shows where it is the first time the code runs, in tests or in development.

## Signed URLs

A signed URL proves your App made it: changing any part of its path or query breaks its signature. Use one for links that act without a sign-in, such as unsubscribing from a newsletter or downloading an invoice from an email. `SignedURL` builds the path of a named route like `URL`, adds a `signature` parameter, and makes the link expire after the duration you pass, or never with `0`. It needs the App's [Encrypter](../security/encryption.md):

```go title="newsletter.go"
	link := appURL + app.SignedURL("newsletter.unsubscribe", 30*24*time.Hour, user.ID)
	// https://example.com/newsletter/unsubscribe/7?signature=eyJ2Ijo…
```

`c.HasValidSignature` checks the request's URL in the handler:

```go title="newsletter.go"
func (h Newsletter) Unsubscribe(c *tinka.Ctx) (tinka.HTML, error) {
	if !c.HasValidSignature() {
		return "", tinka.Forbidden("This link is not valid, or has expired.")
	}
	id, err := c.ParamInt("id")
	if err != nil {
		return "", err
	}
	return "You're unsubscribed.", h.Lists.Remove(c, id)
}
```

The signature covers the path and the query, not the host, so prefix your App's address to the link to send it in an email. A signed URL is readable, so keep secrets out of it.

See also: [Routing](routing.md), [Response](response.md), [Encryption](../security/encryption.md)
