This guide explains how to validate an input variable.
When defining an input variable, as a best practice, validate that the user
enters an appropriate value. For example, if you ask the user to input a
numeral, verifying that they enter 1 instead of a verifies that your step
runs without error.
There are two ways to validate an input variable:
- Client-side validation: With client-side validation, you verify the user's input directly on their device. The user receives immediate feedback and can correct any errors in their input while configuring the step.
- Server-side validation: Server-side validation lets you run logic on the server during validation, which is useful when you need to lookup information that the client doesn't have, like data in other systems or databases.
Client-side validation
There are two ways to implement client-side validation:
- For basic validation, like verifying a widget contains fewer than a certain
number of chracters or contains the
@symbol, invoke theValidationclass of the Google Workspace add-on's Card service. - For robust validation, like comparing widget values with other widget
values, you can add Common Expression Language (CEL)
validation to the following supported card widgets using
CardService.
Invoke the Validation class
The following example validates that a TextInput widget contains 10 or fewer
characters:
Apps Script
const validation = CardService.newValidation().setCharacterLimit('10').setInputType(
CardService.InputType.TEXT);
For additional validation options use CEL validation.
CEL Validation
Common Expression Language (CEL) validation offers instant input checks without the latency of server-side validation by offloading input value checks that are not dependent on the lookup of data from other services to the client side.
You can also use CEL to create card behaviors, like displaying or hiding a widget depending on the result of the validation. This kind of behavior is useful for showing or hiding an error message that helps users correct their inputs.
Building a complete CEL validation involves the following components:
ExpressionDatain Card: Contains the specified validation logic and widget triggering logic when one of the defined Conditions is met.Id: A unique identifier for theExpressionDatawithin the current Card.Expression: The CEL string that defines the validation logic (e.g.,"value1 == value2").Conditions: A list of conditions that contains a selection of predefined validation results (SUCCESS or FAILURE). Conditions are tied to the widget-sideEventActionthroughTriggerswith a sharedactionRuleId.- Card-level
EventAction: Activates CEL validations in the Card and associates theExpressionDatafield to result widgets through post-event triggers.actionRuleId: Unique ID for thisEventAction.ExpressionDataAction: Set toSTART_EXPRESSION_EVALUATIONto indicate this action starts CEL evaluation.Trigger: Connects theConditionsto Widget-sideEventActionsbased on theactionRuleId.
Widget-level
EventAction: Controls the result widget's behavior when the success or failure condition is met. A result widget, for example, can be aTextParagraphthat contains an error message which only becomes visible when the validation fails.actionRuleId: Matches theactionRuleIdin the card-sideTrigger.CommonWidgetAction: Defines actions that don't involve evaluations, such as updating widget visibility.UpdateVisibilityAction: An action that updates a widget's visibility state (VISIBLE or HIDDEN).
The following example demonstrates how to implement CEL validation to check if two text inputs are equal. An error message is shown if they are not equal.
-
Figure 1: When the failConditionis met (inputs are not equal), the error message widget is set toVISIBLEand appears. -
Figure 2: When the successConditionis met (inputs are equal), the error message widget is set toHIDDENand doesn't appear.
Here're the example application code and JSON manifest file:
Apps Script
function onConfig() {
// Create a Card
let card = CardService.newCardBuilder();
const textInput_1 = CardService.newTextInput()
.setTitle("Input number 1")
.setFieldName("value1"); // FieldName's value must match a corresponding ID defined in the inputs[] array in the manifest file.
const textInput_2 = CardService.newTextInput()
.setTitle("Input number 2")
.setFieldName("value2"); // FieldName's value must match a corresponding ID defined in the inputs[] array in the manifest file.
let sections = CardService.newCardSection()
.setHeader("Two number equals")
.addWidget(textInput_1)
.addWidget(textInput_2);
// CEL Validation
// Define Conditions
const condition_success = CardService.newCondition()
.setActionRuleId("CEL_TEXTINPUT_SUCCESS_RULE_ID")
.setExpressionDataCondition(
CardService.newExpressionDataCondition()
.setConditionType(
CardService.ExpressionDataConditionType.EXPRESSION_EVALUATION_SUCCESS));
const condition_fail = CardService.newCondition()
.setActionRuleId("CEL_TEXTINPUT_FAILURE_RULE_ID")
.setExpressionDataCondition(
CardService.newExpressionDataCondition()
.setConditionType(
CardService.ExpressionDataConditionType.EXPRESSION_EVALUATION_FAILURE));
// Define Card-side EventAction
const expressionDataAction = CardService.newExpressionDataAction()
.setActionType(
CardService.ExpressionDataActionType.START_EXPRESSION_EVALUATION);
// Define Triggers for each Condition respectively
const trigger_success = CardService.newTrigger()
.setActionRuleId("CEL_TEXTINPUT_SUCCESS_RULE_ID");
const trigger_failure = CardService.newTrigger()
.setActionRuleId("CEL_TEXTINPUT_FAILURE_RULE_ID");
const eventAction = CardService.newEventAction()
.setActionRuleId("CEL_TEXTINPUT_EVALUATION_RULE_ID")
.setExpressionDataAction(expressionDataAction)
.addPostEventTrigger(trigger_success)
.addPostEventTrigger(trigger_failure);
// Define ExpressionData for the current Card
const expressionData = CardService.newExpressionData()
.setId("expData_id")
.setExpression("value1 == value2") // CEL expression
.addCondition(condition_success)
.addCondition(condition_fail)
.addEventAction(eventAction);
card = card.addExpressionData(expressionData);
// Create Widget-side EventActions and a widget to display error message
const widgetEventActionFail = CardService.newEventAction()
.setActionRuleId("CEL_TEXTINPUT_FAILURE_RULE_ID")
.setCommonWidgetAction(
CardService.newCommonWidgetAction()
.setUpdateVisibilityAction(
CardService.newUpdateVisibilityAction()
.setVisibility(
CardService.Visibility.VISIBLE)));
const widgetEventActionSuccess = CardService.newEventAction()
.setActionRuleId("CEL_TEXTINPUT_SUCCESS_RULE_ID")
.setCommonWidgetAction(
CardService.newCommonWidgetAction()
.setUpdateVisibilityAction(
CardService.newUpdateVisibilityAction()
.setVisibility(
CardService.Visibility.HIDDEN)));
const errorWidget = CardService.newTextParagraph()
.setText("The first and second value must match.")
.setVisibility(CardService.Visibility.HIDDEN) // Initially hidden
.addEventAction(widgetEventActionFail)
.addEventAction(widgetEventActionSuccess);
sections = sections.addWidget(errorWidget);
card = card.addSection(sections);
// Build and return the Card
return card.build();
}
JSON manifest file
{
"timeZone": "America/Los_Angeles",
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"addOns": {
"common": {
"name": "CEL validation example",
"logoUrl": "https://www.gstatic.com/images/branding/productlogos/calculator_search/v1/web-24dp/logo_calculator_search_color_1x_web_24dp.png",
"useLocaleFromApp": true
},
"flows": {
"workflowElements": [
{
"id": "actionElement",
"state": "ACTIVE",
"name": "CEL Demo",
"description": "Demonstrates CEL Validation",
"workflowAction": {
"inputs": [
{
"id": "value1",
"description": "The first number",
"cardinality": "SINGLE",
"dataType": {
"basicType": "INTEGER"
}
},
{
"id": "value2",
"description": "The second number",
"cardinality": "SINGLE",
"dataType": {
"basicType": "INTEGER"
}
}
],
"onConfigFunction": "onConfig",
"onExecuteFunction": "onExecute"
}
}
]
}
}
}
Supported CEL validation widgets and operations
Card widgets that support CEL validation
The following widgets support CEL validation:
TextInputSelectionInputDateTimePicker
Supported CEL validation operations
- Arithmetic operations
+: Adds twoint64,uint64, ordoublenumbers.-: Subtracts twoint64,uint64, ordoublenumbers.*: Multiplies twoint64,uint64, ordoublenumbers./: Divides twoint64,uint64, ordoublenumbers (integer division).%: Computes the modulo of twoint64oruint64numbers.-: Negates anint64oruint64number.
- Logical operations:
&&: Performs a logicalANDoperation on two boolean values.||: Performs a logicalORoperation on two boolean values.!: Performs a logicalNOToperation on a boolean value.
- Comparison Operations:
==: Checks if two values are equal. Supports numbers and lists.!=: Checks if two values are not equal. Supports numbers and lists.<: Checks if the firstint64,uint64, ordoublenumber is less than the second.<=: Checks if the firstint64,uint64, ordoublenumber is less than or equal to the second.>: Checks if the firstint64,uint64, ordoublenumber is greater than the second.>=: Checks if the firstint64,uint64, ordoublenumber is greater than or equal to the second.
- List Operations:
in: Checks if a value is present in a list. Supports numbers, strings and nested lists.size: Returns the number of items in a list. Supports numbers and nested lists.
Unsupported CEL validation scenarios
- Incorrect Argument Sizes for Binary Operations: Binary operations (for example,
add_int64, equals) require exactly two arguments. Providing a different number of arguments will throw an error. - Incorrect Argument Sizes for Unary Operations: Unary operations (for example,
negate_int64) require exactly one argument. Providing a different number of arguments will throw an error. - Unsupported Types in Numerical Operations: Numerical binary and unary operations only accept number arguments. Providing other types (for example, boolean) will throw an error.
Server-side validation
With server-side validation, you can run server-side logic by specifying the
onSaveFunction() in your step's code. When the user navigates away from the
step's configuration card, onSaveFunction() runs and lets you verify the
user's input.
If the user's input is valid, return saveWorkflowAction.
If the user's input is invalid, return a configuration card that displays an error message to the user that explains how to resolve the error.
Because server-side validation is asynchronous, the user might not know about the input error until they publish their flow.
Each validated input's id in the manifest file must match a card widget's
name in the code.
The following example validates that a user text input includes the "@" sign:
Manifest file
The manifest file excerpt specifies an onSaveFunction() named
"onSave":
JSON
{
"timeZone": "America/Los_Angeles",
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"addOns": {
"common": {
"name": "Server-side validation example",
"logoUrl": "https://www.gstatic.com/images/branding/productlogos/calculator_search/v1/web-24dp/logo_calculator_search_color_1x_web_24dp.png",
"useLocaleFromApp": true
},
"flows": {
"workflowElements": [
{
"id": "actionElement",
"state": "ACTIVE",
"name": "Calculate",
"description": "Asks the user for an email address",
"workflowAction": {
"inputs": [
{
"id": "email",
"description": "email address",
"cardinality": "SINGLE",
"required": true,
"dataType": {
"basicType": "STRING"
}
}
],
"onConfigFunction": "onConfigCalculate",
"onExecuteFunction": "onExecuteCalculate",
"onSaveFunction": "onSave"
}
}
]
}
}
}
Application code
The step's code includes a function called onSave(). It validates that a
user-inputted string includes @. If it does, it saves the flow step. If it
doesn't, it returns a configuration card with an error message explaining how to
fix the error.
Apps Script
/**
* Validates user input asynchronously when the user
* navigates away from a step's configuration card.
*/
function onSave(event) {
// "email" matches the input ID specified in the manifest file.
var email = event.workflow.actionInvocation.inputs["email"];
// Validate that the email address contains an "@" sign:
if(email.includes("@")) {
// If successfully validated, save and proceed.
return {
"hostAppAction" : {
"workflowAction" : {
"saveWorkflowAction" : {}
}
}
};
// If the input is invalid, return a card with an error message
} else {
var card = {
"sections": [
{
"header": "Collect Email",
"widgets": [
{
"textInput": {
"name": "email",
"label": "email address",
"hostAppDataSource" : {
"workflowDataSource" : {
"includeVariables" : true
}
}
}
},
{
"textParagraph": {
"text": "<b>Error:</b> Email addresses must include the '@' sign.",
"maxLines": 1
}
}
]
}
]
};
return pushCard(card);
}
}
Related topics
- Define an input variable
- Log activity and errors
- Flows event object
- Common Expression Language (CEL)