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
- Pick a preset from the storage template options or reset to the default template: {{y}}/{{MM}}/{{dd}}/{{filename}}.
- Validate each token against the documented list ({{y}}, {{MM}}, {{filename}}, {{album}}, etc.) and fix typos.
- Remove illegal path constructs (absolute paths, '..', reserved characters) from the template.
- 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
- Use only documented storage tokens ({{y}}, {{MM}}, {{filename}}, ...)
- Test templates in the settings UI preview before saving
- Avoid absolute paths and '..' segments in templates
- Re-validate templates copied from older Immich versions
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
- Unknown CLIP model: . Please check the model name for typos…
- Asset dimensions are not available for editing
- Asset not in stack
- Cannot remove stack's primary asset
- Crop action must be the first edit action
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)