{"record":{"id":"8a04a60ca0c49723","repo":"Budibase/budibase","slug":"invalid-github-url","errorCode":null,"errorMessage":"Invalid Github URL","messagePattern":"Invalid Github URL","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/server/src/api/controllers/plugin/github.ts","lineNumber":13,"sourceCode":"import { utils as coreUtils } from \"@budibase/backend-core\"\nimport {\n  deleteFolderFileSystem,\n  getPluginMetadata,\n} from \"../../../utilities/fileSystem\"\nimport { downloadUnzipTarball } from \"./utils\"\n\nfunction parseGithubUrl(url: string): URL {\n  let parsed: URL\n  try {\n    parsed = new URL(url)\n  } catch {\n    throw new Error(\"Invalid Github URL\")\n  }\n\n  if (parsed.protocol !== \"https:\" || parsed.hostname !== \"github.com\") {\n    throw new Error(\"The plugin origin must be from Github\")\n  }\n\n  return parsed\n}\n\nexport async function request(\n  url: string,\n  headers: Record<string, string> = {},\n  err: string\n) {\n  const response = await coreUtils.fetchWithBlacklist(url, { headers })\n  if (response.status >= 300) {\n    const respErr = await response.text()\n    throw new Error(`Error: ${err} - ${respErr}`)","sourceCodeStart":1,"sourceCodeEnd":31,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/api/controllers/plugin/github.ts#L1-L31","documentation":"parseGithubUrl validates that a plugin source URL is a well-formed URL. If new URL(url) throws (malformed URL), the function throws \"Invalid Github URL\". A subsequent check rejects non-https/non-github hosts with a different message, so this error is purely about URL parse failure.","triggerScenarios":"Calling the GitHub plugin upload endpoint with a URL that is not parseable — missing scheme (\"github.com/user/repo\"), spaces, typos, empty string, or a pasted SSH URL like git@github.com:user/repo.git.","commonSituations":"Pasting \"github.com/budibase/plugins\" without https:// into the builder's plugin import; copying SSH clone URLs instead of HTTPS; trailing garbage from copy/paste; relative paths submitted instead of absolute URLs.","solutions":["Prefix the URL with the scheme: https://github.com/<owner>/<repo>","Use the HTTPS clone URL, not SSH (git@github.com:...)","Trim whitespace and stray characters from the pasted URL","Validate with new URL(url) in the browser console before submitting"],"exampleFix":"// before\nawait api.post(\"/api/plugins/github\", { url: \"github.com/user/repo\" })\n// after\nawait api.post(\"/api/plugins/github\", { url: \"https://github.com/user/repo\" })","handlingStrategy":"validation","validationCode":"const u = new URL(url)\nif (u.protocol !== \"https:\" || u.hostname !== \"github.com\") {\n  throw new Error(`URL must be https://github.com/... got ${url}`)\n}\nawait api.post(\"/api/plugins/github\", { url: u.toString() })\n","typeGuard":"const isGithubUrl = (url: string): boolean => {\n  try { const u = new URL(url); return u.protocol === \"https:\" && u.hostname === \"github.com\" }\n  catch { return false }\n}\n","tryCatchPattern":"try {\n  await api.post(\"/api/plugins/github\", { url })\n} catch (err) {\n  if (err.message === \"Invalid Github URL\") {\n    console.error(`Malformed URL \"${url}\" — use https://github.com/<owner>/<repo>`)\n  } else { throw err }\n}\n","preventionTips":["Always paste full HTTPS URLs including https:// scheme","Use HTTPS GitHub URLs, not SSH (git@github.com:...) forms","Trim whitespace from pasted URLs before submitting","Pre-validate with new URL() in the client before calling the API"],"tags":["plugin","github","url","validation"],"backgroundTag":"invalid-url-format","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}