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 form-content-handler/restEndpoints/validation-endpoint.yaml
|
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 | ||
---|---|---|---|
|
required The type of validator.
Set to |
||
|
required The unique identifier for the specific validator type (for example, |
||
|
optional The identifier for the external service (for example, |
||
|
optional The default error message to display if validation fails (for example, |
||
|
optional A map of additional configuration specific to the validator (for example,
|
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 | ||
---|---|---|---|
|
required The name or path of the field being validated (for example, |
||
|
required for external validators The current value of the field being validated, extracted from the form content (for example,
|
||
|
required A JSON object representing the entire form’s current data, useful for cross-field validation. Example:
|
||
|
required The static |
||
|
required Standardized information about the request context. It contains the following fields:
|
{
"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 likehttps://{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 defaulterrorMessage
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 asvalidatorType
.
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 containingisValid
(boolean),validatorId
(string), and an optionalmessage
(string).
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. |