{"record":{"id":"2ec6d55842a60f5f","repo":"gatsbyjs/gatsby","slug":"could-not-parse-file-extension-from-shopify-image","errorCode":null,"errorMessage":"Could not parse file extension from Shopify image URL: ${url}","messagePattern":"Could not parse file extension from Shopify image URL: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/gatsby-source-shopify/src/helpers.ts","lineNumber":148,"sourceCode":"          createNode,\n          createNodeId,\n          parentNodeId: node.id,\n        })\n\n        image.localFile___NODE = fileNode.id\n      }\n    }\n  }\n}\n\nexport function parseImageExtension(url: string): string {\n  const basename = url.split(`?`)[0]\n  const dot = basename.lastIndexOf(`.`)\n\n  if (dot !== -1) {\n    return basename.slice(dot + 1)\n  } else {\n    throw new Error(\n      `Could not parse file extension from Shopify image URL: ${url}`\n    )\n  }\n}\n","sourceCodeStart":130,"sourceCodeEnd":153,"githubUrl":"https://github.com/gatsbyjs/gatsby/blob/8b06340921ffdf23125a365b9c9923690cb62ce6/packages/gatsby-source-shopify/src/helpers.ts#L130-L153","documentation":"parseImageExtension splits a Shopify image URL on `?` then looks for the last `.` in the basename. If there is no dot, the URL has no detectable file extension and the function throws. The plugin uses the extension to decide whether to download the image as a local file node (gif is excluded by the caller). A URL with no extension means the helper cannot make that decision.","triggerScenarios":"Shopify returns an image URL with no extension (some CDN/S3-style URLs or SVGs served without extension); URL is a base64-like or signature URL where the dot is stripped; URL is malformed/empty; the originalSrc field is a placeholder that lacks an extension.","commonSituations":"Shopify storefront serving newly uploaded images where the CDN URL omits the extension; custom image transforms returning extensionless URLs; third-party app replacing Shopify image URLs; testing against fixtures with malformed image data.","solutions":["Wrap the call in try/catch and skip images whose extension cannot be parsed (do not let one bad URL abort sourcing).","Pre-filter: only call createRemoteFileNode when the URL contains a dot after the query split.","Patch upstream data so originalSrc always carries a real extension.","If SVGs are involved, ensure the URL retains the .svg suffix before it reaches this helper."],"exampleFix":"// before\nif (image && parseImageExtension(image.originalSrc) !== `gif`) {\n  await createRemoteFileNode({ url: image.originalSrc, ... })\n}\n\n// after\ntry {\n  if (image && parseImageExtension(image.originalSrc) !== `gif`) {\n    await createRemoteFileNode({ url: image.originalSrc, ... })\n  }\n} catch (e) {\n  reporter.warn(`Skipping image with unparseable extension: ${image?.originalSrc}`)\n}","handlingStrategy":"try-catch","validationCode":"function safeImageExtension(url) {\n  const dot = url.split('?')[0].lastIndexOf('.')\n  return dot === -1 ? null : url.split('?')[0].slice(dot + 1)\n}","typeGuard":"const hasExtension = (url) => url.split('?')[0].includes('.')","tryCatchPattern":"let ext\ntry { ext = parseImageExtension(url) } catch (e) { reporter.warn(`Skipping image (no extension): ${url}`); continue }","preventionTips":["Filter out extensionless image URLs before calling parseImageExtension","Validate upstream Shopify image data quality","Wrap the call so one bad URL cannot abort the whole build"],"tags":["shopify","image","validation"],"backgroundTag":null,"analyzedSha":"8b06340921ffdf23125a365b9c9923690cb62ce6","analyzedAt":"2026-08-13T02:36:21.405Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}