Shared Link Service Architecture in Immich: How Public Links Are Generated and Managed
Immich implements a two-layer architecture where the web client handles URL construction and UI orchestration via shared-link.service.ts, while the NestJS server manages cryptographic keys, permissions, and persistence in server/src/services/shared-link.service.ts.
The immich-app/immich repository separates public sharing concerns across distinct client and server services. Understanding this shared link service architecture reveals how the platform balances user-friendly URL generation with robust security controls and Open Graph metadata support.
Architectural Overview: Client and Server Separation
Immich divides the public-share feature into two well-defined layers that communicate via the Immich SDK.
- Client Layer (
web/src/lib/services/shared-link.service.ts): Builds UI actions, converts shared-link objects into public URLs, and wraps SDK calls with toast notifications and event management. - Server Layer (
server/src/services/shared-link.service.ts): Enforces permissions, persists records to PostgreSQL, generates cryptographic materials, and constructs Open Graph metadata for social previews.
This separation ensures that sensitive operations like key generation and permission validation remain server-side, while the client focuses on presentation and user interaction.
Public URL Construction and Routing
The asUrl Helper Function
In web/src/lib/services/shared-link.service.ts, the asUrl function transforms a SharedLinkResponseDto into a fully qualified public URL. The implementation supports two routing patterns based on configuration:
export const asUrl = (sharedLink: SharedLinkResponseDto) => {
const path = sharedLink.slug
? `s/${encodeURIComponent(sharedLink.slug)}`
: `share/${encodeURIComponent(sharedLink.key)}`;
return new URL(
path,
serverConfigManager.value.externalDomain || globalThis.location.origin,
).href;
};
Slug vs. Key Routing: When a user provides a human-readable slug, the URL uses the short format /s/:slug. Otherwise, the system falls back to the server-generated cryptographic key using the /share/:key pattern. The domain resolution prioritizes the externalDomain value from serverConfigManager, ensuring correct links in both on-premise and cloud deployments.
CRUD Operations Across the Stack
Client-Side SDK Wrappers
The client service maps UI actions to SDK methods, wrapping each call in error handling and event emission via eventManager. The operation flow follows this pattern:
| Operation | Client Helper | SDK Method | Server Method |
|---|---|---|---|
| Create | handleCreateSharedLink |
createSharedLink |
SharedLinkService.create |
| Update | handleUpdateSharedLink |
updateSharedLink |
SharedLinkService.update |
| Delete | handleDeleteSharedLink |
removeSharedLink |
SharedLinkService.remove |
| Asset Management | handleRemoveSharedLinkAssets |
removeSharedLinkAssets |
SharedLinkService.removeAssets / addAssets |
Server-Side Business Logic
The SharedLinkService in server/src/services/shared-link.service.ts implements several critical security and data integrity mechanisms:
Permission Enforcement: The requireAccess helper (from src/services/base.service.ts) validates that the requesting user owns the target album or assets, checking for Permission.AlbumShare or Permission.AssetShare before allowing mutations.
Cryptographic Key Generation: For links without custom slugs, the server generates high-entropy identifiers using cryptoRepository.randomBytes(50), producing a secure random string stored as the key field.
Slug Uniqueness Constraints: Custom slugs are subject to a PostgreSQL unique constraint (shared_link_slug_uq). When violated, the server catches the error in handleError and converts it to a BadRequestException to prevent collisions.
Security Implementation and Metadata
Password Protection and Token Management
For password-protected links, the server generates access tokens using a SHA-256 hash of the link ID and password:
private asToken(sharedLink: { id: string; password: string }) {
return this.cryptoRepository
.hashSha256(`${sharedLink.id}-${sharedLink.password}`)
.toString('base64');
}
The SharedLinkService.login method validates credentials and returns this token, which clients must include in subsequent authenticated requests to access the shared content.
Open Graph Metadata Generation
The getMetadataTags method constructs social-sharing previews without exposing internal API keys. It generates image URLs that embed the shared-link key as a query parameter (?key=...), allowing external services like Facebook or Twitter to fetch thumbnails while maintaining access control.
Complete Event Flow: From Creation to Sharing
The architecture handles a typical "Create Share" workflow through coordinated client-server interaction:
- User Initiation: The UI invokes
handleCreateSharedLinkwith parameters liketype: SharedLinkType.Albumand an optional expiration date. - SDK Transmission: The client calls
createSharedLink, transmitting aSharedLinkCreateDto(defined insrc/dtos/shared-link.dto.ts) to the server. - Server Validation:
SharedLinkService.createexecutesrequireAccesschecks, generates thekeyor validates theslug, and persists the record. - Client Response Handling: Upon receiving the
SharedLinkResponseDto, the client emitsSharedLinkCreateviaeventManagerand triggershandleShowSharedLinkQrCodeto display the scannable link. - URL Finalization: The QR modal displays the URL produced by
asUrl, completing a shareable, optionally password-protected link with customizable expiration.
Summary
- Two-layer architecture separates UI orchestration (
web/src/lib/services/shared-link.service.ts) from security-critical business logic (server/src/services/shared-link.service.ts). - Dual routing system supports human-readable slugs (
/s/:slug) and cryptographic keys (/share/:key) via theasUrlhelper. - Permission enforcement relies on
requireAccesschecks againstPermission.AlbumShareandPermission.AssetSharebefore any mutation. - Cryptographic security includes 50-byte random keys for default links and SHA-256 hashed tokens for password-protected access.
- Social metadata is generated server-side via
getMetadataTags, enabling external previews without compromising API security.
Frequently Asked Questions
How does Immich handle custom slugs versus auto-generated keys?
Immich prioritizes user-defined slugs for readability. When present, the asUrl function routes to /s/:slug. If omitted, the server generates a 50-byte random key via cryptoRepository.randomBytes(50) and routes to /share/:key. The database enforces slug uniqueness through the shared_link_slug_uq constraint.
What security measures protect password-protected shared links?
The server never transmits plaintext passwords. Instead, SharedLinkService generates a base64-encoded SHA-256 hash via the asToken method, combining the link ID and password. Clients receive this token only after successful authentication through SharedLinkService.login, and must present it for subsequent asset requests.
Can shared links expire automatically?
Yes. The SharedLinkCreateDto and SharedLinkEditDto (defined in src/dtos/shared-link.dto.ts) include an optional expiresAt Date field. The server evaluates this during access attempts, invalidating expired links automatically without requiring manual cleanup.
How does the architecture support on-premise deployments?
The asUrl function dynamically resolves the public domain by checking serverConfigManager.value.externalDomain first, falling back to globalThis.location.origin only when undefined. This ensures generated links reference the correct external domain configured by the administrator, regardless of internal network topology.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →