immich-app/immich · error · BadRequestException
Tag not found
Error message
Tag not found
What it means
Thrown by TagService.create when the client supplies a parentId but no tag with that ID exists in the repository. The service resolves the parent before composing the child tag's value path, so a missing parent blocks creation. It is a 400 BadRequest because the request references a nonexistent resource.
Solutions
- Verify the parentId exists (GET /api/tags) before creating the child tag
- Create the parent tag first, then use its returned id as parentId
- Pass parentId: null/undefined to create a root-level tag instead
- Clear stale local caches and refresh the tag list in the client
Example fix
// before
await api.createTag({ name: 'kids', parentId: 'stale-id' });
// after
const tags = await api.getAllTags();
const parent = tags.find(t => t.value === 'parents');
if (!parent) throw new Error('create parent tag first');
await api.createTag({ name: 'kids', parentId: parent.id }); Defensive patterns
Strategy: validation
Validate before calling
const tags = await api.getAllTags();
if (dto.parentId && !tags.some(t => t.id === dto.parentId)) {
throw new Error(`parent tag ${dto.parentId} does not exist`);
} Try / catch
try { await api.createTag(dto); } catch (e) {
if (e.response?.status === 400 && e.response?.data?.message === 'Tag not found') {
// create as root tag or refresh tag list
}
} Prevention
- Refresh the tag list before nested creation flows
- Create parents before children; never create them in parallel
- Treat parentId as optional and default to root-level creation
When it happens
Trigger: POST /api/tags (or TagService.create) with dto.parentId set to a tag ID that does not exist — e.g. deleted tag, stale ID from another client, or an ID belonging to a different user.
Common situations: Client kept a cached tag list after a parent tag was deleted; UI autocomplete sent a stale ID; copying API examples with fabricated UUIDs; creating child tags before the parent via parallel requests.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- Asset dimensions are not available for editing
- assetIds, albumId, or userId is required
- At least two people are required for merging
- Cannot add another owner
- Cannot merge a person into themselves
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/72775df132722dd9.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/tag.service.ts:42
export class TagService extends BaseService {
async getAll(auth: AuthDto) {
const tags = await this.tagRepository.getAll(auth.user.id);
return tags.map((tag) => mapTag(tag));
}
async get(auth: AuthDto, id: string): Promise<TagResponseDto> {
await this.requireAccess({ auth, permission: Permission.TagRead, ids: [id] });
const tag = await this.findOrFail(id);
return mapTag(tag);
}
async create(auth: AuthDto, dto: TagCreateDto) {
let parent;
if (dto.parentId) {
await this.requireAccess({ auth, permission: Permission.TagRead, ids: [dto.parentId] });
parent = await this.tagRepository.get(dto.parentId);
if (!parent) {
throw new BadRequestException('Tag not found');
}
}
const userId = auth.user.id;
const value = parent ? `${parent.value}/${dto.name}` : dto.name;
const duplicate = await this.tagRepository.getByValue(userId, value);
if (duplicate) {
throw new BadRequestException(`A tag with that name already exists`);
}
const { color } = dto;
const tag = await this.tagRepository.create({ userId, value, color, parentId: parent?.id });
return mapTag(tag);
}
async update(auth: AuthDto, id: string, dto: TagUpdateDto): Promise<TagResponseDto> {
await this.requireAccess({ auth, permission: Permission.TagUpdate, ids: [id] });View on GitHub (pinned to e55ac299a4)