Fix: Value Assigned to Locals Is Not Accepted in Astro

Error message:
Value assigned to `locals` is not accepted.
Middleware & Endpoints 2025-01-25

What Causes This Error?

This error occurs when you try to assign a non-object value to context.locals or Astro.locals. Locals must be an object that you can add properties to.

The Problem

// src/middleware.ts
export const onRequest = defineMiddleware((context, next) => {
  // ❌ Assigning string
  context.locals = 'user data';

  // ❌ Assigning array
  context.locals = ['item1', 'item2'];

  // ❌ Assigning null
  context.locals = null;

  return next();
});

The Fix

Assign Properties to Locals Object

// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';

export const onRequest = defineMiddleware((context, next) => {
  // ✅ Assign properties to the existing object
  context.locals.user = 'John';
  context.locals.isAuthenticated = true;
  context.locals.data = ['item1', 'item2'];

  return next();
});

Common Scenarios

Setting User Data

// ❌ Wrong - replacing locals
context.locals = { user: userData };

// ✅ Correct - adding to locals
context.locals.user = userData;

Multiple Properties

export const onRequest = defineMiddleware(async (context, next) => {
  const session = await getSession(context.cookies);

  // ✅ Add multiple properties
  context.locals.user = session?.user ?? null;
  context.locals.isAuthenticated = !!session;
  context.locals.permissions = session?.permissions ?? [];

  return next();
});

In Pages

---
// ✅ Access locals in pages
const { user, isAuthenticated } = Astro.locals;
---

{isAuthenticated ? (
  <p>Welcome, {user.name}</p>
) : (
  <p>Please log in</p>
)}

TypeScript Typing

// src/env.d.ts
declare namespace App {
  interface Locals {
    user: {
      id: string;
      name: string;
    } | null;
    isAuthenticated: boolean;
  }
}
// src/middleware.ts
export const onRequest = defineMiddleware((context, next) => {
  // Now TypeScript knows the shape
  context.locals.user = { id: '1', name: 'John' };
  context.locals.isAuthenticated = true;
  return next();
});

Conditional Assignment

export const onRequest = defineMiddleware(async (context, next) => {
  // ✅ Conditionally add properties
  if (context.url.pathname.startsWith('/admin')) {
    context.locals.adminMode = true;
  }

  const token = context.cookies.get('token');
  if (token) {
    context.locals.token = token.value;
  }

  return next();
});

Object Spread Won’t Work

// ❌ Can't spread into locals
const userData = { user: 'John', role: 'admin' };
context.locals = { ...context.locals, ...userData };

// ✅ Use Object.assign or individual properties
Object.assign(context.locals, userData);
// or
context.locals.user = userData.user;
context.locals.role = userData.role;

In API Endpoints

// src/pages/api/user.ts
import type { APIRoute } from 'astro';

export const GET: APIRoute = ({ locals }) => {
  // Access locals set by middleware
  const { user, isAuthenticated } = locals;

  if (!isAuthenticated) {
    return new Response('Unauthorized', { status: 401 });
  }

  return Response.json(user);
};

Default Values

export const onRequest = defineMiddleware((context, next) => {
  // ✅ Set defaults
  context.locals.theme = context.locals.theme ?? 'light';
  context.locals.language = context.locals.language ?? 'en';

  return next();
});

Complex Objects

export const onRequest = defineMiddleware((context, next) => {
  // ✅ Complex nested objects are fine
  context.locals.config = {
    api: {
      baseUrl: 'https://api.example.com',
      timeout: 5000,
    },
    features: {
      darkMode: true,
      notifications: false,
    },
  };

  return next();
});

Quick Checklist

  • Don’t replace locals - add properties to it
  • Use context.locals.property = value
  • Type locals in src/env.d.ts
  • Access in pages via Astro.locals
  • Use Object.assign() for bulk properties