diff --git a/drivers/net/mlx5/mlx5_flow_hw.c b/drivers/net/mlx5/mlx5_flow_hw.c
index b89d2cc44f..1f98e1248a 100644
--- a/drivers/net/mlx5/mlx5_flow_hw.c
+++ b/drivers/net/mlx5/mlx5_flow_hw.c
@@ -2047,6 +2047,136 @@ flow_hw_table_destroy(struct rte_eth_dev *dev,
 	return 0;
 }
 
+static int
+flow_hw_validate_action_modify_field(const struct rte_flow_action *action,
+				     const struct rte_flow_action *mask,
+				     struct rte_flow_error *error)
+{
+	const struct rte_flow_action_modify_field *action_conf =
+		action->conf;
+	const struct rte_flow_action_modify_field *mask_conf =
+		mask->conf;
+
+	if (action_conf->operation != mask_conf->operation)
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, action,
+				"modify_field operation mask and template are not equal");
+	if (action_conf->dst.field != mask_conf->dst.field)
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, action,
+				"destination field mask and template are not equal");
+	if (action_conf->dst.field == RTE_FLOW_FIELD_POINTER ||
+	    action_conf->dst.field == RTE_FLOW_FIELD_VALUE)
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, action,
+				"immediate value and pointer cannot be used as destination");
+	if (mask_conf->dst.level != UINT32_MAX)
+		return rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_ACTION, action,
+			"destination encapsulation level must be fully masked");
+	if (mask_conf->dst.offset != UINT32_MAX)
+		return rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_ACTION, action,
+			"destination offset level must be fully masked");
+	if (action_conf->src.field != mask_conf->src.field)
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, action,
+				"destination field mask and template are not equal");
+	if (action_conf->src.field != RTE_FLOW_FIELD_POINTER &&
+	    action_conf->src.field != RTE_FLOW_FIELD_VALUE) {
+		if (mask_conf->src.level != UINT32_MAX)
+			return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, action,
+				"source encapsulation level must be fully masked");
+		if (mask_conf->src.offset != UINT32_MAX)
+			return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, action,
+				"source offset level must be fully masked");
+	}
+	if (mask_conf->width != UINT32_MAX)
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, action,
+				"modify_field width field must be fully masked");
+	return 0;
+}
+
+static int
+flow_hw_action_validate(const struct rte_flow_action actions[],
+			const struct rte_flow_action masks[],
+			struct rte_flow_error *error)
+{
+	int i;
+	bool actions_end = false;
+	int ret;
+
+	for (i = 0; !actions_end; ++i) {
+		const struct rte_flow_action *action = &actions[i];
+		const struct rte_flow_action *mask = &masks[i];
+
+		if (action->type != mask->type)
+			return rte_flow_error_set(error, ENOTSUP,
+						  RTE_FLOW_ERROR_TYPE_ACTION,
+						  action,
+						  "mask type does not match action type");
+		switch (action->type) {
+		case RTE_FLOW_ACTION_TYPE_VOID:
+			break;
+		case RTE_FLOW_ACTION_TYPE_INDIRECT:
+			/* TODO: Validation logic */
+			break;
+		case RTE_FLOW_ACTION_TYPE_MARK:
+			/* TODO: Validation logic */
+			break;
+		case RTE_FLOW_ACTION_TYPE_DROP:
+			/* TODO: Validation logic */
+			break;
+		case RTE_FLOW_ACTION_TYPE_JUMP:
+			/* TODO: Validation logic */
+			break;
+		case RTE_FLOW_ACTION_TYPE_QUEUE:
+			/* TODO: Validation logic */
+			break;
+		case RTE_FLOW_ACTION_TYPE_RSS:
+			/* TODO: Validation logic */
+			break;
+		case RTE_FLOW_ACTION_TYPE_VXLAN_ENCAP:
+			/* TODO: Validation logic */
+			break;
+		case RTE_FLOW_ACTION_TYPE_NVGRE_ENCAP:
+			/* TODO: Validation logic */
+			break;
+		case RTE_FLOW_ACTION_TYPE_VXLAN_DECAP:
+			/* TODO: Validation logic */
+			break;
+		case RTE_FLOW_ACTION_TYPE_NVGRE_DECAP:
+			/* TODO: Validation logic */
+			break;
+		case RTE_FLOW_ACTION_TYPE_RAW_ENCAP:
+			/* TODO: Validation logic */
+			break;
+		case RTE_FLOW_ACTION_TYPE_RAW_DECAP:
+			/* TODO: Validation logic */
+			break;
+		case RTE_FLOW_ACTION_TYPE_MODIFY_FIELD:
+			ret = flow_hw_validate_action_modify_field(action,
+									mask,
+									error);
+			if (ret < 0)
+				return ret;
+			break;
+		case RTE_FLOW_ACTION_TYPE_END:
+			actions_end = true;
+			break;
+		default:
+			return rte_flow_error_set(error, ENOTSUP,
+						  RTE_FLOW_ERROR_TYPE_ACTION,
+						  action,
+						  "action not supported in template API");
+		}
+	}
+	return 0;
+}
+
 /**
  * Create flow action template.
  *
@@ -2075,6 +2205,8 @@ flow_hw_actions_template_create(struct rte_eth_dev *dev,
 	int len, act_len, mask_len, i;
 	struct rte_flow_actions_template *at;
 
+	if (flow_hw_action_validate(actions, masks, error))
+		return NULL;
 	act_len = rte_flow_conv(RTE_FLOW_CONV_OP_ACTIONS,
 				NULL, 0, actions, error);
 	if (act_len <= 0)
