halo-dev/halo · error · Error

No permission to upload attachment

Error message

No permission to upload attachment

What it means

Thrown in UploadDropdownItem.vue (a FormKit attachment input) when iterating selected files: the user has neither the system:attachments:manage nor the uc:attachments:manage permission. The component branches on permission — console upload API for system scope, UC API for user-center scope — and if neither permission is held, there is no valid upload endpoint, so it throws to abort the loop.

Source

Thrown at ui/src/formkit/inputs/attachment/UploadDropdownItem.vue:46

    return;
  }

  const attachments: Attachment[] = [];
  for (const file of files) {
    if (utils.permission.has(["system:attachments:manage"])) {
      const { data } =
        await consoleApiClient.storage.attachment.uploadAttachmentForConsole({
          file: file,
        });
      attachments.push(data);
    } else if (utils.permission.has(["uc:attachments:manage"])) {
      const { data } =
        await ucApiClient.storage.attachment.uploadAttachmentForUc({
          file: file,
        });
      attachments.push(data);
    } else {
      throw new Error("No permission to upload attachment");
    }
  }

  emit("selected", attachments);
});
</script>
<template>
  <VDropdownItem @click="openFileInputDialog()">
    {{ $t("core.common.buttons.upload") }}
  </VDropdownItem>
</template>

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Grant the role the appropriate permission: uc:attachments:manage for user-center uploads or system:attachments:manage for console uploads (System → Roles → edit role → Permissions).
  2. Hide the UploadDropdownItem entirely when the user lacks upload permission — gate its rendering on utils.permission.has([...]) so the option never appears.
  3. Catch the thrown error in the caller and show a localized 'no permission' toast instead of letting it propagate as an unhandled rejection.
  4. Verify the permission identifiers used match the role template definitions (typos in 'uc:attachments:manage' silently fail).

Example fix

// before
} else {
  throw new Error("No permission to upload attachment");
}
// after — surface a localized message and avoid rendering the item for unauthorized users
} else {
  Toast.warning(t("core.attachment.no_upload_permission"));
  return;
}
// and in the template, gate the dropdown item:
// <VDropdownItem v-if="canUpload" @click="openFileInputDialog()">
Defensive patterns

Strategy: validation

Validate before calling

// Check upload permission before showing the picker / iterating files
const canUploadConsole = utils.permission.has(["system:attachments:manage"]);
const canUploadUc = utils.permission.has(["uc:attachments:manage"]);
if (!canUploadConsole && !canUploadUc) {
  Toast.warning(t("core.attachment.no_upload_permission"));
  return;
}
// then proceed with the upload loop

Type guard

function canUploadAttachment(): boolean {
  return (
    utils.permission.has(["system:attachments:manage"]) ||
    utils.permission.has(["uc:attachments:manage"])
  );
}

Try / catch

onFileInputChange(async (files) => {
  try {
    // ... upload loop that may throw 'No permission to upload attachment'
  } catch (e) {
    Toast.error(e instanceof Error ? e.message : "Upload failed");
  }
});

Prevention

When it happens

Trigger: A user opens the attachment upload dropdown in a FormKit input (e.g. editor attachment picker) and selects files, but their role lacks both system:attachments:manage and uc:attachments:manage. The permission checks via utils.permission.has both return false, so the upload is rejected client-side before any network call.

Common situations: A logged-in user whose role was not granted any attachment-upload permission tries to upload through a FormKit field; permissions changed/migrated and the role lost uc:attachments:manage; the component is shown to a user who should not see it (missing UI permission gate); a custom role with a typo'd permission identifier.

Related errors


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