Svelte logo

SvelteKit by Example: Hooks & Locals

Server hooks prepare data before SvelteKit handles a request. locals holds that data for this request's server load functions and actions.

In this example, we'll find the signed-in user before loading their todos.

We'll use a getUserFromSession(token) helper from our own src/lib/server/auth.js module. It checks the token against a saved session, including expiry and revocation, and returns { id, name } or null. A missing or invalid token returns null. We'll look at creating sessions in the next example.

These examples need a server adapter and non-prerendered routes. Never keep the current user in a module-level variable or shared store: server modules are shared by requests.

handle receives the request's event and resolve, which continues to the route and produces its response.

A cookie is untrusted input. Verify the session before assigning locals.user; don't treat a cookie's user ID as proof of identity.

Return resolve(event) after preparing the request data. locals is not automatically sent to the browser.

src/hooks.server.js
import { getUserFromSession } from '#lib/server/auth.js';

/** @type {import('@sveltejs/kit/hooks').Handle} */
export async function handle({ event, resolve }) {
  const token = event.cookies.get('session');




  event.locals.user = await getUserFromSession(token);






  return resolve(event);
}

Add the user shape to App.Locals in your existing declaration file. null represents a signed-out request.

src/app.d.ts
declare global {
  namespace App {
    interface Locals {
      user: { id: string; name: string } | null;
    }
  }
}

export {};

Server load functions read locals. Redirect signed-out visitors before looking up any todos.

Your getTodosForUser(id) database helper must query only that user's todos. Return only browser-safe fields.

src/routes/todos/+page.server.js
import { redirect } from '@sveltejs/kit';
import { getTodosForUser } from '#lib/server/db.js';

/** @type {import('./$types').PageServerLoad} */
export async function load({ locals }) {
  if (!locals.user) redirect(303, '/login');




  return {
    todos: await getTodosForUser(locals.user.id),
  };
}

Protecting requests

A layout redirect alone isn't enough: layout loads don't run for every child navigation, and actions can be posted to directly. Check authentication and ownership wherever protected data is read or changed. The cookies and sessions example adds a protected write action.