• Show or Hide a Subgrid Based on an Option Set in Model-Driven Apps – Two Classic Patterns

    As Power Platform developers, we’ve all faced this requirement a hundred times:

    “When the user selects Yes in the ‘Are there additional impacted items?’ field, show the subgrid that lets them add related records. Otherwise, hide it.”

    It feels like a trivial conditional-visibility task… but when you open the Business Rule designer and realize subgrids are not in the list of controllable elements, the reflex kicks in:

    “Fine. I’ll just write a tiny JavaScript web resource. It’s only 15 lines.”

    That reflex is what I call Pattern 1.

    Pattern 1 – The JavaScript Reflex (What 90% of us do first)

    JavaScript

    function toggleImpactedItemsSubgrid(executionContext) {
        const formContext = executionContext.getFormContext();
        const optionSet = formContext.getAttribute("cr100_arethereadditionalitems");
        const value = optionSet.getValue();
        const subgrid = formContext.getControl("impacted_items_subgrid");
    
        if (subgrid) {
            subgrid.setVisible(value === 100000000); // 100000000 = Yes
        }
    }
    
    // Register on Form OnLoad and on the Option Set OnChange

    Pros of Pattern 1

    • Works instantly
    • You control the subgrid directly
    • Handles even the most complex logic later if needed

    Cons of Pattern 1

    • Custom JavaScript in the solution
    • Harder to maintain for citizen developers
    • Shows up as “custom code” in Solution Checker / ALM warnings
    • The subgrid header and empty space sometimes still linger if it’s in a shared section

    Most of us stop here, commit the script, and move on with life.

    But there is a cleaner way when the layout allows it.

    Pattern 2 – The Low-Code Section Trick (The one Microsoft actually wants you to use)

    Prerequisite: The subgrid is the only control inside its own section.

    Great catch! You do not need two separate Business Rules.

    You can (and should) do it with exactly one Business Rule using the If…Else branch that Microsoft added to the Business Rule designer a few years ago.

    Pattern 2 – Correct & Optimal Version (Single Business Rule)

    1. Create one Business Rule on the entity
    2. Scope = Entity (or Form)
    3. Condition: Are there additional items?is not blank (optional but recommended)
    4. Then add an If branch:
    BranchConditionAction
    IfAre there additional items? equals YesShow section “Additional Impacted Items”
    Else(no condition needed)Hide section “Additional Impacted Items”

    That’s literally it — one rule, two actions, zero JavaScript.

    Why the “Else” works perfectly here

    • When the option set is Yes → Show section
    • When the option set is No or blank/null → Hide section
    • The rule runs on form load and on change of the option set

    When the section is hidden, the entire subgrid (data, header, New button, everything) disappears and the form collapses the space perfectly.

    Pattern 1 vs Pattern 2 – Side-by-Side Comparison

    CriteriaPattern 1 (JavaScript on subgrid)Pattern 2 (Business Rule on section)
    Requires JavaScriptYesNo
    Works when subgrid shares sectionYesNo (section would hide other fields too)
    Completely removes header & spaceSometimes (empty section header may remain)Yes – 100% clean collapse
    Mobile / tablet experienceGoodExcellent
    Citizen developer friendlyNoYes
    Appears in Solution Checker warningsYes (custom JS)No
    Future-proof (Microsoft direction)Discouraged for simple scenariosStrongly encouraged
    PerformanceSlightly more overheadOptimal
    Complexity to implement10–15 lines + event registration1 business rules

    Final Verdict – Which One Should You Pick?

    • Use Pattern 1 (JavaScript) only when:
      • The subgrid shares a section with other fields you must keep visible
      • You need complex multi-field or security-role-based logic
      • You’re already deep into custom JS on that form anyway
    • Use Pattern 2 (Business Rule + dedicated section) everywhere else — especially on new forms.

    Microsoft has repeatedly stated that hiding/showing sections is the supported, low-code way to control subgrid visibility. It’s faster, cleaner, and keeps your solution “healthy” in the eyes of ALM tools.

    So next time you catch yourself reaching for a JavaScript web resource to toggle a subgrid… pause for five seconds and ask:

    “Is this subgrid alone in its section?”

    If the answer is yes → close the code editor and open the Business Rule designer instead.

    You’ll thank yourself later.

  • Email Notifications in Workflow Systems: The Wrong Way vs The Right Way

    The Problem Scenario

    You’re building a Compliance Case Management system.
    Each case moves through stages like:

    Submission → Review → Approval → Closure

    At these points, the system must send different emails:

    • Submission confirmation
    • Reviewer assignment
    • Escalation alerts
    • Closure summaries

    But each case type (Incident, Audit, Exception Request…) has different templates, different recipients, and different business rules.

    Sounds simple.
    But this is exactly where teams slip into the wrong design pattern.


    THE WRONG PATTERN — Hardcoding Everything Into Power Automate

    This is the most common mistake across Power Platform, Appian, Pega, Salesforce, and MuleSoft:

    “Put all notification logic into a flow.”

    Typically, this flow:

    • Uses Switch (CaseType) logic
    • Inside each branch checks If (Stage)
    • Hardcodes subject lines and HTML bodies
    • Hardcodes recipients (“To: compliance@company.com”)
    • Hardcodes conditions (“If severity = high → send escalations”)
    • Repeats similar logic for every case type

    Trigger: “When a row is added or modified” (Dataverse, Case table). Filters on Stage changes (e.g., via a Condition: “If Stage is not equal to triggerOutputs()?[‘body/previousStage’]”). Outputs: Full Case record. Initial Data Fetch:

    • “Get row” for Case details (CaseID, CaseType, Stage, Severity, SubmitterEmail).
    • Parallel branches: “Get rows” for related Users (e.g., Reviewer, Manager) via OData filters like SubmitterID eq ‘@{triggerOutputs()?[‘body/SubmitterID’]}’.

    Core Decision Tree (The Spaghetti Core):

    • Switch on CaseType (e.g., Switch on @{triggerOutputs()?[‘body/CaseType’]} with cases: ‘Incident’, ‘Audit’, ‘Request for Exemption’, Default).
      • This is the top-level router, but each arm duplicates nearly identical logic—violating DRY (Don’t Repeat Yourself).
    • Nested If on Stage (Inside each Switch branch): Condition like “If Stage equals ‘Review’” → Yes: Proceed to notification block; No: Branch to next If (e.g., ‘Approval’, ‘Closure’). This creates a pyramid of 3-5 nested Conditions per type, with fall-throughs for unhandled stages.
    • Hardcoded Subjects and Bodies (In each Stage If/Yes):
      • Compose action for Subject: e.g., For Incident/Review: “Incident Review Assigned: Case ##{CaseID} – Action Required”.
      • Compose for Body: Raw HTML string like “<html><body>Dear Reviewer,<p>Case {CaseID} ({CaseType}) is in {Stage} stage.</p><p>Severity: {Severity}</p><a href='{PortalLink}’>View Case</a></body></html>”. Placeholders replaced via expressions (e.g., replace(variables(‘bodyTemplate’), ‘{CaseID}’, string(triggerOutputs()?[‘body/CaseID’]))). No templating engine—just manual string swaps, prone to escaping errors (e.g., unescaped quotes breaking HTML).
    • Hardcoded Recipients:
      • To: Static like “compliance@company.com” or dynamic-but-rigid like @{triggerOutputs()?[‘body/ReviewerEmail’]} (assumes it’s always populated; fails if null).
      • CC/BCC: Often overlooked, hardcoded as arrays (e.g., [‘manager@company.com’, ‘stakeholders@company.com’]) without role-based resolution.
    • Hardcoded Conditions for Variations (Nested deeper):
      • Another Condition: “If Severity equals ‘High’” → Yes: Add escalation block (e.g., Delay 24 hours, then SendEmail to Manager with subject “URGENT: Escalation for {CaseID}”).
      • For Closure stage: If CaseType=’Audit’, attach a hardcoded PDF summary via “Get file content” from a fixed SharePoint path.
    • Send Actions: Outlook.SendEmailV2 with the composed Subject/Body/To. No error handling beyond basic “Scope” try-catch, which logs to a generic SharePoint list.

    Repetition Across Case Types:

    • For ‘Incident’: Switch arm repeats the full If(Stage)/Hardcode/Condition(Severity)/Send pyramid, but tweaks subjects (e.g., “Incident Alert” vs. “Audit Query”).
    • For ‘Audit’: Identical structure, but bodies include compliance-specific legalese (e.g., “Per SOX 404…”).
    • For ‘Request for Exemption’: Yet another copy-paste variant, with unique recipients like legal@company.com.
    • Default arm: A catch-all that sends a generic “Unknown Type” email—hiding bugs until prod.

    Post-Send Cleanup: Variable updates (e.g., set LastNotifiedDate), maybe a “Update row” on Case. Ends with a “Terminate” on success.

    This results in:

    • One giant monolithic flow OR
    • Multiple similar flows duplicated per case type

    Why this is bad

    ProblemResult
    Hardcoded templatesAny text change requires a developer
    Hardcoded recipientsRe-org breaks flows
    Hardcoded branchingEvery new case type/stage requires changes
    DuplicationLogic drifts across copies
    Difficult to testIncreasingly fragile and unreadable
    Business can’t maintainAll ownership shifts to IT
    Flow becomes hugeLoading and editing becomes painful

    Ultimately, the automation becomes a tangled mess of conditions, instead of a reusable notification engine.


    THE RIGHT PATTERN — A Metadata-Driven Notification Engine

    Instead of burying business rules inside flows, we move all “decision-making” into data (Dataverse tables).

    The flow becomes small, simple, stable, and future-proof.

    The core idea:

    Flows should only orchestrate.
    Data should decide.

    Let’s design this properly.


    1. EmailTemplate Table (WHERE TEMPLATE CODE IS FOUND)

    This is where all templates live.

    EmailTemplate

    ColumnDescription
    EmailTemplateId (PK)Unique identifier
    TemplateCodeCode used by rules (e.g., INCIDENT_SUBMITTED)
    SubjectText with tokens (e.g., “Case @CaseId Submitted”)
    BodyHTML with tokens
    LanguageEN / FR / DE (optional)
    ActiveYes/No

    TemplateCode is the key column the flow will use to fetch the right template.


    2. NotificationRule Table (THE BRAIN OF THE ENGINE)

    This table links Case Type + Stage → TemplateCode + RecipientType.

    NotificationRule

    ColumnDescription
    NotificationRuleId (PK)
    CaseTypeIncident / Audit / Exception
    StageSubmitted / Approved / Closed
    TemplateCode(Lookup/Relation → EmailTemplate.TemplateCode)
    RecipientTypeSubmitter / Reviewer / Manager
    ActiveYes/No

    This table is the bridge between workflow events and email templates.

    👉 This is where the flow finds TemplateCode.
    👉 And TemplateCode maps to EmailTemplate.


    3. RecipientMatrix Table

    Maps RecipientType to actual email addresses.

    RecipientMatrix

    RecipientTypeSource
    SubmitterCase.SubmitterEmail
    ReviewerCase.ReviewerUserId
    ManagerCase.Manager.ManagerEmail
    EscalationListcompliance-escalations@company.com

    No recipient is ever hardcoded in flows.


    4. TokenMap Table (Dynamic Values)

    Defines which placeholders the engine should replace.

    TokenMap

    TokenEntityField
    @CaseIdCaseCaseNumber
    @UserNameUserFullName
    @DueDateCaseDueDate

    These tokens will be replaced inside subject and body.


    Putting It All Together: How the Flow Works

    Here is the entire logic of the unified email notification flow:


    Step 1: Trigger

    When Case status/stage changes.


    Step 2: Read CaseType and Stage

    CaseType = Case.CaseType
    Stage = Case.Stage
    

    Step 3: Lookup NotificationRule

    Query NotificationRule where:

    CaseType = X
    AND Stage = Y
    AND Active = true
    

    The rule returns:

    • TemplateCode
    • RecipientType

    Step 4: Lookup EmailTemplate based on TemplateCode

    Query EmailTemplate where:

    TemplateCode = rule.TemplateCode
    

    Retrieve:

    • Subject
    • Body

    Step 5: Resolve Recipients

    Query RecipientMatrix where:

    RecipientType = rule.RecipientType
    

    This tells the flow where to fetch emails from.


    Step 6: Replace Tokens

    Use TokenMap to replace:

    @CaseId, @UserName, @DueDate…
    

    Step 7: Send Email

    Simple — one “Send Email” action.


    Step 8: Log Email (optional)

    Save to EmailLog table.


    🧩 Why This Model Works

    FeatureBad PatternMetadata Model
    TemplatesHardcoded in flowStored in EmailTemplate
    RecipientsHardcodedStored in RecipientMatrix
    RulesHardcoded branchingStored in NotificationRule
    Token substitutionManual replace()TokenMap table
    Number of flowsManyOne
    ScalabilityLowHigh
    Business ownershipNoneFull (data-driven)
    MaintenanceExpensiveEasy

    🏁 Conclusion

    The biggest mistake teams make is not the flow itself —
    it’s the data model driving the flow.

    When business rules live in code (flows), the system becomes rigid.
    When business rules live in data (tables), the system becomes adaptable.

    A well-designed metadata-driven notification engine:

    • Supports unlimited case types
    • Supports unlimited templates
    • Allows business users to change content
    • Removes branching logic from flows
    • Cleanly separates automation and business policy
    • Scales effortlessly

    This is the architectural pattern every workflow-driven system should follow.

  • Credits to : https://www.linkedin.com/in/asad-sarwar-72307b24a/

    This is a requirement in Microsoft Dynamics 365 CRM that’s surprisingly hard to find documentation for — so sharing it here to help others in the community.

    The requirement:
    Disable the “+ New” inline create button that appears inside lookup dropdowns.
    The business didn’t want users creating new records directly from the lookup field.
    The catch is that users want this removed only from the look-up view and not via the form. Hence we can’t actually control this via the Security Role privilges.


    Most people assume this is not configurable… but it actually is!
    And you don’t need JavaScript or unsupported hacks.
    => Solution: Using XrmToolBox → Form XML Editor
    Load the form using Form XML Manager
    Locate the lookup field inside the form XML
    Inside the <parameters> node, add this line:
    <IsInlineNewEnabled>false</IsInlineNewEnabled>
    Save → Publish the form
    And just like that — the “+ New” button disappears from the lookup dropdown.

    🎉 Why Share This?
    This setting is not available in the modern form designer, so many people don’t know it exists.
    If your business wants strict control over record creation, this trick is incredibly handy.