Data Validators
Data validators in Qwik Router are essential for validating request events and data for actions and loaders. These validations occur on the server-side before the execution of the associated action or loader. Similar to the zod$() function, Qwik provides a dedicated validator$() function for this purpose.
import {
type RequestEvent,
type RequestEventAction,
routeAction$,
validator$,
} from "@qwik.dev/router";
export const useAction = routeAction$(
async (data, requestEvent: RequestEventAction) => {
return { foo: "bar" };
},
validator$(async (ev: RequestEvent, data) => {
if (ev.query.get("secret") === "123") {
return { success: true };
}
return {
success: false,
error: {
message: "secret is not correct",
},
};
}),
);
When submitting a request to a routeAction(), the request event and data undergo validation against the defined validator. If the validation fails, the action will place the validation error in the routeAction.value property.
export default component$(() => {
const action = useAction();
// action value is undefined before submitting
if (action.value) {
if (action.value.failed) {
// action failed if query string has no secret
action.value satisfies { failed: true; message: string };
} else {
action.value satisfies { searchResult: string };
}
}
return (
<button onClick$={() => action.submit({ search: "foo" })}>Submit</button>
);
});
Multiple validators
Actions and loaders can have multiple validators, which are executed in reverse order. In the following example, validators execute in the order validator3 -> validator2 -> validator1.
const validator1 = validator$(/*...*/)
const validator2 = validator$(/*...*/)
const validator3 = validator$(/*...*/)
export const useAction = routeAction$(
async (data, requestEvent: RequestEventAction) => {
return { foo: "bar" };
},
validator1,
validator2,
validator3, // will be executed first
);
If validator3 has a data property in its success return object, this data will be passed to the next validator, validator2. If you don't want to override the original submitted data, avoid putting the data property in the success return object.
export const useAction = routeAction$(
async (data, requestEvent: RequestEventAction) => {
console.log(data); // { message: "hi, I am validator1" }
return { foo: "bar" };
},
// validator1
validator$((ev, data) => {
console.log(data); // { message: "hi, I am validator2" }
return {
success: true,
data: {
message: "hi, I am validator1",
},
};
}),
// validator2
validator$((ev, data) => {
console.log(data); // { message: "hi, I am validator3" }
return {
success: true,
data: {
message: "hi, I am validator2",
},
};
}),
// validator3
validator$((ev, data) => {
console.log(data); // Your submitted data
return {
success: true,
data: {
message: "hi, I am validator3",
},
};
}),
);
Return object
Data validators expect specific properties in their return objects.
Successful validation
The success property must be true for a successful validation.
interface Success {
success: true;
data?: any;
}
Failed validation
interface Fail {
success: false;
error: Record<string, any>;
status?: number;
}
Validator behaves like using the fail() method in the action or loader when it returns a failed object.
const status = 500;
const errorObject = { message: "123" };
export const useAction = routeAction$(
async (_, { fail }) => {
return fail(status, errorObject);
},
validator$(async () => {
return {
success: false,
status,
errorObject,
};
}),
);
Standard Schema validators
Use schema$() to validate actions with any schema library that implements Standard Schema, such as Zod, Valibot, or ArkType. The validator infers the submitted input type, passes the validated output type to the action callback, and returns failed field errors in the same shape as other Qwik validators.
import { component$ } from '@qwik.dev/core';
import { Form, routeAction$, schema$ } from '@qwik.dev/router';
import { z } from 'zod';
const userSchema = z.object({
name: z.string().min(1),
contacts: z.array(
z.object({
email: z.string().email(),
})
),
});
export const useUserAction = routeAction$(
async (user) => {
user satisfies z.output<typeof userSchema>;
return { saved: true };
},
schema$(userSchema)
);
export default component$(() => {
const action = useUserAction();
return (
<Form action={action}>
<input name="name" />
<input name="contacts.0.email" />
{action.value?.failed && action.value.fieldErrors['contacts[].email']?.[0]}
<button type="submit">Save</button>
</Form>
);
});
Array field errors use Qwik validator paths, for example contacts[].email. Root-level issues are returned in formErrors, and the raw Standard Schema issues are available from issues when you need the original message and path data.
if (action.value?.failed) {
const firstIssue = action.value.issues[0];
firstIssue?.path;
}
When a Standard Schema-compatible library needs request-scoped options such as the current locale, create the schema from the RequestEvent passed to schema$(). Unlike zod$(), schema$() does not apply Zod localization automatically because Standard Schema does not define a portable locale option.
import { routeAction$, schema$ } from '@qwik.dev/router';
import { z } from 'zod';
const createUserSchema = (locale: string) =>
z.object({
name: z.string().min(1, {
message: locale === 'es' ? 'El nombre es obligatorio' : 'Name is required',
}),
});
export const useLocalizedUserAction = routeAction$(
async (user) => {
return { saved: true };
},
schema$((ev) => createUserSchema(ev.locale()))
);
Use validator$() with zod$() together in actions
For actions, the typed data validator zod$() should be the second argument of routeAction$, followed by other data validators validator$()s.
export const useAction = routeAction$(
async (data, requestEvent: RequestEventAction) => {
return { foo: "bar" };
},
zod$(/*...*/),
validator$(/*...*/),
validator$(/*...*/),
);