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 as the live photo's motion component is not of type Video. A live photo's motion part must be a video asset; linking an image asset as motion data is invalid and would break live photo playback.

Solutions

  1. Confirm the motion asset's type is Video via the API before linking
  2. Swap the arguments if assetId and livePhotoVideoId were reversed
  3. Re-classify the asset if it was imported with the wrong type
  4. Fix client-side live photo pairing so the video file maps to livePhotoVideoId

Example fix

// before
await onBeforeLink(repos, { userId, livePhotoVideoId: photoAsset.id });
// after
const motion = await assetRepository.getById(candidateId);
if (motion && motion.type === AssetType.Video) {
  await onBeforeLink(repos, { userId, livePhotoVideoId: candidateId });
}
Defensive patterns

Strategy: validation

Validate before calling

if (motionAsset.type !== 'VIDEO') {
  throw new Error('livePhotoVideoId must reference a video asset');
}

Type guard

const isVideoAsset = (a) => a != null && a.type === 'VIDEO';

Try / catch

try {
  await linkLivePhoto(assetId, livePhotoVideoId);
} catch (e) {
  if (e.status === 400 && /must be a video/.test(e.message)) {
    // swap assetId/livePhotoVideoId or re-import with correct type
  }
}

Prevention

When it happens

Trigger: Passing an image asset's ID as livePhotoVideoId when linking a live photo, typically due to swapped IDs in the client or wrong field mapping in the upload pairing logic.

Common situations: Client bug that pairs the image asset as its own motion video; API consumers who reverse the order of assetId and livePhotoVideoId; migrated libraries where file types were misclassified.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/b7923bde48127416. Report an issue: GitHub.

Appendix: 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 e55ac299a4)