# How Partner Sharing Relationships Are Managed in Immich: A Deep Dive into `partner.service.ts`

> Explore how Immich manages partner sharing relationships using PartnerService and understand the four permission scopes for secure access control. Learn to create, read, update, and delete partner connections.

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

---

**Partner sharing relationships in Immich are managed through the `PartnerService` class which provides CRUD operations for creating, reading, updating, and deleting partner connections, enforced by four distinct permission scopes defined in the Permission enum.**

The `immich-app/immich` repository implements partner sharing through a dedicated service layer that handles the business logic for granting library access between users. Understanding how [`partner.service.ts`](https://github.com/immich-app/immich/blob/main/partner.service.ts) manages these relationships and their associated permission scopes is essential for developers extending Immich's access control features or debugging sharing issues.

## Core CRUD Operations in PartnerService

The `PartnerService` class in [`server/src/services/partner.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/partner.service.ts) exposes four primary methods that manipulate the `Partner` entity. Each operation enforces specific permission checks either at the controller layer or within the service itself.

### Creating Partner Relationships

The `create` method establishes a new directional relationship where one user (the caller) shares their library with another user (the target).

```typescript
// server/src/services/partner.service.ts lines 12-20
async create(auth: AuthDto, dto: PartnerCreateDto) {
  const partnerIds: PartnerIds = { sharedById: auth.user.id, sharedWithId: dto.sharedWithId };
  // Verify relationship doesn't already exist
  const existing = await this.partnerRepository.get(partnerIds);
  if (existing) {
    throw new BadRequestException('Partner already exists');
  }
  // Persist new partner relationship
  const partner = await this.partnerRepository.create(partnerIds);
  return this.mapPartner(partner, PartnerDirection.SharedBy);
}

```

This operation requires `Permission.PartnerCreate`, which the controller checks before invoking the service.

### Removing Partner Connections

The `remove` method deletes an existing partner relationship using the same `PartnerIds` composite key structure.

```typescript
// server/src/services/partner.service.ts lines 22-30
async remove(auth: AuthDto, id: string) {
  const partnerIds: PartnerIds = { sharedById: auth.user.id, sharedWithId: id };
  const partner = await this.partnerRepository.get(partnerIds);
  if (!partner) {
    throw new BadRequestException('Partner not found');
  }
  await this.partnerRepository.remove(partnerIds);
}

```

The controller enforces `Permission.PartnerDelete` for this operation.

### Searching and Listing Partners

The `search` method retrieves all partner relationships for the authenticated user, supporting directional filtering via the `PartnerDirection` enum.

```typescript
// server/src/services/partner.service.ts lines 32-48
async search(auth: AuthDto, dto: PartnerSearchDto) {
  const { direction } = dto;
  const partners = await this.partnerRepository.getAll(auth.user.id);
  return partners
    .filter((partner) => {
      const filterBy = direction === PartnerDirection.SharedBy ? partner.sharedById : partner.sharedWithId;
      return filterBy === auth.user.id;
    })
    .filter((partner) => !partner.sharedBy.isDeleted && !partner.sharedWith.isDeleted)
    .map((partner) => this.mapPartner(partner, direction));
}

```

This requires `Permission.PartnerRead` and automatically filters out relationships involving soft-deleted users.

### Updating Partner Settings

The `update` method modifies partner-specific settings, currently limited to the `inTimeline` boolean flag that controls whether the partner's assets appear in the user's timeline.

```typescript
// server/src/services/partner.service.ts lines 50-58
async update(auth: AuthDto, id: string, dto: PartnerUpdateDto) {
  await this.requireAccess({ permission: Permission.PartnerUpdate, ids: [id] });
  const partnerIds: PartnerIds = { sharedById: id, sharedWithId: auth.user.id };
  const partner = await this.partnerRepository.update(partnerIds, { inTimeline: dto.inTimeline });
  return this.mapPartner(partner, PartnerDirection.SharedWith);
}

```

Unlike other methods, `update` enforces `Permission.PartnerUpdate` internally via the `requireAccess` method inherited from `BaseService` ([`server/src/services/base.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/base.service.ts)).

## Permission Scopes for Partner Sharing

The four permission scopes governing partner relationships are defined in [`server/src/enum.ts`](https://github.com/immich-app/immich/blob/main/server/src/enum.ts) (lines 82-86) as part of the central `Permission` enum:

| Enum Member | Scope String | Description |
|-------------|--------------|-------------|
| `PartnerCreate` | `partner.create` | Grants ability to initiate new partner sharing relationships |
| `PartnerRead` | `partner.read` | Grants ability to view existing partner relationships |
| `PartnerUpdate` | `partner.update` | Grants ability to modify partner settings like `inTimeline` |
| `PartnerDelete` | `partner.delete` | Grants ability to terminate partner relationships |

These scopes are enforced at the API controller layer for `create`, `remove`, and `search` operations, while the `update` method performs its permission check internally using `requireAccess` from the base service class.

## Data Mapping and Response DTOs

The private `mapPartner` method (lines 50-57 in [`partner.service.ts`](https://github.com/immich-app/immich/blob/main/partner.service.ts)) transforms database entities into API responses:

```typescript
private mapPartner(partner: PartnerEntity, direction: PartnerDirection): PartnerResponseDto {
  const isSharedBy = direction === PartnerDirection.SharedBy;
  const user = isSharedBy ? partner.sharedWith : partner.sharedBy;
  return {
    ...mapUser(user),
    inTimeline: partner.inTimeline,
  };
}

```

This method determines which user profile to expose based on the query direction, ensuring the API returns the partner's information (not the caller's) while appending the relationship-specific `inTimeline` flag.

## End-to-End Implementation Example

The following example demonstrates the complete lifecycle of a partner sharing relationship as implemented in [`partner.service.ts`](https://github.com/immich-app/immich/blob/main/partner.service.ts):

```typescript
// 1. Alice creates a partner relationship with Bob
await partnerService.create(
  { user: { id: 'alice-id' } },          // AuthDto
  { sharedWithId: 'bob-id' }             // PartnerCreateDto
);
// Controller checks Permission.PartnerCreate
// Persists: { sharedById: 'alice-id', sharedWithId: 'bob-id' }

// 2. Bob searches for partners shared with him
await partnerService.search(
  { user: { id: 'bob-id' } },
  { direction: PartnerDirection.SharedWith }
);
// Controller checks Permission.PartnerRead
// Returns PartnerResponseDto for Alice

// 3. Bob hides Alice's assets from his timeline
await partnerService.update(
  { user: { id: 'bob-id' } },
  'alice-id',                             // sharedById (owner)
  { inTimeline: false }                   // PartnerUpdateDto
);
// Service checks Permission.PartnerUpdate via requireAccess
// Updates inTimeline flag on the Partner row

```

## Summary

- **PartnerService Location**: The core business logic resides in [`server/src/services/partner.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/partner.service.ts), providing `create`, `remove`, `search`, and `update` methods for managing partner relationships.
- **CRUD Operations**: The service handles creation of directional relationships, deletion by composite key (`sharedById` + `sharedWithId`), filtered searching by direction, and updates to the `inTimeline` flag.
- **Permission Enforcement**: Four distinct scopes (`PartnerCreate`, `PartnerRead`, `PartnerUpdate`, `PartnerDelete`) defined in [`server/src/enum.ts`](https://github.com/immich-app/immich/blob/main/server/src/enum.ts) protect these operations, with most enforced at the controller layer and `PartnerUpdate` enforced internally via `requireAccess`.
- **Data Mapping**: The `mapPartner` method transforms database entities into `PartnerResponseDto` objects, ensuring the API returns the correct user profile based on query direction while including relationship-specific metadata.

## Frequently Asked Questions

### What permission is required to create a partner sharing relationship in Immich?

Creating a partner relationship requires the `PartnerCreate` permission (scope string `partner.create`), which is defined in [`server/src/enum.ts`](https://github.com/immich-app/immich/blob/main/server/src/enum.ts) and enforced at the controller layer before `PartnerService.create` is invoked. The authenticated user becomes the `sharedById` while the target user becomes the `sharedWithId`.

### How does PartnerService determine which user is the "other" party in a relationship?

The `mapPartner` method in [`server/src/services/partner.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/partner.service.ts) determines the other party by examining the `direction` parameter. When the direction is `SharedBy`, it returns `partner.sharedWith`; when `SharedWith`, it returns `partner.sharedBy`. This ensures the API always returns the partner's user profile rather than the caller's.

### Can a shared-with user modify the inTimeline setting for a partner relationship?

Yes, the shared-with user can update the `inTimeline` flag via the `update` method, which modifies whether the partner's assets appear in their timeline. This operation requires `Permission.PartnerUpdate`, enforced internally through the `requireAccess` method inherited from `BaseService`, and uses a `PartnerIds` key where `sharedById` is the partner owner and `sharedWithId` is the caller.

### Where are the permission scopes for partner sharing defined in the codebase?

The four permission scopes for partner sharing—`PartnerCreate`, `PartnerRead`, `PartnerUpdate`, and `PartnerDelete`—are defined as enum members in [`server/src/enum.ts`](https://github.com/immich-app/immich/blob/main/server/src/enum.ts) (lines 82-86). These map to scope strings (`partner.create`, `partner.read`, `partner.update`, `partner.delete`) used throughout the API layer and service layer to enforce access control.