withastro/astro · error · ActionError

UNSUPPORTED_MEDIA_TYPE

UNSUPPORTED_MEDIA_TYPE

Error message

This action only accepts FormData.

What it means

The action was defined with accept: 'form', so its server handler (getFormServerHandler) requires the input to be a FormData instance. Receiving any other value (plain object, JSON) throws ActionError code UNSUPPORTED_MEDIA_TYPE (HTTP 415).

Source

Thrown at packages/astro/src/actions/runtime/server.ts:80

	Object.assign(safeServerHandler, {
		orThrow(this: ActionAPIContext, unparsedInput: unknown) {
			if (typeof this === 'function') {
				throw new AstroError(ActionCalledFromServerError);
			}
			return serverHandler(unparsedInput, this);
		},
	});

	return safeServerHandler as ActionClient<TOutput, TAccept, TInputSchema> & string;
}

function getFormServerHandler<TOutput, TInputSchema extends z.$ZodType>(
	handler: ActionHandler<TInputSchema, TOutput>,
	inputSchema?: TInputSchema,
) {
	return async (unparsedInput: unknown, context: ActionAPIContext): Promise<Awaited<TOutput>> => {
		if (!(unparsedInput instanceof FormData)) {
			throw new ActionError({
				code: 'UNSUPPORTED_MEDIA_TYPE',
				message: 'This action only accepts FormData.',
			});
		}

		if (!inputSchema) return await handler(unparsedInput, context);

		const parsed = await parseFormInput(inputSchema, unparsedInput);

		if (!parsed.success) {
			throw new ActionInputError(parsed.error.issues);
		}
		return await handler(parsed.data, context);
	};
}

async function parseFormInput(inputSchema: z.$ZodType, unparsedInput: FormData) {
	const baseSchema = unwrapBaseZ4ObjectSchema(inputSchema, unparsedInput);

View on GitHub (pinned to d081033d5f)

Solutions

  1. Call the action with a real FormData object (e.g. new FormData(formElement)).
  2. If you want JSON/RPC calls, remove accept: 'form' or add a separate JSON action.
  3. Use an HTML <form> posting to the action for the form-accept path.

Example fix

// before
export const subscribe = defineAction({
  accept: 'form',
  input: z.object({ email: z.string().email() }),
  handler: async (i) => i,
});
// caller
await actions.subscribe({ email: 'a@b.c' }); // throws 415
// after
const fd = new FormData();
fd.set('email', 'a@b.c');
await actions.subscribe(fd);
Defensive patterns

Strategy: type-guard

Validate before calling

// Ensure the value is FormData before calling a form-accept action.
if (!(payload instanceof FormData)) {
  throw new Error('This action requires a FormData instance; use accept:"form".');
}

Type guard

function isFormData(v: unknown): v is FormData {
  return typeof FormData !== 'undefined' && v instanceof FormData;
}

Try / catch

try {
  await actions.subscribe(payload);
} catch (e) {
  if (e instanceof ActionError && e.code === 'UNSUPPORTED_MEDIA_TYPE') {
    // build a FormData and retry, or guide the user to use a form
  } else throw e;
}

Prevention

When it happens

Trigger: A form-accept action invoked via RPC/JSON (e.g. actions.myForm({ email }) with a plain object from the client), or called programmatically on the server with a non-FormData value.

Common situations: Declaring an action as form-only but calling it like a normal JSON action from client code; unit-testing the action with a plain object instead of FormData.

Related errors


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