• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » How to securely integrate Shopify headless API endpoints into WordPress custom plugins using Shortcode API

How to securely integrate Shopify headless API endpoints into WordPress custom plugins using Shortcode API

Setting Up Your WordPress Environment for Shopify API Integration

Before we dive into the custom plugin development, ensure your WordPress installation is ready. This involves setting up a local development environment (e.g., Local by Flywheel, Docker with a WordPress image) and having a Shopify store with API credentials. For this guide, we’ll assume you have a Shopify store and have generated API keys (API Key and API Password) from your Shopify Partner dashboard or your store’s private app settings. These credentials will be used to authenticate your requests to the Shopify Headless API.

We’ll be using PHP’s cURL extension for making HTTP requests to the Shopify API. Ensure that the cURL extension is enabled in your PHP installation. You can check this by creating a PHP file with phpinfo(); and looking for the cURL section. If it’s not enabled, you’ll need to uncomment the relevant line in your php.ini file (e.g., extension=curl) and restart your web server.

Creating the WordPress Plugin Structure

Let’s start by creating the basic structure for our custom WordPress plugin. Navigate to your WordPress installation’s wp-content/plugins/ directory and create a new folder for your plugin, for example, shopify-headless-integration. Inside this folder, create the main plugin file, shopify-headless-integration.php.

The main plugin file needs a standard WordPress plugin header to be recognized by the WordPress admin dashboard.

/*
Plugin Name: Shopify Headless Integration
Plugin URI: https://example.com/plugins/shopify-headless-integration/
Description: Integrates Shopify Headless API endpoints into WordPress using Shortcodes.
Version: 1.0
Author: Your Name
Author URI: https://example.com/
License: GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: shopify-headless-integration
Domain Path: /languages
*/

// Prevent direct access to the file
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// Plugin core logic will go here

Storing Shopify API Credentials Securely

Hardcoding API credentials directly into your plugin file is a significant security risk. A better approach is to store them in WordPress’s configuration or use environment variables. For this example, we’ll use WordPress’s constants defined in wp-config.php. This is a common and secure practice.

Open your wp-config.php file (located in the root of your WordPress installation) and add the following lines, replacing the placeholder values with your actual Shopify API Key and Password:

/**
 * Shopify API Credentials
 */
define( 'SHOPIFY_API_KEY', 'YOUR_SHOPIFY_API_KEY' );
define( 'SHOPIFY_API_PASSWORD', 'YOUR_SHOPIFY_API_PASSWORD' );
define( 'SHOPIFY_STORE_DOMAIN', 'your-store-name.myshopify.com' ); // e.g., 'my-awesome-store.myshopify.com'

Now, in your plugin file (shopify-headless-integration.php), you can access these constants.

Fetching Products from Shopify API

We’ll create a function to fetch products from the Shopify API. This function will use cURL to make a GET request to the Shopify Products API endpoint. The authentication will be done using Basic Authentication with your API Key and Password.

The Shopify API endpoint for products is typically https://{store_domain}/admin/api/{api_version}/products.json. For this example, we’ll use a recent API version, but you should always refer to the Shopify Admin API documentation for the latest versions and available endpoints.

/**
 * Fetches products from the Shopify API.
 *
 * @return array|WP_Error An array of products or a WP_Error object on failure.
 */
function fetch_shopify_products() {
    if ( ! defined( 'SHOPIFY_API_KEY' ) || ! defined( 'SHOPIFY_API_PASSWORD' ) || ! defined( 'SHOPIFY_STORE_DOMAIN' ) ) {
        return new WP_Error( 'shopify_api_credentials_missing', __( 'Shopify API credentials are not configured.', 'shopify-headless-integration' ) );
    }

    $api_version = '2023-10'; // Use a current or desired API version
    $url = "https://" . SHOPIFY_STORE_DOMAIN . "/admin/api/{$api_version}/products.json";

    $ch = curl_init();

    curl_setopt( $ch, CURLOPT_URL, $url );
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
    curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json',
        'X-Shopify-Access-Token: ' . SHOPIFY_API_PASSWORD // For private apps, use the password as the token
    ) );
    // For public apps or custom apps with OAuth, you'd use a different authentication method.
    // For basic auth (less common for API access, more for basic HTTP auth):
    // curl_setopt( $ch, CURLOPT_USERPWD, SHOPIFY_API_KEY . ':' . SHOPIFY_API_PASSWORD );

    $response = curl_exec( $ch );
    $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
    $curl_error = curl_error( $ch );

    curl_close( $ch );

    if ( $curl_error ) {
        return new WP_Error( 'shopify_curl_error', sprintf( __( 'cURL Error: %s', 'shopify-headless-integration' ), $curl_error ) );
    }

    if ( $http_code >= 400 ) {
        return new WP_Error( 'shopify_api_error', sprintf( __( 'Shopify API Error (HTTP %d): %s', 'shopify-headless-integration' ), $http_code, $response ) );
    }

    $products_data = json_decode( $response, true );

    if ( json_last_error() !== JSON_ERROR_NONE ) {
        return new WP_Error( 'shopify_json_decode_error', __( 'Failed to decode Shopify API response.', 'shopify-headless-integration' ) );
    }

    // The actual product data is usually nested under a 'products' key
    if ( isset( $products_data['products'] ) ) {
        return $products_data['products'];
    } else {
        return new WP_Error( 'shopify_unexpected_response', __( 'Unexpected response format from Shopify API.', 'shopify-headless-integration' ) );
    }
}

Note on Authentication: The example above uses X-Shopify-Access-Token which is the standard for private apps and custom apps. If you were using basic HTTP authentication (less common for API access and generally not recommended for production), you would uncomment the CURLOPT_USERPWD line. Always consult the Shopify API documentation for the most current and secure authentication methods.

Implementing the Shortcode

Now, let’s create a shortcode that will allow users to display Shopify products on their WordPress posts or pages. We’ll use the WordPress Shortcode API for this.

Add the following code to your shopify-headless-integration.php file:

/**
 * Shortcode to display Shopify products.
 * Usage: [shopify_products limit="10"]
 *
 * @param array $atts Shortcode attributes.
 * @return string HTML output for the products.
 */
function shopify_products_shortcode( $atts ) {
    $atts = shortcode_atts( array(
        'limit' => 5, // Default number of products to display
    ), $atts, 'shopify_products' );

    $limit = intval( $atts['limit'] );
    if ( $limit <= 0 ) {
        $limit = 5; // Ensure a positive limit
    }

    // Fetch products from Shopify API
    $products = fetch_shopify_products();

    if ( is_wp_error( $products ) ) {
        return '

' . esc_html( $products->get_error_message() ) . '

'; } if ( empty( $products ) ) { return '

' . __( 'No products found.', 'shopify-headless-integration' ) . '

'; } // Limit the number of products to display $products_to_display = array_slice( $products, 0, $limit ); // Start output buffering ob_start(); ?>

Featured Products

  • <?php echo esc_attr( $product['title'] ); ?>

    ...

    View Product

In this shortcode function:

  • We define a shortcode [shopify_products] that accepts a limit attribute to control how many products are displayed.
  • It calls our fetch_shopify_products() function.
  • It handles potential errors returned by the API call.
  • It iterates through the fetched products (up to the specified limit) and generates basic HTML to display the product title, the first image, a snippet of the description, and a link to the product on the Shopify store.
  • We use output buffering (ob_start() and ob_get_clean()) to capture the HTML output.
  • esc_html(), esc_url(), esc_attr(), and wp_kses_post() are used for sanitizing output to prevent XSS vulnerabilities.

Displaying Products on a WordPress Page

After activating your Shopify Headless Integration plugin in the WordPress admin area, you can now use the shortcode on any post or page. Simply edit a post or page and insert the shortcode like this:

[shopify_products limit="5"]

This will render a list of the first 5 products from your Shopify store on that page.

Advanced Considerations and Enhancements

This basic implementation provides a foundation. For a production-ready solution, consider the following:

Caching API Responses

Making API calls on every page load can be inefficient and might hit Shopify's API rate limits. Implement caching for API responses. WordPress offers the Transients API for this purpose.

/**
 * Fetches products from the Shopify API with caching.
 *
 * @param int $limit Number of products to fetch.
 * @return array|WP_Error An array of products or a WP_Error object on failure.
 */
function fetch_shopify_products_cached( $limit = 5 ) {
    $cache_key = 'shopify_products_cache_' . md5( $limit );
    $cached_products = get_transient( $cache_key );

    if ( false !== $cached_products ) {
        return $cached_products; // Return cached data if available
    }

    // If not cached, fetch from API
    if ( ! defined( 'SHOPIFY_API_KEY' ) || ! defined( 'SHOPIFY_API_PASSWORD' ) || ! defined( 'SHOPIFY_STORE_DOMAIN' ) ) {
        return new WP_Error( 'shopify_api_credentials_missing', __( 'Shopify API credentials are not configured.', 'shopify-headless-integration' ) );
    }

    $api_version = '2023-10';
    $url = "https://" . SHOPIFY_STORE_DOMAIN . "/admin/api/{$api_version}/products.json?limit=" . $limit; // Add limit to API call

    $ch = curl_init();
    curl_setopt( $ch, CURLOPT_URL, $url );
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
    curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json',
        'X-Shopify-Access-Token: ' . SHOPIFY_API_PASSWORD
    ) );

    $response = curl_exec( $ch );
    $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
    $curl_error = curl_error( $ch );
    curl_close( $ch );

    if ( $curl_error ) {
        return new WP_Error( 'shopify_curl_error', sprintf( __( 'cURL Error: %s', 'shopify-headless-integration' ), $curl_error ) );
    }

    if ( $http_code >= 400 ) {
        return new WP_Error( 'shopify_api_error', sprintf( __( 'Shopify API Error (HTTP %d): %s', 'shopify-headless-integration' ), $http_code, $response ) );
    }

    $products_data = json_decode( $response, true );

    if ( json_last_error() !== JSON_ERROR_NONE ) {
        return new WP_Error( 'shopify_json_decode_error', __( 'Failed to decode Shopify API response.', 'shopify-headless-integration' ) );
    }

    if ( isset( $products_data['products'] ) ) {
        $products = $products_data['products'];
        // Cache the result for 1 hour (3600 seconds)
        set_transient( $cache_key, $products, HOUR_IN_SECONDS );
        return $products;
    } else {
        return new WP_Error( 'shopify_unexpected_response', __( 'Unexpected response format from Shopify API.', 'shopify-headless-integration' ) );
    }
}

// Update the shortcode to use the cached function
function shopify_products_shortcode( $atts ) {
    $atts = shortcode_atts( array(
        'limit' => 5,
    ), $atts, 'shopify_products' );

    $limit = intval( $atts['limit'] );
    if ( $limit <= 0 ) {
        $limit = 5;
    }

    $products = fetch_shopify_products_cached( $limit ); // Use cached function

    if ( is_wp_error( $products ) ) {
        return '

' . esc_html( $products->get_error_message() ) . '

'; } if ( empty( $products ) ) { return '

' . __( 'No products found.', 'shopify-headless-integration' ) . '

'; } // No need to slice if the API call already respects the limit // $products_to_display = array_slice( $products, 0, $limit ); ob_start(); ?>

Featured Products

  • <?php echo esc_attr( $product['title'] ); ?>

    ...

    View Product

Error Handling and Logging

The current error handling returns messages to the user. For debugging, it's crucial to log errors. You can use WordPress's built-in error_log() function or a more sophisticated logging library.

// Example of logging an error
if ( is_wp_error( $products ) ) {
    error_log( 'Shopify Integration Error: ' . $products->get_error_message() );
    return '

' . __( 'An error occurred while fetching products. Please try again later.', 'shopify-headless-integration' ) . '

'; }

Styling the Output

The generated HTML is basic. You'll want to enqueue a CSS file to style the product listings. Create a css/ directory in your plugin folder and add a style.css file. Then, enqueue it in your plugin file:

/**
 * Enqueue plugin styles.
 */
function shopify_integration_enqueue_styles() {
    wp_enqueue_style( 'shopify-integration-style', plugin_dir_url( __FILE__ ) . 'css/style.css', array(), '1.0' );
}
add_action( 'wp_enqueue_scripts', 'shopify_integration_enqueue_styles' );

Your css/style.css could then contain rules for .shopify-products-container, .shopify-product-list, and .shopify-product-item.

Fetching Specific Product Data

You can extend this by creating shortcodes for specific products (using product ID or handle) or product collections. This would involve modifying the fetch_shopify_products() function to accept parameters for filtering or fetching single resources.

/**
 * Fetches a single product by its handle from the Shopify API.
 *
 * @param string $handle The product handle.
 * @return array|WP_Error Product data or WP_Error on failure.
 */
function fetch_shopify_product_by_handle( $handle ) {
    if ( ! defined( 'SHOPIFY_API_KEY' ) || ! defined( 'SHOPIFY_API_PASSWORD' ) || ! defined( 'SHOPIFY_STORE_DOMAIN' ) ) {
        return new WP_Error( 'shopify_api_credentials_missing', __( 'Shopify API credentials are not configured.', 'shopify-headless-integration' ) );
    }

    $api_version = '2023-10';
    $url = "https://" . SHOPIFY_STORE_DOMAIN . "/admin/api/{$api_version}/products/{$handle}.json"; // Endpoint for a single product

    $ch = curl_init();
    curl_setopt( $ch, CURLOPT_URL, $url );
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
    curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json',
        'X-Shopify-Access-Token: ' . SHOPIFY_API_PASSWORD
    ) );

    $response = curl_exec( $ch );
    $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
    $curl_error = curl_error( $ch );
    curl_close( $ch );

    if ( $curl_error ) {
        return new WP_Error( 'shopify_curl_error', sprintf( __( 'cURL Error: %s', 'shopify-headless-integration' ), $curl_error ) );
    }

    if ( $http_code >= 400 ) {
        return new WP_Error( 'shopify_api_error', sprintf( __( 'Shopify API Error (HTTP %d): %s', 'shopify-headless-integration' ), $http_code, $response ) );
    }

    $product_data = json_decode( $response, true );

    if ( json_last_error() !== JSON_ERROR_NONE ) {
        return new WP_Error( 'shopify_json_decode_error', __( 'Failed to decode Shopify API response.', 'shopify-headless-integration' ) );
    }

    // The actual product data is usually nested under a 'product' key for single product requests
    if ( isset( $product_data['product'] ) ) {
        return $product_data['product'];
    } else {
        return new WP_Error( 'shopify_unexpected_response', __( 'Unexpected response format from Shopify API.', 'shopify-headless-integration' ) );
    }
}

// Example shortcode for a single product
function shopify_single_product_shortcode( $atts ) {
    $atts = shortcode_atts( array(
        'handle' => '', // Product handle is required
    ), $atts, 'shopify_single_product' );

    $handle = sanitize_title( $atts['handle'] ); // Sanitize the handle

    if ( empty( $handle ) ) {
        return '

' . __( 'Product handle is required for [shopify_single_product] shortcode.', 'shopify-headless-integration' ) . '

'; } $product = fetch_shopify_product_by_handle( $handle ); if ( is_wp_error( $product ) ) { error_log( 'Shopify Single Product Error: ' . $product->get_error_message() ); return '

' . __( 'Could not load product.', 'shopify-headless-integration' ) . '

'; } ob_start(); ?>

<?php echo esc_attr( $product['title'] ); ?>

Price:

View Product on Shopify

You can then use this shortcode like: [shopify_single_product handle="your-product-handle"].

Conclusion

By leveraging WordPress's Shortcode API and securely handling API credentials, you can effectively integrate Shopify's headless capabilities into your WordPress site. This approach provides flexibility, allowing you to display Shopify products dynamically within your WordPress content. Remember to prioritize security, performance (through caching), and robust error handling for any production deployment.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Debugging Guide: Diagnosing PHP-FPM child process pool exhaustion in multi-site network environments with modern tools
  • Debugging and Resolving complex namespace class loading collisions issues during heavy concurrent database traffic
  • Step-by-Step Guide: Offloading high-frequency customer support tickets metadata writes to a Redis KV store
  • How to refactor legacy event ticket registers queries using modern WP_Query and custom Transient caching
  • Step-by-Step Guide: Offloading high-frequency member profile directories metadata writes to a Redis KV store

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (662)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • PHP (5)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (647)
  • SEO & Growth (492)
  • Server (118)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (726)
  • WordPress Theme Development (357)

Recent Posts

  • Debugging Guide: Diagnosing PHP-FPM child process pool exhaustion in multi-site network environments with modern tools
  • Debugging and Resolving complex namespace class loading collisions issues during heavy concurrent database traffic
  • Step-by-Step Guide: Offloading high-frequency customer support tickets metadata writes to a Redis KV store

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (726)
  • Debugging & Troubleshooting (662)
  • Security & Compliance (647)
  • SEO & Growth (492)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala