Skip to main content
The Redirect response type creates redirect responses using the Location header with various redirect status codes (303, 307, 308).

Basic usage

Create a redirect using one of three constructors:
use axum::{response::Redirect, routing::get, Router};

let app = Router::new()
    .route("/old", get(|| async { Redirect::permanent("/new") }))
    .route("/new", get(|| async { "Hello!" }));

Methods

Redirect::to

Creates a 303 See Other redirect that instructs the client to change the method to GET:
use axum::response::Redirect;

async fn after_form_submit() -> Redirect {
    // After POST form submission, redirect to GET
    Redirect::to("/success")
}
This is useful after successful form submissions or file uploads when you don’t want the redirected page to observe the original request method and body.

Redirect::temporary

Creates a 307 Temporary Redirect that preserves the original HTTP method and body:
use axum::response::Redirect;

async fn maintenance_redirect() -> Redirect {
    // Preserves the request method (POST stays POST)
    Redirect::temporary("/maintenance")
}

Redirect::permanent

Creates a 308 Permanent Redirect for permanent URL changes:
use axum::response::Redirect;

async fn moved_permanently() -> Redirect {
    Redirect::permanent("/new-location")
}

How it works

Type definition

pub struct Redirect {
    status_code: StatusCode,
    location: String,
}
See implementation in axum/src/response/redirect.rs:22-25.

IntoResponse implementation

The Redirect type sets the Location header with the specified status code:
impl IntoResponse for Redirect {
    fn into_response(self) -> Response {
        match HeaderValue::try_from(self.location) {
            Ok(location) => (self.status_code, [(LOCATION, location)]).into_response(),
            Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(),
        }
    }
}
See implementation in axum/src/response/redirect.rs:87-94.
If the location URI contains invalid characters (like newlines), the response will return 500 Internal Server Error.

Status codes explained

303 See Other (Redirect::to)

  • Behavior: Forces the client to use GET for the subsequent request
  • Use case: After POST form submissions, file uploads, or any operation where you want to prevent resubmission
  • Method change: YES - any method becomes GET

307 Temporary Redirect (Redirect::temporary)

  • Behavior: Preserves the original HTTP method and body
  • Use case: Temporary maintenance pages, A/B testing, or temporary URL changes
  • Method change: NO - POST stays POST, GET stays GET

308 Permanent Redirect (Redirect::permanent)

  • Behavior: Indicates permanent URL relocation while preserving method and body
  • Use case: Permanent URL structure changes, domain migrations
  • Method change: NO - method is preserved

Response fields

Location
header
The URI to redirect to. Set from the parameter passed to the constructor.
status
StatusCode
The HTTP status code: 303 (See Other), 307 (Temporary Redirect), or 308 (Permanent Redirect)

Common patterns

Post-login redirect

use axum::{
    extract::Form,
    http::{header::SET_COOKIE, HeaderMap},
    response::Redirect,
};

async fn login(Form(credentials): Form<Credentials>) -> (HeaderMap, Redirect) {
    let session_cookie = create_session(&credentials).await;
    
    let mut headers = HeaderMap::new();
    headers.insert(
        SET_COOKIE,
        format!("session={session_cookie}; Path=/; HttpOnly").parse().unwrap(),
    );
    
    (headers, Redirect::to("/"))
}
Example from examples/oauth/src/main.rs:153-184.

OAuth callback redirect

use axum::response::Redirect;

async fn oauth_callback(/* ... */) -> Redirect {
    // After successful OAuth, redirect back to home
    Redirect::to("/")
}
Example from examples/oauth/src/main.rs:317.

Conditional redirects

use axum::response::Redirect;

async fn logout(session: Option<Session>) -> Redirect {
    match session {
        Some(s) => {
            destroy_session(s).await;
            Redirect::to("/")
        }
        None => Redirect::to("/"),
    }
}

HTTPS redirect

use axum::{
    extract::OriginalUri,
    http::StatusCode,
    response::Redirect,
};

async fn redirect_to_https(OriginalUri(uri): OriginalUri) -> Result<Redirect, StatusCode> {
    let https_uri = format!("https://example.com{}", uri.path());
    Ok(Redirect::permanent(https_uri))
}
Example pattern from examples/tls-rustls/src/main.rs.

Using in custom error responses

use axum::{
    response::{IntoResponse, Redirect, Response},
};

struct AuthRedirect;

impl IntoResponse for AuthRedirect {
    fn into_response(self) -> Response {
        Redirect::temporary("/auth/discord").into_response()
    }
}
Example from examples/oauth/src/main.rs:320-326.

Helper methods

status_code()

Returns the HTTP status code of the redirect:
let redirect = Redirect::permanent("/new");
assert_eq!(redirect.status_code(), StatusCode::PERMANENT_REDIRECT);

location()

Returns the redirect URI as a string slice:
let redirect = Redirect::to("/dashboard");
assert_eq!(redirect.location(), "/dashboard");

See also