Documentation

Sessions

Updated
Browse documentation

Published 2026-08-13. V2 is a prerelease; the web core is release-gated and other previews are named separately. What beta means.

Read this page as HTML, Markdown, or structured JSON—or open the published Markdown with an AI assistant. Gemini receives the prompt through your clipboard because its signed-out page does not reliably prefill URL text; paste when the new tab opens. External assistants need the public site URL.

Sessions

Amber exposes session and flash directly inside a controller. Amber CLI's V2 web template enables the session and flash pipes in this order.

File: config/routes.cr — keep this order inside the generated pipeline :web block.

Crystal
pipeline :web do
  plug Amber::Pipe::Error.new
  plug Amber::Pipe::Logger.new
  plug Amber::Pipe::Session.new
  plug Amber::Pipe::Flash.new
  plug Amber::Pipe::CSRF.new
end

Keep Session before Flash: flash messages are serialized through the session after the request.

Generated configuration

The web template writes the following section to each environment YAML file.

Files: config/environments/development.yml, config/environments/test.yml, and config/environments/production.yml — edit the existing session: section in each environment rather than adding a duplicate key.

YAML
session:
  key: my_app.session
  store: signed_cookie
  adapter: memory
  expires: 0

The V2 session store uses the configured adapter for session values and an encrypted cookie for the session identifier. The built-in memory adapter is useful for local development and tests, but its data is process-local. Choose a shared custom adapter before running multiple application processes or before depending on sessions that must survive a restart. See Session Adapters.

Production also requires a long AMBER_SERVER_SECRET_KEY_BASE; Amber uses it to protect cookies. Do not commit a production secret to the YAML file.

Read, write, and delete values

Session keys accept strings or symbols. Values are stored as strings.

File: src/controllers/logins_controller.cr — place these actions inside LoginsController, then register their routes in config/routes.cr.

Crystal
class LoginsController < ApplicationController
  def create
    # Replace this lookup with your application's authentication logic.
    user_id = "42"
    session[:current_user_id] = user_id

    # Regenerate an adapter-backed session ID after authentication to prevent
    # session fixation. This is a no-op for a cookie-only store.
    context.regenerate_session!

    flash.notice = "Welcome back."
    redirect_to location: "/", status: 302
  end

  def destroy
    session.delete(:current_user_id)
    flash[:notice] = "You have signed out."
    redirect_to location: "/", status: 302
  end
end

File: the controller action that needs the authenticated identity — use the optional lookup where absence is expected.

Crystal
if user_id = session[:current_user_id]?
  # Load the user through the persistence layer selected by the application.
end

Keep session payloads small and non-sensitive. Store a stable identifier, not an entire model or authorization policy, and verify authorization again on every protected request.

Flash messages

Flash values are intended for the next request. Reading a value marks it for removal; keep carries it forward, while now makes a value available only in the current request.

File: the controller action that sets the message.

Crystal
flash[:error] = "Please correct the highlighted fields."
flash.keep(:error)
flash.now(:notice, "The preview was not saved.")

File: src/views/layouts/_flash.ecr — create this reusable partial, then render it from src/views/layouts/application.ecr.

ECR template
<% flash.each do |name, message| %>
  <div class="flash flash-<%= name %>"><%= message %></div>
<% end %>

File: src/views/layouts/application.ecr — add this call where global messages should appear.

ECR template
<%= render(partial: "layouts/_flash.ecr") %>

The V1 guide's inline Redis configuration is not a V2 configuration contract. Implement and register a session adapter instead, then select it with the session.adapter setting.