usememos/memos · error

storageSetting.s3Config is required for S3

Error message

storageSetting.s3Config is required for S3

What it means

Thrown when a STORAGE instance-setting file sets storageType to S3 but provides neither s3Config nor any entries in the storages list. The code comment explains the intent: normalization would silently self-heal this to LOCAL storage, so the validator fails loudly instead, preventing an operator's S3 data from silently going to local disk. Startup aborts.

Solutions

  1. Provide the S3 config inline: add "s3Config": { "accessKeyId": "...", "accessKeySecret": "...", "endpoint": "...", "region": "...", "bucket": "..." } to storageSetting.
  2. Or declare the S3 storage under storageSetting.storages[] with a matching id/actived state.
  3. Or, if local storage is actually intended, set "storageType": "LOCAL".

Example fix

// before
{ "key": "STORAGE", "storageSetting": { "storageType": "S3", "uploadSizeLimitMb": 100 } }

// after
{ "key": "STORAGE", "storageSetting": { "storageType": "S3", "uploadSizeLimitMb": 100,
  "s3Config": { "accessKeyId": "AKIA...", "accessKeySecret": "...", "endpoint": "https://s3.amazonaws.com", "region": "us-east-1", "bucket": "memos" } } }
Defensive patterns

Strategy: validation

Validate before calling

st := setting.GetStorageSetting()
if st.StorageType == storepb.InstanceStorageSetting_S3 && st.S3Config == nil && len(st.Storages) == 0 {
    return errors.New("S3 storageType requires s3Config or storages entries")
}

Type guard

func s3StorageIsConfigured(st *storepb.InstanceStorageSetting) bool {
    if st.GetStorageType() != storepb.InstanceStorageSetting_S3 {
        return true
    }
    return st.GetS3Config() != nil || len(st.GetStorages()) > 0
}

Prevention

When it happens

Trigger: A memos-instance-setting-storage.json with "storageType": "S3" and no "s3Config" block and empty/missing "storages" array; partially migrated S3 config where credentials moved into storages[] but the array got dropped.

Common situations: Declaring intent ("we use S3") before filling in credentials; downgrading from a version that stored S3 config in the database and assuming the server will read it from there; typo in the s3Config key leaving it unset.

Related errors


AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15). Data as JSON: /api/errors/44e3e4ec525feaf3. Report an issue: GitHub.

Appendix: source

Thrown at store/deployment_config.go:239

func validateAndNormalizeDeploymentInstanceSetting(setting *storepb.InstanceSetting) error {
	switch setting.Key {
	case storepb.InstanceSettingKey_GENERAL:
		if setting.GetGeneralSetting() == nil {
			return errors.New("generalSetting must be populated for key GENERAL")
		}
		if offset := setting.GetGeneralSetting().WeekStartDayOffset; offset < -1 || offset > 6 {
			return errors.New("generalSetting.weekStartDayOffset must be between -1 and 6")
		}
	case storepb.InstanceSettingKey_STORAGE:
		storage := setting.GetStorageSetting()
		if storage == nil {
			return errors.New("storageSetting must be populated for key STORAGE")
		}
		// Normalization would silently self-heal this misconfiguration to LOCAL;
		// a deployment file declaring S3 without a config should fail loudly.
		if storage.StorageType == storepb.InstanceStorageSetting_S3 && storage.S3Config == nil && len(storage.Storages) == 0 {
			return errors.New("storageSetting.s3Config is required for S3")
		}
		NormalizeInstanceStorageSetting(storage)
		if storage.UploadSizeLimitMb < 0 {
			return errors.New("storageSetting.uploadSizeLimitMb must not be negative")
		}
		defaultStorage := GetDefaultStorage(storage)
		if defaultStorage != nil && defaultStorage.Type == storepb.StorageType_STORAGE_TYPE_S3 {
			s3Config := defaultStorage.GetS3Config()
			if s3Config == nil {
				return errors.New("storageSetting default storage S3 config is required")
			}
			for _, field := range []struct {
				name  string
				value string
			}{
				{name: "accessKeyId", value: s3Config.AccessKeyId},
				{name: "accessKeySecret", value: s3Config.AccessKeySecret},
				{name: "endpoint", value: s3Config.Endpoint},

View on GitHub (pinned to 14d757ce1f)