immich-app/immich · error · Error

Invalid storage template

Error message

Invalid storage template

What it means

StorageTemplateService validates a candidate storage template by rendering it against a sample asset. If rendering throws (unknown token, bad token syntax, malformed template), the catch rethrows a plain Error 'Invalid storage template' with the original error as cause (storage-template.service.ts:128). This blocks the template from being saved.

Source

Thrown at server/src/services/storage-template.service.ts:128

      this.render(compiled, {
        asset: {
          fileCreatedAt: new Date(),
          originalPath: '/upload/test/IMG_123.jpg',
          type: AssetType.Image,
          id: 'd587e44b-f8c0-4832-9ba3-43268bbf5d4e',
        } as StorageAsset,
        filename: 'IMG_123',
        extension: 'jpg',
        albumName: 'album',
        albumStartDate: new Date(),
        albumEndDate: new Date(),
        make: 'FUJIFILM',
        model: 'X-T50',
        lensModel: 'XF27mm F2.8 R WR',
      });
    } catch (error) {
      this.logger.warn(`Storage template validation failed: ${JSON.stringify(error)}`);
      throw new Error('Invalid storage template', { cause: error });
    }
  }

  getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
    return { ...storageTokens, presetOptions: storagePresets };
  }

  @OnEvent({ name: 'AssetMetadataExtracted' })
  async onAssetMetadataExtracted({ source, assetId }: ArgOf<'AssetMetadataExtracted'>) {
    await this.jobRepository.queue({ name: JobName.StorageTemplateMigrationSingle, data: { source, id: assetId } });
  }

  @OnJob({ name: JobName.StorageTemplateMigrationSingle, queue: QueueName.StorageTemplateMigration })
  async handleMigrationSingle({ id }: JobOf<JobName.StorageTemplateMigrationSingle>): Promise<JobStatus> {
    const config = await this.getConfig({ withCache: true });
    const isStorageTemplateEnabled = config.storageTemplate.enabled;
    if (!isStorageTemplateEnabled) {
      return JobStatus.Skipped;

View on GitHub (pinned to 199723261c)

Solutions

  1. Build the template only from tokens listed by getStorageTemplateOptions() (exposed via the config options endpoint).
  2. Start from a known preset and modify incrementally, validating each change.
  3. After an upgrade, review release notes for renamed/removed storage tokens and update custom templates.

Example fix

// before
{ storageTemplate: { template: '{{albumName}}/{{foo}}/{{fullname}}' } }
// after
{ storageTemplate: { template: '{{y}}/{{MM}}/{{fullname}}' } }
Defensive patterns

Strategy: validation

Validate before calling

const options = await systemConfigApi.getStorageTemplateOptions(); // tokens + presets
const allowedTokens = new Set(Object.keys(options.storageTokens ?? {}));
const usedTokens = extractTokens(newConfig.storageTemplate.template); // e.g. ['y','MM','fullname']
if (usedTokens.some((t) => !allowedTokens.has(t))) {
  throw new Error('Storage template contains an unknown token.');
}
await systemConfigApi.update(newConfig);

Type guard

const isKnownToken = (token: string, allowed: Set<string>): token is string => allowed.has(token);

Try / catch

try {
  await systemConfigApi.update(newConfig);
} catch (e) {
  if (/Invalid storage template/i.test(String((e as Error).message))) {
    showTemplateEditorWithError((e as Error).cause);
  } else throw e;
}

Prevention

When it happens

Trigger: Saving system config (PUT /system-config) with a storageTemplate.template value containing an unrecognized token like {{foo}} or malformed handlebars/bracket syntax that the renderer cannot parse.

Common situations: Hand-editing the template with a token not in storageTokens, version upgrades that renamed/removed tokens, or copy-pasting a template from an incompatible Immich version.

Related errors


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