halo-dev/halo · error · Error

Policy name is required

Error message

Policy name is required

What it means

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.

Source

Thrown at ui/console-src/modules/contents/attachments/components/UploadFromUrl.vue:37

  }
);

const emit = defineEmits<{
  (event: "uploaded", attachment: Attachment): void;
}>();

onMounted(() => {
  setFocus("url");
});

const downloading = ref(false);

async function onSubmit({ url }: { url: string }) {
  try {
    downloading.value = true;

    if (!props.policyName) {
      throw new Error("Policy name is required");
    }

    const { data } =
      await consoleApiClient.storage.attachment.externalTransferAttachment({
        uploadFromUrlRequest: {
          url: url,
          policyName: props.policyName,
          groupName: props.groupName,
        },
      });

    Toast.success(
      t("core.attachment.upload_modal.download_form.toast.success")
    );

    reset("url");
    emit("uploaded", data);
  } catch (error) {

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Configure at least one storage policy (Attachments → Storage Policies) so the upload modal can pass a policyName.
  2. Ensure the parent component binds :policy-name from the active policy selector and that a default policy is selected when the modal opens.
  3. Disable the submit button until a policyName is present: :disabled="!policyName".
  4. If embedding UploadFromUrl in a custom view, pass policyName explicitly as a required value.

Example fix

// before
if (!props.policyName) {
  throw new Error("Policy name is required");
}
// after — validate up front with a clear UX signal and avoid throw in submit
if (!props.policyName) {
  Toast.warning(t("core.attachment.upload_modal.policy_required"));
  return;
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate required props before calling the external-transfer API
function validateUploadFromUrl(policyName: string | undefined, url: string): string[] {
  const errors: string[] = [];
  if (!policyName || !policyName.trim()) errors.push("Policy name is required");
  try { new URL(url); } catch { errors.push("A valid URL is required"); }
  return errors;
}

Type guard

function hasPolicyName(p: string | undefined): p is string {
  return typeof p === "string" && p.trim().length > 0;
}

Try / catch

async function onSubmit({ url }: { url: string }) {
  try {
    if (!hasPolicyName(props.policyName)) {
      throw new Error("Policy name is required");
    }
    // ... externalTransferAttachment
  } catch (error) {
    Toast.error(error instanceof Error ? error.message : "Upload failed");
    return error;
  } finally { downloading.value = false; }
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/21469ee153478d2f. Report an issue: GitHub.