immich-app/immich · warning · BadRequestException
Invalid shared link type
Error message
Invalid shared link type
What it means
SharedLinkService.addAssets() only operates on SharedLinkType.Individual links; if the resolved link's type is not Individual it throws BadRequestException 'Invalid shared link type' (shared-link.service.ts:153, HTTP 400). Album-type links derive their assets from the album, so per-asset add is not permitted.
Source
Thrown at server/src/services/shared-link.service.ts:153
} catch (error) {
this.handleError(error);
}
}
async remove(auth: AuthDto, id: string): Promise<void> {
const sharedLink = await this.findOrFail(auth.user.id, id);
await this.sharedLinkRepository.remove(sharedLink.id);
}
// TODO: replace `userId` with permissions and access control checks
private findOrFail(userId: string, id: string) {
return findOrFail(() => this.sharedLinkRepository.get(userId, id), 'Shared link');
}
async addAssets(auth: AuthDto, id: string, dto: AssetIdsDto): Promise<AssetIdsResponseDto[]> {
const sharedLink = await this.findOrFail(auth.user.id, id);
if (sharedLink.type !== SharedLinkType.Individual) {
throw new BadRequestException('Invalid shared link type');
}
const existingAssetIds = new Set(sharedLink.assets.map((asset) => asset.id));
const notPresentAssetIds = dto.assetIds.filter((assetId) => !existingAssetIds.has(assetId));
const allowedAssetIds = await this.checkAccess({
auth,
permission: Permission.AssetShare,
ids: notPresentAssetIds,
});
const results: AssetIdsResponseDto[] = [];
for (const assetId of dto.assetIds) {
const hasAsset = existingAssetIds.has(assetId);
if (hasAsset) {
results.push({ assetId, success: false, error: AssetIdErrorReason.DUPLICATE });
continue;
}
View on GitHub (pinned to 199723261c)
Solutions
- Only call addAssets on links whose type is SharedLinkType.Individual.
- To change an album shared link's contents, add/remove assets from the underlying album instead.
- Check link.type before exposing the add-assets action in the UI.
Example fix
// before
await addAssets(albumLink.id, { assetIds });
// after
if (link.type === SharedLinkType.Individual) {
await addAssets(link.id, { assetIds });
} Defensive patterns
Strategy: type-guard
Validate before calling
if (link.type !== SharedLinkType.Individual) {
throw new Error('addAssets only applies to individual shared links.');
}
await sharedLinkApi.addAssets(link.id, { assetIds }); Type guard
const isIndividualLink = (link: { type: SharedLinkType }): link is { type: SharedLinkType.Individual } & typeof link =>
link.type === SharedLinkType.Individual; Try / catch
try {
await sharedLinkApi.addAssets(id, { assetIds });
} catch (e) {
if (e instanceof BadRequestException && /shared link type/i.test(e.message)) {
// route the user to manage the album's assets instead
navigateToAlbum(link.albumId);
} else throw e;
} Prevention
- Check link.type before exposing the add-assets action.
- For album links, modify the album's asset membership directly.
When it happens
Trigger: POST /shared-links/:id/assets (addAssets) targeting a SharedLinkType.Album link. Only individual links accept an explicit asset list.
Common situations: Client reusing the add-assets flow for an album share, or stale state where a link's type changed from Individual to Album.
Related errors
- Invalid assetIds
- 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/76602efdc529a7fb.
Report an issue: GitHub.