immich-app/immich · error · BadRequestException
Live photo video must be a video
Error message
Live photo video must be a video
What it means
Thrown by onBeforeLink when the asset referenced by livePhotoVideoId exists but its type is not AssetType.Video. Live Photo motion components must be video assets; linking an image (or any non-video) is rejected with HTTP 400. The check is motionAsset.type !== AssetType.Video.
Source
Thrown at server/src/utils/asset.util.ts:154
partnerIds.add(partner.sharedById);
}
return [...partnerIds];
};
export type AssetHookRepositories = { asset: AssetRepository; event: EventRepository };
export const onBeforeLink = async (
{ asset: assetRepository, event: eventRepository }: AssetHookRepositories,
{ userId, livePhotoVideoId }: { userId: string; livePhotoVideoId: string },
) => {
const motionAsset = await assetRepository.getById(livePhotoVideoId);
if (!motionAsset) {
throw new BadRequestException('Live photo video not found');
}
if (motionAsset.type !== AssetType.Video) {
throw new BadRequestException('Live photo video must be a video');
}
if (motionAsset.ownerId !== userId) {
throw new BadRequestException('Live photo video does not belong to the user');
}
if (motionAsset && motionAsset.visibility === AssetVisibility.Timeline) {
await assetRepository.update({ id: livePhotoVideoId, visibility: AssetVisibility.Hidden });
await eventRepository.emit('AssetHide', { assetId: motionAsset.id, userId });
}
};
export const onBeforeUnlink = async (
{ asset: assetRepository }: AssetHookRepositories,
{ livePhotoVideoId }: { livePhotoVideoId: string },
) => {
const motion = await assetRepository.getById(livePhotoVideoId);
if (!motion) {
return null;View on GitHub (pinned to 199723261c)
Solutions
- Ensure the id passed as livePhotoVideoId corresponds to an asset whose type is 'VIDEO'.
- Upload the motion component as a video (correct MIME type) before linking.
- Audit the asset row and correct its type if it was misclassified.
Example fix
// before
await updateAsset({ id, livePhotoVideoId: stillImageId }); // wrong type
// after
await updateAsset({ id, livePhotoVideoId: motionVideoId }); // motionVideoId.type === 'VIDEO' Defensive patterns
Strategy: validation
Validate before calling
// ensure the motion asset is a video before linking const motion = await assetRepository.getById(livePhotoVideoId); const ok = !!motion && motion.type === AssetType.Video;
Type guard
const isVideoAsset = (a: { type: string } | null | undefined): a is { type: 'VIDEO' } =>
!!a && a.type === 'VIDEO'; Try / catch
try { await updateAsset({ id, livePhotoVideoId }); }
catch (e) { if (e instanceof BadRequestException && /must be a video/.test(e.message)) { notify('Motion asset must be a video'); } else throw e; } Prevention
- On the upload side, classify motion assets as VIDEO before exposing them in the live-photo picker.
- Filter the picker list by asset.type === 'VIDEO'.
When it happens
Trigger: AssetService.update (or asset-media upload) with livePhotoVideoId pointing to an IMAGE asset; passing the still photo's own id as its motion component; cross-linking another user's image asset as a motion video.
Common situations: Client mistakenly uses the still image's id for both the asset and livePhotoVideoId; frontend bug that assigns an image asset id into the motion field; migrating/importing data where the motion asset was mislabeled as an image.
Related errors
- Live photo video not found
- May not request original file
- Asset not found or asset is not a video
- Both assets must exist
- Source and target id must be distinct
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/b7923bde48127416.
Report an issue: GitHub.