passbolt/passbolt_api · error · BadRequestException
You do not have the permission to change a personal tag…
Error message
You do not have the permission to change a personal tag into shared tag.
What it means
This BadRequestException is thrown by UpdatePersonalTagService::update when a user attempts to update a personal (V4) tag but supplies a slug starting with '#', the prefix that marks a tag as shared. Personal tags can only be converted to shared tags via the V5 metadata flow, so the V4 update endpoint rejects the transition outright rather than silently promoting the tag.
Solutions
- Remove the leading '#' from the slug, or omit the slug entirely, when updating a personal tag via the V4 endpoint.
- If the goal is to make the tag shared, migrate to the V5 metadata tag workflow (MetadataTag upgrade services) instead of editing the slug.
- Check the tag's is_shared/user_id ownership before sending an update so clients can render the correct UI (no shared toggle for personal tags in V4).
Example fix
// before
await updateTag(tagId, { slug: '#team-credentials' });
// after
await updateTag(tagId, { slug: 'team-credentials' }); // keep personal, or use V5 metadata API to share Defensive patterns
Strategy: validation
Validate before calling
if (typeof slug === 'string' && slug.startsWith('#')) throw new Error('Cannot set a shared-prefixed slug on a personal tag'); Type guard
const isPersonalSlug = (slug: unknown): slug is string => typeof slug === 'string' && !slug.startsWith('#'); Prevention
- Strip or reject '#' prefixes for personal tags in client-side form validation.
- Track tag ownership (is_shared/user_id) in UI state before enabling rename.
- Use the V5 metadata API for any personal-to-shared conversion.
When it happens
Trigger: PATCH/PUT to the tag update endpoint with a personal tag whose DTO slug is non-null and begins with '#' (mb_substr($slug, 0, 1) === '#'), while V4 tag creation is still enabled.
Common situations: A client or script batch-renames tags and accidentally adds the '#' shared prefix; a user tries to share a personal tag using the legacy V4 API after migrating partially to V5 metadata; automation copies shared-tag slugs onto personal tags.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- The identifier should be a valid UUID.
- The permissions data array keys must be integers.
- The permissions data must be an array.
- The settings selected by your administrator prevent from…
- The transfer is not authorized
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/242f613982324a17.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltEe/Tags/src/Service/Tags/UpdatePersonalTagService.php:93
$options
);
if (!$tagsTable->save($entity)) {
throw new CustomValidationException(
__('Unable to save the tag.'),
$entity->getErrors(),
$tagsTable
);
}
return $entity;
} else {
$this->assertV4TagCreationEnabled();
$slug = $dtoArray['slug'];
if (!is_null($slug) && mb_substr($slug, 0, 1) === '#') {
throw new BadRequestException(
__('You do not have the permission to change a personal tag into shared tag.')
);
}
$this->assertV4DowngradeAllowed($tag);
/** @var \Passbolt\Tags\Model\Table\ResourcesTagsTable $resourcesTagsTable */
$resourcesTagsTable = $this->fetchTable('Passbolt/Tags.ResourcesTags');
return $tagsTable->getConnection()->transactional(function () use (
$tagsTable,
$resourcesTagsTable,
$tag,
$slug,
$uac
) {
$newTag = $tagsTable->findOrCreateTag($slug, $uac);
View on GitHub (pinned to 31c1bbc10f)