Validation
When the form is submitted, the form data is validated to conform to the given JSON schema;
As of version 5, as a result of decoupling the validation implementation from the Form, a validator implementation is required to be passed to all Forms.
React Json Schema Form provides a default @rjsf/validator-ajv8 implementation that uses version 8 of the ajv validator.
The error messages generated by this new validator differ from those provided by the original validator due to it using a newer version.
If you depend on having specifically formatted messages, then using this validator would constitute a breaking change for you.
It is also possible for you to provide your own implementation if you desire, as long as it fulfills the ValidatorType interface specified in @rjsf/utils.
Its isValid() must not change the data it is handed, at any depth: the form checks the same data against many conditions and options, and reuses what it found for data it already checked without looking at the data again.
If your validation engine fills in defaults, coerces values or removes properties as it validates, hand it a copy of the data in isValid(), as @rjsf/validator-ajv8 does under Ajv's useDefaults, coerceTypes and removeAdditional options and with a keyword registered with modifying: true.
API documentation
The documentation for the APIs associated with the AJV 8 validator package can be found here
Precompiled validators
In 5.7.0, support for precompiled validators was added to the @rjsf/validator-ajv8 package.
The main use case for this is to overcome issues with unsafe-eval warnings from the browser caused by strict Content Security Policy settings.
See the Standalone Validation Code section of the AJV documentation for more details about precompiled validators.
Due to how RJSF uses the AJV validator in determining anyOf/oneOf selections and how it resolves dependencies, if-then-else and references ($ref) in schemas via the retrieveSchema() utility method, RJSF provides its own schema compilation API built on-top-of the one provided by AJV 8.
If you are wanting to use a precompiled validator, two steps are required:
- Precompiling the schema into a set of validator functions
- Providing those precompiled validator functions to a
ValidatorTypeimplementation in theForm
Schema precompilation
The first step in the process is to compile a schema into a set of validator functions that are saved into a commonJS-based Javascript file.
The @rjsf/validator-ajv8 package exports the compileSchemaValidators() function that does this.
It is expected that this function will be used in a manner similar to the following:
import compileSchemaValidators from '@rjsf/validator-ajv8/compileSchemaValidators';
import yourSchema from 'path_to/yourSchema.js'; // If your schema is a js file
compileSchemaValidators(yourSchema, 'path_to/yourCompiledSchema.cjs');
The examples name the output .cjs because it is CommonJS, which a .js file cannot hold in an app whose package.json sets "type": "module".
In an app whose package.json doesn't set it, a .js name works too, and it is the one to use with a toolchain that loads only .js and .mjs files as code.
Create React App 5 is one: it turns an imported .cjs file into the URL of a static asset, so createPrecompiledValidator() throws No precompiled validator function was found for the given schema.
If you are currently using the customizeValidator() function to provide additionalMetaSchemas, customFormats,
ajvOptionsOverrides and/or ajvFormatOptions then you can pass those in as the optional third parameter to the
compileSchemaValidators() function in a manner similar to:
import compileSchemaValidators from '@rjsf/validator-ajv8/compileSchemaValidators';
import yourSchema from 'path_to/yourSchema.json' with { type: 'json' }; // If your schema is a json file
import draft06 from 'ajv/lib/refs/json-schema-draft-06.json' with { type: 'json' };
const options = {
additionalMetaSchemas: [draft06],
customFormats: { 'phone-us': /\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{4}$/, 'area-code': /\d{3}/ },
ajvOptionsOverrides: {
$data: true,
verbose: true,
},
ajvFormatOptions: {
mode: 'fast',
},
};
compileSchemaValidators(yourSchema, 'path_to/yourCompiledSchema.cjs', options);
The functions compiled are the ones getValidationSchemas() lists for the schema: the root schema itself, and, for each if, anyOf/oneOf option, discriminator property and dependency condition the form checks, a $ref to each place it sits in the root, exported as the function compiled for that part of the root.
An option whose required scoring has to drop is compiled as a copy without it.
The list depends on the content of the root schema alone, never on form data, on how allOf is merged or on which objects the schema shares.
A subschema held in several places, as a schema module that reuses one object holds it, is compiled at each place, so the code compiled from such a module serves an equal copy of the schema, such as its JSON, and the other way round.
Each function is looked up by the $id of the schema it validates, or by a hash of that schema when it has none.
An $id of the empty string names nothing, so a schema carrying one is looked up by its hash instead.
A schema that a merge makes has no place in the root schema and has no compiled function: an anyOf/oneOf option a customMergeAllOf rewrites, or one made by merging the oneOfs of two patternProperties that match the same key, throws No precompiled validator function was found for the given schema when the form scores it.
Regenerate the precompiled code whenever you upgrade RJSF, since what is compiled can change between releases.
Compiled with useDefaults, coerceTypes or removeAdditional, each function in the generated file is marked changesData, and the precompiled validator's isValid() hands such a function a copy of the data, as the validator customizeValidator() returns does (see ajvOptionsOverrides), except that data it can't copy makes isValid() throw.
A hand-written function that changes the data it validates can set changesData: true on itself to be handed a copy too.
Give the form's customMergeAllOf to createPrecompiledValidator(), since the validator resolves the root schema it is handed with it.
It is highly recommended to create a compileYourSchema.js file (or what ever name you want) with code similar to what is shown above and then, using node, run the code as follows:
node compileYourSchema.js
NOTE: You must have your schema provided within a file that can be parsed and turned into the set of precompiled validator functions.
Additional Note: If you are using Webpack or NextJS and are encountering an issue resolving
fsduring development, consult this blog post for how to solve the issue.
Using the precompiled validator
After you have completed step 1 having generated your precompiled schema functions into the yourCompiledSchema.cjs output file (or whatever you called it), then you need to create a ValidatorType implementation from it to use in the Form.
The @rjsf/validator-ajv8 package exports the createPrecompiledValidator() function for this.
Here is an example of how to use your precompiled validator with your Form:
import { createPrecompiledValidator } from '@rjsf/validator-ajv8';
import Form, { type FormProps } from '@rjsf/core'; // Or whatever theme you use
import yourSchema from 'path_to/yourSchema.json' with { type: 'json' }; // This needs to be the same file that was precompiled
import * as precompiledValidatorFns from 'path_to/yourCompiledSchema.cjs';
// Neither the compiled functions nor the schema change at runtime, so one validator, created when the module loads,
// serves every render
const validator = createPrecompiledValidator(precompiledValidatorFns, yourSchema);
function MyForm(props: Omit<FormProps, 'validator' | 'schema'>) {
return <Form {...props} schema={yourSchema} validator={validator} />;
}
render(<MyForm />, document.getElementById('app'));
Things to know about the generated file:
- It is not always self-contained: depending on the keywords the schema uses, it
require()s helpers fromajv, and fromajv-formatswhen the schema uses one of its formats. Those are resolved from the folder the file is kept in, so the app must listajv(andajv-formats) as its own dependencies. Depending on@rjsf/validator-ajv8alone is not enough when they are not hoisted, as under pnpm's default layout. - It is CommonJS. Webpack and Vite 8 production builds handle it, and so does Rollup with
@rollup/plugin-commonjs. Production builds before Vite 8 convert CommonJS only undernode_modulesunlessbuild.commonjsOptions.includealso matches the file. Vite's dev server does not convert CommonJS files inside your source folder. Either way the page throwsexports is not definedwhen it loads the file. - It has no type declarations, so under
strictTypeScript reportsTS7016: Could not find a declaration filefor the import. Adddeclare module '*.cjs';to a.d.tsfile, or enableallowJs.
Dynamically pre-compiling validators
For more advanced cases when schema needs to be precompiled on request - compileSchemaValidatorsCode can be used.
import { compileSchemaValidatorsCode } from '@rjsf/validator-ajv8/compileSchemaValidators';
const code = compileSchemaValidatorsCode(schema, options);
For the most part it is the same as compileSchemaValidators, but instead of writing the file - it returns generated code directly.
It takes the same options.
To use it on browser side - some modifications are needed to provide runtime dependencies in generated code needs to be provided.
Example implementation of it:
import type { ValidatorFunctions } from '@rjsf/validator-ajv8';
import ajvRuntimeEqual from 'ajv/dist/runtime/equal';
import {
parseJson as ajvRuntimeparseJson,
parseJsonNumber as ajvRuntimeparseJsonNumber,
parseJsonString as ajvRuntimeparseJsonString,
} from 'ajv/dist/runtime/parseJson';
import ajvRuntimeQuote from 'ajv/dist/runtime/quote';
// import ajvRuntimeRe2 from 'ajv/dist/runtime/re2';
import ajvRuntimeTimestamp from 'ajv/dist/runtime/timestamp';
import ajvRuntimeUcs2length from 'ajv/dist/runtime/ucs2length';
import ajvRuntimeUri from 'ajv/dist/runtime/uri';
import * as ajvFormats from 'ajv-formats/dist/formats';
// dependencies to replace in generated code, to be provided by at runtime
const validatorsBundleReplacements: Record<string, [string, unknown]> = {
// '<code to be replaced>': ['<variable name to use as replacement>', <runtime dependency>],
'require("ajv/dist/runtime/equal").default': ['ajvRuntimeEqual', ajvRuntimeEqual],
'require("ajv/dist/runtime/parseJson").parseJson': ['ajvRuntimeparseJson', ajvRuntimeparseJson],
'require("ajv/dist/runtime/parseJson").parseJsonNumber': ['ajvRuntimeparseJsonNumber', ajvRuntimeparseJsonNumber],
'require("ajv/dist/runtime/parseJson").parseJsonString': ['ajvRuntimeparseJsonString', ajvRuntimeparseJsonString],
'require("ajv/dist/runtime/quote").default': ['ajvRuntimeQuote', ajvRuntimeQuote],
// re2 by default is not in dependencies for ajv and so is likely not normally used
// 'require("ajv/dist/runtime/re2").default': ['ajvRuntimeRe2', ajvRuntimeRe2],
'require("ajv/dist/runtime/timestamp").default': ['ajvRuntimeTimestamp', ajvRuntimeTimestamp],
'require("ajv/dist/runtime/ucs2length").default': ['ajvRuntimeUcs2length', ajvRuntimeUcs2length],
'require("ajv/dist/runtime/uri").default': ['ajvRuntimeUri', ajvRuntimeUri],
// formats
'require("ajv-formats/dist/formats")': ['ajvFormats', ajvFormats],
};
const regexp = new RegExp(
Object.keys(validatorsBundleReplacements)
.map((key) => key.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'))
.join('|'),
'g',
);
function wrapAjvBundle(code: string) {
return `function(${Object.values(validatorsBundleReplacements)
.map(([name]) => name)
.join(', ')}){\nvar exports = {};\n${code.replace(
regexp,
(req) => validatorsBundleReplacements[req][0],
)};\nreturn exports;\n}`;
}
const windowValidatorOnLoad = '__rjsf_validatorOnLoad';
const schemas = new Map<
string,
{ promise: Promise<ValidatorFunctions>; resolve: (result: ValidatorFunctions) => void }
>();
if (typeof window !== 'undefined') {
// @ts-ignore
window[windowValidatorOnLoad] = (loadedId: string, fn: (...args: unknown[]) => ValidatorFunctions) => {
const validator = fn(...Object.values(validatorsBundleReplacements).map(([, dep]) => dep));
let validatorLoader = schemas.get(loadedId);
if (validatorLoader) {
validatorLoader.resolve(validator);
} else {
throw new Error(`Unknown validator loaded id="${loadedId}"`);
}
};
}
/**
* Evaluate precompiled validator in browser using script tag
* @param id Identifier to avoid evaluating the same code multiple times
* @param code Code generated server side using `compileSchemaValidatorsCode`
* @param nonce nonce attribute to be added to script tag (https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce#using_nonce_to_allowlist_a_script_element)
*/
export function evaluateValidator(id: string, code: string, nonce: string): Promise<ValidatorFunctions> {
let maybeValidator = schemas.get(id);
if (maybeValidator) return maybeValidator.promise;
let resolveValidator: (result: ValidatorFunctions) => void;
const validatorPromise = new Promise<ValidatorFunctions>((resolve) => {
resolveValidator = resolve;
});
schemas.set(id, {
promise: validatorPromise,
resolve: resolveValidator!,
});
const scriptElement = document.createElement('script');
scriptElement.setAttribute('nonce', nonce);
scriptElement.text = `window["${windowValidatorOnLoad}"]("${id}", ${wrapAjvBundle(code)})`;
document.body.appendChild(scriptElement);
return validatorPromise;
}
From React component this can be used as following:
let [precompiledValidator, setPrecompiledValidator] = React.useState<ValidatorFunctions>();
React.useEffect(() => {
evaluateValidator(
schemaId, // some schema id to avoid evaluating it multiple times
code, // result of compileSchemaValidatorsCode returned from the server
nonce, // nonce script tag attribute to allow this ib content security policy for the page
).then(setPrecompiledValidator);
}, [entityType.id]);
if (!precompiledValidator) {
// render loading screen
}
const validator = createPrecompiledValidator(precompiledValidator, schema);
Precompiled validators with @rjsf/validator-ata
@rjsf/validator-ata has the same two functions. Compile the schema in a Node script:
import compileSchemaValidators from '@rjsf/validator-ata/compileSchemaValidators';
import yourSchema from 'path_to/yourSchema.json' with { type: 'json' };
// The third argument takes the options customizeValidator() takes, with the limits listed below.
compileSchemaValidators(yourSchema, 'path_to/yourCompiledSchema.cjs', {
customFormats: { 'phone-us': /\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{4}$/ },
});
Then use it in the app:
import { createPrecompiledValidator, type ValidatorFunctions } from '@rjsf/validator-ata';
import yourSchema from 'path_to/yourSchema.json' with { type: 'json' }; // The same schema that was precompiled
import precompiledValidatorFns from 'path_to/yourCompiledSchema.cjs';
const validator = createPrecompiledValidator(precompiledValidatorFns as ValidatorFunctions, yourSchema);
Things to know:
customFormatsandataOptionsOverridesapply at compile time. Custom formats must be aRegExpor a pattern string: a function cannot be written into the generated file and is rejected.additionalMetaSchemasandataOptionsOverrides.schemasare compiled into the generated file under the$ida$refreaches each by, so a custom meta-schema named in$schemaapplies there too. For an entry inataOptionsOverrides.schemasread as draft-04 (by its own$schema, or a draft-04 form, with thehttp://spelling ata reads as draft-04) that is itsid, unless it has a$ref; a boolean record entry goes in as the object schema that means the same. They go in as the runtime validator ends up with them:additionalMetaSchemasafterataOptionsOverrides.schemas, a later entry replacing an earlier one with the same$id, and the form's own schemas winning over an entry with their$id. A record entry whose own$iddiffers from its key is compiled in twice, once under each, as ata registers it under both. An entry with only a fragment$idsuch as#fragis left out, since a$refto#fragnames a fragment of the referring document and never reaches it. A json-schema.org meta-schema you pass in is compiled in too, with a warning giving its size; ata already ships the draft-04, draft-06, draft-07, 2019-09 and 2020-12 ones, so an unchanged copy of one of those does not need to be passed. Entries without an$idare left out with a warning; ata needs one to register them, so the runtime validator rejects them too.extenderFnand theataOptionsOverrideskeyscoerceTypes,removeAdditionalandkeywordscannot be carried into the generated file. They are ignored with a warning, logged once like the form's own warnings, and the generated file and the runtime validator may then disagree, in either direction: a dropped keyword lets data through that the runtime validator rejects, and a droppedcoerceTypesrejects data it accepts.- The generated file never fills in
defaultvalues. The runtime validator does (useDefaultsis on by default in ata), so for a required field that is missing but has adefault, the runtime validator fills it in and accepts, while the generated file rejects. The form fills in defaults itself (getDefaultFormState), so this shows only for data that does not come from the form. - If your
Formis given acustomMergeAllOf, pass it to both the compile step andcreatePrecompiledValidator(), as with@rjsf/validator-ajv8. - Pass
suppressDuplicateFilteringtocreatePrecompiledValidator(). Passing it to the compile step has no effect and logs a warning. - The generated file imports nothing, so
ata-validatoris not in your bundle, as long as the app imports onlycreatePrecompiledValidator,ATAPrecompiledValidator(whichcreatePrecompiledValidator()returns) and types. Importing the default export,customizeValidatororATAValidatorbrings the runtime validator back. - The generated file is CommonJS with no type declarations, like the one from
@rjsf/validator-ajv8, so the bundler and TypeScript notes under Using the precompiled validator apply to it too, except that where it is not converted the page throwsmodule is not definedinstead. With TypeScript, also cast it toValidatorFunctionsas above. - Run the compile step again when the schema changes.
- To get the code as a string instead of a file, use
compileSchemaValidatorsCode(schema, options)from@rjsf/validator-ata/compileSchemaValidators. The code imports nothing, so unlike the ajv8 example above it needs no runtime dependencies replaced.
Live validation
By default, form data are only validated when the form is submitted, or through validateForm() on the form's ref.
You can enable live form data validation by passing a liveValidate prop to the Form component, and set it to 'onChange'. Then, every time a value changes within the form data tree (e.g. the user entering a character in a field), a validation operation is performed, and the validation results are reflected into the form state.
Be warned that this is an expensive strategy, with possibly strong impact on performances.
Live validation shows every error in the form, including those of fields the user hasn't reached yet. Pass liveErrors='field' to show a field's errors only once the user has changed it, or, with liveValidate='onBlur', left it, and every error once a submit was attempted or validateForm() ran.
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
const schema: RJSFSchema = {
type: ['string'],
const: 'test',
};
const formData = 'a';
render(
<Form schema={schema} initialFormData={formData} validator={validator} liveValidate='onChange' />,
document.getElementById('app'),
);
Validate form programmatically
It is possible to programmatically validate a form using the validateForm() function on Form.
Add a ref to your Form component and call the validateForm() method to validate the form programmatically.
The validateForm() method returns true if the form is valid, false otherwise.
If you have provided an onError callback it will be called with the list of errors when the validatorForm() method returns false.
import { createRef } from 'react';
import Form, { type FormRef } from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
const formRef = createRef();
const onError = (errors) => alert(errors);
const schema: RJSFSchema = {
type: 'string',
};
render(<Form schema={schema} validator={validator} onError={onError} ref={formRef} />, document.getElementById('app'));
if (formRef.current.validateForm()) {
alert('Form is valid');
}
HTML5 Validation
By default, the form uses HTML5 validation. This may cause unintuitive results because the HTML5 validation errors (such as when a field is required) may be displayed before the form is submitted, and thus these errors will display differently from the react-jsonschema-form validation errors. You can turn off HTML validation by setting the noHtml5Validate to true.
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
const schema: RJSFSchema = {
type: 'object',
properties: {
name: {
type: 'string',
required: true,
},
},
};
render(<Form schema={schema} validator={validator} noHtml5Validate />, document.getElementById('app'));
Custom validation rules
Form data is always validated against the JSON schema.
But it is possible to define your own custom validation rules that will run in addition to (and after) the validator implementation.
This is especially useful when the validation depends on several interdependent fields.
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
function customValidate(formData, errors, uiSchema, errorSchema) {
if (formData.pass1 !== formData.pass2) {
errors.pass2.addError("Passwords don't match");
}
return errors;
}
const schema: RJSFSchema = {
type: 'object',
properties: {
pass1: { type: 'string', minLength: 3 },
pass2: { type: 'string', minLength: 3 },
},
};
render(<Form schema={schema} validator={validator} customValidate={customValidate} />, document.getElementById('app'));
Notes:
- The
customValidate()function must implement theCustomValidatorinterface found in@rjsf/utils.- The
customValidate()function must always return theerrorsobject received as second argument.- The
customValidate()function is called after the JSON schema validation.- The
customValidate()function is passed theuiSchemaas the third argument. This allows thecustomValidate()function to be able to derive additional information from it for generating errors.- The
customValidate()function is passed theerrorSchemaas the fourth argument. This allows thecustomValidate()function to be able to access errors raised by JSON schema validation.
Custom error messages
Validation error messages are provided by the JSON Schema validation by default. If you need to change these messages or make any other modifications to the errors from the JSON Schema validation, you can define a transform function that receives the list of JSON Schema errors and returns a new list.
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
function transformErrors(errors, uiSchema) {
return errors.map((error) => {
if (error.name === 'pattern') {
error.message = 'Only digits are allowed';
}
return error;
});
}
const schema: RJSFSchema = {
type: 'object',
properties: {
onlyNumbersString: { type: 'string', pattern: '^\\d*$' },
},
};
render(
<Form schema={schema} validator={validator} transformErrors={transformErrors} />,
document.getElementById('app'),
);
Notes:
- The
transformErrors()function must implement theErrorTransformerinterface found in@rjsf/utils.- The
transformErrors()function must return the list of errors. Modifying the list in place without returning it will result in an error.- The
transformErrors()function is passed theuiSchemaas the second argument. This allows thetransformErrors()function to be able to derive additional information from it for transforming errors.
Each element in the errors list passed to transformErrors is a RJSFValidationError interface (in @rjsf/utils) and has the following properties:
name: optional name of the error, for example, "required" or "minLength"message: optional message, for example, "is a required property" or "should NOT be shorter than 3 characters"params: optional object with the error params returned by ajv (see doc for more info).property: optional string in Javascript property accessor notation to the data path of the field with the error. For example,.nameor.first-name.schemaPath: optional JSON pointer to the schema of the keyword that failed validation. For example,#/fields/firstName/required. (Note: this may sometimes be wrong due to a bug in ajv).stack: full error name, for example ".name is a required property".title: optional string containing title property for the field with error.
Error List Display
To take control over how the form errors are displayed, you can define an error list template for your form. This list is the form global error list that appears at the top of your forms.
An error list template is basically a React stateless component being passed errors as props, so you can render them as you like:
import Form from '@rjsf/core';
import { RJSFSchema, ErrorListProps } from "@rjsf/utils";
import validator from "@rjsf/validator-ajv8";
function ErrorListTemplate(props: ErrorListProps) {
const { errors } = props;
return (
<div>
<h2>Custom error list</h2>
<ul>
{errors.map(error => (
<li key={error.stack}>
{error.stack}
</li>
))}
</ul>
</div>
);
}
const schema: RJSFSchema = {
type: "string",
const: "test"
};
render((
<Form schema={schema}
validator={validator}
showErrorList='top'
initialFormData={""}
liveValidate='onChange'
templates: {{ ErrorListTemplate }} />
), document.getElementById("app"));
Note: Your custom
ErrorListtemplate will only render whenshowErrorLististoporbotttom.
The following props are passed to ErrorList as defined by the ErrorListProps interface in @rjsf/utils:
errors: An array of the errors.errorSchema: The errorSchema constructed byForm.schema: The schema that was passed toForm.uiSchema: The uiSchema that was passed toForm.formContext: TheformContextobject that you passed toForm.
The case of empty strings
When a text input is empty, the field in form data is set to undefined.
However, since undefined isn't a valid JSON value according to the official JSON standard (ECMA-404, Section 5), the values get stored as null.
String fields that use enum and a select widget will have an empty option at the top of the options list that when selected will result in the field being null.
One consequence of this is that if you have an empty string in your enum array, selecting that option in the select input will cause the field to be set to null, not an empty string.
If you want to have the field set to a default value when empty you can provide a ui:emptyValue field in the uiSchema object.
Custom meta schema validation
To have your schemas validated against any other meta schema than draft-07 (the current version of JSON Schema), make sure your schema has a $schema attribute that enables the validator to use the correct meta schema.
For example:
{
"$schema": "http://json-schema.org/draft-04/schema#",
...
}
Note that react-jsonschema-form supports JSON Schema draft-07 by default.
To support additional meta schemas, you can create and pass to the Form component a customized @rjsf/validator-ajv8:
additionalMetaSchemas
The additionalMetaSchemas option of customizeValidator() adds one or more JSON Schema meta schemas to the validator, such as JSON Schema draft-06.
A meta schema describes schemas, not form data: the validator checks your schema against the meta schema its $schema names, and reports a schema that breaks it as schema is invalid: ....
Without the meta schema its $schema names, every validation of the form reports no schema with key or ref "http://json-schema.org/draft-06/schema#" instead of validating the data.
In this example the schema names draft-06, so the validator is given the draft-06 meta schema that Ajv 8 ships:
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import { customizeValidator } from '@rjsf/validator-ajv8';
import metaSchemaDraft06 from 'ajv/lib/refs/json-schema-draft-06.json' with { type: 'json' };
const validator = customizeValidator({ additionalMetaSchemas: [metaSchemaDraft06] });
const schema: RJSFSchema = {
$schema: 'http://json-schema.org/draft-06/schema#',
type: 'string',
};
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
Ajv 8 doesn't ship a draft-04 meta schema to pass this way. For draft-04, build the validator with the Ajv class of ajv-draft-04 instead:
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import { customizeValidator } from '@rjsf/validator-ajv8';
import AjvDraft04 from 'ajv-draft-04';
const validator = customizeValidator({ AjvClass: AjvDraft04 });
const schema: RJSFSchema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'string',
};
return <Form schema={schema} validator={validator} />;
A draft-04 keyword that RJSFSchema doesn't declare, such as id, needs declaring before a schema typed RJSFSchema can hold it; see Keywords of your own.
customFormats
Pre-defined semantic formats are limited.
react-jsonschema-form adds two formats, color and data-url, to support certain alternative widgets.
To add formats of your own, you can create and pass to the Form component a customized @rjsf/validator-ajv8:
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import { customizeValidator } from '@rjsf/validator-ajv8';
const schema: RJSFSchema = {
type: 'string',
format: 'phone-us',
};
const customFormats = {
'phone-us': /\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{4}$/,
};
const validator = customizeValidator({ customFormats });
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
Format values can be anything AJV's addFormat method accepts.
Async validation
Handling async errors is an important part of many applications. Support for this is added in the form of the extraErrors prop.
For example, a request could be made to some backend when the user submits the form. If that request fails, the errors returned by the backend should be formatted like in the following example.
import Form from '@rjsf/core';
import { RJSFSchema, ErrorSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
const schema: RJSFSchema = {
type: 'object',
properties: {
foo: {
type: 'string',
},
candy: {
type: 'object',
properties: {
bar: {
type: 'string',
},
},
},
},
};
const extraErrors: ErrorSchema = {
foo: {
__errors: ['some error that got added as a prop'],
},
candy: {
bar: {
__errors: ['some error that got added as a prop'],
},
},
};
render(<Form schema={schema} validator={validator} extraErrors={extraErrors} />, document.getElementById('app'));
By default, these errors also block the user from submitting the form, the same as JSON Schema validation errors.
Set the extraErrorsAreWarnings prop to true to make them "display only" instead, so the user can still submit the form while they're the only errors shown.
ajvOptionsOverrides
In version 5, with the advent of the decoupling of the validation implementation from the Form, it is now possible to provide additional options to the ajv instance used within @rjsf/validator-ajv8.
For instance, if you need more information from ajv about errors via the verbose option, now you can turn it on!
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import { customizeValidator } from '@rjsf/validator-ajv8';
const schema: RJSFSchema = {
type: 'string',
format: 'phone-us',
};
const ajvOptionsOverrides = {
verbose: true,
};
const validator = customizeValidator({ ajvOptionsOverrides });
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
Ajv's useDefaults, coerceTypes and removeAdditional options change the data Ajv validates: it fills in defaults, converts values to the type the schema names and deletes properties the schema doesn't allow.
So may a keyword registered with modifying: true, which Ajv requires of a keyword whose validate or compile function changes the data, such as ajv-keywords' dynamicDefaults.
Under any of them, isValid(), which the form calls to check each condition and option against the data, validates a copy of the data, so those checks never change the form's data.
The copy holds arrays and plain objects copied key by key and any other object copied with structuredClone(), so a class instance is checked as a plain object of its own properties, and data holding an object that can't be copied, such as a class instance holding a function, fails the check with a warning.
validateFormData() and rawValidation() validate the data they are handed, so those changes reach it.
Under the other options, with no such keyword registered, nothing is copied.
A keyword added through extenderFn or validator.ajv that writes into the data through the code it generates, such as ajv-keywords' transform, declares no modifying, so it doesn't make isValid() copy the data, though isValid() must not change it.
Such a keyword needs useDefaults, coerceTypes or removeAdditional set, so that isValid() validates a copy (each of them also changes what Ajv accepts), or else a copy of its own, such as a ValidatorType that wraps this one and hands its isValid() a copy of the data.
Using the raw Ajv instance
The customizeValidator() function returns the AJV8Validator implementation class, which has an internal raw ajv instance within it.
If you need to do some deep customization of the instance using any of the ajv libraries (like ajv-keywords), you can do so using this raw instance as follows:
import { customizeValidator } from '@rjsf/validator-ajv8';
import ajvKeywords from 'ajv-keywords';
const validator = customizeValidator();
ajvKeywords(validator.ajv, ['your-keyword']);
// use your update validator with a `Form`
A schema typed RJSFSchema can hold the added keywords, such as transform, once they are declared; see Keywords of your own.
A keyword that writes into the data, such as transform, needs isValid() to validate a copy; see ajvOptionsOverrides.
Ajv8 validator
Our implementation of @rjsf/validator-ajv8 utilizes Ajv's internal cache to avoid unnecessarily re-compiling schemas, which can be an expensive operation. The cache key is the schema $id.
The ajvOptionsOverrides for the Ajv 8 validator are the ones supported by that version.
Second, the data formats previously provided in Ajv 6 now need to be added explicitly using the ajv-formats package.
A new ajvFormatOptions option is available on the customizeValidator() API to be able to configure this.
Additionally, a new AjvClass option is available on the customizeValidator() API to support using one of the other JSON schema versions provided by Ajv 8 besides the draft-07 default.
Finally, the Ajv 8 validator supports the localization of error messages.
ajvFormatOptions
By default, ALL formats are being added to the default @rjsf/validator-ajv8 that you get when you import it.
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
const schema: RJSFSchema = {
type: 'string',
format: 'email',
};
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
If you don't actually need any of the ajv-formats and want to reduce the memory footprint, then you can turn it off as follows:
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import { customizeValidator } from '@rjsf/validator-ajv8';
const schema: RJSFSchema = {
type: 'string',
};
const validator = customizeValidator({ ajvFormatOptions: false });
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
If you only need some of them, you can pass any of the options to the formatter:
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import { customizeValidator } from '@rjsf/validator-ajv8';
const schema: RJSFSchema = {
type: 'string',
format: 'date',
};
const validator = customizeValidator({ ajvFormatOptions: ['date'] });
// or
// const validator = customizeValidator({ ajvFormatOptions: { mode: "fast", formats: ["date"], keywords: true } });
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
AjvClass
By default, the @rjsf/validator-ajv8 uses the draft-07 schema version.
It is possible to use one of the other version it supports, like draft-2019-09 or draft-2020-12.
NOTE: draft-2020-12 has breaking changes and hasn't been fully tested with @rjsf.
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import { customizeValidator } from '@rjsf/validator-ajv8';
import Ajv2019 from 'ajv/dist/2019';
const schema: RJSFSchema = {
type: 'string',
format: 'date',
};
const validator = customizeValidator({ AjvClass: Ajv2019 });
// or
// const validator = customizeValidator({ AjvClass: Ajv2020 });
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
extenderFn
If you need to use an additional library, such as ajv-errors, with our validators you can do so by creating a custom validator and pass it the ajv library "extender" via the extenderFn prop on the options parameter.
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import { customizeValidator } from '@rjsf/validator-ajv8';
import ajvErrors from 'ajv-errors';
const schema: RJSFSchema = {
type: 'string',
format: 'date',
};
const validator = customizeValidator({ extenderFn: ajvErrors });
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
suppressDuplicateFiltering
When AJV validates against anyOf or oneOf schemas, it generates a separate error for each failed branch, which often produces many duplicate messages.
By default, the validator filters out these duplicates so that only the first occurrence of each unique error is shown.
Use suppressDuplicateFiltering to override this behavior when you need to see all errors from every branch.
The option accepts a SuppressDuplicateFilteringType value (exported from @rjsf/validator-ajv8):
| Value | Behavior |
|---|---|
'none' (default) | Duplicates filtered for both anyOf and oneOf |
'anyOf' | anyOf duplicate filtering disabled; oneOf duplicates still filtered |
'oneOf' | oneOf duplicate filtering disabled; anyOf duplicates still filtered |
'all' | All duplicate filtering disabled; every error from every branch is returned |
For a standard validator via customizeValidator():
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import { customizeValidator } from '@rjsf/validator-ajv8';
const schema: RJSFSchema = {
type: 'object',
properties: {
asc: {
title: 'Low to high',
type: 'boolean',
default: true,
},
desc: {
title: 'High to low',
type: 'boolean',
default: true,
},
},
anyOf: [
{
required: ['asc'],
properties: {
asc: {
const: true,
},
},
},
{
required: ['desc'],
properties: {
desc: {
const: true,
},
},
},
],
};
// Disable all duplicate filtering — show every error from every anyOf/oneOf branch
const validator = customizeValidator({ suppressDuplicateFiltering: 'all' });
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
For a precompiled validator, pass the value in the options object createPrecompiledValidator() takes after the schema:
import { createPrecompiledValidator } from '@rjsf/validator-ajv8';
import * as precompiledValidatorFns from 'path_to/yourCompiledSchema.cjs';
import yourSchema from 'path_to/yourSchema.json' with { type: 'json' };
const validator = createPrecompiledValidator(precompiledValidatorFns, yourSchema, { suppressDuplicateFiltering: 'all' });
Localization (L10n) support
The Ajv 8 validator supports the localization of error messages using ajv-i18n.
In addition, you may provide a custom solution by implementing a function that conforms to the Localizer interface if your language is not supported.
import { ErrorObject } from 'ajv';
/** The type describing a function that takes a list of Ajv `ErrorObject`s and localizes them
*/
export type Localizer = (errors?: null | ErrorObject[]) => void;
NOTE: The ajv-i18n validators implement the Localizer interface.
Some examples
Using a specific locale while including all of ajv-i18n:
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import { customizeValidator } from '@rjsf/validator-ajv8';
import localizer from 'ajv-i18n';
const schema: RJSFSchema = {
type: 'string',
};
const validator = customizeValidator({}, localizer.it);
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
Using a specific locale minimizing the bundle size
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import { customizeValidator } from '@rjsf/validator-ajv8';
import spanishLocalizer from 'ajv-i18n/localize/es';
const schema: RJSFSchema = {
type: 'string',
};
const validator = customizeValidator({}, spanishLocalizer);
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
An example of a custom Localizer implementation:
import Form from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import { customizeValidator } from '@rjsf/validator-ajv8';
import { ErrorObject } from 'ajv';
function localize_ru(errors: null | ErrorObject[] = []) {
if (!(errors && errors.length)) return;
errors.forEach(function (error) {
let outMessage = '';
switch (error.keyword) {
case 'pattern': {
outMessage = 'должно соответствовать образцу "' + error.params.pattern + '"';
break;
}
case 'required': {
outMessage = 'поле обязательно для заполнения';
break;
}
default:
outMessage = error.message;
}
error.message = outMessage;
});
}
const schema: RJSFSchema = {
type: 'string',
};
const validator = customizeValidator({}, localize_ru);
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
NOTES:
- If you provided your own function, set the
messageof each error in the list in place. The function is handed copies of the errors, and only themessageit sets on each is kept. - You must process all the cases which you need by yourself. See the full list of possible cases here.
- Each element in the
errorslist passed to the custom function is a copy of a raw error object returned by ajv (see doc), except that the property names in itsmissingProperty,propertyanddepsparams are quoted, such as'foo', so that RJSF can replace them in your messages with the property'stitleorui:title. Strip the quotes before looking a param up, such as in a map of labels. A property with no title keeps its quoted key in the message. - HTML 5 Validation will cause the browser to display a "popup" message, the text of which is entirely under control of the browser. If you are seeing messages which aren't translated "popping up", then add
noHtml5Validateto your form.