{"record":{"id":"fa6319015a3095cb","repo":"immich-app/immich","slug":"editing-live-photos-is-not-supported","errorCode":null,"errorMessage":"Editing live photos is not supported","messagePattern":"Editing live photos is not supported","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/asset.service.ts","lineNumber":545,"sourceCode":"      assetId: id,\n      edits,\n    };\n  }\n\n  async editAsset(auth: AuthDto, id: string, dto: AssetEditsCreateDto): Promise<AssetEditsResponseDto> {\n    await this.requireAccess({ auth, permission: Permission.AssetEditCreate, ids: [id] });\n\n    const asset = await this.assetRepository.getForEdit(id);\n    if (!asset) {\n      throw new BadRequestException('Asset not found');\n    }\n\n    if (asset.type !== AssetType.Image) {\n      throw new BadRequestException('Only images can be edited');\n    }\n\n    if (asset.livePhotoVideoId) {\n      throw new BadRequestException('Editing live photos is not supported');\n    }\n\n    if (isPanorama(asset)) {\n      throw new BadRequestException('Editing panorama images is not supported');\n    }\n\n    if (asset.originalPath?.toLowerCase().endsWith('.gif')) {\n      throw new BadRequestException('Editing GIF images is not supported');\n    }\n\n    if (asset.originalPath?.toLowerCase().endsWith('.svg')) {\n      throw new BadRequestException('Editing SVG images is not supported');\n    }\n\n    // check that crop parameters will not go out of bounds\n    const { width: assetWidth, height: assetHeight } = getDimensions(asset);\n\n    if (!assetWidth || !assetHeight) {","sourceCodeStart":527,"sourceCodeEnd":563,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/asset.service.ts#L527-L563","documentation":"editAsset rejects live photos: if the asset has a livePhotoVideoId (the paired video component), throwing BadRequestException('Editing live photos is not supported'). Image transforms like crop/rotate are not applied to the paired video, so the operation is blocked to avoid inconsistent results.","triggerScenarios":"Submitting image edits for an iOS live photo where asset.livePhotoVideoId is set, e.g. PUT crop/rotate edits on a Live Photo asset.","commonSituations":"Bulk editing an iPhone library where many assets are live photos; clients not exposing a live-photo distinction in their UI; sync tools applying saved edits uniformly across a library.","solutions":["Detect live photos (livePhotoVideoId present in the asset response) and skip them in edit flows","Inform users that live photos cannot be edited and offer to edit only the still part if the API allows","Exclude assets with livePhotoVideoId from bulk edit selections","If editing the still is essential, duplicate/export the still frame as a standalone image and edit that"],"exampleFix":"// before\nawait applyEdits(asset.id, edits);\n// after\nif (asset.livePhotoVideoId) {\n  skippedLivePhotos.push(asset.id);\n  return;\n}\nawait applyEdits(asset.id, edits);","handlingStrategy":"validation","validationCode":"if (asset.livePhotoVideoId) throw new Error(`Asset ${asset.id} is a live photo; editing not supported`);","typeGuard":"const isLivePhoto = (a: { livePhotoVideoId?: string | null }) => !!a.livePhotoVideoId;","tryCatchPattern":"try { await api.applyEdits(id, edits); } catch (e) { if (String(e?.response?.data?.message).includes('live photos')) { skippedLivePhotos.add(id); return; } throw e; }","preventionTips":["Exclude assets with livePhotoVideoId from edit selections","Surface a live-photo badge in the edit UI","Bulk tools should prefetch asset details and split live photos out","Keep iOS imports flagged so downstream jobs can skip them"],"tags":["asset","edit","live-photo","unsupported"],"backgroundTag":"unsupported-operation","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}