{"record":{"id":"950e2257403ffeea","repo":"Budibase/budibase","slug":"plugin-must-be-compressed-into-a-gzipped-tarball-950e22","errorCode":null,"errorMessage":"Plugin must be compressed into a gzipped tarball.","messagePattern":"Plugin must be compressed into a gzipped tarball\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/server/src/api/controllers/plugin/url.ts","lineNumber":20,"sourceCode":"import {\n  deleteFolderFileSystem,\n  getPluginMetadata,\n} from \"../../../utilities/fileSystem\"\n\nfunction parseTarGzUrl(url: string): URL {\n  let parsed: URL\n  try {\n    parsed = new URL(url)\n  } catch {\n    throw new Error(\"Invalid plugin URL.\")\n  }\n\n  if (parsed.protocol !== \"https:\") {\n    throw new Error(\"Plugin URL must use HTTPS.\")\n  }\n\n  if (!parsed.pathname.endsWith(\".tar.gz\")) {\n    throw new Error(\"Plugin must be compressed into a gzipped tarball.\")\n  }\n\n  return parsed\n}\n\nexport async function urlUpload(url: string, name = \"\", headers = {}) {\n  parseTarGzUrl(url)\n\n  const path = await downloadUnzipTarball(url, name, headers, {\n    followRedirects: false,\n  })\n  try {\n    return await getPluginMetadata(path)\n  } catch (err) {\n    deleteFolderFileSystem(path)\n    throw err\n  }\n}","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/api/controllers/plugin/url.ts#L2-L38","documentation":"Thrown when the URL is a valid HTTPS URL but its pathname does not end with '.tar.gz', so it is not recognized as a gzipped tarball plugin archive.","triggerScenarios":"urlUpload called with URLs pointing to .zip files, bare package pages, URLs with query strings after the filename, or extensionless download endpoints.","commonSituations":"Pointing at a GitHub repo page instead of a release .tar.gz asset; using .zip archives from a CI artifact; URLs like https://host/plugin.tar.gz?token=x (fails the endsWith check).","solutions":["Provide a direct link to a .tar.gz archive","For GitHub, use the release asset or https://github.com/<org>/<repo>/archive/refs/tags/<tag>.tar.gz","Strip query strings, or host the file at a path ending in .tar.gz"],"exampleFix":"// before\nawait urlUpload('https://github.com/org/repo/releases/latest')\n// after\nawait urlUpload('https://github.com/org/repo/releases/download/v1.0.0/plugin-1.0.0.tar.gz')","handlingStrategy":"validation","validationCode":"const u = new URL(url)\nif (!u.pathname.endsWith('.tar.gz')) throw new Error('URL must point directly at a .tar.gz archive (no query strings)')","typeGuard":"function isTarGzUrl(url: string): boolean {\n  try { return new URL(url).pathname.endsWith('.tar.gz') } catch { return false }\n}","tryCatchPattern":"try {\n  await urlUpload(url)\n} catch (err) {\n  if (err.message === 'Plugin must be compressed into a gzipped tarball.') {\n    // point the URL at the actual .tar.gz asset\n  }\n}","preventionTips":["Link to the release .tar.gz asset, not the repo or release page","Avoid query strings appended to .tar.gz paths (pathname check ignores them)","Repackage .zip artifacts as .tar.gz"],"tags":["validation","plugins","tarball","url"],"backgroundTag":"invalid-archive-format","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}