diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h
index a5565dc391..7a6f13536e 100644
--- a/drivers/net/mlx5/mlx5.h
+++ b/drivers/net/mlx5/mlx5.h
@@ -1678,6 +1678,8 @@ struct mlx5_priv {
 	struct mlx5_hw_q *hw_q;
 	/* HW steering rte flow table list header. */
 	LIST_HEAD(flow_hw_tbl, rte_flow_template_table) flow_hw_tbl;
+	struct mlx5dr_action *hw_push_vlan[MLX5DR_TABLE_TYPE_MAX];
+	struct mlx5dr_action *hw_pop_vlan[MLX5DR_TABLE_TYPE_MAX];
 	struct mlx5dr_action **hw_vport;
 	/* HW steering global drop action. */
 	struct mlx5dr_action *hw_drop[2];
diff --git a/drivers/net/mlx5/mlx5_flow.h b/drivers/net/mlx5/mlx5_flow.h
index 9621c167d7..ae1d30b0d0 100644
--- a/drivers/net/mlx5/mlx5_flow.h
+++ b/drivers/net/mlx5/mlx5_flow.h
@@ -2454,4 +2454,8 @@ int mlx5_flow_pattern_validate(struct rte_eth_dev *dev,
 		struct rte_flow_error *error);
 int flow_hw_table_update(struct rte_eth_dev *dev,
 			 struct rte_flow_error *error);
+int mlx5_flow_item_field_width(struct rte_eth_dev *dev,
+			   enum rte_flow_field_id field, int inherit,
+			   const struct rte_flow_attr *attr,
+			   struct rte_flow_error *error);
 #endif /* RTE_PMD_MLX5_FLOW_H_ */
diff --git a/drivers/net/mlx5/mlx5_flow_dv.c b/drivers/net/mlx5/mlx5_flow_dv.c
index 0b65167451..b7f3ee3d7e 100644
--- a/drivers/net/mlx5/mlx5_flow_dv.c
+++ b/drivers/net/mlx5/mlx5_flow_dv.c
@@ -1322,7 +1322,7 @@ flow_dv_convert_action_modify_ipv6_dscp
 					     MLX5_MODIFICATION_TYPE_SET, error);
 }
 
-static int
+int
 mlx5_flow_item_field_width(struct rte_eth_dev *dev,
 			   enum rte_flow_field_id field, int inherit,
 			   const struct rte_flow_attr *attr,
diff --git a/drivers/net/mlx5/mlx5_flow_hw.c b/drivers/net/mlx5/mlx5_flow_hw.c
index ac349d35ef..0c4e18a4bd 100644
--- a/drivers/net/mlx5/mlx5_flow_hw.c
+++ b/drivers/net/mlx5/mlx5_flow_hw.c
@@ -44,12 +44,22 @@
 /* Lowest priority for HW non-root table. */
 #define MLX5_HW_LOWEST_PRIO_NON_ROOT (UINT32_MAX)
 
+#define MLX5_HW_VLAN_PUSH_TYPE_IDX 0
+#define MLX5_HW_VLAN_PUSH_VID_IDX 1
+#define MLX5_HW_VLAN_PUSH_PCP_IDX 2
+
 static int flow_hw_flush_all_ctrl_flows(struct rte_eth_dev *dev);
 static int flow_hw_translate_group(struct rte_eth_dev *dev,
 				   const struct mlx5_flow_template_table_cfg *cfg,
 				   uint32_t group,
 				   uint32_t *table_group,
 				   struct rte_flow_error *error);
+static __rte_always_inline int
+flow_hw_set_vlan_vid_construct(struct rte_eth_dev *dev,
+			       struct mlx5_hw_q_job *job,
+			       struct mlx5_action_construct_data *act_data,
+			       const struct mlx5_hw_actions *hw_acts,
+			       const struct rte_flow_action *action);
 
 const struct mlx5_flow_driver_ops mlx5_flow_hw_drv_ops;
 
@@ -1065,6 +1075,52 @@ flow_hw_cnt_compile(struct rte_eth_dev *dev, uint32_t  start_pos,
 	return 0;
 }
 
+static __rte_always_inline bool
+is_of_vlan_pcp_present(const struct rte_flow_action *actions)
+{
+	/*
+	 * Order of RTE VLAN push actions is
+	 * OF_PUSH_VLAN / OF_SET_VLAN_VID [ / OF_SET_VLAN_PCP ]
+	 */
+	return actions[MLX5_HW_VLAN_PUSH_PCP_IDX].type ==
+		RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_PCP;
+}
+
+static __rte_always_inline bool
+is_template_masked_push_vlan(const struct rte_flow_action_of_push_vlan *mask)
+{
+	/*
+	 * In masked push VLAN template all RTE push actions are masked.
+	 */
+	return mask && mask->ethertype != 0;
+}
+
+static rte_be32_t vlan_hdr_to_be32(const struct rte_flow_action *actions)
+{
+/*
+ * OpenFlow Switch Specification defines 801.1q VID as 12+1 bits.
+ */
+	rte_be32_t type, vid, pcp;
+#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN
+	rte_be32_t vid_lo, vid_hi;
+#endif
+
+	type = ((const struct rte_flow_action_of_push_vlan *)
+		actions[MLX5_HW_VLAN_PUSH_TYPE_IDX].conf)->ethertype;
+	vid = ((const struct rte_flow_action_of_set_vlan_vid *)
+		actions[MLX5_HW_VLAN_PUSH_VID_IDX].conf)->vlan_vid;
+	pcp = is_of_vlan_pcp_present(actions) ?
+	      ((const struct rte_flow_action_of_set_vlan_pcp *)
+		      actions[MLX5_HW_VLAN_PUSH_PCP_IDX].conf)->vlan_pcp : 0;
+#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN
+	vid_hi = vid & 0xff;
+	vid_lo = vid >> 8;
+	return (((vid_lo << 8) | (pcp << 5) | vid_hi) << 16) | type;
+#else
+	return (type << 16) | (pcp << 13) | vid;
+#endif
+}
+
 /**
  * Translate rte_flow actions to DR action.
  *
@@ -1167,6 +1223,26 @@ __flow_hw_actions_translate(struct rte_eth_dev *dev,
 				priv->hw_tag[!!attr->group];
 			flow_hw_rxq_flag_set(dev, true);
 			break;
+		case RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN:
+			action_pos = at->actions_off[actions - at->actions];
+			acts->rule_acts[action_pos].action =
+				priv->hw_push_vlan[type];
+			if (is_template_masked_push_vlan(masks->conf))
+				acts->rule_acts[action_pos].push_vlan.vlan_hdr =
+					vlan_hdr_to_be32(actions);
+			else if (__flow_hw_act_data_general_append
+					(priv, acts, actions->type,
+					 actions - action_start, action_pos))
+				goto err;
+			actions += is_of_vlan_pcp_present(actions) ?
+					MLX5_HW_VLAN_PUSH_PCP_IDX :
+					MLX5_HW_VLAN_PUSH_VID_IDX;
+			break;
+		case RTE_FLOW_ACTION_TYPE_OF_POP_VLAN:
+			action_pos = at->actions_off[actions - at->actions];
+			acts->rule_acts[action_pos].action =
+				priv->hw_pop_vlan[type];
+			break;
 		case RTE_FLOW_ACTION_TYPE_JUMP:
 			action_pos = at->actions_off[actions - at->actions];
 			if (masks->conf &&
@@ -1787,8 +1863,17 @@ flow_hw_actions_construct(struct rte_eth_dev *dev,
 		cnt_id_t cnt_id;
 
 		action = &actions[act_data->action_src];
-		MLX5_ASSERT(action->type == RTE_FLOW_ACTION_TYPE_INDIRECT ||
-			    (int)action->type == act_data->type);
+		/*
+		 * action template construction replaces
+		 * OF_SET_VLAN_VID with MODIFY_FIELD
+		 */
+		if (action->type == RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID)
+			MLX5_ASSERT(act_data->type ==
+				    RTE_FLOW_ACTION_TYPE_MODIFY_FIELD);
+		else
+			MLX5_ASSERT(action->type ==
+				    RTE_FLOW_ACTION_TYPE_INDIRECT ||
+				    (int)action->type == act_data->type);
 		switch (act_data->type) {
 		case RTE_FLOW_ACTION_TYPE_INDIRECT:
 			if (flow_hw_shared_action_construct
@@ -1804,6 +1889,10 @@ flow_hw_actions_construct(struct rte_eth_dev *dev,
 			      (action->conf))->id);
 			rule_acts[act_data->action_dst].tag.value = tag;
 			break;
+		case RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN:
+			rule_acts[act_data->action_dst].push_vlan.vlan_hdr =
+				vlan_hdr_to_be32(action);
+			break;
 		case RTE_FLOW_ACTION_TYPE_JUMP:
 			jump_group = ((const struct rte_flow_action_jump *)
 						action->conf)->group;
@@ -1855,10 +1944,16 @@ flow_hw_actions_construct(struct rte_eth_dev *dev,
 				    act_data->encap.len);
 			break;
 		case RTE_FLOW_ACTION_TYPE_MODIFY_FIELD:
-			ret = flow_hw_modify_field_construct(job,
-							     act_data,
-							     hw_acts,
-							     action);
+			if (action->type == RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID)
+				ret = flow_hw_set_vlan_vid_construct(dev, job,
+								     act_data,
+								     hw_acts,
+								     action);
+			else
+				ret = flow_hw_modify_field_construct(job,
+								     act_data,
+								     hw_acts,
+								     action);
 			if (ret)
 				return -1;
 			break;
@@ -2562,9 +2657,14 @@ flow_hw_table_create(struct rte_eth_dev *dev,
 			mlx5_ipool_destroy(tbl->flow);
 		mlx5_free(tbl);
 	}
-	rte_flow_error_set(error, err,
-			  RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
-			  "fail to create rte table");
+	if (error != NULL) {
+		rte_flow_error_set(error, err,
+				error->type == RTE_FLOW_ERROR_TYPE_NONE ?
+				RTE_FLOW_ERROR_TYPE_UNSPECIFIED : error->type,
+				NULL,
+				error->message == NULL ?
+				"fail to create rte table" : error->message);
+	}
 	return NULL;
 }
 
@@ -2868,28 +2968,76 @@ flow_hw_action_meta_copy_insert(const struct rte_flow_action actions[],
 				uint16_t *ins_pos)
 {
 	uint16_t idx, total = 0;
-	bool ins = false;
+	uint16_t end_idx = UINT16_MAX;
 	bool act_end = false;
+	bool modify_field = false;
+	bool rss_or_queue = false;
 
 	MLX5_ASSERT(actions && masks);
 	MLX5_ASSERT(new_actions && new_masks);
 	MLX5_ASSERT(ins_actions && ins_masks);
 	for (idx = 0; !act_end; idx++) {
-		if (idx >= MLX5_HW_MAX_ACTS)
-			return -1;
-		if (actions[idx].type == RTE_FLOW_ACTION_TYPE_RSS ||
-		    actions[idx].type == RTE_FLOW_ACTION_TYPE_QUEUE) {
-			ins = true;
-			*ins_pos = idx;
-		}
-		if (actions[idx].type == RTE_FLOW_ACTION_TYPE_END)
+		switch (actions[idx].type) {
+		case RTE_FLOW_ACTION_TYPE_RSS:
+		case RTE_FLOW_ACTION_TYPE_QUEUE:
+			/* It is assumed that application provided only single RSS/QUEUE action. */
+			MLX5_ASSERT(!rss_or_queue);
+			rss_or_queue = true;
+			break;
+		case RTE_FLOW_ACTION_TYPE_MODIFY_FIELD:
+			modify_field = true;
+			break;
+		case RTE_FLOW_ACTION_TYPE_END:
+			end_idx = idx;
 			act_end = true;
+			break;
+		default:
+			break;
+		}
 	}
-	if (!ins)
+	if (!rss_or_queue)
 		return 0;
-	else if (idx == MLX5_HW_MAX_ACTS)
+	else if (idx >= MLX5_HW_MAX_ACTS)
 		return -1; /* No more space. */
 	total = idx;
+	/*
+	 * If actions template contains MODIFY_FIELD action, then meta copy action can be inserted
+	 * at the template's end. Position of MODIFY_HDR action is based on the position of the
+	 * first MODIFY_FIELD flow action.
+	 */
+	if (modify_field) {
+		*ins_pos = end_idx;
+		goto insert_meta_copy;
+	}
+	/*
+	 * If actions template does not contain MODIFY_FIELD action, then meta copy action must be
+	 * inserted at aplace conforming with action order defined in steering/mlx5dr_action.c.
+	 */
+	act_end = false;
+	for (idx = 0; !act_end; idx++) {
+		switch (actions[idx].type) {
+		case RTE_FLOW_ACTION_TYPE_COUNT:
+		case RTE_FLOW_ACTION_TYPE_METER:
+		case RTE_FLOW_ACTION_TYPE_METER_MARK:
+		case RTE_FLOW_ACTION_TYPE_CONNTRACK:
+		case RTE_FLOW_ACTION_TYPE_VXLAN_ENCAP:
+		case RTE_FLOW_ACTION_TYPE_NVGRE_ENCAP:
+		case RTE_FLOW_ACTION_TYPE_RAW_ENCAP:
+		case RTE_FLOW_ACTION_TYPE_RSS:
+		case RTE_FLOW_ACTION_TYPE_QUEUE:
+			*ins_pos = idx;
+			act_end = true;
+			break;
+		case RTE_FLOW_ACTION_TYPE_END:
+			act_end = true;
+			break;
+		default:
+			break;
+		}
+	}
+insert_meta_copy:
+	MLX5_ASSERT(*ins_pos != UINT16_MAX);
+	MLX5_ASSERT(*ins_pos < total);
 	/* Before the position, no change for the actions. */
 	for (idx = 0; idx < *ins_pos; idx++) {
 		new_actions[idx] = actions[idx];
@@ -2906,6 +3054,73 @@ flow_hw_action_meta_copy_insert(const struct rte_flow_action actions[],
 	return 0;
 }
 
+static int
+flow_hw_validate_action_push_vlan(struct rte_eth_dev *dev,
+				  const
+				  struct rte_flow_actions_template_attr *attr,
+				  const struct rte_flow_action *action,
+				  const struct rte_flow_action *mask,
+				  struct rte_flow_error *error)
+{
+#define X_FIELD(ptr, t, f) (((ptr)->conf) && ((t *)((ptr)->conf))->f)
+
+	const bool masked_push =
+		X_FIELD(mask + MLX5_HW_VLAN_PUSH_TYPE_IDX,
+			const struct rte_flow_action_of_push_vlan, ethertype);
+	bool masked_param;
+
+	/*
+	 * Mandatory actions order:
+	 * OF_PUSH_VLAN / OF_SET_VLAN_VID [ / OF_SET_VLAN_PCP ]
+	 */
+	RTE_SET_USED(dev);
+	RTE_SET_USED(attr);
+	/* Check that mark matches OF_PUSH_VLAN */
+	if (mask[MLX5_HW_VLAN_PUSH_TYPE_IDX].type !=
+	    RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN)
+		return rte_flow_error_set(error, EINVAL,
+					  RTE_FLOW_ERROR_TYPE_ACTION,
+					  action, "OF_PUSH_VLAN: mask does not match");
+	/* Check that the second template and mask items are SET_VLAN_VID */
+	if (action[MLX5_HW_VLAN_PUSH_VID_IDX].type !=
+	    RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID ||
+	    mask[MLX5_HW_VLAN_PUSH_VID_IDX].type !=
+	    RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID)
+		return rte_flow_error_set(error, EINVAL,
+					  RTE_FLOW_ERROR_TYPE_ACTION,
+					  action, "OF_PUSH_VLAN: invalid actions order");
+	masked_param = X_FIELD(mask + MLX5_HW_VLAN_PUSH_VID_IDX,
+			       const struct rte_flow_action_of_set_vlan_vid,
+			       vlan_vid);
+	/*
+	 * PMD requires OF_SET_VLAN_VID mask to must match OF_PUSH_VLAN
+	 */
+	if (masked_push ^ masked_param)
+		return rte_flow_error_set(error, EINVAL,
+					  RTE_FLOW_ERROR_TYPE_ACTION, action,
+					  "OF_SET_VLAN_VID: mask does not match OF_PUSH_VLAN");
+	if (is_of_vlan_pcp_present(action)) {
+		if (mask[MLX5_HW_VLAN_PUSH_PCP_IDX].type !=
+		     RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_PCP)
+			return rte_flow_error_set(error, EINVAL,
+						  RTE_FLOW_ERROR_TYPE_ACTION,
+						  action, "OF_SET_VLAN_PCP: missing mask configuration");
+		masked_param = X_FIELD(mask + MLX5_HW_VLAN_PUSH_PCP_IDX,
+				       const struct
+				       rte_flow_action_of_set_vlan_pcp,
+				       vlan_pcp);
+		/*
+		 * PMD requires OF_SET_VLAN_PCP mask to must match OF_PUSH_VLAN
+		 */
+		if (masked_push ^ masked_param)
+			return rte_flow_error_set(error, EINVAL,
+						  RTE_FLOW_ERROR_TYPE_ACTION, action,
+						  "OF_SET_VLAN_PCP: mask does not match OF_PUSH_VLAN");
+	}
+	return 0;
+#undef X_FIELD
+}
+
 static int
 flow_hw_actions_validate(struct rte_eth_dev *dev,
 			const struct rte_flow_actions_template_attr *attr,
@@ -2996,6 +3211,18 @@ flow_hw_actions_validate(struct rte_eth_dev *dev,
 		case RTE_FLOW_ACTION_TYPE_CONNTRACK:
 			/* TODO: Validation logic */
 			break;
+		case RTE_FLOW_ACTION_TYPE_OF_POP_VLAN:
+		case RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID:
+			break;
+		case RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN:
+			ret = flow_hw_validate_action_push_vlan
+					(dev, attr, action, mask, error);
+			if (ret != 0)
+				return ret;
+			i += is_of_vlan_pcp_present(action) ?
+				MLX5_HW_VLAN_PUSH_PCP_IDX :
+				MLX5_HW_VLAN_PUSH_VID_IDX;
+			break;
 		case RTE_FLOW_ACTION_TYPE_END:
 			actions_end = true;
 			break;
@@ -3023,6 +3250,8 @@ static enum mlx5dr_action_type mlx5_hw_dr_action_types[] = {
 	[RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT] = MLX5DR_ACTION_TYP_VPORT,
 	[RTE_FLOW_ACTION_TYPE_COUNT] = MLX5DR_ACTION_TYP_CTR,
 	[RTE_FLOW_ACTION_TYPE_CONNTRACK] = MLX5DR_ACTION_TYP_ASO_CT,
+	[RTE_FLOW_ACTION_TYPE_OF_POP_VLAN] = MLX5DR_ACTION_TYP_POP_VLAN,
+	[RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN] = MLX5DR_ACTION_TYP_PUSH_VLAN,
 };
 
 static int
@@ -3139,6 +3368,14 @@ flow_hw_dr_actions_template_create(struct rte_flow_actions_template *at)
 				goto err_actions_num;
 			action_types[curr_off++] = MLX5DR_ACTION_TYP_FT;
 			break;
+		case RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN:
+			type = mlx5_hw_dr_action_types[at->actions[i].type];
+			at->actions_off[i] = curr_off;
+			action_types[curr_off++] = type;
+			i += is_of_vlan_pcp_present(at->actions + i) ?
+				MLX5_HW_VLAN_PUSH_PCP_IDX :
+				MLX5_HW_VLAN_PUSH_VID_IDX;
+			break;
 		default:
 			type = mlx5_hw_dr_action_types[at->actions[i].type];
 			at->actions_off[i] = curr_off;
@@ -3166,6 +3403,89 @@ flow_hw_dr_actions_template_create(struct rte_flow_actions_template *at)
 	return NULL;
 }
 
+static void
+flow_hw_set_vlan_vid(struct rte_eth_dev *dev,
+		     struct rte_flow_action *ra,
+		     struct rte_flow_action *rm,
+		     struct rte_flow_action_modify_field *spec,
+		     struct rte_flow_action_modify_field *mask,
+		     int set_vlan_vid_ix)
+{
+	struct rte_flow_error error;
+	const bool masked = rm[set_vlan_vid_ix].conf &&
+		(((const struct rte_flow_action_of_set_vlan_vid *)
+			rm[set_vlan_vid_ix].conf)->vlan_vid != 0);
+	const struct rte_flow_action_of_set_vlan_vid *conf =
+		ra[set_vlan_vid_ix].conf;
+	rte_be16_t vid = masked ? conf->vlan_vid : 0;
+	int width = mlx5_flow_item_field_width(dev, RTE_FLOW_FIELD_VLAN_ID, 0,
+					       NULL, &error);
+	*spec = (typeof(*spec)) {
+		.operation = RTE_FLOW_MODIFY_SET,
+		.dst = {
+			.field = RTE_FLOW_FIELD_VLAN_ID,
+			.level = 0, .offset = 0,
+		},
+		.src = {
+			.field = RTE_FLOW_FIELD_VALUE,
+			.level = vid,
+			.offset = 0,
+		},
+		.width = width,
+	};
+	*mask = (typeof(*mask)) {
+		.operation = RTE_FLOW_MODIFY_SET,
+		.dst = {
+			.field = RTE_FLOW_FIELD_VLAN_ID,
+			.level = 0xffffffff, .offset = 0xffffffff,
+		},
+		.src = {
+			.field = RTE_FLOW_FIELD_VALUE,
+			.level = masked ? (1U << width) - 1 : 0,
+			.offset = 0,
+		},
+		.width = 0xffffffff,
+	};
+	ra[set_vlan_vid_ix].type = RTE_FLOW_ACTION_TYPE_MODIFY_FIELD;
+	ra[set_vlan_vid_ix].conf = spec;
+	rm[set_vlan_vid_ix].type = RTE_FLOW_ACTION_TYPE_MODIFY_FIELD;
+	rm[set_vlan_vid_ix].conf = mask;
+}
+
+static __rte_always_inline int
+flow_hw_set_vlan_vid_construct(struct rte_eth_dev *dev,
+			       struct mlx5_hw_q_job *job,
+			       struct mlx5_action_construct_data *act_data,
+			       const struct mlx5_hw_actions *hw_acts,
+			       const struct rte_flow_action *action)
+{
+	struct rte_flow_error error;
+	rte_be16_t vid = ((const struct rte_flow_action_of_set_vlan_vid *)
+			   action->conf)->vlan_vid;
+	int width = mlx5_flow_item_field_width(dev, RTE_FLOW_FIELD_VLAN_ID, 0,
+					       NULL, &error);
+	struct rte_flow_action_modify_field conf = {
+		.operation = RTE_FLOW_MODIFY_SET,
+		.dst = {
+			.field = RTE_FLOW_FIELD_VLAN_ID,
+			.level = 0, .offset = 0,
+		},
+		.src = {
+			.field = RTE_FLOW_FIELD_VALUE,
+			.level = vid,
+			.offset = 0,
+		},
+		.width = width,
+	};
+	struct rte_flow_action modify_action = {
+		.type = RTE_FLOW_ACTION_TYPE_MODIFY_FIELD,
+		.conf = &conf
+	};
+
+	return flow_hw_modify_field_construct(job, act_data, hw_acts,
+					      &modify_action);
+}
+
 /**
  * Create flow action template.
  *
@@ -3191,14 +3511,18 @@ flow_hw_actions_template_create(struct rte_eth_dev *dev,
 			struct rte_flow_error *error)
 {
 	struct mlx5_priv *priv = dev->data->dev_private;
-	int len, act_num, act_len, mask_len;
+	int len, act_len, mask_len;
+	unsigned int act_num;
 	unsigned int i;
 	struct rte_flow_actions_template *at = NULL;
-	uint16_t pos = MLX5_HW_MAX_ACTS;
+	uint16_t pos = UINT16_MAX;
 	struct rte_flow_action tmp_action[MLX5_HW_MAX_ACTS];
 	struct rte_flow_action tmp_mask[MLX5_HW_MAX_ACTS];
-	const struct rte_flow_action *ra;
-	const struct rte_flow_action *rm;
+	struct rte_flow_action *ra = (void *)(uintptr_t)actions;
+	struct rte_flow_action *rm = (void *)(uintptr_t)masks;
+	int set_vlan_vid_ix = -1;
+	struct rte_flow_action_modify_field set_vlan_vid_spec = {0, };
+	struct rte_flow_action_modify_field set_vlan_vid_mask = {0, };
 	const struct rte_flow_action_modify_field rx_mreg = {
 		.operation = RTE_FLOW_MODIFY_SET,
 		.dst = {
@@ -3238,21 +3562,58 @@ flow_hw_actions_template_create(struct rte_eth_dev *dev,
 		return NULL;
 	if (priv->sh->config.dv_xmeta_en == MLX5_XMETA_MODE_META32_HWS &&
 	    priv->sh->config.dv_esw_en) {
+		/* Application should make sure only one Q/RSS exist in one rule. */
 		if (flow_hw_action_meta_copy_insert(actions, masks, &rx_cpy, &rx_cpy_mask,
 						    tmp_action, tmp_mask, &pos)) {
 			rte_flow_error_set(error, EINVAL,
 					   RTE_FLOW_ERROR_TYPE_ACTION, NULL,
 					   "Failed to concatenate new action/mask");
 			return NULL;
+		} else if (pos != UINT16_MAX) {
+			ra = tmp_action;
+			rm = tmp_mask;
 		}
 	}
-	/* Application should make sure only one Q/RSS exist in one rule. */
-	if (pos == MLX5_HW_MAX_ACTS) {
-		ra = actions;
-		rm = masks;
-	} else {
-		ra = tmp_action;
-		rm = tmp_mask;
+	for (i = 0; ra[i].type != RTE_FLOW_ACTION_TYPE_END; ++i) {
+		switch (ra[i].type) {
+		/* OF_PUSH_VLAN *MUST* come before OF_SET_VLAN_VID */
+		case RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN:
+			i += is_of_vlan_pcp_present(ra + i) ?
+				MLX5_HW_VLAN_PUSH_PCP_IDX :
+				MLX5_HW_VLAN_PUSH_VID_IDX;
+			break;
+		case RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID:
+			set_vlan_vid_ix = i;
+			break;
+		default:
+			break;
+		}
+	}
+	/*
+	 * Count flow actions to allocate required space for storing DR offsets and to check
+	 * if temporary buffer would not be overrun.
+	 */
+	act_num = i + 1;
+	if (act_num >= MLX5_HW_MAX_ACTS) {
+		rte_flow_error_set(error, EINVAL,
+				   RTE_FLOW_ERROR_TYPE_ACTION, NULL, "Too many actions");
+		return NULL;
+	}
+	if (set_vlan_vid_ix != -1) {
+		/* If temporary action buffer was not used, copy template actions to it */
+		if (ra == actions && rm == masks) {
+			for (i = 0; i < act_num; ++i) {
+				tmp_action[i] = actions[i];
+				tmp_mask[i] = masks[i];
+				if (actions[i].type == RTE_FLOW_ACTION_TYPE_END)
+					break;
+			}
+			ra = tmp_action;
+			rm = tmp_mask;
+		}
+		flow_hw_set_vlan_vid(dev, ra, rm,
+				     &set_vlan_vid_spec, &set_vlan_vid_mask,
+				     set_vlan_vid_ix);
 	}
 	act_len = rte_flow_conv(RTE_FLOW_CONV_OP_ACTIONS, NULL, 0, ra, error);
 	if (act_len <= 0)
@@ -3262,10 +3623,6 @@ flow_hw_actions_template_create(struct rte_eth_dev *dev,
 	if (mask_len <= 0)
 		return NULL;
 	len += RTE_ALIGN(mask_len, 16);
-	/* Count flow actions to allocate required space for storing DR offsets. */
-	act_num = 0;
-	for (i = 0; ra[i].type != RTE_FLOW_ACTION_TYPE_END; ++i)
-		act_num++;
 	len += RTE_ALIGN(act_num * sizeof(*at->actions_off), 16);
 	at = mlx5_malloc(MLX5_MEM_ZERO, len + sizeof(*at),
 			 RTE_CACHE_LINE_SIZE, rte_socket_id());
@@ -4513,7 +4870,11 @@ flow_hw_create_tx_default_mreg_copy_table(struct rte_eth_dev *dev,
 		.attr = tx_tbl_attr,
 		.external = false,
 	};
-	struct rte_flow_error drop_err;
+	struct rte_flow_error drop_err = {
+		.type = RTE_FLOW_ERROR_TYPE_NONE,
+		.cause = NULL,
+		.message = NULL,
+	};
 
 	RTE_SET_USED(drop_err);
 	return flow_hw_table_create(dev, &tx_tbl_cfg, &pt, 1, &at, 1, &drop_err);
@@ -4794,6 +5155,60 @@ flow_hw_ct_pool_create(struct rte_eth_dev *dev,
 	return NULL;
 }
 
+static void
+flow_hw_destroy_vlan(struct rte_eth_dev *dev)
+{
+	struct mlx5_priv *priv = dev->data->dev_private;
+	enum mlx5dr_table_type i;
+
+	for (i = MLX5DR_TABLE_TYPE_NIC_RX; i < MLX5DR_TABLE_TYPE_MAX; i++) {
+		if (priv->hw_pop_vlan[i]) {
+			mlx5dr_action_destroy(priv->hw_pop_vlan[i]);
+			priv->hw_pop_vlan[i] = NULL;
+		}
+		if (priv->hw_push_vlan[i]) {
+			mlx5dr_action_destroy(priv->hw_push_vlan[i]);
+			priv->hw_push_vlan[i] = NULL;
+		}
+	}
+}
+
+static int
+flow_hw_create_vlan(struct rte_eth_dev *dev)
+{
+	struct mlx5_priv *priv = dev->data->dev_private;
+	enum mlx5dr_table_type i;
+	const enum mlx5dr_action_flags flags[MLX5DR_TABLE_TYPE_MAX] = {
+		MLX5DR_ACTION_FLAG_HWS_RX,
+		MLX5DR_ACTION_FLAG_HWS_TX,
+		MLX5DR_ACTION_FLAG_HWS_FDB
+	};
+
+	for (i = MLX5DR_TABLE_TYPE_NIC_RX; i <= MLX5DR_TABLE_TYPE_NIC_TX; i++) {
+		priv->hw_pop_vlan[i] =
+			mlx5dr_action_create_pop_vlan(priv->dr_ctx, flags[i]);
+		if (!priv->hw_pop_vlan[i])
+			return -ENOENT;
+		priv->hw_push_vlan[i] =
+			mlx5dr_action_create_push_vlan(priv->dr_ctx, flags[i]);
+		if (!priv->hw_pop_vlan[i])
+			return -ENOENT;
+	}
+	if (priv->sh->config.dv_esw_en && priv->master) {
+		priv->hw_pop_vlan[MLX5DR_TABLE_TYPE_FDB] =
+			mlx5dr_action_create_pop_vlan
+				(priv->dr_ctx, MLX5DR_ACTION_FLAG_HWS_FDB);
+		if (!priv->hw_pop_vlan[MLX5DR_TABLE_TYPE_FDB])
+			return -ENOENT;
+		priv->hw_push_vlan[MLX5DR_TABLE_TYPE_FDB] =
+			mlx5dr_action_create_push_vlan
+				(priv->dr_ctx, MLX5DR_ACTION_FLAG_HWS_FDB);
+		if (!priv->hw_pop_vlan[MLX5DR_TABLE_TYPE_FDB])
+			return -ENOENT;
+	}
+	return 0;
+}
+
 /**
  * Configure port HWS resources.
  *
@@ -4996,6 +5411,9 @@ flow_hw_configure(struct rte_eth_dev *dev,
 		if (priv->hws_cpool == NULL)
 			goto err;
 	}
+	ret = flow_hw_create_vlan(dev);
+	if (ret)
+		goto err;
 	return 0;
 err:
 	if (priv->hws_ctpool) {
@@ -5013,6 +5431,7 @@ flow_hw_configure(struct rte_eth_dev *dev,
 		if (priv->hw_tag[i])
 			mlx5dr_action_destroy(priv->hw_tag[i]);
 	}
+	flow_hw_destroy_vlan(dev);
 	if (dr_ctx)
 		claim_zero(mlx5dr_context_close(dr_ctx));
 	mlx5_free(priv->hw_q);
@@ -5072,6 +5491,7 @@ flow_hw_resource_release(struct rte_eth_dev *dev)
 		if (priv->hw_tag[i])
 			mlx5dr_action_destroy(priv->hw_tag[i]);
 	}
+	flow_hw_destroy_vlan(dev);
 	flow_hw_free_vport_actions(priv);
 	if (priv->acts_ipool) {
 		mlx5_ipool_destroy(priv->acts_ipool);
