<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Maintask</title>
    <description>The latest articles on DEV Community by Maintask (@maintask).</description>
    <link>https://dev.to/maintask</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3690118%2Fa71b34cf-5829-43d5-820e-96a967d8452f.png</url>
      <title>DEV Community: Maintask</title>
      <link>https://dev.to/maintask</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maintask"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Maintask</dc:creator>
      <pubDate>Wed, 17 Jun 2026 09:57:23 +0000</pubDate>
      <link>https://dev.to/maintask/-4b9</link>
      <guid>https://dev.to/maintask/-4b9</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/maintask/your-apex-might-return-fewer-records-in-summer-26-and-not-throw-a-single-error-26de" class="crayons-story__hidden-navigation-link"&gt;Your Apex Might Return Fewer Records in Summer '26 — and Not Throw a Single Error&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/maintask" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3690118%2Fa71b34cf-5829-43d5-820e-96a967d8452f.png" alt="maintask profile" class="crayons-avatar__image" width="340" height="340"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/maintask" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Maintask
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Maintask
                
              
              &lt;div id="story-author-preview-content-3922772" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/maintask" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3690118%2Fa71b34cf-5829-43d5-820e-96a967d8452f.png" class="crayons-avatar__image" alt="" width="340" height="340"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Maintask&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/maintask/your-apex-might-return-fewer-records-in-summer-26-and-not-throw-a-single-error-26de" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 17&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/maintask/your-apex-might-return-fewer-records-in-summer-26-and-not-throw-a-single-error-26de" id="article-link-3922772"&gt;
          Your Apex Might Return Fewer Records in Summer '26 — and Not Throw a Single Error
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/salesforce"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;salesforce&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/apex"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;apex&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/security"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;security&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/maintask/your-apex-might-return-fewer-records-in-summer-26-and-not-throw-a-single-error-26de" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/maintask/your-apex-might-return-fewer-records-in-summer-26-and-not-throw-a-single-error-26de#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Your Apex Might Return Fewer Records in Summer '26 — and Not Throw a Single Error</title>
      <dc:creator>Maintask</dc:creator>
      <pubDate>Wed, 17 Jun 2026 09:57:09 +0000</pubDate>
      <link>https://dev.to/maintask/your-apex-might-return-fewer-records-in-summer-26-and-not-throw-a-single-error-26de</link>
      <guid>https://dev.to/maintask/your-apex-might-return-fewer-records-in-summer-26-and-not-throw-a-single-error-26de</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4j5g7kctveabkwdwdz2c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4j5g7kctveabkwdwdz2c.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you maintain custom Apex on a Salesforce org, Summer '26 (API v67.0) ships a change that can quietly alter what your code returns — no exception, no failed deploy, just &lt;em&gt;different results&lt;/em&gt;. It's a good change. It's also the kind of change that surprises a production org if nobody saw it coming.&lt;/p&gt;

&lt;p&gt;Here's what's happening and how to get ahead of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem: Apex used to ignore the running user's permissions
&lt;/h2&gt;

&lt;p&gt;For most of Apex's history, database operations ran in &lt;strong&gt;system mode&lt;/strong&gt; by default. That means SOQL, SOSL, DML, and &lt;code&gt;Database&lt;/code&gt; methods ignored the running user's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;object permissions,
&lt;/li&gt;
&lt;li&gt;field-level security (FLS), and
&lt;/li&gt;
&lt;li&gt;sharing rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A query in a controller would happily return records the logged-in user was never supposed to see, and write to fields they couldn't access — unless you explicitly added guards. In a nonprofit org, that's exactly how a volunteer with a restricted profile ends up able to read every donor's giving history through a custom Lightning component nobody pen-tested.&lt;/p&gt;

&lt;p&gt;The platform gave us tools to opt &lt;em&gt;into&lt;/em&gt; security (&lt;code&gt;WITH SECURITY_ENFORCED&lt;/code&gt;, &lt;code&gt;Security.stripInaccessible()&lt;/code&gt;, &lt;code&gt;with sharing&lt;/code&gt; on classes), but they were opt-in. If you forgot, your code ran wide open. Secure was the thing you had to remember; insecure was the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution: Summer '26 flips the default to user mode
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;API version 67.0&lt;/strong&gt;, that default inverts. Database operations now run in &lt;strong&gt;user mode&lt;/strong&gt; unless you explicitly say otherwise. Concretely, on v67.0:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SOQL / SOSL / DML / &lt;code&gt;Database&lt;/code&gt; methods enforce the running user's object permissions, FLS, and sharing rules by default.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A class with no sharing declaration now defaults to &lt;code&gt;with sharing&lt;/code&gt;&lt;/strong&gt; (previously an omitted declaration effectively inherited the caller's mode — often &lt;code&gt;without sharing&lt;/code&gt;).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;WITH SECURITY_ENFORCED&lt;/code&gt; is removed&lt;/strong&gt; and no longer compiles — you migrate to &lt;code&gt;WITH USER_MODE&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triggers always run in system mode&lt;/strong&gt; and can't declare a sharing or access mode.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important nuance: &lt;strong&gt;this is keyed to the API version of the Apex class.&lt;/strong&gt; Existing classes stay on their current API version and behave as before until &lt;em&gt;you&lt;/em&gt; bump them to 67.0. So this is a migration you control — not an overnight change forced on every line of code you own. New classes you create on 67.0 get the new behavior immediately.&lt;/p&gt;

&lt;p&gt;You opt back into system mode explicitly, per operation, when you genuinely need it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apex"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SOQL — inline&lt;/span&gt;
&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Opportunity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;gifts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Opportunity&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;USER_MODE&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;   &lt;span class="c1"&gt;// enforce user access (now the default)&lt;/span&gt;
&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Opportunity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Opportunity&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;SYSTEM_MODE&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// intentional escape hatch&lt;/span&gt;

&lt;span class="c1"&gt;// DML&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;newGifts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// respect the user's create/FLS&lt;/span&gt;
&lt;span class="k"&gt;update&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="n"&gt;staleRows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// intentional system-mode write&lt;/span&gt;

&lt;span class="c1"&gt;// Database methods — pass the AccessLevel enum&lt;/span&gt;
&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Opportunity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dyn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s2"&gt;SELECT Id, Amount FROM Opportunity'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AccessLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;SYSTEM_MODE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newGifts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AccessLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;USER_MODE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mental model: &lt;strong&gt;user mode is the safe default; &lt;code&gt;SYSTEM_MODE&lt;/code&gt; / &lt;code&gt;without sharing&lt;/code&gt; is now the thing you write down on purpose, in the open, where a reviewer can see it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: a donor-giving controller before and after
&lt;/h2&gt;

&lt;p&gt;Say you have a Lightning component that shows a contact's giving history. Pre-v67, a common (and quietly unsafe) version looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apex"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pre-Summer '26 — no sharing declaration, system-mode query&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DonorGivingController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@AuraEnabled(&lt;/span&gt;&lt;span class="n"&gt;cacheable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Opportunity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getGifts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="n"&gt;contactId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Runs in system mode: returns gifts regardless of who is asking,&lt;/span&gt;
        &lt;span class="c1"&gt;// and exposes Amount even if the user has no FLS on it.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CloseDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StageName&lt;/span&gt;
            &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Opportunity&lt;/span&gt;
            &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Primary_Contact__c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;contactId&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On v67.0, the same class — with no code changes — now behaves as &lt;code&gt;with sharing&lt;/code&gt; in user mode. If a restricted user opens the component:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gifts on records they can't see via sharing are filtered out, and
&lt;/li&gt;
&lt;li&gt;if they lack FLS on &lt;code&gt;Amount&lt;/code&gt;, the query throws instead of leaking the field.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the behavior you almost certainly wanted all along. The explicit, reviewer-friendly version makes the intent obvious:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apex"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Summer '26 — intent is explicit and secure by default&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="kd"&gt;sharing&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DonorGivingController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@AuraEnabled(&lt;/span&gt;&lt;span class="n"&gt;cacheable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Opportunity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getGifts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="n"&gt;contactId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CloseDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StageName&lt;/span&gt;
            &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Opportunity&lt;/span&gt;
            &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Primary_Contact__c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;contactId&lt;/span&gt;
            &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;USER_MODE&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when you &lt;em&gt;do&lt;/em&gt; have a legitimate system job — a nightly rollup that must see every gift regardless of the running user — you say so, out loud:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apex"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;without&lt;/span&gt; &lt;span class="kd"&gt;sharing&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NightlyGivingRollup&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Batchable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SObject&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;QueryLocator&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;BatchableContext&lt;/span&gt; &lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getQueryLocator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s2"&gt;SELECT Id, Amount, Primary_Contact__c FROM Opportunity'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;AccessLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;SYSTEM_MODE&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pitfalls: what actually breaks, and how to catch it
&lt;/h2&gt;

&lt;p&gt;The reason this deserves a real migration pass — not just a version bump — is that the failures are often &lt;em&gt;silent&lt;/em&gt;. Here are the ones to watch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Queries return fewer rows, with no error.&lt;/strong&gt; A query that returned 500 records can return 40 once user access is enforced, and nothing throws. Downstream logic that assumed a full result set produces wrong numbers — bad rollups, missing list items, under-counted reports. To confirm a row drop is access-related, compare the two modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apex"&gt;&lt;code&gt;&lt;span class="n"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;asUser&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nf"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Opportunity&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;USER_MODE&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="n"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;asSystem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nf"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Opportunity&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;SYSTEM_MODE&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s2"&gt;user='&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;asUser&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s2"&gt;  system='&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;asSystem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A large gap means the running user simply can't see those rows — decide whether that's correct (it usually is) or whether this code is a true system context that needs &lt;code&gt;SYSTEM_MODE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;WITH SECURITY_ENFORCED&lt;/code&gt; won't compile.&lt;/strong&gt; If you bump a class to 67.0 and it still contains the old clause, deployment fails. Find-and-replace it with &lt;code&gt;WITH USER_MODE&lt;/code&gt;, which is stricter (it also enforces sharing, not just FLS/object perms), so re-test afterward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apex"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before (fails to compile on v67.0)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Contact&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;SECURITY_ENFORCED&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// After&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Contact&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;USER_MODE&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Integration and automation users hit walls.&lt;/strong&gt; This is the big one for nonprofits with connected apps (Click &amp;amp; Pledge, FormAssembly, ETL/data loads, scheduled jobs). Code that runs as an integration user and assumed system-level reach will now be filtered to that user's permissions. The fix is a deliberate choice per context: either grant the integration user the object/field/sharing access it legitimately needs, &lt;strong&gt;or&lt;/strong&gt; mark the genuinely-system code paths &lt;code&gt;without sharing&lt;/code&gt; + &lt;code&gt;SYSTEM_MODE&lt;/code&gt;. Don't blanket-&lt;code&gt;SYSTEM_MODE&lt;/code&gt; everything to make red tests green — that just recreates the old wide-open default you're trying to leave behind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The sharing-declaration default flips.&lt;/strong&gt; A class you &lt;em&gt;relied on&lt;/em&gt; to run unrestricted (because an omitted declaration used to behave like &lt;code&gt;without sharing&lt;/code&gt; in many call paths) may now enforce sharing as &lt;code&gt;with sharing&lt;/code&gt;. Audit every class with no explicit declaration and write the intent down — &lt;code&gt;with sharing&lt;/code&gt; or &lt;code&gt;without sharing&lt;/code&gt; — so behavior no longer depends on who called it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Triggers no longer enforce sharing.&lt;/strong&gt; All triggers now run in system mode, period. If any logic quietly leaned on sharing being enforced inside a trigger, move it into a handler class that declares &lt;code&gt;with sharing&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apex"&gt;&lt;code&gt;&lt;span class="n"&gt;trigger&lt;/span&gt; &lt;span class="n"&gt;OpportunityTrigger&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;Opportunity&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="k"&gt;update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OpportunityTriggerHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;afterUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Trigger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Trigger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;oldMap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="kd"&gt;sharing&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OpportunityTriggerHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;afterUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Opportunity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Opportunity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;oldMap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// sharing is enforced here because the class declares it&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A pragmatic migration order
&lt;/h2&gt;

&lt;p&gt;You don't have to do this all at once — the API version is per-class, so migrate deliberately:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inventory first.&lt;/strong&gt; Search your codebase for &lt;code&gt;WITH SECURITY_ENFORCED&lt;/code&gt; (must change) and for classes with no sharing declaration (behavior may change).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bump high-risk classes in a sandbox&lt;/strong&gt;, one batch at a time: controllers, &lt;code&gt;@AuraEnabled&lt;/code&gt; methods, REST endpoints, and anything an integration user runs.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run your tests, but don't trust green alone&lt;/strong&gt; — user-mode bugs are about &lt;em&gt;data visibility&lt;/em&gt;, which weak test setups miss. Add tests that run as a restricted user via &lt;code&gt;System.runAs()&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide each escape hatch on purpose.&lt;/strong&gt; Every &lt;code&gt;SYSTEM_MODE&lt;/code&gt; / &lt;code&gt;without sharing&lt;/code&gt; should be a conscious, commented decision, not a reflex to silence a failure.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only then bump the API version in production&lt;/strong&gt;, class group by class group.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Done this way, a change that could have been a silent production incident becomes a routine, secure-by-default upgrade — which is exactly what it's meant to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;p&gt;If you want the rest of the Summer '26 picture — key dates, the enforced updates, Flow and reporting changes, and a release checklist — we put together a full breakdown here: &lt;a href="https://www.maintask.com/articles/salesforce-summer-26-release-updates" rel="noopener noreferrer"&gt;Salesforce Summer '26 Release: Key Dates, Features, Flow Updates &amp;amp; Release Checklist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep focusing on your priorities. We take care of your CRM.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>salesforce</category>
      <category>apex</category>
      <category>security</category>
    </item>
    <item>
      <title>Five Salesforce Reports Every Nonprofit Leadership Team Should Have</title>
      <dc:creator>Maintask</dc:creator>
      <pubDate>Mon, 25 May 2026 10:44:35 +0000</pubDate>
      <link>https://dev.to/maintask/five-salesforce-reports-every-nonprofit-leadership-team-should-have-nl9</link>
      <guid>https://dev.to/maintask/five-salesforce-reports-every-nonprofit-leadership-team-should-have-nl9</guid>
      <description>&lt;p&gt;Nonprofit leadership teams often rely on Salesforce for fundraising, program tracking, grant management, and operational reporting.&lt;/p&gt;

&lt;p&gt;But having the data in Salesforce is not the same as having useful reports.&lt;/p&gt;

&lt;p&gt;A report is only valuable if it helps someone make a decision.&lt;/p&gt;

&lt;p&gt;For nonprofit leaders, that usually means answering questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are we on track to meet our fundraising goal?
&lt;/li&gt;
&lt;li&gt;Which donors are at risk of lapsing?
&lt;/li&gt;
&lt;li&gt;Are our programs reaching the right people?
&lt;/li&gt;
&lt;li&gt;Which grant deadlines are coming up?
&lt;/li&gt;
&lt;li&gt;Can we trust the data in this dashboard?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article walks through five Salesforce reports that can help nonprofit leadership teams make better decisions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwb2wi6mohcg01i8p6zj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwb2wi6mohcg01i8p6zj.png" alt=" " width="800" height="724"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Problem&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A nonprofit may have thousands of records in Salesforce. The data exists, but leadership still struggles to answer basic questions.&lt;/p&gt;

&lt;p&gt;Common symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The board asks for a fundraising forecast, but only closed donations are reported.
&lt;/li&gt;
&lt;li&gt;Program impact numbers are pulled manually from spreadsheets.
&lt;/li&gt;
&lt;li&gt;Grant deadlines live in inboxes or individual calendars.
&lt;/li&gt;
&lt;li&gt;Lapsed donors are noticed months too late.
&lt;/li&gt;
&lt;li&gt;Dashboards look accurate, but the underlying data is incomplete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core problem is not usually Salesforce itself. The problem is that reports are often built around available fields instead of leadership decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Solution&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Build a small set of reports around the questions leadership asks most often. A practical nonprofit leadership reporting set should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fundraising Pipeline Report
&lt;/li&gt;
&lt;li&gt;Donor Retention and Lapsed Donor Report
&lt;/li&gt;
&lt;li&gt;Program Impact Report
&lt;/li&gt;
&lt;li&gt;Grant Tracking and Compliance Report
&lt;/li&gt;
&lt;li&gt;Data Quality and Operational Health Report&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each report should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A clear owner
&lt;/li&gt;
&lt;li&gt;A clear audience
&lt;/li&gt;
&lt;li&gt;A clear business question
&lt;/li&gt;
&lt;li&gt;A defined update frequency
&lt;/li&gt;
&lt;li&gt;A next action when something looks wrong&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to create more reports. The goal is to create reports people actually trust.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Example 1: Fundraising Pipeline Report&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What it answers&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Are we likely to meet our fundraising target?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Many nonprofit teams report only on closed donations. That shows what already happened, but it does not show what is likely to happen next. &lt;/p&gt;

&lt;p&gt;A pipeline report gives leadership visibility into expected revenue.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Useful fields&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Opportunity Name
&lt;/li&gt;
&lt;li&gt;Donor or Account
&lt;/li&gt;
&lt;li&gt;Amount
&lt;/li&gt;
&lt;li&gt;Stage
&lt;/li&gt;
&lt;li&gt;Probability
&lt;/li&gt;
&lt;li&gt;Expected Close Date
&lt;/li&gt;
&lt;li&gt;Campaign
&lt;/li&gt;
&lt;li&gt;Owner
&lt;/li&gt;
&lt;li&gt;Next Step
&lt;/li&gt;
&lt;li&gt;Last Activity Date&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Suggested grouping&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Group by Stage
&lt;/li&gt;
&lt;li&gt;Then group by Expected Close Month&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Why it matters&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If many major gifts are stuck in “Proposal Sent”, the issue may not be the fundraising strategy. It may be the follow-up.&lt;/p&gt;

&lt;p&gt;If several grants are expected to close this quarter but have no next step, leadership can act before the forecast becomes a surprise.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Simple report logic&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Object: Opportunities
Filter:
  IsClosed = False
  Amount &amp;gt; 0
  CloseDate = Current Fiscal Year

Group:
  Stage
  Close Month

Show:
  Sum of Amount
  Sum of Expected Revenue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Example 2: Donor Retention and Lapsed Donor Report&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What it answers&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Which donors are at risk of disappearing?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A donor may not actively cancel or complain. They may simply stop giving.&lt;/p&gt;

&lt;p&gt;That makes retention reporting important, especially for recurring donors and major donors.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Useful fields&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Donor Name
&lt;/li&gt;
&lt;li&gt;Last Gift Date
&lt;/li&gt;
&lt;li&gt;Last Gift Amount
&lt;/li&gt;
&lt;li&gt;Total Giving
&lt;/li&gt;
&lt;li&gt;Current Year Giving
&lt;/li&gt;
&lt;li&gt;Previous Year Giving
&lt;/li&gt;
&lt;li&gt;Giving Frequency
&lt;/li&gt;
&lt;li&gt;Recurring Donation Status
&lt;/li&gt;
&lt;li&gt;Last Activity Date
&lt;/li&gt;
&lt;li&gt;Owner or Relationship Manager&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Simple report logic&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Object: Contacts or Accounts with Opportunities

Filter:
  Previous Year Giving &amp;gt; 0
  Current Year Giving = 0

Optional:
  Last Activity Date older than 90 days
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Why it matters&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This report turns donor retention into an operational process.&lt;/p&gt;

&lt;p&gt;Instead of asking “Why is donor retention down?” leadership can ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which donors need follow-up?
&lt;/li&gt;
&lt;li&gt;Who owns the relationship?
&lt;/li&gt;
&lt;li&gt;What action should happen next?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Example 3: Program Impact Report&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What it answers&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;What work are we delivering, and what outcomes are we seeing?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Fundraising reports show how money comes in. Program impact reports show what that money supports.&lt;/p&gt;

&lt;p&gt;For nonprofits, this is often the report leadership needs for board updates, grant reporting, and strategic planning.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Useful fields&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Program Name
&lt;/li&gt;
&lt;li&gt;Participant or Beneficiary
&lt;/li&gt;
&lt;li&gt;Service Type
&lt;/li&gt;
&lt;li&gt;Service Date
&lt;/li&gt;
&lt;li&gt;Status
&lt;/li&gt;
&lt;li&gt;Outcome
&lt;/li&gt;
&lt;li&gt;Location
&lt;/li&gt;
&lt;li&gt;Funding Source
&lt;/li&gt;
&lt;li&gt;Owner
&lt;/li&gt;
&lt;li&gt;Completion Date&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Useful metrics&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Participants served
&lt;/li&gt;
&lt;li&gt;Programs completed
&lt;/li&gt;
&lt;li&gt;Open cases or active enrollments
&lt;/li&gt;
&lt;li&gt;Completion rate
&lt;/li&gt;
&lt;li&gt;Outcome achievement rate
&lt;/li&gt;
&lt;li&gt;Services delivered by location
&lt;/li&gt;
&lt;li&gt;Demand by program area&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Simple report logic&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Object: Program Participation or Custom Program Object

Filter:
  Service Date = Current Fiscal Year

Group:
  Program Name
  Status
  Outcome

Show:
  Record Count
  Completion Rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Why it matters&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Counting records is not enough. A useful impact report should separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Started
&lt;/li&gt;
&lt;li&gt;In progress
&lt;/li&gt;
&lt;li&gt;Completed
&lt;/li&gt;
&lt;li&gt;Dropped
&lt;/li&gt;
&lt;li&gt;Outcome achieved
&lt;/li&gt;
&lt;li&gt;Outcome not achieved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents teams from reporting activity as impact.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Example 4: Grant Tracking and Compliance Report&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What it answers&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;What grant deadlines, deliverables, or reports are at risk?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Grant tracking often breaks when key information lives outside Salesforce.&lt;/p&gt;

&lt;p&gt;Common places where grant details get lost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email threads
&lt;/li&gt;
&lt;li&gt;Individual calendars
&lt;/li&gt;
&lt;li&gt;Spreadsheets
&lt;/li&gt;
&lt;li&gt;PDF award letters
&lt;/li&gt;
&lt;li&gt;Staff memory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A Salesforce grant tracking report creates a shared source of visibility.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1gl1acujh1t6pok972f.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1gl1acujh1t6pok972f.jpeg" alt=" " width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Useful fields&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Grant Name
&lt;/li&gt;
&lt;li&gt;Funder
&lt;/li&gt;
&lt;li&gt;Amount Requested
&lt;/li&gt;
&lt;li&gt;Amount Awarded
&lt;/li&gt;
&lt;li&gt;Stage
&lt;/li&gt;
&lt;li&gt;Application Deadline
&lt;/li&gt;
&lt;li&gt;Award Date
&lt;/li&gt;
&lt;li&gt;Reporting Deadline
&lt;/li&gt;
&lt;li&gt;Restricted Purpose
&lt;/li&gt;
&lt;li&gt;Program Funded
&lt;/li&gt;
&lt;li&gt;Responsible Owner
&lt;/li&gt;
&lt;li&gt;Deliverable Status
&lt;/li&gt;
&lt;li&gt;Next Step&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Simple report logic&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Object: Grants or Opportunities

Filter:
  Grant Status = Active
  Reporting Deadline = Next 90 Days

Group:
  Reporting Deadline Month
  Responsible Owner

Show:
  Grant Amount
  Deliverable Status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Why it matters&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This report reduces reliance on memory. Leadership can see what is due, who owns it, and where risk is building before a deadline is missed.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Example 5: Data Quality and Operational Health Report&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What it answers&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Can we trust the data behind the dashboard?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This report is usually less exciting than fundraising or impact reporting, but it is also one of the most important.&lt;/p&gt;

&lt;p&gt;If Salesforce data is incomplete or inconsistent, every dashboard built on top of it becomes questionable.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Simple report logic&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Object: Contacts

Filter:
  Email = Blank
  Created Date = Last 30 Days

Group:
  Created By
  Owner

Show:
  Record Count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Object: Opportunities

Filter:
  Campaign = Blank
  Close Date = Current Fiscal Year

Group:
  Owner
  Stage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Why it matters&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Data quality reporting helps teams identify process issues.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If many gifts are missing campaigns, the donation entry process may need adjustment.
&lt;/li&gt;
&lt;li&gt;If many contacts are missing emails, form mapping may be incomplete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This report should not be used to blame users.&lt;/p&gt;

&lt;p&gt;It should be used to improve the system.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Pitfalls&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Pitfall 1: Creating too many reports&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Leadership does not need 80 reports.&lt;/p&gt;

&lt;p&gt;They need a small number of reports that answer important questions.&lt;/p&gt;

&lt;p&gt;Too many reports often create confusion because different teams may use different filters, definitions, or date ranges.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Pitfall 2: Reporting on fields no one updates&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A dashboard is only as reliable as the fields behind it.&lt;/p&gt;

&lt;p&gt;If “Stage”, “Status”, “Outcome”, or “Next Step” fields are not maintained consistently, leadership reports will become unreliable.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Pitfall 3: Confusing activity with impact&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For program reporting, avoid treating every record count as an outcome.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;“1,000 participants registered” is not the same as “1,000 participants completed the program”.&lt;/p&gt;

&lt;p&gt;The report should make that distinction clear.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Pitfall 4: Building dashboards before fixing data quality&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Dashboards can make bad data look official.&lt;/p&gt;

&lt;p&gt;Before relying on dashboards, build data quality reports that show whether key fields are complete and consistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Practical Checklist&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before publishing a Salesforce leadership report, confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this report answer a leadership question?
&lt;/li&gt;
&lt;li&gt;Is the audience clear?
&lt;/li&gt;
&lt;li&gt;Are field definitions consistent?
&lt;/li&gt;
&lt;li&gt;Is there a known action when numbers look wrong?
&lt;/li&gt;
&lt;li&gt;Can users trust the underlying data?
&lt;/li&gt;
&lt;li&gt;Should this be part of a dashboard?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Further Reading&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For Salesforce CRM reporting, automation, and nonprofit implementation support, see &lt;a href="https://maintask.com/" rel="noopener noreferrer"&gt;Maintask&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Final Thought&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Good Salesforce reporting is not about having more dashboards.&lt;/p&gt;

&lt;p&gt;It is about creating a small set of reports that leadership can trust.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpz3lk5622doadchka11t.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpz3lk5622doadchka11t.jpeg" alt=" " width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For many nonprofits, these five reports are a strong starting point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fundraising Pipeline
&lt;/li&gt;
&lt;li&gt;Donor Retention
&lt;/li&gt;
&lt;li&gt;Program Impact
&lt;/li&gt;
&lt;li&gt;Grant Tracking
&lt;/li&gt;
&lt;li&gt;Data Quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those are in place, board meetings become less about reconciling numbers and more about making decisions.&lt;/p&gt;

</description>
      <category>salesforce</category>
      <category>crm</category>
      <category>nonprofit</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Reporting Failure Patterns We Keep Seeing in Nonprofit Salesforce Orgs</title>
      <dc:creator>Maintask</dc:creator>
      <pubDate>Mon, 04 May 2026 15:20:17 +0000</pubDate>
      <link>https://dev.to/maintask/reporting-failure-patterns-we-keep-seeing-in-nonprofit-salesforce-orgs-kp6</link>
      <guid>https://dev.to/maintask/reporting-failure-patterns-we-keep-seeing-in-nonprofit-salesforce-orgs-kp6</guid>
      <description>&lt;p&gt;If you have ever inherited a nonprofit Salesforce org with eighty reports, three dashboards, and zero confidence in any of them, you already know the real problem: the report is usually not broken. The reporting design is.&lt;/p&gt;

&lt;p&gt;Most of the time, the bad number on the dashboard is just the last visible symptom. The real issue sits deeper: no one agreed on stage definitions, recurring-donation exceptions are mixed into retention reporting, programme counts are based on row volume instead of unique participants, or critical grant deadlines still live in someone's calendar.&lt;/p&gt;

&lt;p&gt;This post is not a list of "reports leaders should have." It is a list of the reporting patterns that keep making leadership stop trusting the CRM in the first place.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A quick note before the examples: names ending in &lt;code&gt;__c&lt;/code&gt; indicate custom objects or custom fields — adapt those names to your own org. In NPSP and nonprofit orgs generally, package fields and custom models vary, and Salesforce recommends checking the actual object schema in the target org before hard-coding anything.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Pattern 1 — Stale pipeline disguised as forecast
&lt;/h2&gt;

&lt;p&gt;When someone asks, &lt;em&gt;"what is likely to close this quarter?"&lt;/em&gt;, they are not asking for a sum of every open opportunity in the system. They are asking for a forecast that excludes deals nobody has touched in weeks.&lt;/p&gt;

&lt;p&gt;The first fix is to separate &lt;strong&gt;forecast reporting&lt;/strong&gt; from &lt;strong&gt;stale-opportunity cleanup&lt;/strong&gt;. For a quarter view, use a grouped query or report that looks at stage and close-date timing together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;StageName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CALENDAR_MONTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CloseDate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Opportunity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;IsClosed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;FALSE&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;CloseDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;THIS_QUARTER&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;StageName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CALENDAR_MONTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CloseDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then pair it with a stale-opportunity view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StageName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CloseDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OwnerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LastActivityDate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Opportunity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;IsClosed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;FALSE&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;CloseDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;THIS_QUARTER&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LastActivityDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;LastActivityDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;LAST_N_DAYS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;CloseDate&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;COUNT_DISTINCT&lt;/code&gt;, date grouping functions like &lt;code&gt;CALENDAR_MONTH()&lt;/code&gt;, and relative date literals in &lt;code&gt;WHERE&lt;/code&gt; clauses are all documented parts of SOQL, so this pattern is technically sound. The pitfall is not the query. It is trying to "clean the pipeline" before the team has written down what each stage actually means. Skip that conversation, and the team will move records around for weeks while quietly redefining the stages on the next call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2 — Recurring revenue drift hiding inside donor retention
&lt;/h2&gt;

&lt;p&gt;In NPSP, recurring donations are their own object, and Salesforce distinguishes between open-ended and fixed-length recurring gifts. That matters because &lt;em&gt;"lapsed donor"&lt;/em&gt; and &lt;em&gt;"recurring gift stopped processing"&lt;/em&gt; are not the same operational problem — and if the org also relies on soft credits to attribute gifts to multiple contacts, the reporting layer gets messier still (&lt;a href="https://www.maintask.com/articles/understanding-soft-credits-in-salesforce-npsp" rel="noopener noreferrer"&gt;more on that pattern here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Merging the two into a single report usually produces a noisy list nobody owns. The cleaner approach is to split it into two views:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a classic retention report for donors who gave in the previous period but not the current one&lt;/li&gt;
&lt;li&gt;a recurring-donation exception report for open recurring gifts with no recent successful payment or no expected next-payment movement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact field names depend on the org, the NPSP version, and whether the team is on classic NPSP or has moved to the &lt;a href="https://www.maintask.com/articles/from-npsp-to-nonprofit-cloud" rel="noopener noreferrer"&gt;newer Nonprofit Cloud product line&lt;/a&gt; — which Salesforce has been increasingly pushing as the primary path for new implementations. Official NPSP source code confirms package fields such as &lt;code&gt;npe03__Amount__c&lt;/code&gt;, &lt;code&gt;npe03__Contact__c&lt;/code&gt;, and &lt;code&gt;npe03__Open_Ended_Status__c&lt;/code&gt;, but reporting should stay field-agnostic until the exact schema in the target org is confirmed. A field name that fails on day one undermines the whole report — and the trust attached to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3 — Participant numbers inflated by reporting design
&lt;/h2&gt;

&lt;p&gt;This one shows up constantly: a team says it served 1,200 participants, but the report is really counting enrolment rows, not people. Duplicates, dropouts, reopened cases, and migration artefacts quietly inflate the number.&lt;/p&gt;

&lt;p&gt;The fastest fix is to stop treating raw row count as impact. In Salesforce reports, &lt;strong&gt;unique count&lt;/strong&gt; on a column and &lt;strong&gt;row-level formulas&lt;/strong&gt; (or other report formulas) handle per-record logic — the supported way to do "completed vs dropped vs active" style analysis inside reporting.&lt;/p&gt;

&lt;p&gt;If the org uses a custom enrolment object, the aggregate logic can be simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Program__c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;COUNT_DISTINCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Contact__c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Program_Enrollment__c&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Enrollment_Date__c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;THIS_FISCAL_YEAR&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;Program__c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What should not be published as runnable SOQL is SQL-style &lt;code&gt;SUM(CASE WHEN ...)&lt;/code&gt;. Salesforce's documented SOQL syntax does not include standard &lt;code&gt;CASE&lt;/code&gt; expressions, so conditional completed/dropped metrics belong in report formulas, formula fields, or separate grouped reports. The query above gives unique participants per programme; everything beyond that — completed, dropped, in-progress — belongs in the reporting layer or a custom formula field, not in the SOQL itself.&lt;/p&gt;

&lt;p&gt;The pitfall here is not technical. It is that the program team has to commit to a written definition of "completed." Otherwise the report gets rebuilt three times in six months as definitions drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4 — Grant obligations stored outside the CRM
&lt;/h2&gt;

&lt;p&gt;A grant can be in Salesforce and still be operationally unsafe if the reporting obligations live in email or in one person's calendar. The opportunity exists, but the actual compliance work is invisible.&lt;/p&gt;

&lt;p&gt;This is where a separate obligation model works better. Not because Salesforce ships a standard &lt;code&gt;Grant_Obligation__c&lt;/code&gt; object — it does not; names ending in &lt;code&gt;__c&lt;/code&gt; indicate a custom object — but because the reporting requirement is operationally different from the revenue record.&lt;/p&gt;

&lt;p&gt;A simple custom object pattern is enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Grant_Obligation__c
- Grant__c           (Lookup -&amp;gt; Opportunity)
- Obligation_Type__c (Picklist: Interim Report, Final Report, Deliverable, Compliance Doc)
- Due_Date__c        (Date, required)
- OwnerId            (User, required)
- Status__c          (Picklist: Not Started, In Progress, Submitted, Late)
- Days_Until_Due__c  (Formula: Due_Date__c - TODAY())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single dashboard component over &lt;code&gt;Status__c != 'Submitted'&lt;/code&gt; and &lt;code&gt;Days_Until_Due__c &amp;lt;= 60&lt;/code&gt;, grouped by owner, gives leadership a clean early-warning view.&lt;/p&gt;

&lt;p&gt;The mistake that keeps showing up here is trying to auto-generate every obligation from the grant itself. In real organisations, grant requirements vary too much, and the auto-generated records will be wrong often enough that the grant manager will quietly stop trusting them — and go back to the inbox. A manual but mandatory creation step is usually more reliable than a clever automation nobody believes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 5 — Dashboards without a health layer
&lt;/h2&gt;

&lt;p&gt;Every leadership dashboard depends on an assumption about the data underneath it. If duplicates are rising, campaign attribution is patchy, or overdue tasks are piling up, the dashboard can look polished while the organisation slowly loses trust in it.&lt;/p&gt;

&lt;p&gt;A small admin-facing "org health" dashboard sitting next to the executive one solves this. It does not need to be fancy — and in &lt;a href="https://www.maintask.com/case-studies/internal-administrator-services" rel="noopener noreferrer"&gt;long-running admin engagements that function as a nonprofit's internal Salesforce team&lt;/a&gt;, this is one of the first things to set up. The cost is low, the trust dividend is enormous.&lt;/p&gt;

&lt;p&gt;The dashboard just needs to answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how many duplicate contacts are being carried?&lt;/li&gt;
&lt;li&gt;how many won opportunities are missing campaign attribution?&lt;/li&gt;
&lt;li&gt;how many donor records are missing critical contact data?&lt;/li&gt;
&lt;li&gt;where are overdue operational tasks accumulating?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each component should have an explicit target — &lt;em&gt;duplicates &amp;lt; 2%&lt;/em&gt;, &lt;em&gt;missing campaign attribution &amp;lt; 5%&lt;/em&gt; — so that "good" and "bad" stop being subjective.&lt;/p&gt;

&lt;p&gt;The pitfall here is cultural, not technical. The moment a data-quality dashboard gets framed as a tool for blaming staff, the data quietly gets &lt;em&gt;worse&lt;/em&gt;. People stop logging things honestly. Frame it as a process indicator instead: &lt;em&gt;"if duplicates are climbing, the intake form needs revisiting,"&lt;/em&gt; not &lt;em&gt;"Sarah created twelve duplicates last month."&lt;/em&gt; The goal is traceability, not accountability theatre.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern behind the patterns
&lt;/h2&gt;

&lt;p&gt;Trace all five back and they share one thing: &lt;strong&gt;the report was built around the fields that were available, not around the decision somebody needed to make.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the underlying technical debt in most nonprofit Salesforce orgs. It is not the schema, it is not the automation, it is not the integration — it is the gap between &lt;em&gt;"what data do we have?"&lt;/em&gt; and &lt;em&gt;"what decision needs to be made?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Closing that gap is mostly a scoping skill, not a Salesforce skill. The five patterns above are just the most common places it shows up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://trailhead.salesforce.com/content/learn/modules/fundraising-reports-and-dashboards-with-nonprofit-success-pack" rel="noopener noreferrer"&gt;Reports and Dashboards with Nonprofit Success Pack&lt;/a&gt; — Salesforce's own Trailhead module on the topic&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_agg_functions.htm" rel="noopener noreferrer"&gt;SOQL aggregate functions reference&lt;/a&gt; — for the SOQL syntax used above&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.maintask.com/solutions/nonprofit-cloud" rel="noopener noreferrer"&gt;Maintask — Salesforce work for US nonprofits&lt;/a&gt; — a consultancy behind many of the implementations these patterns come from&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>salesforce</category>
      <category>crm</category>
      <category>nonprofit</category>
      <category>automation</category>
    </item>
    <item>
      <title>Avoiding the “$100K Spreadsheet” Trap in Nonprofit Salesforce Projects</title>
      <dc:creator>Maintask</dc:creator>
      <pubDate>Mon, 23 Feb 2026 15:37:29 +0000</pubDate>
      <link>https://dev.to/maintask/avoiding-the-100k-spreadsheet-trap-in-nonprofit-salesforce-projects-1j8p</link>
      <guid>https://dev.to/maintask/avoiding-the-100k-spreadsheet-trap-in-nonprofit-salesforce-projects-1j8p</guid>
      <description>&lt;p&gt;Salesforce projects in nonprofits rarely fail because of the platform.&lt;/p&gt;

&lt;p&gt;They fail because unclear processes, inconsistent definitions, and messy data get automated instead of resolved.&lt;/p&gt;

&lt;p&gt;When that happens, Salesforce becomes what many teams privately call it: "A very expensive spreadsheet."&lt;/p&gt;

&lt;p&gt;This post breaks down why that happens and how to avoid it from an implementation perspective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem: Automating Without Structural Clarity
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjdaznogqw431t7swztk.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjdaznogqw431t7swztk.jpeg" alt=" " width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nonprofits typically move to Salesforce when operational complexity increases. More donors. More grants. More reporting pressure. More cross-team collaboration.&lt;/p&gt;

&lt;p&gt;Spreadsheets stop scaling. Manual reconciliation becomes painful. Leadership loses visibility.&lt;/p&gt;

&lt;p&gt;The problem is not the decision to adopt Salesforce. The problem is how the implementation starts.&lt;/p&gt;

&lt;p&gt;Many projects begin with configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating custom objects&lt;/li&gt;
&lt;li&gt;Building automation flows&lt;/li&gt;
&lt;li&gt;Designing dashboards&lt;/li&gt;
&lt;li&gt;Importing legacy data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the foundational questions are often unresolved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What exactly qualifies as a "major donor"?&lt;/li&gt;
&lt;li&gt;When is a fundraising opportunity officially "closed won"?&lt;/li&gt;
&lt;li&gt;How are grant stages defined?&lt;/li&gt;
&lt;li&gt;What metrics matter to the board?&lt;/li&gt;
&lt;li&gt;Who owns data quality?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If these definitions vary across teams, automation will only make inconsistencies permanent.&lt;/p&gt;

&lt;p&gt;Technology scales whatever it is given — clarity or confusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution: Sequence Matters More Than Features
&lt;/h2&gt;

&lt;p&gt;A stable Salesforce implementation in a nonprofit context follows a predictable sequence. It is less about technical complexity and more about order of operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define Outcomes Before Building Objects
&lt;/h3&gt;

&lt;p&gt;Before touching configuration, define success.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What decisions need better visibility?&lt;/li&gt;
&lt;li&gt;What reporting gaps currently exist?&lt;/li&gt;
&lt;li&gt;What operational friction are you trying to reduce?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If leadership cannot articulate the outcomes, dashboards will not fix that later.&lt;/p&gt;

&lt;p&gt;Salesforce is a system of record. It should reflect decisions, not create them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Standardize Process Definitions
&lt;/h3&gt;

&lt;p&gt;Most CRM inconsistencies are semantic.&lt;/p&gt;

&lt;p&gt;If one fundraiser marks a donor as "major" at $5,000 and another at $25,000, segmentation breaks immediately. If grant tracking stages differ across departments, reporting becomes unreliable.&lt;/p&gt;

&lt;p&gt;Before building automation, document and align on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fundraising lifecycle stages&lt;/li&gt;
&lt;li&gt;Opportunity definitions&lt;/li&gt;
&lt;li&gt;Program tracking structure&lt;/li&gt;
&lt;li&gt;Grant state transitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This alignment reduces downstream rework significantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Treat Data Migration as a Data Project
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzmvfk2u0nvpn4kugs96m.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzmvfk2u0nvpn4kugs96m.jpeg" alt=" " width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Legacy nonprofit databases often contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Duplicate records (sometimes 20–30%)&lt;/li&gt;
&lt;li&gt;Inconsistent formatting&lt;/li&gt;
&lt;li&gt;Missing emails&lt;/li&gt;
&lt;li&gt;Partial donation histories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this data is imported without cleansing, Salesforce will reflect those flaws at scale.&lt;/p&gt;

&lt;p&gt;User trust erodes quickly when reports do not match expectations.&lt;/p&gt;

&lt;p&gt;Clean data is not a cosmetic improvement. It is a prerequisite for adoption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Resist Early Over-Customization
&lt;/h3&gt;

&lt;p&gt;Nonprofit Cloud includes a strong baseline architecture — household data model, campaign tracking, standard opportunity handling, and built-in reporting.&lt;/p&gt;

&lt;p&gt;It is tempting to customize everything to replicate legacy workflows. But over-customization introduces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Technical debt&lt;/li&gt;
&lt;li&gt;Upgrade friction&lt;/li&gt;
&lt;li&gt;Increased admin burden&lt;/li&gt;
&lt;li&gt;Dependency on one "system expert."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with standard functionality. Customize only when process requirements truly demand it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: Two Implementation Paths
&lt;/h2&gt;

&lt;p&gt;Consider two mid-sized nonprofits implementing Salesforce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Path A: Configuration-First&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The organization imports 50,000 records without deduplication. Each department keeps its own opportunity definitions. Custom objects are created to match historical spreadsheets. Dashboards are built late in the process.&lt;/p&gt;

&lt;p&gt;Two months later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Segmentation produces inconsistent counts&lt;/li&gt;
&lt;li&gt;Grant reporting requires manual adjustment&lt;/li&gt;
&lt;li&gt;Leadership questions data accuracy&lt;/li&gt;
&lt;li&gt;Admin workload increases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The platform functions technically, but it does not create operational clarity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Path B: Process-First&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before configuration begins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fundraising stages are standardized&lt;/li&gt;
&lt;li&gt;Grant lifecycle definitions are aligned&lt;/li&gt;
&lt;li&gt;Duplicate records are reduced&lt;/li&gt;
&lt;li&gt;Reporting requirements are mapped&lt;/li&gt;
&lt;li&gt;Governance ownership is assigned&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Configuration then reflects those agreements.&lt;/p&gt;

&lt;p&gt;Result: reliable dashboards, higher adoption, lower long-term maintenance, reduced reporting friction.&lt;/p&gt;

&lt;p&gt;Same platform. Different discipline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls to Avoid
&lt;/h2&gt;

&lt;p&gt;Most nonprofit Salesforce failures share similar patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Treating CRM implementation as an IT task instead of an organizational alignment exercise&lt;/li&gt;
&lt;li&gt;Importing legacy data without validation or deduplication&lt;/li&gt;
&lt;li&gt;Allowing uncontrolled field creation post go-live&lt;/li&gt;
&lt;li&gt;Building automation before definitions are finalized&lt;/li&gt;
&lt;li&gt;Skipping governance planning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are technical limitation. There are sequencing issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Salesforce does not inherently create clarity. It scales it.&lt;/p&gt;

&lt;p&gt;If your nonprofit has agreed-upon definitions, clean and structured data, documented processes, and clear governance ownership, Salesforce becomes a strategic asset.&lt;/p&gt;

&lt;p&gt;If not, it becomes a powerful system delivering spreadsheet-level value.&lt;/p&gt;

&lt;p&gt;The difference is rarely about advanced automation or complex architecture. It is about implementation order and organizational alignment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;If you're exploring a structured, strategy-first approach to nonprofit Salesforce implementation, you can see how Maintask approaches CRM delivery here:&lt;a href="https://maintask.com" rel="noopener noreferrer"&gt;https://maintask.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>salesforce</category>
      <category>crm</category>
      <category>nonprofit</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
