immich-app/immich · error

Invalid storage template

Error message

Invalid storage template

What it means

During config validation, StorageTemplateService renders the configured storage template against a sample asset (FUJIFILM X-T50 sample metadata); if the template string or rendering fails, it wraps the cause in 'Invalid storage template', rejecting the config save.

Solutions

  1. Pick a preset from the storage template options or reset to the default template: {{y}}/{{MM}}/{{dd}}/{{filename}}.
  2. Validate each token against the documented list ({{y}}, {{MM}}, {{filename}}, {{album}}, etc.) and fix typos.
  3. Remove illegal path constructs (absolute paths, '..', reserved characters) from the template.
  4. Check the logged 'Storage template validation failed' warning for the underlying cause and fix accordingly.

Example fix

// before
storageTemplate: { template: '{{YYYY}}-{{MM}}/{{originalFileName}}' }
// after
storageTemplate: { template: '{{y}}/{{MM}}/{{filename}}' }
Defensive patterns

Strategy: try-catch

Validate before calling

// render the template against sample data before saving
import { renderTemplate } from 'src/utils/storage-template';
await renderTemplate({ template, asset: sampleAsset, album: null }); // throws if invalid

Try / catch

try {
  await api.updateConfig(config);
} catch (e) {
  if (/Invalid storage template/.test(e.message)) {
    // reset to a known-good preset and re-open the editor
    config.storageTemplate.template = '{{y}}/{{MM}}/{{filename}}';
  } else throw e;
}

Prevention

When it happens

Trigger: Saving a storage template containing unknown tokens, malformed template syntax, illegal characters/paths (e.g. leading '/', '..'), or expressions that fail to render with sample EXIF data.

Common situations: Hand-editing the template with typos like {{Y}} instead of {{y}}; using removed/unsupported variables; templates that produce empty paths or duplicate-segment mistakes; copy-pasted templates from older Immich versions.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/3fc3e839050a2778. Report an issue: GitHub.

Appendix: 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(): ConfigTemplateStorageOptionDto {
    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 e55ac299a4)