withastro/astro · error · AstroError

LocalsNotAnObject

LocalsNotAnObject

Error message

`locals` can only be assigned to an object. Other values like numbers, strings, etc. are not accepted.

What it means

Thrown by the locals getter on the APIContext from createContext when the locals variable is not an object type. Astro.locals must be an object so properties can be attached; if it was somehow set to a primitive (string, number, null), the getter refuses to return it.

Source

Thrown at packages/astro/src/core/middleware/index.ts:114

		get preferredLocaleList(): string[] | undefined {
			return (preferredLocaleList ??= computePreferredLocaleList(request, userDefinedLocales));
		},
		get currentLocale(): string | undefined {
			return (currentLocale ??= computeCurrentLocale(route, userDefinedLocales, defaultLocale));
		},
		url,
		get originPathname() {
			return getOriginPathname(request);
		},
		get clientAddress() {
			if (clientAddress) {
				return clientAddress;
			}
			throw new AstroError(AstroErrorData.StaticClientAddressNotAvailable);
		},
		get locals() {
			if (typeof locals !== 'object') {
				throw new AstroError(AstroErrorData.LocalsNotAnObject);
			}
			return locals;
		},
		set locals(_) {
			throw new AstroError(AstroErrorData.LocalsReassigned);
		},
		session: undefined,
		cache: new DisabledAstroCache(),
		csp: undefined,
		logger: {
			info() {},
			warn() {},
			error() {},
		},
	};
	return Object.assign(context, {
		getActionResult: createGetActionResult(context.locals),
		callAction: createCallAction(context),

View on GitHub (pinned to d081033d5f)

Solutions

  1. Ensure locals is initialized to an object ({} ) by the adapter/integration that creates the context.
  2. In tests, set locals to a plain object, not null or a primitive.
  3. If you control context creation, always pass locals: {} as the default.

Example fix

// before
const ctx = createContext({ request, locals: null });

// after
const ctx = createContext({ request, locals: {} });
Defensive patterns

Strategy: validation

Validate before calling

// Ensure locals is an object before reading it.
if (typeof locals !== 'object' || locals === null) {
  locals = {}; // initialize
}
const ctx = createContext({ request, locals });

Type guard

function isLocalsObject(locals: unknown): locals is Record<string, unknown> {
  return typeof locals === 'object' && locals !== null;
}

Try / catch

try {
  ctx.locals;
} catch (e) {
  if (e instanceof Error && /assigned to an object/i.test(e.message)) {
    locals = {}; // initialize and retry
  } else throw e;
}

Prevention

When it happens

Trigger: Reading context.locals when the underlying locals value is not typeof 'object' (e.g. it was replaced with a primitive somewhere, or initialization left it as null/undefined). The check `typeof locals !== 'object'` in the getter throws LocalsNotAnObject.

Common situations: An adapter or integration that initializes locals to a non-object; tests that stub context with locals as null; edge cases where locals was never initialized to an empty object.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/9891a41dde6ab0b1. Report an issue: GitHub.