# Shared Link Service Architecture in Immich: How Public Links Are Generated and Managed

> Explore the two-layer architecture for generating and managing public shared links in Immich. Learn how the web client and NestJS server collaborate for secure link management.

- Repository: [Immich/immich](https://github.com/immich-app/immich)
- Tags: architecture
- Published: 2026-02-27

---

**Immich implements a two-layer architecture where the web client handles URL construction and UI orchestration via [`shared-link.service.ts`](https://github.com/immich-app/immich/blob/main/shared-link.service.ts), while the NestJS server manages cryptographic keys, permissions, and persistence in [`server/src/services/shared-link.service.ts`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/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:

```typescript
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`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/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:

```typescript
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:

1. **User Initiation**: The UI invokes `handleCreateSharedLink` with parameters like `type: SharedLinkType.Album` and an optional expiration date.
2. **SDK Transmission**: The client calls `createSharedLink`, transmitting a `SharedLinkCreateDto` (defined in [`src/dtos/shared-link.dto.ts`](https://github.com/immich-app/immich/blob/main/src/dtos/shared-link.dto.ts)) to the server.
3. **Server Validation**: `SharedLinkService.create` executes `requireAccess` checks, generates the `key` or validates the `slug`, and persists the record.
4. **Client Response Handling**: Upon receiving the `SharedLinkResponseDto`, the client emits `SharedLinkCreate` via `eventManager` and triggers `handleShowSharedLinkQrCode` to display the scannable link.
5. **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`](https://github.com/immich-app/immich/blob/main/web/src/lib/services/shared-link.service.ts)) from security-critical business logic ([`server/src/services/shared-link.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/shared-link.service.ts)).
- **Dual routing system** supports human-readable slugs (`/s/:slug`) and cryptographic keys (`/share/:key`) via the `asUrl` helper.
- **Permission enforcement** relies on `requireAccess` checks against `Permission.AlbumShare` and `Permission.AssetShare` before 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`](https://github.com/immich-app/immich/blob/main/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.