Microsoft Defender XDR Custom Detection Rules: A Complete Guide & Best Practices

Microsoft Defender XDR custom detection rules have changed a lot in the past year’s. In October 2024, near-real-time (NRT) detections entered preview. In October 2025, custom detections became the default for new rules across Defender XDR and Sentinel. In January 2026, NRT support expanded to Sentinel data. Then in April 2026, Microsoft added a SentinelScope_CF requirement that can stop some analysts from seeing alerts if it’s missed.

None of this is hidden. It’s all in Microsoft’s docs. The problem is that the docs are split by feature, not by the questions people actually have. Should I move this rule to NRT? Why is my alert missing custom details? Which columns do I need for entity mapping? Why can’t the Sentinel analyst see my alert? The answers exist, but they’re spread across several pages.

Custom rules let you say: “When this happens in my environment, alert me and take action if needed.”

For example, if you have a break-glass account like [email protected] that should only be used in an emergency, you can create a rule that alerts whenever that account signs in.

This guide puts everything in one place so you can quickly find what matters.

How the whole thing works — the big picture:Think of building a custom detection rule like setting up a security camera with a smart alarm

1. Anatomy of a custom detection rule

Before Defender XDR accepts your query, it checks for certain key columns. These columns give the system the minimum information it needs to know who or what triggered the alert.

This check happens when you click Create detection rule. If the query is missing required columns, the wizard will reject it. The exact columns depend on the table family, and a common mistake is using summarize, which can remove ReportId and other needed fields.

The rule frequency is another important choice. It affects whether the rule can use NRT, how scoping works and how far back the rule looks.

Also, when you save a new rule, Defender XDR runs a 30-day initial scan. If the rule is too broad, it can create a flood of historical alerts. While testing, use a guard like ingestion_time() > ago(1d) to keep results under control.

Example that works:--This works because it keeps the required fields like Timestamp, ReportId and DeviceId.
DeviceProcessEvents
| where FileName == "mshta.exe"
| where ProcessCommandLine has "javascript:"
| project Timestamp, ReportId, DeviceId, DeviceName, ProcessCommandLine

Example that fails:--This fails because summarize drops fields like Timestamp, ReportId and DeviceId. In this case, arg_max() is the better choice.
DeviceProcessEvents
| where FileName == "mshta.exe"
| summarize Count = count() by DeviceName

2. Frequency: how often should the alarm check?

This is the most important decision you make when creating a rule. Think of it like choosing between a smoke alarm that checks every second vs one that checks once a day.

Lookback periods per frequency:

Which frequency fits which scenario:

3. NRT Rules: High-Speed Detection and its speed bumps

NRT (Near Real-Time) means your rule runs roughly every 2 minutes instead of once an hour. It is the fastest detection available - but it has strict requirements.

Think of NRT like the express lane at the airport. You get through faster, but you have to meet stricter rules to use it.

No comments at all. Even a single // line breaks NRT eligibility. Strip all inline comments and move documentation to the rule description field or your detection-as-code repo.

Double-escaped regex. The pattern \A must be written as "\\A" in KQL for NRT rules. Single-backslash regex that works in scheduled rules fails NRT validation.

GA columns only. If your migration silently does not take effect, a preview column is the most likely cause.

No joins, unions and external data. This disqualifies most cross-table correlation rules. Accept the fixed schedule or restructure the detection logic.

4. Making alerts useful: enrichment and entity mapping

When an alert fires at 3 AM, your analyst should understand it in seconds: what happened, who is involved and what to do next. This section is about putting that context directly into the alert.

Entity mapping helps Defender XDR group related alerts into incidents. If you skip it, each alert stays separate which means less correlation and more noise. For Defender XDR data entities are mapped automatically from the required columns. For Sentinel data, you have to map them yourself, which is why many ported rules create messy, ungrouped incidents.

SigninLogs
| where ResultType == 0
| where RiskLevelDuringSignIn == "high"
| project TimeGenerated, UserPrincipalName, IPAddress, Location, DeviceDetail

Rule setup:

  • Alert title: High-risk sign-in: {{UserPrincipalName}} from {{Location}}
  • Custom details: IP Address → {{IPAddress}}, Device → {{DeviceDetail}}
  • Entity mapping: Account → UserPrincipalName, IP → IPAddress

Result: the analyst can open the alert and quickly see who signed in, from where, and on what device.

One important limit is the 4 KB cliff. If all custom detail key-value pairs together exceed 4 KB, Defender XDR drops the entire custom details section. The alert still appears, but the enrichment panel will be empty. To avoid this, keep the projected fields small and trim long strings in the query.

Required identifier columns must also be present for entity mapping to work. If summarize removes them, use arg_max() instead so the important fields stay in the output.

5. Response actions: what happens automatically when the alert fires

Defender XDR can take automatic action when a rule fires, so a human does not have to step in every time. But each action has some risk, so it helps to know what it really does before you turn it on.

  • Isolate device cuts the machine off from the network the device can only reach Defender’s management channel. Use this only when the rule is very reliable, because a false positive can disrupt a real user.
  • Block file applies at the device group level. If you block a file hash, every device in that group will be blocked from running it, not just the one that triggered the alert.
  • Mark user as compromised raises the user’s Entra ID risk to high. If you do not have a Conditional Access policy that reacts to high-risk users, this may not do anything visible.
  • Email actions need both NetworkMessageId and RecipientEmailAddress. If either one is missing from the query output, the action will not run.
  • User actions for hybrid identities need both SID and ObjectId. For cloud-only Entra identities, AccountObjectId is enough. The rule may save fine even if the fields are missing, but the action can fail silently later.

A practical guide to pairing rules with actions:

6. The Scoping Mistake That Makes Alerts Disappear

This is the April 2026 change that can surprise teams. If your organization uses scoped workspaces, you now need to include a specific column in your query or some teams may not see the alert at all.

Scoping only applies to newly ingested data. The 30-day initial scan that runs when a rule is created uses older, unscoped history, so scoped analysts may or may not see those alerts depending on when scoping was added. Also, XDR tables do not directly support Sentinel scoping, so if your rule uses Defender XDR tables, test the behavior in your own tenant.

To see whether this affects you, check if any of these are true:

  • You work with an MSSP that has scoped access to your workspace.
  • You have regional SOC teams that only see their own data.
  • You have different access levels for Tier 1 and Tier 2 analysts.

If any of those apply, include SentinelScope_CF in your rules.

7. Understanding The Limits and Their Impact

The 150-alert cap: If your rule finds more than 200 matches in one run, Defender XDR only creates alerts for the first 150. The rest are ignored for that run. This is why tight, specific rules are better than broad ones-not just to reduce noise, but to avoid missing events during spikes.

The 4 KB custom details limit: The entire enrichment array is dropped if the total exceeds 4 KB. Truncate long values in the query before they reach the rule.

The 30-day initial scan: When you save a new rule, it immediately scans 30 days of history. That can create a flood of alerts at deployment time. During testing, use a guard like ingestion_time() > ago(1d) to keep results under control, then remove it once you’re confident in production volume.

8. Best practices

i). Align time filters to the rule lookback — or remove them Let the platform manage the window using ingestion_time(), e.g:

DeviceProcessEvents
| where ingestion_time() > ago(1h)
| where ProcessCommandLine has_any ("javascript:", "vbscript:")
| summarize (Timestamp, ReportId)=arg_max(Timestamp, ReportId) by DeviceId, ProcessCommandLine

ii). Project conservatively to stay under the 4 KB cliff Only include essential fields in custom details and truncate long strings in the query Leave full payloads in the underlying tables where the analyst can pivot if needed.

DeviceProcessEvents
| where ingestion_time() > ago(1h)
| where ProcessCommandLine has_any ("javascript", "vbscript:")
| extend CommandLineTruncated = substring(ProcessCommandLine, 0, 100)
| summarize (Timestamp, ReportId)=arg_max(Timestamp, ReportId) by DeviceId, CommandLineTruncated

iii). Use arg_max when aggregating count() and summarize can strip required identifier columns. Use arg_max to keep the most recent row’s identifiers and enrichment fields:

DeviceFileEvents
| where ActionType == "FileCreated"
| where FolderPath has @"\Windows\Temp"
| summarize (Timestamp, ReportId)=arg_max(Timestamp, ReportId),FileCount=count() by DeviceId, FileName
| where FileCount > 50

iv). Reserve NRT for cases where speed matters NRT runs ~every 2 minutes and magnifies whatever signal you send. A noisy NRT rule wakes people up faster a high-fidelity rule without a response playbook wastes that speed. Score NRT candidates on damage velocity, response capacity, signal quality, containment value and triage simplicity before switching frequency.

v). Validate before clicking “Migrate now” button in the portal looks like a helpful automation. Here is what it actually checks vs what it does NOT check: The portal’s migration check is limited (single table, no comments, no joins, GA columns, etc.). It does not check false-positive rate, whether a runbook exists, correct time filters for NRT cadence or whether your SOC can respond in 2 minutes.

Safe migration steps:

  • Open one rule.
  • Review its volume and false positives for the last 30 days.
  • Confirm a 2-minute response runbook exists.
  • Fix time filters and other issues.
  • Migrate.
  • Monitor for one week.
  • Repeat for the next rule.

Conclusion

Custom detection rules are one of the most powerful feature of Defender XDR, but they are also easy to misconfigure. A single column, setting or character can be the difference between a rule that helps your SOC and one that hides alerts, floods the queue or drops enrichment.

This guide covered how to build, configure and run Defender XDR custom detection rules as of May 2026. The bigger question now is not just how to write the rules, but which detections should be custom, which should run in real time and how to manage the overall detection portfolio.

Microsoft Defender XDR Custom Detection Rules: A Complete Guide & Best Practices was originally published in Detect FYI on Medium, where people are continuing the conversation by highlighting and responding to this story.

Introduction to Malware Binary Triage (IMBT) Course

Looking to level up your skills? Get 10% off using coupon code: MWNEWS10 for any flavor.

Enroll Now and Save 10%: Coupon Code MWNEWS10

Note: Affiliate link – your enrollment helps support this platform at no extra cost to you.

Article Link: https://detect.fyi/microsoft-defender-xdr-custom-detection-rules-a-complete-guide-best-practices-9faf8c837797?source=rss----d5fd8f494f6a---4