Free shipping on orders over $75  ·  Shop Now

Printify Product and Variant Synchronization

Part 4 of 6 · Printify API · PHP · JSON · MySQL

What We Are Building in Part 4

The local storefront should not require manual copying every time a Printify product, image, price, or variant changes. This chapter builds a controlled synchronization layer between Printify and MySQL.

API Connection

  • Personal access token
  • Shop ID discovery
  • Required request headers
  • Private configuration

Product Import

  • Printify product IDs
  • Blueprint IDs
  • Print provider IDs
  • Titles and descriptions

Variant Sync

  • Color and size
  • Cost and retail price
  • Enabled status
  • Availability

Production Data

  • Mockup images
  • Print-area placeholders
  • Decoration methods
  • Provider and shipping data
Synchronization should update operational data without erasing local storefront content.

Product descriptions, collection copy, SEO fields, likes, reviews, and custom merchandising decisions remain under local control.

Contents

How Synchronization Works

1
API request

Retrieve Printify Products

PHP requests paginated products for the configured Printify shop.

2
Normalization

Convert JSON into Local Records

Product, provider, image, option, and variant values are converted into a predictable local structure.

3
Database

Insert or Update MySQL

Existing records are updated by Printify ID while new products and variants are inserted.

4
Result

Record the Synchronization

The system stores timestamps, counts, warnings, and errors for review.

Understand the Printify Data Model

Blueprint The blank catalog product
Print Provider The manufacturer fulfilling that blueprint
Variant A specific option combination such as Black / XL
Catalog

Blueprint Data

  • Brand and model
  • Catalog images
  • Available providers
  • Possible print areas
Production

Provider Data

  • Provider ID and title
  • Location
  • Available variants
  • Decoration methods
Merchant shop

Product Data

  • Artwork and print areas
  • Enabled variants
  • Retail prices
  • Mockup images
Sellable option

Variant Data

  • Variant ID
  • Color and size
  • Cost and price
  • Enabled and available state

Create and Protect the API Token

A personal merchant integration uses a Printify Personal Access Token. The token should include only the scopes required by the synchronization process.

Scope Purpose
shops.read Retrieve available Printify shops
products.read Retrieve merchant products and variants
catalog.read Retrieve blueprints and catalog variant data
print_providers.read Retrieve print provider information
uploads.read Retrieve uploaded production image information
The token should never appear in browser JavaScript, HTML, public repositories, screenshots, or customer-facing error messages.

Create the Private Printify Configuration

PHP
<?php
/**
 * /private/printify.php
 */

declare(strict_types=1);

const PRINTIFY_API_BASE_URL =
    'https://api.printify.com/v1';

const PRINTIFY_API_TOKEN =
    'REPLACE_WITH_YOUR_PERSONAL_ACCESS_TOKEN';

const PRINTIFY_SHOP_ID =
    12345678;

const PRINTIFY_USER_AGENT =
    'TheConspiracyShirtCompany/1.0 PHP';

function printifyApiUrl(string $path): string
{
    return rtrim(
        PRINTIFY_API_BASE_URL,
        '/'
    ) . '/' . ltrim($path, '/');
}

Create a Reusable Printify API Helper

PHP
<?php

function printifyRequest(
    string $method,
    string $path,
    ?array $body = null
): array {
    $url = printifyApiUrl($path);

    $curl = curl_init($url);

    $headers = [
        'Authorization: Bearer ' .
            PRINTIFY_API_TOKEN,
        'Accept: application/json',
        'Content-Type: application/json;charset=utf-8',
        'User-Agent: ' .
            PRINTIFY_USER_AGENT,
    ];

    $options = [
        CURLOPT_CUSTOMREQUEST => strtoupper($method),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 40,
        CURLOPT_HTTPHEADER => $headers,
    ];

    if ($body !== null) {
        $options[CURLOPT_POSTFIELDS] = json_encode(
            $body,
            JSON_THROW_ON_ERROR |
            JSON_UNESCAPED_SLASHES
        );
    }

    curl_setopt_array($curl, $options);

    $responseBody = curl_exec($curl);
    $curlError    = curl_error($curl);
    $statusCode   = (int) curl_getinfo(
        $curl,
        CURLINFO_HTTP_CODE
    );

    curl_close($curl);

    if ($responseBody === false) {
        throw new RuntimeException(
            'Printify request failed: ' .
            $curlError
        );
    }

    $response = json_decode(
        $responseBody,
        true,
        512,
        JSON_THROW_ON_ERROR
    );

    if ($statusCode < 200 || $statusCode >= 300) {
        throw new RuntimeException(
            sprintf(
                'Printify returned HTTP %d: %s',
                $statusCode,
                json_encode($response)
            )
        );
    }

    return $response;
}

Retrieve the Shop ID

PHP
<?php

$shops = printifyRequest(
    'GET',
    '/shops.json'
);

foreach ($shops as $shop) {
    printf(
        "%d - %s (%s)\n",
        (int) $shop['id'],
        $shop['title'],
        $shop['sales_channel']
    );
}

Store the correct numeric shop ID in the private configuration so every product request targets the intended shop.

Retrieve Every Product Page

Printify product listings are paginated. A complete synchronization should continue until the current page reaches the last page.

PHP
<?php

function fetchAllPrintifyProducts(
    int $shopId,
    int $perPage = 50
): array {
    $allProducts = [];
    $page = 1;

    do {
        $response = printifyRequest(
            'GET',
            sprintf(
                '/shops/%d/products.json?page=%d&limit=%d',
                $shopId,
                $page,
                $perPage
            )
        );

        $products = $response['data'] ?? [];

        foreach ($products as $product) {
            $allProducts[] = $product;
        }

        $currentPage = (int) (
            $response['current_page'] ?? $page
        );

        $lastPage = (int) (
            $response['last_page'] ?? $currentPage
        );

        $page++;
    } while ($currentPage < $lastPage);

    return $allProducts;
}

Retrieve One Complete Product

The list response is useful for discovery, but a detailed sync should request each product individually.

PHP
<?php

function fetchPrintifyProduct(
    int $shopId,
    string $productId
): array {
    return printifyRequest(
        'GET',
        sprintf(
            '/shops/%d/products/%s.json',
            $shopId,
            rawurlencode($productId)
        )
    );
}

Add Synchronization Fields

Store enough platform data to identify changes without replacing locally managed content.

SQL
ALTER TABLE shirts
    ADD COLUMN printify_product_id VARCHAR(100) DEFAULT NULL,
    ADD COLUMN printify_blueprint_id INT UNSIGNED DEFAULT NULL,
    ADD COLUMN printify_provider_id INT UNSIGNED DEFAULT NULL,
    ADD COLUMN printify_status VARCHAR(50) DEFAULT NULL,
    ADD COLUMN printify_synced_at DATETIME DEFAULT NULL,
    ADD COLUMN printify_updated_at DATETIME DEFAULT NULL,
    ADD COLUMN printify_raw_hash CHAR(64) DEFAULT NULL,
    ADD UNIQUE KEY uq_printify_product_id (
        printify_product_id
    ),
    ADD KEY idx_printify_blueprint_id (
        printify_blueprint_id
    ),
    ADD KEY idx_printify_provider_id (
        printify_provider_id
    );
SQL
ALTER TABLE shirt_variants
    ADD COLUMN printify_variant_id INT UNSIGNED DEFAULT NULL,
    ADD COLUMN printify_cost_cents INT UNSIGNED DEFAULT NULL,
    ADD COLUMN printify_price_cents INT UNSIGNED DEFAULT NULL,
    ADD COLUMN printify_sku VARCHAR(150) DEFAULT NULL,
    ADD COLUMN printify_synced_at DATETIME DEFAULT NULL,
    ADD UNIQUE KEY uq_printify_variant (
        shirt_id,
        printify_variant_id
    ),
    ADD KEY idx_printify_variant_id (
        printify_variant_id
    );

Insert or Update the Local Product

Match the local record by printify_product_id. Update operational fields while leaving local descriptions, SEO, likes, reviews, and collections untouched.

PHP
<?php

function upsertPrintifyProduct(
    PDO $pdo,
    array $product
): int {
    $rawHash = hash(
        'sha256',
        json_encode(
            $product,
            JSON_THROW_ON_ERROR |
            JSON_UNESCAPED_SLASHES
        )
    );

    $stmt = $pdo->prepare("
        INSERT INTO shirts (
            item_number,
            title,
            price,
            active,
            printify_product_id,
            printify_blueprint_id,
            printify_provider_id,
            printify_status,
            printify_synced_at,
            printify_updated_at,
            printify_raw_hash
        ) VALUES (
            :item_number,
            :title,
            :price,
            :active,
            :printify_product_id,
            :blueprint_id,
            :provider_id,
            :status,
            UTC_TIMESTAMP(),
            :printify_updated_at,
            :raw_hash
        )
        ON DUPLICATE KEY UPDATE
            title = VALUES(title),
            active = VALUES(active),
            printify_blueprint_id =
                VALUES(printify_blueprint_id),
            printify_provider_id =
                VALUES(printify_provider_id),
            printify_status =
                VALUES(printify_status),
            printify_synced_at =
                UTC_TIMESTAMP(),
            printify_updated_at =
                VALUES(printify_updated_at),
            printify_raw_hash =
                VALUES(printify_raw_hash),
            id = LAST_INSERT_ID(id)
    ");

    $enabledPrices = [];

    foreach ($product['variants'] ?? [] as $variant) {
        if (!empty($variant['is_enabled'])) {
            $enabledPrices[] = (int) (
                $variant['price'] ?? 0
            );
        }
    }

    $lowestPriceCents = $enabledPrices
        ? min($enabledPrices)
        : 0;

    $stmt->execute([
        ':item_number' =>
            nextLocalItemNumber($pdo),
        ':title' =>
            trim((string) $product['title']),
        ':price' =>
            $lowestPriceCents / 100,
        ':active' =>
            !empty($product['visible']) ? 1 : 0,
        ':printify_product_id' =>
            $product['id'],
        ':blueprint_id' =>
            $product['blueprint_id'] ?? null,
        ':provider_id' =>
            $product['print_provider_id'] ?? null,
        ':status' =>
            $product['is_locked'] ?? false
                ? 'locked'
                : 'available',
        ':printify_updated_at' =>
            normalizePrintifyDate(
                $product['updated_at'] ?? null
            ),
        ':raw_hash' =>
            $rawHash,
    ]);

    return (int) $pdo->lastInsertId();
}

Normalize Printify Variants

Product option names can vary by blueprint. Normalize them into local color and size fields before saving.

PHP
<?php

function buildOptionMap(array $product): array
{
    $map = [];

    foreach ($product['options'] ?? [] as $option) {
        $optionName = strtolower(
            trim((string) ($option['name'] ?? ''))
        );

        foreach ($option['values'] ?? [] as $value) {
            $valueId = (int) ($value['id'] ?? 0);

            $map[$valueId] = [
                'type' => $optionName,
                'title' => trim(
                    (string) ($value['title'] ?? '')
                ),
            ];
        }
    }

    return $map;
}

function normalizePrintifyVariant(
    array $variant,
    array $optionMap
): array {
    $color = '';
    $size  = '';

    foreach ($variant['options'] ?? [] as $optionId) {
        $option = $optionMap[(int) $optionId]
            ?? null;

        if (!$option) {
            continue;
        }

        if ($option['type'] === 'color') {
            $color = $option['title'];
        }

        if ($option['type'] === 'size') {
            $size = $option['title'];
        }
    }

    return [
        'printify_variant_id' =>
            (int) $variant['id'],
        'color' => $color,
        'size' => $size,
        'cost_cents' =>
            (int) ($variant['cost'] ?? 0),
        'price_cents' =>
            (int) ($variant['price'] ?? 0),
        'enabled' =>
            !empty($variant['is_enabled']),
        'available' =>
            !empty($variant['is_available']),
        'sku' =>
            trim((string) ($variant['sku'] ?? '')),
    ];
}

Synchronize Variants Safely

PHP
<?php

function syncPrintifyVariants(
    PDO $pdo,
    int $shirtId,
    array $product
): array {
    $optionMap = buildOptionMap($product);
    $seenIds   = [];
    $inserted  = 0;
    $updated   = 0;

    $existingStmt = $pdo->prepare("
        SELECT id
        FROM shirt_variants
        WHERE shirt_id = :shirt_id
          AND printify_variant_id =
              :printify_variant_id
        LIMIT 1
    ");

    $upsertStmt = $pdo->prepare("
        INSERT INTO shirt_variants (
            shirt_id,
            color,
            size,
            price,
            enabled,
            available,
            printify_variant_id,
            printify_cost_cents,
            printify_price_cents,
            printify_sku,
            printify_synced_at
        ) VALUES (
            :shirt_id,
            :color,
            :size,
            :price,
            :enabled,
            :available,
            :printify_variant_id,
            :cost_cents,
            :price_cents,
            :sku,
            UTC_TIMESTAMP()
        )
        ON DUPLICATE KEY UPDATE
            color = VALUES(color),
            size = VALUES(size),
            price = VALUES(price),
            enabled = VALUES(enabled),
            available = VALUES(available),
            printify_cost_cents =
                VALUES(printify_cost_cents),
            printify_price_cents =
                VALUES(printify_price_cents),
            printify_sku = VALUES(printify_sku),
            printify_synced_at =
                UTC_TIMESTAMP()
    ");

    foreach ($product['variants'] ?? [] as $variant) {
        $normalized = normalizePrintifyVariant(
            $variant,
            $optionMap
        );

        $printifyVariantId =
            $normalized['printify_variant_id'];

        $seenIds[] = $printifyVariantId;

        $existingStmt->execute([
            ':shirt_id' => $shirtId,
            ':printify_variant_id' =>
                $printifyVariantId,
        ]);

        $exists = (bool) $existingStmt->fetchColumn();

        $upsertStmt->execute([
            ':shirt_id' => $shirtId,
            ':color' => $normalized['color'],
            ':size' => $normalized['size'],
            ':price' =>
                $normalized['price_cents'] / 100,
            ':enabled' =>
                $normalized['enabled'] ? 1 : 0,
            ':available' =>
                $normalized['available'] ? 1 : 0,
            ':printify_variant_id' =>
                $printifyVariantId,
            ':cost_cents' =>
                $normalized['cost_cents'],
            ':price_cents' =>
                $normalized['price_cents'],
            ':sku' =>
                $normalized['sku'] ?: null,
        ]);

        $exists ? $updated++ : $inserted++;
    }

    markMissingVariantsUnavailable(
        $pdo,
        $shirtId,
        $seenIds
    );

    return [
        'inserted' => $inserted,
        'updated' => $updated,
        'seen' => count($seenIds),
    ];
}

Synchronize Product Images

Store image metadata separately so the storefront can distinguish front, back, sleeve, default, and color-specific mockups.

SQL
CREATE TABLE shirt_images (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

    shirt_id BIGINT UNSIGNED NOT NULL,

    printify_image_id VARCHAR(150) DEFAULT NULL,
    position VARCHAR(50) DEFAULT NULL,
    color VARCHAR(100) DEFAULT NULL,
    image_url VARCHAR(1000) NOT NULL,

    is_default TINYINT(1) NOT NULL DEFAULT 0,
    sort_order INT NOT NULL DEFAULT 0,

    printify_synced_at DATETIME DEFAULT NULL,

    CONSTRAINT fk_shirt_images_shirt
        FOREIGN KEY (shirt_id)
        REFERENCES shirts(id)
        ON DELETE CASCADE,

    UNIQUE KEY uq_shirt_image_url (
        shirt_id,
        image_url(250)
    ),

    KEY idx_shirt_images_shirt (
        shirt_id
    ),

    KEY idx_shirt_images_default (
        shirt_id,
        is_default
    )
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_unicode_ci;
PHP
<?php

function syncPrintifyImages(
    PDO $pdo,
    int $shirtId,
    array $product
): int {
    $deleteStmt = $pdo->prepare("
        DELETE FROM shirt_images
        WHERE shirt_id = :shirt_id
    ");

    $deleteStmt->execute([
        ':shirt_id' => $shirtId,
    ]);

    $insertStmt = $pdo->prepare("
        INSERT INTO shirt_images (
            shirt_id,
            printify_image_id,
            position,
            color,
            image_url,
            is_default,
            sort_order,
            printify_synced_at
        ) VALUES (
            :shirt_id,
            :printify_image_id,
            :position,
            :color,
            :image_url,
            :is_default,
            :sort_order,
            UTC_TIMESTAMP()
        )
    ");

    $count = 0;

    foreach ($product['images'] ?? [] as $index => $image) {
        $insertStmt->execute([
            ':shirt_id' => $shirtId,
            ':printify_image_id' =>
                $image['id'] ?? null,
            ':position' =>
                $image['position'] ?? null,
            ':color' =>
                $image['color'] ?? null,
            ':image_url' =>
                $image['src'],
            ':is_default' =>
                !empty($image['is_default']) ? 1 : 0,
            ':sort_order' => (int) $index,
        ]);

        $count++;
    }

    return $count;
}

Load Blueprint and Print Provider Data

Merchant products contain blueprint and provider IDs. Catalog endpoints add brand, model, provider, print-area, and option details.

PHP
<?php

function fetchBlueprint(int $blueprintId): array
{
    return printifyRequest(
        'GET',
        sprintf(
            '/catalog/blueprints/%d.json',
            $blueprintId
        )
    );
}

function fetchBlueprintProviders(
    int $blueprintId
): array {
    return printifyRequest(
        'GET',
        sprintf(
            '/catalog/blueprints/%d/print_providers.json',
            $blueprintId
        )
    );
}

function fetchProviderVariants(
    int $blueprintId,
    int $providerId
): array {
    return printifyRequest(
        'GET',
        sprintf(
            '/catalog/blueprints/%d/print_providers/%d/variants.json',
            $blueprintId,
            $providerId
        )
    );
}

Store Print Areas and Production Data

Provider variant responses can include option combinations, placeholders, print-area dimensions, and decoration methods.

SQL
CREATE TABLE printify_print_areas (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

    shirt_id BIGINT UNSIGNED NOT NULL,
    printify_variant_id INT UNSIGNED NOT NULL,

    position VARCHAR(100) NOT NULL,
    decoration_method VARCHAR(100) DEFAULT NULL,

    width_px INT UNSIGNED DEFAULT NULL,
    height_px INT UNSIGNED DEFAULT NULL,

    synced_at DATETIME NOT NULL,

    CONSTRAINT fk_print_area_shirt
        FOREIGN KEY (shirt_id)
        REFERENCES shirts(id)
        ON DELETE CASCADE,

    UNIQUE KEY uq_print_area (
        shirt_id,
        printify_variant_id,
        position,
        decoration_method
    )
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_unicode_ci;

Use a Database Transaction

The product, variants, images, and production records should either all update successfully or all roll back.

PHP
<?php

$pdo->beginTransaction();

try {
    $shirtId = upsertPrintifyProduct(
        $pdo,
        $product
    );

    $variantResult = syncPrintifyVariants(
        $pdo,
        $shirtId,
        $product
    );

    $imageCount = syncPrintifyImages(
        $pdo,
        $shirtId,
        $product
    );

    $pdo->commit();
} catch (Throwable $exception) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }

    throw $exception;
}

Handle Missing and Disabled Variants

A variant that disappears from the latest API response should not remain purchasable locally.

PHP
<?php

function markMissingVariantsUnavailable(
    PDO $pdo,
    int $shirtId,
    array $seenIds
): void {
    if (!$seenIds) {
        $stmt = $pdo->prepare("
            UPDATE shirt_variants
            SET
                available = 0,
                enabled = 0
            WHERE shirt_id = :shirt_id
        ");

        $stmt->execute([
            ':shirt_id' => $shirtId,
        ]);

        return;
    }

    $placeholders = implode(
        ',',
        array_fill(
            0,
            count($seenIds),
            '?'
        )
    );

    $sql = "
        UPDATE shirt_variants
        SET available = 0
        WHERE shirt_id = ?
          AND printify_variant_id
              NOT IN ({$placeholders})
    ";

    $stmt = $pdo->prepare($sql);

    $stmt->execute([
        $shirtId,
        ...$seenIds,
    ]);
}
Marking variants unavailable preserves historical platform mappings, order references, troubleshooting data, and the ability to restore a variant later.

Add Synchronization Logs

SQL
CREATE TABLE printify_sync_log (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

    sync_type VARCHAR(50) NOT NULL,
    status VARCHAR(30) NOT NULL,

    products_seen INT UNSIGNED NOT NULL DEFAULT 0,
    products_updated INT UNSIGNED NOT NULL DEFAULT 0,
    variants_updated INT UNSIGNED NOT NULL DEFAULT 0,
    images_updated INT UNSIGNED NOT NULL DEFAULT 0,

    warning_count INT UNSIGNED NOT NULL DEFAULT 0,
    error_message TEXT DEFAULT NULL,

    started_at DATETIME NOT NULL,
    finished_at DATETIME DEFAULT NULL,

    KEY idx_sync_started_at (started_at),
    KEY idx_sync_status (status)
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_unicode_ci;

Build the Complete Synchronization Script

PHP
<?php
/**
 * /admin/sync-printify.php
 */

declare(strict_types=1);

if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

require_once __DIR__ . '/../../private/connection.php';
require_once __DIR__ . '/../../private/printify.php';
require_once __DIR__ . '/includes/admin-auth.php';
require_once __DIR__ . '/includes/printify-sync-functions.php';

requireAdmin();

set_time_limit(0);

$startedAt = gmdate('Y-m-d H:i:s');

$summary = [
    'products_seen' => 0,
    'products_updated' => 0,
    'variants_updated' => 0,
    'images_updated' => 0,
    'warnings' => [],
];

try {
    $productSummaries = fetchAllPrintifyProducts(
        PRINTIFY_SHOP_ID
    );

    foreach ($productSummaries as $summaryProduct) {
        $summary['products_seen']++;

        $product = fetchPrintifyProduct(
            PRINTIFY_SHOP_ID,
            $summaryProduct['id']
        );

        $pdo->beginTransaction();

        try {
            $shirtId = upsertPrintifyProduct(
                $pdo,
                $product
            );

            $variantResult =
                syncPrintifyVariants(
                    $pdo,
                    $shirtId,
                    $product
                );

            $imageCount =
                syncPrintifyImages(
                    $pdo,
                    $shirtId,
                    $product
                );

            $pdo->commit();

            $summary['products_updated']++;
            $summary['variants_updated'] +=
                $variantResult['seen'];
            $summary['images_updated'] +=
                $imageCount;
        } catch (Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }

            $summary['warnings'][] = sprintf(
                '%s: %s',
                $summaryProduct['id'],
                $exception->getMessage()
            );

            error_log(
                'Printify product sync failed: ' .
                $summaryProduct['id'] .
                ' - ' .
                $exception->getMessage()
            );
        }
    }

    recordPrintifySyncLog(
        $pdo,
        'full',
        'completed',
        $summary,
        $startedAt
    );
} catch (Throwable $exception) {
    recordPrintifySyncLog(
        $pdo,
        'full',
        'failed',
        $summary,
        $startedAt,
        $exception->getMessage()
    );

    throw $exception;
}

Run Synchronization from the Admin Area

Protected administration

Synchronization Controls

The admin page should require authentication, show the current shop, display the last sync result, and require a deliberate action before starting a full import.

Confirm the configured Printify shop
Display the last successful sync time
Sync one selected product
Run a complete catalog synchronization
Review warnings and unavailable variants
Open detailed private logs

Part 4 Synchronization Checklist

  • The Printify token is stored outside the public web root.
  • The token has only the required access scopes.
  • Every API request includes Authorization and User-Agent headers.
  • All API calls run through PHP rather than browser JavaScript.
  • The correct Printify shop ID is stored in configuration.
  • Product pagination continues through the final page.
  • Detailed product records are requested before synchronization.
  • Products are matched by Printify product ID.
  • Variants are matched by Printify variant ID.
  • Prices are converted from cents correctly.
  • Provider and blueprint IDs are stored separately.
  • Enabled and available states remain separate.
  • Missing variants are marked unavailable.
  • Local SEO and merchandising fields are preserved.
  • Product images are synchronized separately.
  • Print areas and decoration methods can be stored.
  • Each product sync uses a database transaction.
  • One failed product does not stop the entire catalog import.
  • Warnings and errors are written to private logs.
  • The last synchronization time is visible in the admin area.
  • Full synchronization requires authenticated access.
  • Synchronization does not publish or change live products accidentally.
  • API rate limits and retry behavior are respected.
  • Tokens are rotated before expiration.

Printify Product Synchronization FAQ

What authentication does the Printify API use?

An individual merchant can use a Printify Personal Access Token. The token is sent as a Bearer token in the Authorization header, and requests must include a User-Agent header.

How do I find my Printify shop ID?

Request the list of shops from the shops endpoint, choose the correct shop, and store its numeric ID in private configuration. Product endpoints use that shop ID in their URLs.

Why should Printify requests run through PHP?

The Printify API does not support browser CORS requests, and the personal access token must remain private. A server-side PHP application safely stores the token and performs the API calls.

What is the difference between a blueprint and a product?

A blueprint is a blank catalog product such as a specific shirt model. A merchant product is created after artwork, variants, pricing, and print areas are configured for a shop.

What is a Printify print provider?

A print provider manufactures the selected blueprint. Providers may differ by location, available variants, print methods, print areas, production costs, and shipping options.

What price format does Printify use?

Many Printify API monetary values are represented as integers in the smallest currency unit, such as cents. Convert them carefully before storing or displaying dollar amounts.

Should Printify product data replace my local descriptions and SEO fields?

No. Synchronization should update operational fields such as platform IDs, variants, prices, images, and availability while preserving local storefront copy, collections, SEO, likes, and reviews.

How should deleted or unavailable Printify variants be handled?

Do not delete local rows immediately. Mark missing variants unavailable or disabled, record the synchronization time, and review changes before permanently deleting historical mappings.

How often should products be synchronized?

Run synchronization after important product edits and on a regular schedule appropriate for the catalog. Avoid unnecessary rapid polling and respect current API rate limits.

Why use database transactions during synchronization?

A transaction prevents the local product from being left partially updated when a variant, image, or database operation fails in the middle of the process.

Should the sync script publish products to Shopify?

Keep synchronization and publishing as separate operations. Importing Printify data should not automatically publish, change prices, or alter the live storefront unless that behavior is deliberate.

Can the Printify API provide catalog and shipping data?

Yes. Catalog endpoints expose blueprints, print providers, variants, print areas, and shipping information. Some shipping capabilities, including economy shipping cost listings, are available through the V2 catalog API.

Continue to Part 5

The local database can now receive and update Printify products, variants, images, providers, pricing, and production information. Part 5 turns those synchronization functions into practical private administration tools.

0
cart