Express CSV Logo

Refine

Sometimes, the built-in validation is not enough. You can run your own validation logic, including async code, to enforce exactly the refinement logic you need with .refine() and .refineBatch().

x.string().refine(
  (value) => value.startsWith("ACC-"),
  { message: 'Must start with "ACC-"' }
)

Async Validator

You can also make async calls for things like database lookups, API calls, or checking external systems:

x.string().refine(async (value) => {
  const exists = await checkDatabaseId(value);
  return { valid: exists, message: `ID "${value}" not found` };
})

Batch Validation

.refineBatch() validates values from the column in batches. The function receives an array of values from the column and must return a result array of the same length. This is ideal for batching lookups that would be expensive to run for each value individually.

x.string().refineBatch(async (values) => {
  // One batched DB lookup for the whole column chunk, not one per cell.
  const found = await bulkCheckExists(values);
  return found.map((exists, i) => ({
    valid: exists,
    message: exists ? undefined : `Not found: "${values[i]}"`,
  }));
})

Suggested Fixes

Both .refine() and .refineBatch() can return a suggestedFix object. The widget presents these as one-click fixes the user can apply during the review step.

x.string().refine((value) => {
  const trimmed = value.trim();

  if (trimmed === value) {
    return { valid: true };
  }

  return {
    valid: false,
    message: "Remove leading or trailing whitespace",
    suggestedFix: {
      id: "trim-whitespace",
      value: trimmed,
      description: "Trim whitespace",
    },
  };
})

On this page