# How Immich Generates Personalized "On This Day" Memories in `memory.service.ts`

> Discover how Immich generates personalized On This Day memories in memory service. Learn about the daily job, asset querying, and memory record creation.

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

---

**Immich creates personalized "On This Day" memories through a daily background job that queries each user's assets by day-of-year, groups them by original year, and generates time-boxed memory records visible only to the asset owner.**

Immich's Memories feature surfaces nostalgic photo collections from years past, automatically appearing in users' timelines without manual curation. This functionality is orchestrated by [`memory.service.ts`](https://github.com/immich-app/immich/blob/main/memory.service.ts) in the `immich-app/immich` repository, which implements a sophisticated background job pipeline that ensures each user sees only their own historical content.

## The Background Job Architecture

The memory generation process is triggered by the `MemoryGenerate` job, registered via the `@OnJob` decorator in [`server/src/services/memory.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/memory.service.ts). This attaches the `onMemoriesCreate()` method to the background task queue, ensuring execution occurs automatically on schedule.

```typescript
@OnJob({ name: JobName.MemoryGenerate, queue: QueueName.BackgroundTask })
async onMemoriesCreate() {
  // Implementation handles per-user memory generation
}

```

When the scheduler fires this daily job, the service iterates through all active users to create personalized batches.

## The Memory Generation Pipeline

The core logic in [`memory.service.ts`](https://github.com/immich-app/immich/blob/main/memory.service.ts) follows a stateful, idempotent workflow that prevents duplicate processing while handling failures gracefully.

### User Enumeration and Isolation

The service fetches all active users via `this.userRepository.getList({ withDeleted: false })`. This enumeration ensures memories are generated individually for each user, establishing the foundation for personalization through the subsequent `ownerId` parameter passing.

### State Management and Deduplication

To prevent concurrent execution conflicts, the service acquires `DatabaseLock.MemoryCreation`. It tracks progress in the `systemMetadata` table under the `MemoriesState` key, storing the `lastOnThisDayDate`. This persistence ensures that if a job fails midway, the next run resumes from the correct date without regenerating existing memories.

```typescript
// Acquire lock and check state
await this.databaseRepository.withLock(DatabaseLock.MemoryCreation, async () => {
  const lastOnThisDayDate = await this.systemMetadataRepository.get(SystemMetadataKey.MemoriesState);
  // Process only unprocessed dates...
});

```

### Rolling Date Window Calculation

Immich examines a configurable window of three days before and after the current date (defined by the `DAYS` constant). For each unprocessed date in this range, the system triggers memory creation, allowing users to discover memories slightly early or catch up on recent ones they might have missed.

### Asset Selection by Day-of-Year

The `assetRepository.getByDayOfYear([ownerId], target)` method performs the critical "On this day" matching. It queries assets where the month and day match the target date, regardless of year, but restricts results to the specific `ownerId`. The repository returns data grouped by original year as `{ year, assets }` pairs, enabling the creation of separate memories for "2018 on this day" versus "2019 on this day."

```typescript
const memories = await this.assetRepository.getByDayOfYear([ownerId], target);
// Returns: [{ year: 2018, assets: [...] }, { year: 2019, assets: [...] }]

```

### Memory Record Creation

For each year-grouped asset collection, the service calls `memoryRepository.create()` with specific temporal boundaries:

- **`ownerId`**: Ties the record to the specific user
- **`type: MemoryType.OnThisDay`**: Categorizes the memory type
- **`data: { year }`**: Stores the original year for UI display
- **`memoryAt`**: The target date with the original year baked in, positioning the memory historically
- **`showAt` and `hideAt`**: ISO timestamps defining the 24-hour visibility window (start and end of the target day)

```typescript
await Promise.all(
  memories.map(({ year, assets }) =>
    this.memoryRepository.create(
      {
        ownerId,
        type: MemoryType.OnThisDay,
        data: { year },
        memoryAt: target.set({ year }).toISO()!,
        showAt: target.startOf('day').toISO(),
        hideAt: target.endOf('day').toISO(),
      },
      new Set(assets.map(({ id }) => id)),
    ),
  ),
);

```

## Personalization Mechanisms

Personalization in Immich's memory system operates at multiple layers, ensuring strict data isolation between users.

### Per-User Asset Isolation

Every database query filters by `ownerId`. The `getByDayOfYear` repository method accepts an array of owner IDs (though typically called with a single user in this context), and all memory repository queries (`memoryRepository.search`, `memoryRepository.statistics`) enforce user-scoped filtering. This guarantees users never see assets belonging to others, even in shared instance deployments.

### Timeline Visibility Enforcement

Beyond ownership, the system respects Immich's standard visibility rules. Asset queries enforce `asset.visibility = Timeline` and `asset.deletedAt IS NULL`, ensuring that trashed photos or assets hidden from the timeline do not appear in memories. This mirrors the main timeline view, providing a consistent, personalized experience.

## Key Implementation Files

The memory ecosystem spans several critical files in the `server/src` directory:

- **[`services/memory.service.ts`](https://github.com/immich-app/immich/blob/main/services/memory.service.ts)**: Core orchestration logic, background job handling, and per-user processing loops
- **[`repositories/memory.repository.ts`](https://github.com/immich-app/immich/blob/main/repositories/memory.repository.ts)**: Database abstraction for CRUD operations, search functionality, and asset linking
- **[`repositories/asset.repository.ts`](https://github.com/immich-app/immich/blob/main/repositories/asset.repository.ts)**: Provides `getByDayOfYear` for date-based asset retrieval
- **[`controllers/memory.controller.ts`](https://github.com/immich-app/immich/blob/main/controllers/memory.controller.ts)**: HTTP endpoints exposing memory management to clients, using `auth.user.id` for filtering
- **[`enum.ts`](https://github.com/immich-app/immich/blob/main/enum.ts)**: Defines `MemoryType.OnThisDay` and related constants

## Summary

- Immich generates memories via a daily `MemoryGenerate` background job registered in [`memory.service.ts`](https://github.com/immich-app/immich/blob/main/memory.service.ts) using the `@OnJob` decorator
- The system processes a rolling 3-day window around the current date, tracking state in `systemMetadata` under `MemoriesState` to prevent duplicates
- Asset selection uses `assetRepository.getByDayOfYear` to match photos by month-day across all years, grouped by original year
- Each memory record is bound to a specific `ownerId` with 24-hour visibility windows (`showAt`/`hideAt`) and type classification `MemoryType.OnThisDay`
- Strict personalization is enforced through per-user asset isolation, timeline visibility filters, and user-scoped database queries

## Frequently Asked Questions

### How often does Immich generate new memories?

The `MemoryGenerate` job runs once daily as a background task. It checks a rolling window of three days before and after the current date to ensure users don't miss memories if the job fails temporarily, while state tracking in `systemMetadata` prevents duplicate generation.

### Why do I see memories from different years grouped separately?

Immich groups assets by their original year in the `getByDayOfYear` repository method. Each year generates a distinct memory record with `data: { year }`, allowing the UI to display "On this day in 2018" and "On this day in 2019" as separate collections rather than one mixed gallery.

### Can other users see my generated memories?

No. Every memory record stores your `ownerId`, and all repository queries filter by this ID. The `memoryRepository.search` method specifically requires `auth.user.id`, ensuring strict isolation. Even on shared Immich instances, memories remain private to the asset owner.

### What happens if the memory generation job fails mid-process?

The system uses `DatabaseLock.MemoryCreation` to prevent concurrent runs and persists the `lastOnThisDayDate` in `systemMetadata` after each day is processed. If a failure occurs, the next job execution resumes from the last successful date stored in `MemoriesState`, ensuring no days are skipped or duplicated.