# Validate user data input
URL: https://pro.rv-grid.com/guides/validate-input/
Source: src/content/docs/guides/validate-input.mdx
Description: Explore strict and soft cell validation for spreadsheet-style data entry.
This demo exposes the rules as controls. Try strict mode to reject invalid edits, soft mode to save invalid data with markers, and the seed/reset actions to simulate cleanup workflows.

The **CellValidatePlugin** validates edits before they are written to the grid source. Use it when the grid should enforce spreadsheet-style rules such as required fields, numeric ranges, enum membership, cross-field limits, and approval gates.

Install it together with **EventManagerPlugin** so edit events are collected through the Pro event pipeline:

```ts
import {
  CellValidatePlugin,
  EventManagerPlugin,
} from '@revolist/revogrid-pro';

grid.plugins = [
  EventManagerPlugin,
  CellValidatePlugin,
];

grid.eventManager = {
  applyEventsToSource: true,
};
```

:::note
`EventManagerPlugin` should be registered before `CellValidatePlugin`. The event manager emits the unified `gridedit` event, and `CellValidatePlugin` cancels that event when strict validation fails.
:::

## Strict and soft validation

By default validation is strict. If `validate(value, model)` returns `false`, the edit is rejected and the source keeps the previous value.

```ts
const columns: ColumnRegular[] = [
  {
    name: 'Price',
    prop: 'price',
    validate: (value, model) => {
      const price = Number(value);
      return Number.isFinite(price) && price >= 50 && price <= 1200;
    },
    validationTooltip: (value) =>
      `Price ${value} must be between 50 and 1200`,
  },
];
```

Set `softValidation: true` when invalid values should still be saved and marked visually. This is useful for imported data cleanup flows where users need to see and fix invalid rows in place. Pair it with `validationRenderer` when you want saved invalid values to stay visible after the edit.

```ts
{
  prop: 'discount',
  softValidation: true,
  ...validationRenderer(),
  validate: (value, model) => Number(value) <= Number(model.price) * 0.3,
  validationTooltip: (value, model) =>
    `Discount cannot exceed 30% of price (${Math.round(Number(model.price) * 0.3)})`,
}
```

## Candidate row model

Both `validate(value, model)` and `validationTooltip(value, model)` receive the row model. During strict edit validation, the model includes the pending row patch, so cross-field validation works for single edits and pasted batches. If the edit is rejected, the tooltip renderer receives the rejected value and candidate model, which lets the message explain what failed even though the source was not changed.

```ts
{
  name: 'Approved',
  prop: 'approved',
  validate: (value, model) => {
    const netPrice = Number(model.price) - Number(model.discount ?? 0);
    return netPrice <= 900 || value === true;
  },
  validationTooltip: () => 'High-value rows require approval',
}
```

`validationTooltip` also receives the column as a third argument: `validationTooltip(value, model, column)`. Use that when one shared message resolver needs column metadata.

## Cross-column example

The validation function can also block a cell based on another column in the same row. This example prevents fees on expired services.
