diff --git a/doc/guides/prog_guide/rte_flow.rst b/doc/guides/prog_guide/rte_flow.rst
index 86f8c8bda2..aa9d4e9573 100644
--- a/doc/guides/prog_guide/rte_flow.rst
+++ b/doc/guides/prog_guide/rte_flow.rst
@@ -3626,6 +3626,130 @@ Return values:
 
 - 0 on success, a negative errno value otherwise and ``rte_errno`` is set.
 
+Flow templates
+~~~~~~~~~~~~~~
+
+Oftentimes in an application, many flow rules share a common structure
+(the same pattern and/or action list) so they can be grouped and classified
+together. This knowledge may be used as a source of optimization by a PMD/HW.
+The flow rule creation is done by selecting a table, an item template
+and an action template (which are bound to the table), and setting unique
+values for the items and actions. This API is not thread-safe.
+
+Item templates
+^^^^^^^^^^^^^^
+
+The item template defines a common pattern (the item mask) without values.
+The mask value is used to select a field to match on, spec/last are ignored.
+The item template may be used by multiple tables and must not be destroyed
+until all these tables are destroyed first.
+
+.. code-block:: c
+
+	struct rte_flow_item_template *
+	rte_flow_item_template_create(uint16_t port_id,
+				const struct rte_flow_item_template_attr *it_attr,
+				const struct rte_flow_item items[],
+				struct rte_flow_error *error);
+
+For example, to create an item template to match on the destination MAC:
+
+.. code-block:: c
+
+	struct rte_flow_item root_items[2] = {{0}};
+	struct rte_flow_item_eth eth_m = {0};
+	items[0].type = RTE_FLOW_ITEM_TYPE_ETH;
+	eth_m.dst.addr_bytes = "\xff\xff\xff\xff\xff\xff";
+	items[0].mask = &eth_m;
+	items[1].type = RTE_FLOW_ITEM_TYPE_END;
+
+	struct rte_flow_item_template *it =
+		rte_flow_item_template_create(port, &itr, &items, &error);
+
+The concrete value to match on will be provided at the rule creation.
+
+Action templates
+^^^^^^^^^^^^^^^^
+
+The action template holds a list of action types to be used in flow rules.
+The mask parameter allows specifying a shared constant value for every rule.
+The action template may be used by multiple tables and must not be destroyed
+until all these tables are destroyed first.
+
+.. code-block:: c
+
+	struct rte_flow_action_template *
+	rte_flow_action_template_create(uint16_t port_id,
+				const struct rte_flow_action_template_attr *at_attr,
+				const struct rte_flow_action actions[],
+				const struct rte_flow_action masks[],
+				struct rte_flow_error *error);
+
+For example, to create an action template with the same Mark ID
+but different Queue Index for every rule:
+
+.. code-block:: c
+
+	struct rte_flow_action actions[] = {
+		/* Mark ID is constant (4) for every rule, Queue Index is unique */
+		[0] = {.type = RTE_FLOW_ACTION_TYPE_MARK,
+			   .conf = &(struct rte_flow_action_mark){.id = 4}},
+		[1] = {.type = RTE_FLOW_ACTION_TYPE_QUEUE},
+		[2] = {.type = RTE_FLOW_ACTION_TYPE_END,},
+	};
+	struct rte_flow_action masks[] = {
+		/* Assign to MARK mask any non-zero value to make it constant */
+		[0] = {.type = RTE_FLOW_ACTION_TYPE_MARK,
+			   .conf = &(struct rte_flow_action_mark){.id = 1}},
+		[1] = {.type = RTE_FLOW_ACTION_TYPE_QUEUE},
+		[2] = {.type = RTE_FLOW_ACTION_TYPE_END,},
+	};
+
+	struct rte_flow_action_template *at =
+		rte_flow_action_template_create(port, &atr, &actions, &masks, &error);
+
+The concrete value for Queue Index will be provided at the rule creation.
+
+Flow table
+^^^^^^^^^^
+
+A table combines a number of item and action templates along with shared flow
+rule attributes (group ID, priority and traffic direction). This way a PMD/HW
+can prepare all the resources needed for efficient flow rules creation in
+the datapath. To avoid any hiccups due to memory reallocation, the maximum
+number of flow rules is defined at table creation time. Any flow rule
+creation beyond the maximum table size is rejected. Application may create
+another table to accommodate more rules in this case.
+
+.. code-block:: c
+
+	struct rte_flow_table *
+	rte_flow_table_create(uint16_t port_id,
+				const struct rte_flow_table_attr *table_attr,
+				struct rte_flow_item_template *item_templates[],
+				uint8_t nb_item_templates,
+				struct rte_flow_action_template *action_templates[],
+				uint8_t nb_action_templates,
+				struct rte_flow_error *error);
+
+A table can be created only after the Flow Rules management is configured
+and item and action templates are created.
+
+.. code-block:: c
+
+	rte_flow_configure(port, *port_attr, *error);
+
+	struct rte_flow_item_template *it[0] =
+		rte_flow_item_template_create(port, &itr, &items, &error);
+	struct rte_flow_action_template *at[0] =
+		rte_flow_action_template_create(port, &atr, &actions, &masks, &error);
+
+	struct rte_flow_table *table =
+		rte_flow_table_create(port, *table_attr,
+				*it, nb_item_templates,
+				*at, nb_action_templates,
+				*error);
+
 .. _flow_isolated_mode:
 
 Flow isolated mode
diff --git a/doc/guides/rel_notes/release_22_03.rst b/doc/guides/rel_notes/release_22_03.rst
index 71b3f0a651..af56f54bc4 100644
--- a/doc/guides/rel_notes/release_22_03.rst
+++ b/doc/guides/rel_notes/release_22_03.rst
@@ -58,6 +58,14 @@ New Features
 * ethdev: Added ``rte_flow_configure`` API to configure Flow Management
   library, allowing to pre-allocate some resources for better performance.
 
+* ethdev: Added ``rte_flow_table_create`` API to group flow rules with
+  the same flow attributes and common matching patterns and actions
+  defined by ``rte_flow_item_template_create`` and
+  ``rte_flow_action_template_create`` respectively.
+  Corresponding functions to destroy these entities are:
+  ``rte_flow_table_destroy``, ``rte_flow_item_template_destroy``
+  and ``rte_flow_action_template_destroy`` respectively.
+
 Removed Items
 -------------
 
diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 5b78780ef9..20613f6bed 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -1411,3 +1411,144 @@ rte_flow_configure(uint16_t port_id,
 				  RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
 				  NULL, rte_strerror(ENOTSUP));
 }
+
+struct rte_flow_item_template *
+rte_flow_item_template_create(uint16_t port_id,
+			const struct rte_flow_item_template_attr *it_attr,
+			const struct rte_flow_item items[],
+			struct rte_flow_error *error)
+{
+	struct rte_eth_dev *dev = &rte_eth_devices[port_id];
+	const struct rte_flow_ops *ops = rte_flow_ops_get(port_id, error);
+	struct rte_flow_item_template *template;
+
+	if (unlikely(!ops))
+		return NULL;
+	if (likely(!!ops->item_template_create)) {
+		template = ops->item_template_create(dev, it_attr,
+						     items, error);
+		if (template == NULL)
+			flow_err(port_id, -rte_errno, error);
+		return template;
+	}
+	rte_flow_error_set(error, ENOTSUP,
+			   RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+			   NULL, rte_strerror(ENOTSUP));
+	return NULL;
+}
+
+int
+rte_flow_item_template_destroy(uint16_t port_id,
+			struct rte_flow_item_template *it,
+			struct rte_flow_error *error)
+{
+	struct rte_eth_dev *dev = &rte_eth_devices[port_id];
+	const struct rte_flow_ops *ops = rte_flow_ops_get(port_id, error);
+
+	if (unlikely(!ops))
+		return -rte_errno;
+	if (likely(!!ops->item_template_destroy)) {
+		return flow_err(port_id,
+				ops->item_template_destroy(dev, it, error),
+				error);
+	}
+	return rte_flow_error_set(error, ENOTSUP,
+				  RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+				  NULL, rte_strerror(ENOTSUP));
+}
+
+struct rte_flow_action_template *
+rte_flow_action_template_create(uint16_t port_id,
+			const struct rte_flow_action_template_attr *at_attr,
+			const struct rte_flow_action actions[],
+			const struct rte_flow_action masks[],
+			struct rte_flow_error *error)
+{
+	struct rte_eth_dev *dev = &rte_eth_devices[port_id];
+	const struct rte_flow_ops *ops = rte_flow_ops_get(port_id, error);
+	struct rte_flow_action_template *template;
+
+	if (unlikely(!ops))
+		return NULL;
+	if (likely(!!ops->action_template_create)) {
+		template = ops->action_template_create(dev, at_attr,
+						       actions, masks, error);
+		if (template == NULL)
+			flow_err(port_id, -rte_errno, error);
+		return template;
+	}
+	rte_flow_error_set(error, ENOTSUP,
+			   RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+			   NULL, rte_strerror(ENOTSUP));
+	return NULL;
+}
+
+int
+rte_flow_action_template_destroy(uint16_t port_id,
+			struct rte_flow_action_template *at,
+			struct rte_flow_error *error)
+{
+	struct rte_eth_dev *dev = &rte_eth_devices[port_id];
+	const struct rte_flow_ops *ops = rte_flow_ops_get(port_id, error);
+
+	if (unlikely(!ops))
+		return -rte_errno;
+	if (likely(!!ops->action_template_destroy)) {
+		return flow_err(port_id,
+				ops->action_template_destroy(dev, at, error),
+				error);
+	}
+	return rte_flow_error_set(error, ENOTSUP,
+				  RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+				  NULL, rte_strerror(ENOTSUP));
+}
+
+struct rte_flow_table *
+rte_flow_table_create(uint16_t port_id,
+		      const struct rte_flow_table_attr *table_attr,
+		      struct rte_flow_item_template *item_templates[],
+		      uint8_t nb_item_templates,
+		      struct rte_flow_action_template *action_templates[],
+		      uint8_t nb_action_templates,
+		      struct rte_flow_error *error)
+{
+	struct rte_eth_dev *dev = &rte_eth_devices[port_id];
+	const struct rte_flow_ops *ops = rte_flow_ops_get(port_id, error);
+	struct rte_flow_table *table;
+
+	if (unlikely(!ops))
+		return NULL;
+	if (likely(!!ops->table_create)) {
+		table = ops->table_create(dev, table_attr,
+					  item_templates, nb_item_templates,
+					  action_templates, nb_action_templates,
+					  error);
+		if (table == NULL)
+			flow_err(port_id, -rte_errno, error);
+		return table;
+	}
+	rte_flow_error_set(error, ENOTSUP,
+			   RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+			   NULL, rte_strerror(ENOTSUP));
+	return NULL;
+}
+
+int
+rte_flow_table_destroy(uint16_t port_id,
+		       struct rte_flow_table *table,
+		       struct rte_flow_error *error)
+{
+	struct rte_eth_dev *dev = &rte_eth_devices[port_id];
+	const struct rte_flow_ops *ops = rte_flow_ops_get(port_id, error);
+
+	if (unlikely(!ops))
+		return -rte_errno;
+	if (likely(!!ops->table_destroy)) {
+		return flow_err(port_id,
+				ops->table_destroy(dev, table, error),
+				error);
+	}
+	return rte_flow_error_set(error, ENOTSUP,
+				  RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+				  NULL, rte_strerror(ENOTSUP));
+}
diff --git a/lib/ethdev/rte_flow.h b/lib/ethdev/rte_flow.h
index e145e68525..2e54e9d0e3 100644
--- a/lib/ethdev/rte_flow.h
+++ b/lib/ethdev/rte_flow.h
@@ -4916,6 +4916,275 @@ rte_flow_configure(uint16_t port_id,
 		   const struct rte_flow_port_attr *port_attr,
 		   struct rte_flow_error *error);
 
+/**
+ * Opaque type returned after successful creation of item template.
+ * This handle can be used to manage the created item template.
+ */
+struct rte_flow_item_template;
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Flow item template attributes.
+ */
+__extension__
+struct rte_flow_item_template_attr {
+	/**
+	 * Version of the struct layout, should be 0.
+	 */
+	uint32_t version;
+	/**
+	 * Relaxed matching policy, PMD may match only on items
+	 * with mask member set and skip matching on protocol
+	 * layers specified without any masks.
+	 * If not set, PMD will match on protocol layers
+	 * specified without any masks as well.
+	 * Packet data must be stacked in the same order as the
+	 * protocol layers to match inside packets,
+	 * starting from the lowest.
+	 */
+	uint32_t relaxed_matching:1;
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Create item template.
+ * The item template defines common matching fields (item mask) without values.
+ * For example, matching on 5 tuple TCP flow, the template will be
+ * eth(null) + IPv4(source + dest) + TCP(s_port + d_port),
+ * while values for each rule will be set during the flow rule creation.
+ * The number and order of items in the template must be the same
+ * at the rule creation.
+ *
+ * @param port_id
+ *   Port identifier of Ethernet device.
+ * @param[in] it_attr
+ *   Item template attributes.
+ * @param[in] items
+ *   Pattern specification (list terminated by the END pattern item).
+ *   The spec member of an item is not used unless the end member is used.
+ * @param[out] error
+ *   Perform verbose error reporting if not NULL.
+ *   PMDs initialize this structure in case of error only.
+ *
+ * @return
+ *   Handle on success, NULL otherwise and rte_errno is set.
+ */
+__rte_experimental
+struct rte_flow_item_template *
+rte_flow_item_template_create(uint16_t port_id,
+			const struct rte_flow_item_template_attr *it_attr,
+			const struct rte_flow_item items[],
+			struct rte_flow_error *error);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Destroy item template.
+ * This function may be called only when
+ * there are no more tables referencing this template.
+ *
+ * @param port_id
+ *   Port identifier of Ethernet device.
+ * @param[in] it
+ *   Handle of the template to be destroyed.
+ * @param[out] error
+ *   Perform verbose error reporting if not NULL.
+ *   PMDs initialize this structure in case of error only.
+ *
+ * @return
+ *   0 on success, a negative errno value otherwise and rte_errno is set.
+ */
+__rte_experimental
+int
+rte_flow_item_template_destroy(uint16_t port_id,
+			struct rte_flow_item_template *it,
+			struct rte_flow_error *error);
+
+/**
+ * Opaque type returned after successful creation of action template.
+ * This handle can be used to manage the created action template.
+ */
+struct rte_flow_action_template;
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Flow action template attributes.
+ */
+struct rte_flow_action_template_attr {
+	/**
+	 * Version of the struct layout, should be 0.
+	 */
+	uint32_t version;
+	/* No attributes so far. */
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Create action template.
+ * The action template holds a list of action types without values.
+ * For example, the template to change TCP ports is TCP(s_port + d_port),
+ * while values for each rule will be set during the flow rule creation.
+ * The number and order of actions in the template must be the same
+ * at the rule creation.
+ *
+ * @param port_id
+ *   Port identifier of Ethernet device.
+ * @param[in] at_attr
+ *   Template attributes.
+ * @param[in] actions
+ *   Associated actions (list terminated by the END action).
+ *   The spec member is only used if @p masks spec is non-zero.
+ * @param[in] masks
+ *   List of actions that marks which of the action's member is constant.
+ *   A mask has the same format as the corresponding action.
+ *   If the action field in @p masks is not 0,
+ *   the corresponding value in an action from @p actions will be the part
+ *   of the template and used in all flow rules.
+ *   The order of actions in @p masks is the same as in @p actions.
+ *   In case of indirect actions present in @p actions,
+ *   the actual action type should be present in @p mask.
+ * @param[out] error
+ *   Perform verbose error reporting if not NULL.
+ *   PMDs initialize this structure in case of error only.
+ *
+ * @return
+ *   Handle on success, NULL otherwise and rte_errno is set.
+ */
+__rte_experimental
+struct rte_flow_action_template *
+rte_flow_action_template_create(uint16_t port_id,
+			const struct rte_flow_action_template_attr *at_attr,
+			const struct rte_flow_action actions[],
+			const struct rte_flow_action masks[],
+			struct rte_flow_error *error);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Destroy action template.
+ * This function may be called only when
+ * there are no more tables referencing this template.
+ *
+ * @param port_id
+ *   Port identifier of Ethernet device.
+ * @param[in] at
+ *   Handle to the template to be destroyed.
+ * @param[out] error
+ *   Perform verbose error reporting if not NULL.
+ *   PMDs initialize this structure in case of error only.
+ *
+ * @return
+ *   0 on success, a negative errno value otherwise and rte_errno is set.
+ */
+__rte_experimental
+int
+rte_flow_action_template_destroy(uint16_t port_id,
+			struct rte_flow_action_template *at,
+			struct rte_flow_error *error);
+
+
+/**
+ * Opaque type returned after successful creation of table.
+ * This handle can be used to manage the created table.
+ */
+struct rte_flow_table;
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Table attributes.
+ */
+struct rte_flow_table_attr {
+	/**
+	 * Version of the struct layout, should be 0.
+	 */
+	uint32_t version;
+	/**
+	 * Flow attributes that will be used in the table.
+	 */
+	struct rte_flow_attr flow_attr;
+	/**
+	 * Maximum number of flow rules that this table holds.
+	 */
+	uint32_t nb_flows;
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Create table.
+ * Table is a group of flow rules with the same flow attributes
+ * (group ID, priority and traffic direction) defined for it.
+ * The table holds multiple item and action templates to build a flow rule.
+ * Each rule is free to use any combination of item and action templates
+ * and specify particular values for items and actions it would like to change.
+ *
+ * @param port_id
+ *   Port identifier of Ethernet device.
+ * @param[in] table_attr
+ *   Table attributes.
+ * @param[in] item_templates
+ *   Array of item templates to be used in this table.
+ * @param[in] nb_item_templates
+ *   The number of item templates in the item_templates array.
+ * @param[in] action_templates
+ *   Array of action templates to be used in this table.
+ * @param[in] nb_action_templates
+ *   The number of action templates in the action_templates array.
+ * @param[out] error
+ *   Perform verbose error reporting if not NULL.
+ *   PMDs initialize this structure in case of error only.
+ *
+ * @return
+ *   Handle on success, NULL otherwise and rte_errno is set.
+ */
+__rte_experimental
+struct rte_flow_table *
+rte_flow_table_create(uint16_t port_id,
+		      const struct rte_flow_table_attr *table_attr,
+		      struct rte_flow_item_template *item_templates[],
+		      uint8_t nb_item_templates,
+		      struct rte_flow_action_template *action_templates[],
+		      uint8_t nb_action_templates,
+		      struct rte_flow_error *error);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Destroy table.
+ * This function may be called only when
+ * there are no more flow rules referencing this table.
+ *
+ * @param port_id
+ *   Port identifier of Ethernet device.
+ * @param[in] table
+ *   Handle to the table to be destroyed.
+ * @param[out] error
+ *   Perform verbose error reporting if not NULL.
+ *   PMDs initialize this structure in case of error only.
+ *
+ * @return
+ *   0 on success, a negative errno value otherwise and rte_errno is set.
+ */
+__rte_experimental
+int
+rte_flow_table_destroy(uint16_t port_id,
+		       struct rte_flow_table *table,
+		       struct rte_flow_error *error);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/ethdev/rte_flow_driver.h b/lib/ethdev/rte_flow_driver.h
index 5f722f1a39..cda021c302 100644
--- a/lib/ethdev/rte_flow_driver.h
+++ b/lib/ethdev/rte_flow_driver.h
@@ -157,6 +157,43 @@ struct rte_flow_ops {
 		(struct rte_eth_dev *dev,
 		 const struct rte_flow_port_attr *port_attr,
 		 struct rte_flow_error *err);
+	/** See rte_flow_item_template_create() */
+	struct rte_flow_item_template *(*item_template_create)
+		(struct rte_eth_dev *dev,
+		 const struct rte_flow_item_template_attr *it_attr,
+		 const struct rte_flow_item items[],
+		 struct rte_flow_error *err);
+	/** See rte_flow_item_template_destroy() */
+	int (*item_template_destroy)
+		(struct rte_eth_dev *dev,
+		 struct rte_flow_item_template *it,
+		 struct rte_flow_error *err);
+	/** See rte_flow_action_template_create() */
+	struct rte_flow_action_template *(*action_template_create)
+		(struct rte_eth_dev *dev,
+		 const struct rte_flow_action_template_attr *at_attr,
+		 const struct rte_flow_action actions[],
+		 const struct rte_flow_action masks[],
+		 struct rte_flow_error *err);
+	/** See rte_flow_action_template_destroy() */
+	int (*action_template_destroy)
+		(struct rte_eth_dev *dev,
+		 struct rte_flow_action_template *at,
+		 struct rte_flow_error *err);
+	/** See rte_flow_table_create() */
+	struct rte_flow_table *(*table_create)
+		(struct rte_eth_dev *dev,
+		 const struct rte_flow_table_attr *table_attr,
+		 struct rte_flow_item_template *item_templates[],
+		 uint8_t nb_item_templates,
+		 struct rte_flow_action_template *action_templates[],
+		 uint8_t nb_action_templates,
+		 struct rte_flow_error *err);
+	/** See rte_flow_table_destroy() */
+	int (*table_destroy)
+		(struct rte_eth_dev *dev,
+		 struct rte_flow_table *table,
+		 struct rte_flow_error *err);
 };
 
 /**
diff --git a/lib/ethdev/version.map b/lib/ethdev/version.map
index 7645796739..cfd5e2a3e4 100644
--- a/lib/ethdev/version.map
+++ b/lib/ethdev/version.map
@@ -259,6 +259,12 @@ EXPERIMENTAL {
 
 	# added in 22.03
 	rte_flow_configure;
+	rte_flow_item_template_create;
+	rte_flow_item_template_destroy;
+	rte_flow_action_template_create;
+	rte_flow_action_template_destroy;
+	rte_flow_table_create;
+	rte_flow_table_destroy;
 };
 
 INTERNAL {
