{"record":{"id":"badaf2ad573a0ce0","repo":"netbirdio/netbird","slug":"connector-type-change-not-allowed","errorCode":null,"errorMessage":"connector type change not allowed","messagePattern":"connector type change not allowed","errorType":"validation","errorClass":null,"httpStatus":500,"severity":"error","filePath":"idp/dex/connector.go","lineNumber":100,"sourceCode":"\t\t\tp.logger.Warn(\"failed to parse connector\", \"id\", conn.ID, \"error\", err)\n\t\t\tcontinue\n\t\t}\n\t\tresult = append(result, cfg)\n\t}\n\n\treturn result, nil\n}\n\n// UpdateConnector updates an existing connector in Dex storage.\n// It overlays user-mutable config fields (issuer, clientID, clientSecret,\n// redirectURI) onto the stored connector config, and updates the connector name\n// when cfg.Name is set. Empty fields on cfg leave stored values unchanged, so\n// partial updates preserve create-time defaults such as scopes, claimMapping,\n// and userIDKey.\nfunc (p *Provider) UpdateConnector(ctx context.Context, cfg *ConnectorConfig) error {\n\tif err := p.storage.UpdateConnector(ctx, cfg.ID, func(old storage.Connector) (storage.Connector, error) {\n\t\tif cfg.Type != \"\" && cfg.Type != inferIdentityProviderType(old.Type, cfg.ID, nil) {\n\t\t\treturn storage.Connector{}, errors.New(\"connector type change not allowed\")\n\t\t}\n\n\t\tconfigData, err := overlayConnectorConfig(old.Config, cfg)\n\t\tif err != nil {\n\t\t\treturn storage.Connector{}, fmt.Errorf(\"failed to overlay connector config: %w\", err)\n\t\t}\n\n\t\tname := cfg.Name\n\t\tif name == \"\" {\n\t\t\tname = old.Name\n\t\t}\n\n\t\treturn storage.Connector{\n\t\t\tID:     cfg.ID,\n\t\t\tType:   old.Type,\n\t\t\tName:   name,\n\t\t\tConfig: configData,\n\t\t}, nil","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/idp/dex/connector.go#L82-L118","documentation":"Thrown by Provider.UpdateConnector in the Dex IdP integration (idp/dex/connector.go:100). A stored Dex connector's type is immutable identity: an update may only leave the type field empty (keep current) or set it to the value inferred from the stored connector (inferIdentityProviderType(old.Type, cfg.ID, nil)). Any other type value is rejected before the config overlay is applied.","triggerScenarios":"Calling UpdateConnector with cfg.Type set to a different connector type than the stored one, e.g. updating a connector created as 'google' with Type: \"oidc\", or an editor round-tripping a full config object that carries a stale/edited type field.","commonSituations":"Management UI edit forms that submit the whole object including a type dropdown the user changed; API clients PUTting a full config where the type was normalized differently (case or alias); attempting to 'convert' a connector in place instead of recreating it.","solutions":["Omit the type field on updates (empty means unchanged).","To switch provider type, delete the connector and create a new one with the desired type.","If you believe the type is unchanged, compare against inferIdentityProviderType's expected value for the stored connector and send exactly that."],"exampleFix":"// before\nerr := provider.UpdateConnector(ctx, &ConnectorConfig{\n    ID: \"netbird\", Type: \"oidc\", // stored connector was created as \"google\"\n})\n\n// after: leave type empty to preserve it\nerr := provider.UpdateConnector(ctx, &ConnectorConfig{\n    ID: \"netbird\", Type: \"\", // type unchanged; only overlay fields are updated\n    ClientID: \"new-client\", ClientSecret: \"new-secret\",\n})","handlingStrategy":"validation","validationCode":"// Type is immutable: only send it when it matches the stored connector.\nif cfg.Type != \"\" && cfg.Type != expectedStoredType {\n    return fmt.Errorf(\"cannot change connector type %q to %q; recreate the connector instead\", expectedStoredType, cfg.Type)\n}","typeGuard":null,"tryCatchPattern":"if err := provider.UpdateConnector(ctx, cfg); err != nil {\n    if strings.Contains(err.Error(), \"connector type change not allowed\") {\n        // recreate with the new type instead of updating\n    }\n    return err\n}","preventionTips":["Send partial updates: omit the type field unless you are sure it matches.","Model connector type as immutable in clients; offer delete + create for type changes."],"tags":["go","netbird","idp","dex","connector","validation"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}