External validators

Magnolia’s New UI Forms engine features a robust validation system that can connect to both internal Java-based services and external, custom-built web services. This allows you to keep simple, content-related validations inside Magnolia while offloading complex business logic to dedicated microservices.

How it works

The validation system comprises several components that work together to route and handle validation requests.

  • A Content App Frontend (built with React/TypeScript) renders the form, handles client-side validation, and sends validation requests to the appropriate endpoint.

  • A Form Schema Service (Java) provides the form definitions, including validator configurations, from the YAML configuration.

  • A Form Content Handler (Java REST API) manages form data CRUD operations, interacting with the Validation Dispatcher for content validation.

  • Validators

    • All validators, whether internal or external, adhere to the same API contract, receiving a standard request payload and returning a standard response.

      • For internal validators, the request is routed to the Validation Dispatcher (a Java REST API implemented by the info.magnolia.warp.engine.form.endpoint.ValidationEndpoint class). The dispatcher uses a Validator Provider Registry (Java) to route requests to specific internal validator services, such as the Node Name Validator Service, which validates against the JCR repository.

      • For external validators, the request should be routed through a CDN to the customer’s hosted service, based on a unique provider ID in the URL (for example, {provider}.example.com/…​). The customer’s validator service (for example, hosted on Kubernetes) routes requests to specific integrations, such as Shopify or an ERP system, based on the validator ID.

The core of Magnolia’s internal routing is the ValidationEndpoint, defined in validation-endpoint.yaml, which is responsible for receiving and dispatching validation calls.

form-content-handler/restEndpoints/validation-endpoint.yaml
class: info.magnolia.rest.registry.ConfiguredEndpointDefinition
implementationClass: info.magnolia.warp.engine.form.endpoint.ValidationEndpoint

Validator definition

Everything starts in your form definition YAML.

To use a external validator, you add an entry to the validators list with the name remote.

Properties

Property Description

name

required

The type of validator. Set to remote for external validators.

validatorId

required

The unique identifier for the specific validator type (for example, sku-exists-validator). This is used in the URL path for validation requests (for example, /validate/{validatorId}).

provider

optional

The identifier for the external service (for example, my-erp-system). Determines the destination URL for external validation requests (for example, https://{provider}.example.com). If omitted, Magnolia assumes an internal validator.

errorMessage

optional

The default error message to display if validation fails (for example, This SKU does not exist in the ERP.). Used by the frontend when the validator response doesn’t provide a custom message.

config

optional

A map of additional configuration specific to the validator (for example, { "region": "eu-west-1" }). Contains settings or parameters needed by the validator.

Sensitive data, such as API keys, should be configured at the provider level, not in the form definition.
Example form field definition
form:
  fields:
    - name: productSku
      label: 'Product SKU'
      type: 'textfield'
      validators:
        - name: 'remote'
          validatorId: 'sku-exists-validator' (1)
          provider: 'my-erp-system' (2)
          errorMessage: 'This SKU does not exist in the ERP.'
          config: (3)
            region: 'eu-west-1'
1 This ID is used in the URL path: /validate/{validatorId}.
2 The provider property is crucial. It determines the request’s destination URL. If omitted, Magnolia assumes an internal validator. When present, the frontend uses it to construct the external URL.
3 You can pass a static config to your remote service.

Validation payload and routing

Based on the form definition, the frontend sends a request containing a standardized payload to the appropriate destination.

The validation request payload

The body of the POST request is a JSON object with a consistent structure, defined by the ValidationRequest.java and ValidationContext.java records.

The validator ID is specified in the URL path (/validate/{validatorId}), and the provider ID is used to determine the routing destination for external validators (for example, {provider}.example.com).

Properties

Property Description

fieldPath

required

The name or path of the field being validated (for example, productSku).

fieldValue

required for external validators

The current value of the field being validated, extracted from the form content (for example, PROD-123).

This field is required for external validators but may not be sent for internal validators.

content

required

A JSON object representing the entire form’s current data, useful for cross-field validation.

Example:

{ "productSku": "PROD-123", "productName": "My Awesome Product", "stock": 100 }).

config

required

The static config object from the form definition YAML (for example, { "region": "eu-west-1" }).

context

required

Standardized information about the request context. It contains the following fields:

  • itemId (string): The ID of the content item being validated (for example, d14950fc-7fc2-4631-92f1-b6f60a1fe40a).

  • contentType (string): The name of the content app (for example, contacts).

  • mode (string): The validation mode, either ADD or EDIT.

  • locale (string): The current locale (for example, en).

Example JSON request body
{
  "fieldPath": "productSku",
  "fieldValue": "PROD-123",
  "content": {
    "productSku": "PROD-123",
    "productName": "My Awesome Product",
    "stock": 100
  },
  "context": {
    "itemId": "d14950fc-7fc2-4631-92f1-b6f60a1fe40a",
    "contentType": "products-app",
    "mode": "EDIT",
    "locale": "en"
  },
  "config": {
    "region": "eu-west-1"
  }
}

Routing the request

  • For Internal Validators (no provider specified): The request is sent to Magnolia’s local endpoint at /warp/v0/validate/{validatorId}, where the Validation Dispatcher routes it to the appropriate internal validator service via the Validator Provider Registry.

  • For External Validators (a provider specified): The request is sent to an external URL like https://{provider}.example.com/validate/{validatorId}, routed through the CDN to the customer’s validator service.

Validation response

The validation response, returned by both internal and external validators, is a JSON object that adheres to the API contract defined in the remote-validator-api.yaml specification.

It contains the following fields:

  • isValid (boolean, required): Indicates whether the field value passes validation (true for valid, false for invalid).

  • message (string, optional): A custom error message to display when validation fails. If not provided, the frontend uses the default errorMessage from the form YAML (for example, "This SKU does not exist in the ERP.").

  • validatorId (string, required): Echoes the validator ID from the request’s URL path (for example, sku-exists-validator) for verification and debugging. In some internal contexts, this may be referred to as validatorType.

The response can represent three scenarios:

  • Successful validation:

    { "isValid": true, "validatorId": "sku-exists-validator" }

  • Failed validation with custom message:

    { "isValid": false, "validatorId": "sku-exists-validator", "message": "SKU 'PROD-123' not found in region eu-west-1." }

  • Failed validation without custom message, in which case the frontend uses the default message from the form YAML:

    { "isValid": false, "validatorId": "sku-exists-validator" }

Implementing a custom validator service

Your external service must be able to process the request payload detailed above and return a simple JSON response.

Your service must:

  • Be hosted at the public URL your provider ID routes to (for example, {provider}.example.com).

  • Expose an endpoint for POST requests at the path /validate/{validatorId}.

  • Process the ValidationRequest JSON payload.

  • Return a ValidationResponse JSON object containing isValid (boolean), validatorId (string), and an optional message (string).

Example Node.js external validator service
const express = require('express');
const app = express();
app.use(express.json());

app.post('/validate/sku-exists-validator', async (req, res) => { (1)
  const { fieldPath, fieldValue, content, context, config } = req.body; (2)
  const validatorId = req.params.validatorId; (3)

  console.log(`Validating in ${context.mode} mode for item ${context.itemId}.`); (4)
  const skuExists = await checkErpSystem(config.region, fieldValue); (5)

  if (skuExists) { (6)
    res.json({
      isValid: true,
      validatorId: validatorId
    });
  } else {
    res.status(200).json({
      isValid: false,
      validatorId: validatorId,
      message: `SKU '${fieldValue}' not found in region ${config.region}.`
    });
  }
});

async function checkErpSystem(region, sku) { (7)
  console.log(`Checking SKU ${sku} in ERP region ${region}...`); (8)
  return sku === 'PROD-123'; (9)
}

app.listen(3000, () => console.log('Custom validator service running on port 3000')); (10)
1 The service listens for POST requests at the /validate/sku-exists-validator endpoint.
2 Destructure the request payload to extract fieldPath, fieldValue, content, context, and config.
3 Extract the validatorId from the URL path parameter.
4 Log the validation context for debugging, using the mode and itemId from the request’s context.
5 Call the checkErpSystem function to validate the SKU against the ERP system, using the region from config and the fieldValue.
6 Send the structured response adhering to the API contract, returning isValid: true if the SKU exists, or isValid: false with a custom error message if it doesn’t.
7 Define an async function to simulate checking the SKU against an external ERP system.
8 Log the SKU and region for debugging purposes.
9 Return true only if the SKU matches 'PROD-123' (simulated logic).
10 Start the service on port 3000, running on the customer’s infrastructure.
Feedback

DX Core

×

Location

This widget lets you know where you are on the docs site.

You are currently perusing through the DX Core docs.

Main doc sections

DX Core Headless PaaS Legacy Cloud Incubator modules