Overview
PATCH lets an identity provider update a SCIM resource without sending the full representation. Instead of replacing the entire user object, you send a list of targeted operations. theAuth implements the RFC 7644 §3.5.2 PATCH protocol atPATCH /scim/v2/Users/{id}. The endpoint deserializes the current database row into a SCIM view, applies your operations against that view using the path engine in scim-patch.ts, and then maps the mutated fields back to database columns.
Operation types
Every PATCH request body must include the patch schema and anOperations array with at least one entry.
add
Adds a value. On a multi-valued attribute (likeemails), it appends rather than replaces. On a scalar, it sets the value. When path is omitted, the value object is merged into the resource root.
replace
Overwrites the targeted attribute. On a multi-valued attribute targeted by a value filter, it updates only the matching elements. Without a path, it merges thevalue object into the root (same merge behavior as add).
remove
Removes the targeted attribute or, with a value filter, drops matching array elements.remove without a path is always rejected with noTarget.
Path expressions
Paths follow the RFC 7644 §3.5.2 grammar:Simple attribute
Targets a top-level attribute directly.Nested attribute
A dot separates the base attribute from a sub-attribute. The engine creates intermediate objects if they do not exist.Multi-valued attribute
Targeting the base attribute without a filter operates on the whole array.Value-filter selector
Brackets contain a filter expression that selects elements of the array. The filter uses SCIM attribute operators:eq, ne, co, sw, ew, pr, gt, lt, ge, le, and logical operators and/or/not.
Value-filter with sub-attribute
Append.attrName after the closing bracket to target a specific field on each matching element.
name.sub[filter] is rejected with invalidPath.
URN-prefixed attribute
Extension schema attributes use the full URN as a namespace prefix. The engine splits on the last: to extract the schema URN and the attribute name, then writes the value under the URN key in the resource object.
schemaUrn:urn:ietf:params:scim:schemas:extension:enterprise:2.0:Userbase:department
Add semantics
Whenop is add and the target is a multi-valued attribute (an existing array), the engine appends rather than replaces. This is intentional per RFC 7644 §3.5.2.2.
replace, which overwrites the array:
add has the same append behavior for arrays nested inside the value object:
Remove semantics
remove with a plain path sets the attribute to undefined:
remove with a value filter drops all matching array elements and keeps the rest:
remove without any path is rejected:
No-path operations
add and replace accept operations without a path. The value must be an object. Each key in the object is merged into the resource root.
replace operations with explicit paths. Immutable checks still apply: if the value object contains id, schemas, or meta, the entire operation is rejected before any write happens.
remove without a path is always rejected regardless of what value contains.
Immutable attributes
The following paths cannot be modified by any PATCH operation. They are server-controlled per RFC 7643 §7.
Attempting to PATCH any of these returns:
id, the first write does not persist.
Caller-defined readonly paths
ThehandlePatchUser handler passes an extra readonlyPaths option to the engine:
externalId cannot be changed via PATCH after the resource is created, even though it is not in the base immutable list. Paths in readonlyPaths are lowercased before comparison, so externalId, externalid, and ExternalID all match.
If you build a custom SCIM handler, you can pass any additional paths you want to lock:
DoS guardrails
The engine caps the number of operations per request. The default is 1000. If theOperations array exceeds the cap, the engine throws before processing any operation.
maxOperations:
Error reference
Worked examples
1. Deactivate a user (Okta-style)
Okta sends a singlereplace on active to suspend a user.
op values are case-insensitive. Replace and replace are equivalent.
2. Update display name and email in one request (Okta-style)
3. No-path merge (Azure AD-style)
Azure AD often sendsreplace without a path, merging a partial object.
4. Add a phone number (Azure AD-style)
phoneNumbers already exists, this appends the new entry.
5. Set enterprise extension attribute (Google Workspace-style)
Google Workspace sends URN-prefixed paths for extension attributes.6. Remove the home email
7. Replace nested name fields
8. Add multiple emails at once
Current limitations
Groups PATCH is partial.PATCH /scim/v2/Groups/{id} routes through a separate handler (handlePatchGroup) that does not use the scim-patch.ts engine. Value-filter operations on group members are not supported. A full rewrite to use the shared engine is planned for a follow-up release.
Value-filter member operations. Filtering on members[value eq "user-id"] in a group context is not yet evaluated against the engine’s AST walker. Sending such a path against the groups endpoint will not produce a predictable error — it will silently fail or return unexpected results.
Filter operators. The current AST walker in scim-filter.ts supports the comparison operators (eq, ne, co, sw, ew, pr, gt, lt, ge, le) and logical operators (and, or, not). Complex nested logical expressions with more than two clauses may not parse correctly.
Security posture
The path parser and value-filter evaluator are pure AST walkers. No user-provided string is ever passed toeval, new Function, or a dynamic property accessor outside a whitelisted attribute list.
Immutable checks happen before any write begins. If an operation batch is partially valid and the first invalid operation is the third in the list, no writes from operations one or two persist.
The operation count cap prevents request-amplification attacks. Without it, a single PATCH request could encode thousands of operations, each triggering DB reads and writes.
Audit logs record the final resource state after the patch is applied, not the raw operation strings. This prevents log injection through maliciously crafted path expressions or filter values.