{"record":{"id":"21469ee153478d2f","repo":"halo-dev/halo","slug":"policy-name-is-required","errorCode":null,"errorMessage":"Policy name is required","messagePattern":"Policy name is required","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ui/console-src/modules/contents/attachments/components/UploadFromUrl.vue","lineNumber":37,"sourceCode":"  }\n);\n\nconst emit = defineEmits<{\n  (event: \"uploaded\", attachment: Attachment): void;\n}>();\n\nonMounted(() => {\n  setFocus(\"url\");\n});\n\nconst downloading = ref(false);\n\nasync function onSubmit({ url }: { url: string }) {\n  try {\n    downloading.value = true;\n\n    if (!props.policyName) {\n      throw new Error(\"Policy name is required\");\n    }\n\n    const { data } =\n      await consoleApiClient.storage.attachment.externalTransferAttachment({\n        uploadFromUrlRequest: {\n          url: url,\n          policyName: props.policyName,\n          groupName: props.groupName,\n        },\n      });\n\n    Toast.success(\n      t(\"core.attachment.upload_modal.download_form.toast.success\")\n    );\n\n    reset(\"url\");\n    emit(\"uploaded\", data);\n  } catch (error) {","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/ui/console-src/modules/contents/attachments/components/UploadFromUrl.vue#L19-L55","documentation":"Thrown in UploadFromUrl.vue's onSubmit before calling consoleApiClient.storage.attachment.externalTransferAttachment. The external-transfer API requires a policyName (the storage policy that will own the remote-fetched attachment). If the parent did not pass policyName (it defaults to undefined), the call is aborted with this guard to prevent sending an invalid uploadFromUrlRequest.","triggerScenarios":"The UploadFromUrl component is rendered/mounted and the user submits a URL, but the parent (the upload modal) did not bind a policyName prop. externalTransferAttachment needs uploadFromUrlRequest.policyName; without it the backend would reject, so the client throws first. Also trips if the policy selector in the parent is empty (no storage policy configured or none selected).","commonSituations":"No storage policies are configured in the system, so the parent's policy selector is empty and policyName is undefined; the upload modal was opened in a context that skips policy selection; a refactor changed the prop binding; groupName is optional but policyName is required and the parent confused the two.","solutions":["Configure at least one storage policy (Attachments → Storage Policies) so the upload modal can pass a policyName.","Ensure the parent component binds :policy-name from the active policy selector and that a default policy is selected when the modal opens.","Disable the submit button until a policyName is present: :disabled=\"!policyName\".","If embedding UploadFromUrl in a custom view, pass policyName explicitly as a required value."],"exampleFix":"// before\nif (!props.policyName) {\n  throw new Error(\"Policy name is required\");\n}\n// after — validate up front with a clear UX signal and avoid throw in submit\nif (!props.policyName) {\n  Toast.warning(t(\"core.attachment.upload_modal.policy_required\"));\n  return;\n}","handlingStrategy":"validation","validationCode":"// Validate required props before calling the external-transfer API\nfunction validateUploadFromUrl(policyName: string | undefined, url: string): string[] {\n  const errors: string[] = [];\n  if (!policyName || !policyName.trim()) errors.push(\"Policy name is required\");\n  try { new URL(url); } catch { errors.push(\"A valid URL is required\"); }\n  return errors;\n}","typeGuard":"function hasPolicyName(p: string | undefined): p is string {\n  return typeof p === \"string\" && p.trim().length > 0;\n}","tryCatchPattern":"async function onSubmit({ url }: { url: string }) {\n  try {\n    if (!hasPolicyName(props.policyName)) {\n      throw new Error(\"Policy name is required\");\n    }\n    // ... externalTransferAttachment\n  } catch (error) {\n    Toast.error(error instanceof Error ? error.message : \"Upload failed\");\n    return error;\n  } finally { downloading.value = false; }\n}","preventionTips":["Bind :policy-name from the parent's policy selector and ensure a default policy is selected on modal open.","Disable the Download/Submit button until policyName is present.","Configure at least one storage policy so policyName is never empty in normal use."],"tags":["attachments","upload-from-url","storage-policy","validation","vue","api-client"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}