How to Query Loop the Download Files in Single Product Template

Hi, I want Query Loop the “Product Download Files” in Single Product Template with Bricks. How can I do that?

The old fashioned way, I use PHP WordPress Shortcode for this. My code:

public function init(): void {
		add_shortcode( 'get_product_downloads', [ $this, 'get_product_downloads' ] );

public function get_product_downloads(): void {
	global $product;
	$downloads = [];
	if ( $product->is_downloadable() ) {
		foreach ( $product->get_downloads() as $file_id => $file ) {
			$downloads[] = [ 'id' => $file_id, 'name' => $file['name'], 'file' => $file['file'] ];

	echo '<table>';
	foreach ( $downloads as $download ) {
		$name = $download['name'];
		$file = $download['file'];
		echo '<tr>';
		echo '<td>' . $name . '</td>';
		echo '<td>' . "<a href='{$file}' target='_blank'>" . 'Download</a>' . '</td>';
		echo '</tr>';
	echo '</table>';

In Bricks I try the new way. My steps:

  1. I create the Single Product Template
  2. In Single Product Template I create a Block “Download Files” with Query Loop PHP
  3. Inside Block “Download Files” I add Button.
    => What PHP I need for query Download Files of Downloadable Product?
    => What Dynamics Tag {what} I add to Button to get the Download File Name and Download File Url?

Hope for your help, thanks :heart:

You could create a custom query that returns the array of downloads. Each download item in the loop could be exposed as a superglobal, so your dynamic data tag can access it. It just did something like that in one of my projects.

I created two libraries that come in helpful to register queries in loops:

You would need a custom query that work like this:

	'product_downloads', // Query name
	'Product Downloads', // Query label
	function(array $args, $query_obj, Query $query) { // Callback for query args

		global $product;
		$downloads = [];
		if ( $product->is_downloadable() ) {
			foreach ( $product->get_downloads() as $file_id => $file ) {
				$downloads[] = [ 'id' => $file_id, 'name' => $file['name'], 'file' => $file['file'] ];

			return $downloads;

		return [];


DDT_Registry::getInstance()->set( 'product_download', 'Product Download', 'Your Group', function( $post, $context, $filters ) {

	global $product_downloads_obj;
	if (empty($product_downloads_obj) || !is_array($product_downloads_obj)) {
		return "";

	$filter = $filters[0] ?? '';

	switch ($filter) {
		case 'id':
			return $product_downloads_obj['id'];
		case 'file':
			return $product_downloads_obj['file'];
			return $product_downloads_obj['name'];

This way you just need to select your query and could use dynamic data tags inside that query like this: {product_download:id} or {product_download:file}

The libraries are completely optional of course and you could implement the same with native bricksbuilder hooks etc.