๐ Recipe ยท Entra ID & Identity
Create Dynamic Groups in Entra ID with Location and Group Membership Rules
Combine location-based criteria and group membership using nested dynamic groups
Complexity
Intermediate
Impact
access-management + group-lifecycle + automation + entra-id
Context
Why This Matters
Dynamic groups in Microsoft Entra ID automatically populate membership based on user attributes (city, state, department) or membership in other groups. This recipe addresses a common requirement: combining location-based criteria with group membership in a single logical grouping โ for example, "everyone in a specific region plus everyone in a specific team."
A critical Entra ID limitation drives this design: the memberOf attribute cannot be combined with other rules (like user.city or user.state) in a single dynamic membership rule. Attempting to do so returns a DynamicGroupQueryParseError. The workaround is a three-group architecture: two dynamic groups (one per rule type) rolled up under a static parent group using nested group membership.
Run this recipe when you need to create distribution lists, access groups, or notification targets that combine geographic/organizational attributes with explicit team membership.
Expected Outcomes
After completing this recipe you will have:
- A location-based dynamic security group that auto-populates from user attributes (city, state, country).
- A memberOf dynamic security group that mirrors the membership of a source team/group.
- A static parent security group with both dynamic groups nested as members, providing a single handle for the combined population.
- A reusable architecture pattern for future requests that combine dynamic criteria with group membership.
Risks & Considerations
Known limitations
- memberOf cannot be combined with other rules โ this is why we use 3 groups instead of 1.
- Nested group transitivity is inconsistent across M365 services. Group-based licensing, some Conditional Access scenarios, and certain Exchange features do not resolve nested group membership. Test your specific use case.
- Security groups are not mail-enabled. If you need a distribution list for email, create a mail-enabled distribution group or Microsoft 365 group separately โ security groups cannot be converted.
- Licensing: Dynamic membership requires Entra ID P1 or P2 licenses for every user in scope. memberOf dynamic groups are in preview with a tenant limit of 500 groups.
Gotchas
- Initial population of dynamic groups can take several minutes after creation โ don't panic if membership is empty at first.
- User attributes like
cityandstateare free-text. Your rule must account for variations (e.g.,"Vaud"vs"VD"). - Using
contains()in Graph$filteron groups is not supported โ usestartswith()oreq. - The correct memberOf syntax is
user.memberOf -any (group.objectId -in ['guid']), notgroup.id.
Required Permissions
| Permission | Why It's Needed |
|---|---|
| Group.ReadWrite.All | Required to create security groups, set dynamic membership rules, and add members to the parent group. |
| Directory.Read.All | Required to look up the source group (e.g. ClinBio) and inspect user attributes when designing the membership rule. |
The fastest way to get this done: just ask Dex. Copy the prompt below and paste it into your Dex conversation.
For IT Admins
Paste into Dex Pro
Create the three groups via the Entra admin center
Step 1 โ Create the location-based dynamic group
- Sign in to the Microsoft Entra admin center as at least a Groups Administrator.
- Navigate to .
- Configure:
- Group type: Security
- Group name:
{region}-location-members(e.g.lausanne-vaud-members) - Membership type: Dynamic User
- Click Add dynamic query and enter a rule like:
(user.state -eq "Vaud") or (user.state -eq "VD") or (user.city -eq "Lausanne") - Click Save, then Create.
Step 2 โ Create the memberOf dynamic group
- Repeat with name
{region}-{team}-members(e.g.lausanne-clinbio-members). - Membership type: Dynamic User
- In the dynamic query, use syntax edit mode and enter:
user.memberOf -any (group.objectId -in ['<SOURCE-GROUP-GUID>']) - Save and create.
Step 3 โ Create the static parent group
- Create a new group with name
{region}-members(e.g.lausanne-members). - Membership type: Assigned (static).
- After creation, open the group โ .
- Search for and add both dynamic groups created in steps 1 and 2.
Step 4 โ Verify
- Wait 5โ10 minutes for dynamic rules to process.
- Open each dynamic group โ and confirm users are populated.
- Open the parent group and confirm both dynamic groups appear as members.
Graph API recipe
All calls use https://graph.microsoft.com/v1.0 as the base URL. Requires Group.ReadWrite.All.
1. Find the source group (to get its objectId)
GET /groups?$filter=startswith(displayName,'{TeamName}')&$select=id,displayNameNote the returned id โ you'll use it as <SOURCE-GROUP-GUID> below.
2. Create the location-based dynamic group
POST /groups
Content-Type: application/json
{
"displayName": "{region}-location-members",
"description": "Users located in {region}.",
"mailEnabled": false,
"securityEnabled": true,
"groupTypes": ["DynamicMembership"],
"mailNickname": "{region}-location-members",
"membershipRule": "(user.state -eq \"Vaud\") or (user.state -eq \"VD\") or (user.city -eq \"Lausanne\")",
"membershipRuleProcessingState": "On"
}Response 201 Created returns the new group's id. Save it as <LOCATION-GROUP-ID>.
3. Create the memberOf dynamic group
POST /groups
{
"displayName": "{region}-{team}-members",
"mailEnabled": false,
"securityEnabled": true,
"groupTypes": ["DynamicMembership"],
"mailNickname": "{region}-{team}-members",
"membershipRule": "user.memberOf -any (group.objectId -in ['<SOURCE-GROUP-GUID>'])",
"membershipRuleProcessingState": "On"
}Save the returned id as <MEMBEROF-GROUP-ID>.
4. Create the static parent group
POST /groups
{
"displayName": "{region}-members",
"mailEnabled": false,
"securityEnabled": true,
"groupTypes": [],
"mailNickname": "{region}-members"
}Save the returned id as <PARENT-GROUP-ID>.
5. Nest the two dynamic groups under the parent
POST /groups/<PARENT-GROUP-ID>/members/$ref
{ "@odata.id": "https://graph.microsoft.com/v1.0/groups/<LOCATION-GROUP-ID>" }POST /groups/<PARENT-GROUP-ID>/members/$ref
{ "@odata.id": "https://graph.microsoft.com/v1.0/groups/<MEMBEROF-GROUP-ID>" }Both calls return 204 No Content on success.
Common errors
DynamicGroupQueryParseError: Unsupported property 'id'โ usegroup.objectId, notgroup.id, in memberOf rules.Filter operator 'Contains' is not supportedโ usestartswith()when searching groups.
PowerShell walkthrough
This script uses the Microsoft Graph PowerShell SDK. Install it once with Install-Module Microsoft.Graph -Scope CurrentUser.
1. Connect to Graph
Connect-MgGraph -Scopes "Group.ReadWrite.All","Directory.Read.All"2. Look up the source group
$sourceGroup = Get-MgGroup -Filter "startswith(displayName,'ClinBio')" | Select-Object -First 1
$sourceGroupId = $sourceGroup.Id3. Create the location-based dynamic group
$locationGroup = New-MgGroup -BodyParameter @{
displayName = "lausanne-vaud-members"
mailEnabled = $false
securityEnabled = $true
groupTypes = @("DynamicMembership")
mailNickname = "lausanne-vaud-members"
membershipRule = '(user.state -eq "Vaud") or (user.state -eq "VD") or (user.city -eq "Lausanne")'
membershipRuleProcessingState = "On"
}4. Create the memberOf dynamic group
$memberOfRule = "user.memberOf -any (group.objectId -in ['$sourceGroupId'])"
$memberOfGroup = New-MgGroup -BodyParameter @{
displayName = "lausanne-clinbio-members"
mailEnabled = $false
securityEnabled = $true
groupTypes = @("DynamicMembership")
mailNickname = "lausanne-clinbio-members"
membershipRule = $memberOfRule
membershipRuleProcessingState = "On"
}5. Create the parent group and nest the dynamic groups
$parent = New-MgGroup -DisplayName "lausanne-members" `
-MailEnabled:$false -SecurityEnabled:$true `
-MailNickname "lausanne-members"
New-MgGroupMember -GroupId $parent.Id -DirectoryObjectId $locationGroup.Id
New-MgGroupMember -GroupId $parent.Id -DirectoryObjectId $memberOfGroup.IdThe parameterized, production-ready version is available as a downloadable script below.
Next steps