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

  1. Only call addAssets on links whose type is SharedLinkType.Individual.
  2. To change an album shared link's contents, add/remove assets from the underlying album instead.
  3. 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

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


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/76602efdc529a7fb. Report an issue: GitHub.