C3 AI Documentation Home

Validate fields with custom logic using epics

Use custom validation when built-in constraints — like required, min, or range — do not meet your validation needs. Epics allow you to define rules using full TypeScript logic and update validation status based on any condition you define. Custom validation is ideal for use cases that depend on contextual logic, dynamic values, or data conditions not represented in a static UI.

This example validates two form fields:

  • The manufacturerDate must be a past date.
  • The location field must not equal "San Francisco".

Connect inputs to validation logic

To run custom validation, connect field input events to an epic using effectTriggers. The JSON below defines the structure of the form and the conditions under which the platform invokes your validation logic. When the user enters a value into either field, the platform triggers a custom effect defined in your TypeScript epic.

The key configuration field is:

  • effectTriggers: Binds a form-level trigger event (in this case, INPUT_EXTERNAL_VALIDITY) to a named epic that will validate the field.

Think of effectTriggers as a contract between the form and your validation logic. You tell the platform, “When this specific event happens—for example, a user types into a field—run this validation code.” The system listens for that event, then hands the form’s current values to your epic so it can decide what is valid or not.

Trigger custom validation

This JSON defines a form that includes a location text input and a manufacturerDate date picker. The configuration also defines an effectTrigger that tells the platform to invoke the EpicValidateFormInput epic when either field value changes.

JSON
/* examplePackage/ui/c3/meta/examplePackage.SimpleFormCustomValidation.json */

{
  "type": "UiSdlConnected<UiSdlForm>",
  "component": {
    "title": { "title": "Custom Validation Example" }, // Title shown at the top of the form
    "subtitle": { "title": "Uses an epic to enforce custom rules" }, // Subtitle explaining the validation method
    "width": 20, // Optional form width
    "dataSpec": {
      "fieldSets": {
        "type": "[UiSdlFormFieldSet]",
        "value": [
          {
            "type": "UiSdlFormFieldSet",
            "fields": [
              {
                "inputElement": {
                  "type": "UiSdlTextInput", // Standard text input component
                  "placeholder": "Enter location" // Hint text
                },
                "fieldName": "location", // Maps this input to the location field in the form state
                "label": "Location" // Label shown to users
              },
              {
                "inputElement": {
                  "type": "UiSdlDateTimeInput", // Date picker component
                  "allowKeyboardEdit": true, // Allows users to type a date
                  "placeholder": "Enter manufactured date", // Hint text
                  "showTimePicker": false // Hides the time selector
                },
                "fieldName": "manufacturerDate", // Maps this input to the manufacturerDate field in the form state
                "label": "Manufacturer Date" // Label shown to users
              }
            ]
          }
        ]
      }
    }
  },
  "effectTriggers": [
    {
      "trigger": "uiprototype.SimpleFormValidation.INPUT_EXTERNAL_VALIDITY", // Trigger event fired when a field is edited
      "effectType": "EpicValidateFormInput", // Epic to run when the trigger fires
      "payload": {
        "formId": "uiprototype.SimpleFormValidation" // Used to identify the form instance in the epic
      },
      "payloadStrategy": "MERGE" // Merges the payload into the triggered action
    }
  ]
}

Declare the validation epic Type

Declare the epic as a UI Type. The UI framework uses this definition to register the epic in metadata and ensure it is discoverable at runtime.

The EpicValidateFormInput Type defines a UI-side data transformation handler that contains a single epic function. The platform automatically resolves and invokes this function when the specified trigger fires.

Type
/* examplePackage/src/epics/EpicValidateFormInput.c3typ */

/**
 * Epic to validate form fields using custom logic.
 */
@typeScript
type EpicValidateFormInput mixes UiSdlEpic {
  epic: ~ ts-client // Entry point for the custom TypeScript function
}

The EpicValidateFormInput mixes UiSdlEpic. UiSdlEpic inherits the contract for epics, including expected function shape and execution lifecycle.

The epic function points to the TypeScript implementation located in the client environment.

This declaration is required for the platform to invoke your epic.

Implement the epic to validate fields

Define your validation logic in a custom TypeScript epic. In this example, the epic inspects the updated field name and value. If the field is manufacturerDate, the epic checks that the date is in the past. If the field is location, it checks that the value is not equal to "GE". The epic uses updateFieldValidityAction to update the field’s validity in Redux state.

The key function call is:

  • updateFieldValidityAction: Sets the valid or invalid state for a specific form field.

When your form triggers a validation event, the platform passes control to this epic. The epic acts like a traffic controller — it examines what changed, applies your custom rules, and responds by marking fields as valid or invalid. If the input meets your condition, the form moves forward. If not, the user sees a validation error and must correct it before submitting.

Implement custom validation rules

This epic responds to the INPUT_EXTERNAL_VALIDITY trigger and returns updated field validity for each rule. The TypeScript logic gives you complete control over how each field passes or fails validation.

Think of this function as your form’s decision-maker. Every time a user enters or updates a field, this code steps in to ask, “Does this value follow the rules?” If it does, the field passes. If it doesn’t, the form blocks submission and can highlight the issue to the user. You define the rules. The platform simply follows your instructions.

The example below enforces two validation rules:

  • A manufacturerDate must be earlier than today's date.
  • A location cannot equal "GE".
TypeScript
/* examplePackage/src/epics/EpicValidateFormInput.ts */

import { map } from "rxjs/operators";
import { updateFieldValidityAction } from "@c3/ui/UiSdlForm";
import {
  UiSdlActionsObservable,
  UiSdlStatesObservable,
  UiSdlReduxAction,
} from "@c3/types";

// Epic that validates location and manufacturerDate fields based on custom rules
export function epic(
  actionStream: UiSdlActionsObservable,
  stateStream: UiSdlStatesObservable
): UiSdlActionsObservable {
  return actionStream.pipe(
    map(function (action: UiSdlReduxAction) {
      // Rule 1: Validate that manufacturerDate is in the past
      if (action.payload.field === "manufacturerDate") {
        const validDate = isPastDate(action.payload.value);
        return updateFieldValidityAction(
          action.payload.formId,
          action.payload.field,
          validDate
        );
      }

      // Rule 2: Disallow 'GE' as a location value
      if (action.payload.field === "location") {
        const isValid = action.payload.value !== "GE";
        return updateFieldValidityAction(
          action.payload.formId,
          action.payload.field,
          isValid
        );
      }

      // No validation for other fields
      return { type: "empty" };
    })
  );
}

// Returns true if the date is before the current date
function isPastDate(date: Date): boolean {
  const now = new Date();
  return date.getTime() < now.getTime();
}

This validation pattern gives you full control over when and how validation happens:

  1. Set up an effectTrigger on the form to respond to field changes.
  2. Create an epic to evaluate the updated field’s value.
  3. Use updateFieldValidityAction to mark each field as valid or invalid.

Use this approach to apply rules that involve cross-field logic or context-specific constraints. Unlike required, min, or range, epics give you complete control over the input validation lifecycle.

Was this page helpful?