Remove all images for a product. GraphQL

Topic summary

Removing all images for a product via GraphQL. No built-in “delete all” mutation exists; you must fetch media IDs and delete them.

Latest update: productDeleteImages is deprecated. Use productDeleteMedia(productId, mediaIds) to delete images by ID.

Practical approach shared:

  • Query product.media to collect MediaImage IDs using pagination (first:250, after cursor).
  • Split IDs into chunks (e.g., 200 per request) and call productDeleteMedia for each chunk.
  • Handle errors and rate limits (retry on exceptions; continue on HTTP 429). The mutation returns deletedProductImageIds.

Resources: A Laravel gist with the three steps (query IDs, delete, re-add) was provided. Another implementation showed updated code using productDeleteMedia.

Key terms:

  • GraphQL mutation: an operation that modifies data.
  • productDeleteMedia: Shopify GraphQL mutation to delete media (images) from a product by ID.
  • MediaImage: image nodes within product media.

Outcome: A clear, working method is documented; no single-call “delete all” solution. The suggestion to add a delete-all media mutation remains open. Code snippets are central to understanding the solution.

Summarized with AI on January 2. AI used: gpt-5.

Hi guys .. I cannot see a way to do this but is it possible to run a GraphQL mutation to delete ALL images for a product?

I need to regularly do a refresh for a product.

The way it stands, it seems I will have to do a request to get the existing product image ID’s then perform a mutation to delete the image ID’s that have been acquired.

Is there a better way?

1 Like

Hey Mattayres,

great question, did you find an answer to it? :slightly_smiling_face:

Cheers,

Matthias

I found this process super cumbersome too, they should add a delete all media mutation. Here is my laravel class that has all 3 graphQL queries (query for IDs/delete/re-add) for your reference:

https://gist.github.com/wasalwayshere/903def4d3b711f916ad5e49beaa67685

1 Like

I am deleting all product images with the following method

I actually realized the productDeleteImages method is deprecated, and I should use productDeleteMedia instead.

This is what my new code looks like

public function deleteAllImages(User $shop, string $productId)
{
    $mediaIds = $this->getMediaIds($shop, $productId);

    if (empty($mediaIds)) {
        return [];
    }

    // Split the mediaIds array into chunks of 200 items each
    $mediaIdsChunks = array_chunk($mediaIds, 200);

    foreach ($mediaIdsChunks as $mediaIdsChunk) {
        $mutation = <<<GQL
            mutation productDeleteMedia(\$productId: ID!, \$mediaIds: [ID!]!) {
                productDeleteMedia(productId: \$productId, mediaIds: \$mediaIds) {
                    deletedProductImageIds
                }
            }
        GQL;

        $variables = [
            'productId' => $productId,
            'mediaIds' => $mediaIdsChunk,
        ];

        try {
            $result = $shop->api()->graph($mutation, $variables);
        } catch (\Throwable $throwable) {
            //Try again
            Log::error('Error deleting images. Reason:' . $throwable->getMessage());
            report($throwable);
            $result = $shop->api()->graph($mutation, $variables);
        }

        if (!empty($result['errors'])) {
            $errorMessage = 'Error deleting images. Reason:' . json_encode($result['errors']);
            Log::error($errorMessage);
            $exception = new \Exception($errorMessage);
            report($exception);
            throw $exception;
        }

        $this->graphqlRateLimitHandler->handleRateLimit($result);
    }

    return [];
}

public function getMediaIds(User $shop, string $productId)
{
    $imageIds = [];
    $cursor = 'null';
    $hasNextPage = false;

    do {
        $query = <<<GQL
        {
            product(id: "{$productId}") {
                media(first: 250, after: {$cursor}) {
                    pageInfo {
                        hasNextPage
                    }
                    edges {
                        cursor
                        node {
                            ... on MediaImage {
                                id
                            }
                        }
                    }
                }
            }
        }
        GQL;

        $result = $shop->api()->graph($query);

        if (!empty($result['errors'])) {
            $errorMessage = 'Error getting media ids. Reason:' . json_encode($result['errors']);
            Log::error($errorMessage);
            $exception = new \Exception($errorMessage);
            report($exception);
            throw $exception;
        }

        $this->graphqlRateLimitHandler->handleRateLimit($result);

        if ($result['status'] === 429) {
            continue;
        }

        $edges = $result['body']['data']['product']['media']['edges'];

        $edges = $edges->toArray();

        $imageIds = array_merge($imageIds, array_map(function ($edge) {
            if (empty($edge['node']['id'])) {
                return null;
            }
            return $edge['node']['id'];
        }, $edges));

        if (!empty($edges)) {
            $lastEdge = end($edges);
            if (isset($lastEdge['cursor'])) {
                $cursor = '"' . $lastEdge['cursor'] . '"';
            }
        }

        $hasNextPage = $result['body']['data']['product']['media']['pageInfo']['hasNextPage'];

    } while ($hasNextPage);

    if (empty($imageIds)) {
        return [];
    }

    $imageIds = array_filter($imageIds);
    $imageIds = array_values($imageIds);

    return $imageIds;
}
1 Like