Skip to content

Request Validation Plugin

Request Validation Plugin validates requests against your contract before they are sent to the server. This is useful when your application relies on server-side validation.

Setup

ts
import { RequestValidationLinkPlugin } from '@orpc/contract/plugins'

const link = new RPCLink({
  plugins: [
    new RequestValidationLinkPlugin(contract),
  ],
})

INFO

If you do not have a contract, you can use a unlazied router instead.

INFO

The link can be any supported oRPC link, such as RPCLink, OpenAPILink, or a custom one.

Forward Validated Input

By default, the plugin does not reuse validated input for the rest of the request. Some schemas transform input in ways that can cause server-side validation to fail. If your schemas do not do that, set forwardValidatedInput to true.

ts
const link = new RPCLink({
  plugins: [
    new RequestValidationLinkPlugin(contract, {
      forwardValidatedInput: true,
    }),
  ],
})

Custom Validation Errors

If you have already customized validation errors on the server, you can use interceptors to catch and map the validation errors thrown by this plugin so they match your server-side errors.

ts
import { ORPCError } from '@orpc/client'
import { ValidationError } from '@orpc/contract'

const link = new RPCLink({
  plugins: [
    new RequestValidationLinkPlugin(contract),
  ],
  interceptors: [
    async ({ next }) => {
      try {
        return await next()
      }
      catch (error) {
        if (
          error instanceof ORPCError
          && error.code === 'BAD_REQUEST'
          && error.cause instanceof ValidationError
        ) {
          throw new CustomInputValidationError(error.cause.issues)
        }

        throw error
      }
    }
  ]
})

Form Validation

You can pair this plugin with Form Data Helpers to avoid heavier form validation libraries and keep your contract as the single source of truth on both the client and server.

tsx
import { getIssueMessage, parseFormData } from '@orpc/openapi/helpers'

export function ContactForm() {
  const [error, setError] = useState()

  const handleSubmit = async (form: FormData) => {
    try {
      const output = await client.someProcedure(parseFormData(form))
      console.log(output)
    }
    catch (error) {
      setError(error)
    }
  }

  return (
    <form action={handleSubmit}>
      <input name="user[name]" type="text" />
      <span>{getIssueMessage(error, 'user[name]')}</span>

      <input name="user[emails][]" type="email" />
      <span>{getIssueMessage(error, 'user[emails][]')}</span>

      <button type="submit">Submit</button>
    </form>
  )
}

Learn More

For implementation details, see the source code.

Released under the MIT License.