diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go
index fecfe6200888a5c6b4567c91fdefea1eb5611470..21129f22272332a6634bed6c9cbfd34fddecf64d 100644
--- a/connector/oidc/oidc.go
+++ b/connector/oidc/oidc.go
@@ -89,6 +89,27 @@ type Config struct {
 		// Configurable key which contains the groups claims
 		GroupsKey string `json:"groups"` // defaults to "groups"
 	} `json:"claimMapping"`
+
+	// ClaimMutations holds all claim mutations options
+	ClaimMutations struct {
+		NewGroupFromClaims []NewGroupFromClaims `json:"newGroupFromClaims"`
+	} `json:"claimModifications"`
+}
+
+// NewGroupFromClaims creates a new group from a list of claims and appends it to the list of existing groups.
+type NewGroupFromClaims struct {
+	// List of claim to join together
+	Claims []string `json:"claims"`
+
+	// String to separate the claims
+	Delimiter string `json:"delimiter"`
+
+	// Should Dex remove the Delimiter string from claim values
+	// This is done to keep resulting claim structure in full control of the Dex operator
+	ClearDelimiter bool `json:"clearDelimiter"`
+
+	// String to place before the first claim
+	Prefix string `json:"prefix"`
 }
 
 // Domains that don't support basic auth. golang.org/x/oauth2 has an internal
@@ -192,6 +213,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
 		preferredUsernameKey:      c.ClaimMapping.PreferredUsernameKey,
 		emailKey:                  c.ClaimMapping.EmailKey,
 		groupsKey:                 c.ClaimMapping.GroupsKey,
+		newGroupFromClaims:        c.ClaimMutations.NewGroupFromClaims,
 	}, nil
 }
 
@@ -220,6 +242,7 @@ type oidcConnector struct {
 	preferredUsernameKey      string
 	emailKey                  string
 	groupsKey                 string
+	newGroupFromClaims        []NewGroupFromClaims
 }
 
 func (c *oidcConnector) Close() error {
@@ -443,6 +466,30 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
 		}
 	}
 
+	for _, config := range c.newGroupFromClaims {
+		newGroupSegments := []string{
+			config.Prefix,
+		}
+		for _, claimName := range config.Claims {
+			claimValue, ok := claims[claimName].(string)
+			if !ok { // Non string claim value are ignored, concatenating them doesn't really make any sense
+				continue
+			}
+
+			if config.ClearDelimiter {
+				// Removing the delimiter string from the concatenated claim to ensure resulting claim structure
+				// is in full control of Dex operator
+				claimValue = strings.ReplaceAll(claimValue, config.Delimiter, "")
+			}
+
+			newGroupSegments = append(newGroupSegments, claimValue)
+		}
+
+		if len(newGroupSegments) > 1 {
+			groups = append(groups, strings.Join(newGroupSegments, config.Delimiter))
+		}
+	}
+
 	cd := connectorData{
 		RefreshToken: []byte(token.RefreshToken),
 	}
diff --git a/connector/oidc/oidc_test.go b/connector/oidc/oidc_test.go
index 29e8875ea760daff36d43b2da26a9a86dc1b1a1e..4bb84a40d62f11170a5bafbf6102c992ddfe09dd 100644
--- a/connector/oidc/oidc_test.go
+++ b/connector/oidc/oidc_test.go
@@ -62,6 +62,7 @@ func TestHandleCallback(t *testing.T) {
 		expectPreferredUsername   string
 		expectedEmailField        string
 		token                     map[string]interface{}
+		newGroupFromClaims        []NewGroupFromClaims
 	}{
 		{
 			name:               "simpleCase",
@@ -288,6 +289,79 @@ func TestHandleCallback(t *testing.T) {
 				"email_verified": true,
 			},
 		},
+		{
+			name:               "newGroupFromClaims",
+			userIDKey:          "", // not configured
+			userNameKey:        "", // not configured
+			expectUserID:       "subvalue",
+			expectUserName:     "namevalue",
+			expectGroups:       []string{"group1", "gh::acme::pipeline-one", "clr_delim-acme-foobar", "keep_delim-acme-foo-bar", "bk-emailvalue"},
+			expectedEmailField: "emailvalue",
+			newGroupFromClaims: []NewGroupFromClaims{
+				{ // The basic functionality, should create "gh::acme::pipeline-one".
+					Claims: []string{
+						"organization",
+						"pipeline",
+					},
+					Delimiter: "::",
+					Prefix:    "gh",
+				},
+				{ // Non existing claims, should not generate any any new group claim.
+					Claims: []string{
+						"non-existing1",
+						"non-existing2",
+					},
+					Delimiter: "::",
+					Prefix:    "tfe",
+				},
+				{ // In this case the delimiter character("-") should be removed removed from "claim-with-delimiter" claim to ensure the resulting
+					// claim structure is in full control of the Dex operator and not the person creating a new pipeline.
+					// Should create "clr_delim-acme-foobar" and not "tfe-acme-foo-bar".
+					Claims: []string{
+						"organization",
+						"claim-with-delimiter",
+					},
+					Delimiter:      "-",
+					ClearDelimiter: true,
+					Prefix:         "clr_delim",
+				},
+				{ // In this case the delimiter character("-") should be NOT removed from "claim-with-delimiter" claim.
+					// Should create  "keep_delim-acme-foo-bar".
+					Claims: []string{
+						"organization",
+						"claim-with-delimiter",
+					},
+					Delimiter: "-",
+					// ClearDelimiter: false,
+					Prefix: "keep_delim",
+				},
+				{ // Ignore non string claims (like arrays), this should result in "bk-emailvalue".
+					Claims: []string{
+						"non-string-claim",
+						"non-string-claim2",
+						"email",
+					},
+					Delimiter: "-",
+					Prefix:    "bk",
+				},
+			},
+
+			token: map[string]interface{}{
+				"sub":                  "subvalue",
+				"name":                 "namevalue",
+				"groups":               "group1",
+				"organization":         "acme",
+				"pipeline":             "pipeline-one",
+				"email":                "emailvalue",
+				"email_verified":       true,
+				"claim-with-delimiter": "foo-bar",
+				"non-string-claim": []string{
+					"element1",
+					"element2",
+				},
+				"non-string-claim2": 666,
+			},
+		},
 	}
 
 	for _, tc := range tests {
@@ -323,6 +397,7 @@ func TestHandleCallback(t *testing.T) {
 			config.ClaimMapping.PreferredUsernameKey = tc.preferredUsernameKey
 			config.ClaimMapping.EmailKey = tc.emailKey
 			config.ClaimMapping.GroupsKey = tc.groupsKey
+			config.ClaimMutations.NewGroupFromClaims = tc.newGroupFromClaims
 
 			conn, err := newConnector(config)
 			if err != nil {