How To Paginate Shopify Products with Product REST API

So you have finally learned how to display products using Shopify Product REST API. However, you have a bit of a problem, you want to display products with pagination and the only thing you think you can do is to create an array of products and display them depending on their keys.

Believe it or not, it’s not actually a bad idea but offers a TON of work.

Shopify Product API (version 2021-07 and beyond) provides a link header that you can use for requesting paginated REST Admin API endpoints and that’s what we’re going to learn today.

Keep in mind that before proceeding, you must know already how to use Shopify API or you have created a Shopify app using our Shopify app development tutorial.

If in case this is your first time, we highly suggest you read our tutorial below.

Read more: How To Create Shopify Apps from Scratch

Video Tutorial

If you prefer watching video tutorials, you can watch the video version of this article here:

Getting Started

Before you can create pagination, it’s important to understand that this will only work for API version 2021-01 and beyond. So if you’re using an old version of the API, then we highly recommend you to go to your app settings and update your API version.

Another important thing is to understand how the product API works. By default, you can return up to 50 products per page but you can also maximize the number up to 250 products.

Each page will provide you a set of links (aka. page info) in the header that you can use to go to the next or previous set of items.

To properly explain this… Let’s say we have 200 products, and we want to display 10 products per page.

If we are on the very first page of our products, the API will give us just one link in the header, for us to be able to go to the next page.

If we are on the second page, the API will give us two links in the header, one for the previous page and one for the next page.

And so on, and so forth.

REST API Function

If this is your first time working with PHP, keep in mind that we are using the following functions.php. In this PHP file, we have a function that allows us to do API calls. So if you don’t have this function, you won’t be able to get products from Shopify API.

<?php function rest_api($token, $shop, $api_endpoint, $query = array(), $method = 'GET', $request_headers = array()) { $url = "https://" . $shop . $api_endpoint; if (!is_null($query) && in_array($method, array('GET', 'DELETE'))) $url = $url . "?" . http_build_query($query); $curl = curl_init($url); curl_setopt($curl, CURLOPT_HEADER, TRUE); curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE); curl_setopt($curl, CURLOPT_MAXREDIRS, 3); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30); curl_setopt($curl, CURLOPT_TIMEOUT, 30); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); $request_headers[] = ""; $headers[] = "Content-Type: application/json"; if (!is_null($token)) $request_headers[] = "X-Shopify-Access-Token: " . $token; curl_setopt($curl, CURLOPT_HTTPHEADER, $request_headers); if ($method != 'GET' && in_array($method, array('POST', 'PUT'))) { if (is_array($query)) $query = json_encode($query); curl_setopt ($curl, CURLOPT_POSTFIELDS, $query); } $response = curl_exec($curl); $error_number = curl_errno($curl); $error_message = curl_error($curl); curl_close($curl); if ($error_number) { return $error_message; } else { $response = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2); $headers = array(); $header_data = explode("\n",$response[0]); $headers['status'] = $header_data[0]; array_shift($header_data); foreach($header_data as $part) { $h = explode(":", $part); $headers[trim($h[0])] = trim($h[1]); } return array('headers' => $headers, 'data' => $response[1]); } }
Code language: HTML, XML (xml)

Displaying Products

If you have been following us for a while, then you probably know already how to display Shopify products using PHP and Shopify API.

Let’s say we have the following code:

<?php require_once("inc/functions.php"); $shop_url = $_GET['shop']; $access_token = 'shpca_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; $products = rest_api($access_token, $shop_url, "/admin/api/2021-07/products.json", array(), 'GET'); $products = json_decode($products['data'], true); ?> <!DOCTYPE html> <html> <head> <title>Shopify Example App</title> </head> <body> <div> <ul id="product-list"> <?php foreach($products as $product){ foreach($product as $key => $value){ echo '<li>' . $value['title'] . '</li>'; } } ?> </ul> </div> </body> </html>
Code language: HTML, XML (xml)

The code above will simply display all the products you have in your store like below.

Displaying all products with Shopify Product API

In total, we have 27 products.

Now, the next thing that we’re going to do is to make limitations for our API. Let’s say we just want to display 5 products per page.

To do that, we need to create an array where we indicate how many items we want to display using the parameter limit.

$array = array( 'limit' => 5 ); $products = rest_api($access_token, $shop_url, "/admin/api/2021-07/products.json", $array, 'GET'); $products = json_decode($products['data'], true);
Code language: PHP (php)
Shopify Product API limit parameter

Awesome! Now we’re just displaying the first 5 products.

Previous & Next Buttons

Now that we have successfully displayed and limit our products. Let’s create the buttons that we’re going to use to navigate to the next page (…and also the previous page of course).

Just underneath your unordered list element, add the following code.

<button type="button" data-info="" data-rel="previous" data-store="<?php echo $shop_url; ?>">Previous</button> <button type="button" data-info="" data-rel="next" data-store="<?php echo $shop_url; ?>">Next</button>
Code language: HTML, XML (xml)
Shopify Product Pagination API previous and next buttons


Updating functions.php

If you’re using the same function that we used for developing Shopify apps, then you’ll most likely encounter the very same bug that we experience whenever we use the rest_api() function.

If you use the said function just for the sake of returning Shopify’s response, then you might think that there’s no problem at all.

However, when you use the function to get headers, specifically for making pagination. You will notice that there’s something wrong/missing and that is the link header.

Shopify API getting the link in the header for pagination

The main reason why that’s happening is because of the ‘colons’ in the header.

The functions.php file is simply using the explode() function to create an array for the header. Then loop through each key in the array, and explode once again with the help of colon this time. That’s also where the issue starts to develop.

Since URLs have their own colon (https://), the function explode will obviously malfunction.

So with all that being said, let’s update our functions.php.

Look for the following lines of code:

foreach($header_data as $part) { $h = explode(":", $part); $headers[trim($h[0])] = trim($h[1]); }
Code language: PHP (php)

and change it to…

foreach($header_data as $part) { $h = explode(":", $part, 2); $headers[trim($h[0])] = trim($h[1]); }
Code language: PHP (php)

The file functions.php is located in inc folder.

Very simple, we only have to add a limit to our explode function. If the explode function hits the first separator, it should end the process.

Now let’s go back to our index file and update the following code:

$array = array( 'limit' => 5 ); $products = rest_api($access_token, $shop_url, "/admin/api/2021-07/products.json", $array, 'GET'); $products = json_decode($products['data'], true);
Code language: PHP (php)


$array = array( 'limit' => 5 ); $products = rest_api($access_token, $shop_url, "/admin/api/2021-07/products.json", $array, 'GET'); $headers = $products['headers']; $products = json_decode($products['data'], true); foreach ($headers as $key => $value) { echo '<div>[' . $key . '] =>' . $value . '</div>'; }
Code language: PHP (php)

In the code above, we simply created a new variable and retrieved the headers.

And then underneath the $products variable, we check each value inside the $headers variable using foreach function

You should have the following output.

Shopify header response for link and product pagination

If you look closely, you can clearly see that the link key only a value of ; rel="next".

That’s because the rest of its value is being converted to an HTML tag instead of a string.

So if you go to your inspector, you will see that everything was enclosed with the <https></https> tag.

Shopify Header API link header being HTML

To solve that issue, we need to create our own function to extract the string between the angle brackets.

So open your functions.php file once again and just underneath your rest_api() function, add the following code.

//shopify_call() { ... } <--- make sure you add the code outside this function function str_btwn($string, $start, $end){ $string = ' ' . $string; $ini = strpos($string, $start); if ($ini == 0) return ''; $ini += strlen($start); $len = strpos($string, $end, $ini) - $ini; return substr($string, $ini, $len); }
Code language: PHP (php)

Going back to index.php file. And just underneath the foreach() function, add the following code.

$nextPageURL = str_btwn($headers['link'], '<', '>'); $nextPageURLparam = parse_url($nextPageURL); parse_str($nextPageURLparam['query'], $value); $page_info = $value['page_info'];
Code language: PHP (php)

Next, we’ll update our Next button’s data-info attribute from.

<button type="button" data-info="" data-rel="next" data-store="<?php echo $shop_url; ?>">Next</button>
Code language: HTML, XML (xml)


<button type="button" data-info="<?php echo $page_info; ?>" data-rel="next" data-store="<?php echo $shop_url; ?>">Next</button>
Code language: HTML, XML (xml)

Scripting Shopify Pagination with AJAX

We basically got everything we need to display our products and pagination. Now let’s work with AJAX so we can retrieve the next and previous products.

Just before your body closing tag, add the following code.

<script src=""></script> <script> $('button').on('click', function(e) { var data_info = $(this).attr('data-info'); var data_rel = $(this).attr('data-rel'); var data_store = $(this).attr('data-store'); if(data_info != '') { $.ajax({ type: "GET", url: "pagination.php", data: { page_info: data_info, rel: data_rel, url: data_store }, dataType: "json", success: function(response) { console.log(response); if( response['prev'] != '' ) { $('button[data-rel="previous"]').attr('data-info', response['prev']); } else { $('button[data-rel="previous"]').attr('data-info', ""); } if( response['next'] != '' ) { $('button[data-rel="next"]').attr('data-info', response['next']); } else { $('button[data-rel="next"]').attr('data-info', ""); } if( response['html'] != '' ) { $('#product-list').html(response['html']); } } }); } }); </script>
Code language: HTML, XML (xml)

Don’t forget to save your files.

The Pagination Script

Now that we have finally programmed our index file, let’s create a new PHP script for our AJAX. Of course, we’ll call it pagination.php.

<?php require_once("inc/functions.php"); $shop_url = $_GET['shop']; $access_token = 'shpca_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; $rel = $_GET['rel']; $page_info = $_GET['page_info'];
Code language: HTML, XML (xml)

First of all, we need to get everything that we need such as our access token, our shop subdomain, and the data values that we passed through AJAX.

Next, we’ll create the API. So just underneath the code, add the following:

//Create an array for the API $array = array( 'limit' => 5, 'page_info' => $page_info, 'rel' => $rel ); $products = rest_api($access_token, $shop_url, "/admin/api/2021-07/products.json", $array, 'GET');
Code language: PHP (php)

Next, we’ll create everything we need for our headers, check if the link header contains two page_infos, otherwise retrieve the default value.

//Get the headers $headers = $products['headers']; //Create an array for link header $link_array = array(); //Check if there's more than one links / page infos. Otherwise, get the one and only link provided if( strpos( $headers['link'], ',' ) !== false ) { $link_array = explode(',', $headers['link'] ); } else { $link = $headers['link']; }
Code language: PHP (php)

Next, we’ll create another set of variables and check if the $link_array count is greater than one. If it is, then we take the two page_infos from each link array. Otherwise, just get what’s provided.

//Create variables for the new page infos $prev_link = ''; $next_link = ''; //Check if the $link_array variable's size is more than one if( sizeof( $link_array ) > 1 ) { $prev_link = $link_array[0]; $prev_link = str_btwn($prev_link, '<', '>'); $param = parse_url($prev_link); parse_str($param['query'], $prev_link); $prev_link = $prev_link['page_info']; $next_link = $link_array[1]; $next_link = str_btwn($next_link, '<', '>'); $param = parse_url($next_link); parse_str($param['query'], $next_link); $next_link = $next_link['page_info']; } else { $rel = explode(";", $headers['link']); $rel = str_btwn($rel[1], '"', '"'); if($rel == "previous") { $prev_link = $link; $prev_link = str_btwn($prev_link, '<', '>'); $param = parse_url($prev_link); parse_str($param['query'], $prev_link); $prev_link = $prev_link['page_info']; $next_link = ""; } else { $next_link = $link; $next_link = str_btwn($next_link, '<', '>'); $param = parse_url($next_link); parse_str($param['query'], $next_link); $next_link = $next_link['page_info']; $prev_link = ""; } }
Code language: PHP (php)

And last but not least, we’ll create the variable for our frontend to display the next set of products.

//Create and loop through the next or previous products $html = ''; $products = json_decode($products['data'], true); foreach($products as $product) { foreach($product as $key => $value) { $html .= '<li>' . $value['title'] . '</li>'; } }
Code language: PHP (php)

Seriously though, this is the last…

Let’s encode everything into JSON and pass it back to AJAX.

//Then we return the values back to ajax echo json_encode( array( 'prev' => $prev_link, 'next' => $next_link, 'html' => $html ) );
Code language: PHP (php)

Save all your files and you should have the following output.


If you have reached this part then good job! You have successfully learned how to create pagination for your Product API. Obviously, there are plenty of other ways to do this but this tutorial is just to give you an idea that it is possible to make paginations for products.

If you have concerns or questions, don’t hesitate to let us know in the comments below!


    1. Hello J,

      Thank you for reading our tutorials! Have you tried outputting the response from shopify_call() function? See what the response is giving you and maybe that gives you an idea of what’s happening.

      Best regards,

  1. Hi, can you please paste the complete code of index.php and functions.php and pagination.php please or add a download button and provide the working file in a download. I have tried with your tutorial multiple times but still pagination is not working. Thank you

  2. Hi Bernard,
    This post really helped me in looping REST API calls to get products on stores that exceed 250 products. I’ve been looking everywhere on where to get the value for “page_info” to retrieve the next group of products.

    The first call is: /admin/api/2021-10/collections/xxxxx/products.json?limit=250
    The second call is: /admin/api/2021-10/collections/xxxxx/products.json?limit=250&page_info=1234567890

    Both of these work in retrieving the correct products in the correct order, however, the third time through the loop, the call goes back to the first set of 250. Most likely because the page_info value has a “prev” and “next” value to it.

    How can I distinguish that I want the NEXT set (or third page) of products in my API call? I tried these examples to no avail:

    and so on.

    Many thanks to everything you have posted thus far!

  3. I had a eureka moment to my earlier question. in the loop I replaced ” $nextPageURL = str_btwn($headers[‘link’], ”);”

    with :

    ” if($i<1){
    $nextPageURL = str_btwn($headers['link'], '’);
    } else {
    $nextPageURL = str_btwn($headers[‘link’], ‘previous’, ‘>’);

    Worked like a charm!

Leave a Reply

Your email address will not be published. Required fields are marked *