<?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: Rubasri Srikanthan</title>
    <description>The latest articles on DEV Community by Rubasri Srikanthan (@rubasri_srikanthan).</description>
    <link>https://dev.to/rubasri_srikanthan</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3714872%2F69d6337e-ca8a-4303-af67-417c5b04a941.png</url>
      <title>DEV Community: Rubasri Srikanthan</title>
      <link>https://dev.to/rubasri_srikanthan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rubasri_srikanthan"/>
    <language>en</language>
    <item>
      <title>Building Skill Align - Part 7 - Project Staffing Assistant(Frontend)</title>
      <dc:creator>Rubasri Srikanthan</dc:creator>
      <pubDate>Sat, 07 Mar 2026 09:46:25 +0000</pubDate>
      <link>https://dev.to/rubasri_srikanthan/building-skill-align-part-7-project-staffing-assistantfrontend-1go3</link>
      <guid>https://dev.to/rubasri_srikanthan/building-skill-align-part-7-project-staffing-assistantfrontend-1go3</guid>
      <description>&lt;p&gt;In the previous part of this series, I built the backend logic that evaluates employees against project skill requirements and calculates a gap score.&lt;/p&gt;

&lt;p&gt;With the backend logic in place, the next step was to design a &lt;strong&gt;frontend experience&lt;/strong&gt; that helps managers interpret those results effectively.&lt;/p&gt;




&lt;h3&gt;
  
  
  Building the UI from an initial version to an improved dashboard
&lt;/h3&gt;

&lt;p&gt;If you look at the first version of the Project Assistant UI, it displayed the data correctly, but the interface itself was quite plain and number-heavy.&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%2Ffupe1po1pccclhma3gop.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%2Ffupe1po1pccclhma3gop.png" alt="First version of Project Assistant UI" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Managers would have had to interpret a lot of raw information before making decisions.&lt;/p&gt;

&lt;p&gt;That made me realize something important:&lt;/p&gt;

&lt;p&gt;A decision-support system should not just &lt;strong&gt;display data&lt;/strong&gt; — it should &lt;strong&gt;help users understand what the data means&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So I redesigned the interface to make the information more &lt;strong&gt;actionable and easier to interpret&lt;/strong&gt; for project managers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Goal of the Frontend
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Project Staffing Assistant&lt;/strong&gt; needed to answer three important questions for project managers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Which employees are the best candidates for this project?&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How risky is assigning this employee based on skill gaps?&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Which specific skills contribute to that risk?&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To support this workflow, I built a &lt;strong&gt;Project Staffing Overview dashboard&lt;/strong&gt;.&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%2Fn4oglgalp9fwekxaw1qb.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%2Fn4oglgalp9fwekxaw1qb.png" alt="Project Staffing Assistant" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Each employee is displayed as a &lt;strong&gt;candidate card&lt;/strong&gt; containing key information. This layout allows managers to &lt;strong&gt;quickly evaluate candidates and assign them to the project&lt;/strong&gt;.&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%2Fm67fh162fojackel1u1f.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%2Fm67fh162fojackel1u1f.png" alt="Employee candidate cards showing skill match scores, risk indicators, and assignment options for project staffing." width="800" height="149"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Limiting Results with Top Candidate Selection
&lt;/h3&gt;

&lt;p&gt;In my &lt;strong&gt;initial implementation&lt;/strong&gt;, I hardcoded the system to return &lt;strong&gt;Top 5 candidates&lt;/strong&gt; from the backend. At that stage, my focus was primarily on displaying the detailed skill breakdown.&lt;/p&gt;

&lt;p&gt;My plan was to finish the &lt;strong&gt;core feature first&lt;/strong&gt; and improve usability later.&lt;/p&gt;

&lt;p&gt;However, while testing with &lt;strong&gt;a larger employee dataset&lt;/strong&gt;, I realized that limiting results to a fixed number could restrict flexibility for project managers.&lt;/p&gt;

&lt;p&gt;It required only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;a &lt;strong&gt;minor backend adjustment&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a &lt;strong&gt;small UI enhancement&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I introduced a &lt;strong&gt;Top Candidate selector&lt;/strong&gt; in UI.&lt;/p&gt;

&lt;p&gt;Managers can now choose &lt;strong&gt;how many candidates they want to evaluate&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  LWC Implementation
&lt;/h3&gt;

&lt;h4&gt;
  
  
  HTML
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lightning-combobox
    label="Top Candidates"
    value={topN}
    options={topOptions}
    onchange={handleTopChange}&amp;gt;
&amp;lt;/lightning-combobox&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  JS
&lt;/h4&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;topOptions = [
 { label: 'Top 5', value: '5' },
 { label: 'Top 10', value: '10' },
 { label: 'Top 20', value: '20' },
 { label: 'Top 30', value: '30' }
];

handleTopChange(event) {
    this.topN = event.detail.value;
    // call backend to fetch employees
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fdnjeu2wmixp760pa102p.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%2Fdnjeu2wmixp760pa102p.png" alt="Top Candidate Selector" width="800" height="158"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling Assigned and Recommended Candidates
&lt;/h2&gt;

&lt;p&gt;Initially, my approach was simple: &lt;em&gt;return the &lt;strong&gt;Top N candidates&lt;/strong&gt; for a project.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But after implementing the &lt;strong&gt;assignment functionality&lt;/strong&gt;, I noticed an important usability issue.&lt;/p&gt;

&lt;p&gt;If candidates from the Top N list get &lt;strong&gt;assigned&lt;/strong&gt;, the system should ideally bring in the &lt;strong&gt;next eligible candidates&lt;/strong&gt; so that managers still have enough options to choose from.&lt;/p&gt;

&lt;p&gt;At the same time, managers should also have &lt;strong&gt;visibility into which candidates are already assigned&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Top 5 Candidates

A (Assigned)
B (Assigned)
C
D
E
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In this scenario, managers effectively have only &lt;strong&gt;three new candidates&lt;/strong&gt; to consider.&lt;/p&gt;

&lt;p&gt;To solve this, I decided to return both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Assigned candidates&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Eligible new candidates&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures managers always see both &lt;strong&gt;current assignments and fresh recommendations&lt;/strong&gt;, maintaining enough options when staffing the project.&lt;/p&gt;




&lt;h3&gt;
  
  
  Backend Support
&lt;/h3&gt;

&lt;p&gt;To enable this behavior, I added a flag to the evaluation results.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@AuraEnabled public Boolean hasActiveAssignment;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;During evaluation, Apex checks whether an employee is already assigned to the selected project.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;candidate.hasActiveAssignment = activeAssignments.containsKey(
    buildProjectEmployeeKey(projectId, employeeId)
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fkgjj1vuqxz91v093ja04.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%2Fkgjj1vuqxz91v093ja04.png" alt="Candidate list showing assigned employees and recommended candidates." width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This flag allows the frontend to clearly distinguish between employees who are already assigned and new recommended candidates, giving managers better visibility while making staffing decisions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Future Enhancement
&lt;/h4&gt;

&lt;p&gt;As the dataset grows, I plan to introduce &lt;strong&gt;pagination&lt;/strong&gt;, which will allow managers to navigate larger candidate lists more efficiently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Risk-Based Evaluation
&lt;/h2&gt;

&lt;p&gt;Initially, the backend returned a &lt;strong&gt;gap score&lt;/strong&gt;, but raw numbers were not very intuitive for reviewing candidates.&lt;/p&gt;

&lt;p&gt;So I introduced a new metric: &lt;strong&gt;Required Skills Risk&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Required Skills Risk → 52% (Medium)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This metric focuses specifically on &lt;strong&gt;required project skills&lt;/strong&gt;, giving managers a clearer picture of &lt;strong&gt;potential delivery risk&lt;/strong&gt; when assigning an employee.&lt;/p&gt;




&lt;h3&gt;
  
  
  How Required Skills Risk is Calculated
&lt;/h3&gt;

&lt;p&gt;The Required Skills Risk represents the delivery risk caused by gaps in &lt;strong&gt;required project skills&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Required Risk % = (Total Required Impact / 
                   Maximum Possible Required Impact) × 100    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Total Required Impact → Sum of the impact values for all required skill gaps.&lt;/li&gt;
&lt;li&gt;Maximum Possible Required Impact → The worst-case impact if an employee had no proficiency in any required skill.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To maintain transparency for managers, I also added a tooltip explaining the calculation.&lt;/p&gt;

&lt;h4&gt;
  
  
  HTML
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lightning-helptext
 content={riskHelpText}&amp;gt;
&amp;lt;/lightning-helptext&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fiosr4psgmwxkcfh2y9ti.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%2Fiosr4psgmwxkcfh2y9ti.png" alt="Required Skills percentage help text" width="800" height="356"&gt;&lt;/a&gt;&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%2Fg0ygbo2bh0flp8sdwzu6.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%2Fg0ygbo2bh0flp8sdwzu6.png" alt="Total Gap Help Text" width="800" height="326"&gt;&lt;/a&gt;&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%2Fjeao37j6f9k2ajkqwm79.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%2Fjeao37j6f9k2ajkqwm79.png" alt="Risk help text for Risk column" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Expandable Skill Breakdown
&lt;/h3&gt;

&lt;p&gt;High-level metrics are useful, but sometimes managers need &lt;strong&gt;deeper insight before making an assignment decision&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To support this, I added an &lt;strong&gt;expandable skill breakdown&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When expanded, the dashboard displays detailed skill gap information. This allows managers to quickly understand why a candidate has a certain risk score.&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%2Fifek6rpmhw896xi3sxh5.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%2Fifek6rpmhw896xi3sxh5.png" alt="Detailed Skill Gap" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Toggle Implementation LWC
&lt;/h3&gt;

&lt;h4&gt;
  
  
  HTML
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lightning-button
 label={toggleLabel}
 onclick={toggleSkills}&amp;gt;
&amp;lt;/lightning-button&amp;gt;

&amp;lt;template if:true={showSkills}&amp;gt;
   &amp;lt;!-- Skill breakdown table --&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  JS
&lt;/h4&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;showSkills = false;

get toggleLabel() {
 return this.showSkills ? 'Hide Risk Breakdown' : 'View Risk Breakdown';
}

toggleSkills() {
 this.showSkills = !this.showSkills;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This keeps the primary view simple, while still allowing detailed inspection on demand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Skill Metadata with Informational Pills
&lt;/h2&gt;

&lt;p&gt;Each skill row also displays contextual indicators that help managers interpret the data quickly.&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%2F8i3ic9a04jptlx6r73w1.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%2F8i3ic9a04jptlx6r73w1.png" alt="Skill metadata pills showing importance, weight, and source." width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  HTML
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lightning-pill label={skill.importance}&amp;gt;&amp;lt;/lightning-pill&amp;gt;
&amp;lt;lightning-pill label={'Weight ' + skill.weight}&amp;gt;&amp;lt;/lightning-pill&amp;gt;
&amp;lt;lightning-pill label={skill.source}&amp;gt;&amp;lt;/lightning-pill&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h3&gt;
  
  
  Skill-Level Risk Visualization
&lt;/h3&gt;

&lt;p&gt;Instead of simply listing missing skills, the UI highlights &lt;strong&gt;risk contribution for each skill&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This logic is calculated in Apex using the impact value, which combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;skill deficit&lt;/li&gt;
&lt;li&gt;skill importance&lt;/li&gt;
&lt;li&gt;skill weight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SOQL → 80% (High Risk)
LWC → 40% (Medium Risk)
OmniStudio → 10% (Low Risk)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Risk Bucket UI
&lt;/h3&gt;
&lt;h4&gt;
  
  
  HTML
&lt;/h4&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lightning-badge label={skill.riskBand}&amp;gt;&amp;lt;/lightning-badge&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Each skill is categorized into a risk band:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High&lt;/li&gt;
&lt;li&gt;Medium&lt;/li&gt;
&lt;li&gt;Low&lt;/li&gt;
&lt;li&gt;None&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it easy for managers to quickly identify &lt;strong&gt;which skills contribute most to delivery risk&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Readiness Indicator LWC
&lt;/h2&gt;

&lt;p&gt;Each candidate card also includes a &lt;strong&gt;Project Readiness badge&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Employees are classified as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready&lt;/strong&gt; – Required skills sufficiently met&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Not Ready&lt;/strong&gt; – Significant skill gaps remain&lt;/p&gt;

&lt;h4&gt;
  
  
  HTML
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lightning-badge label={employee.readiness}&amp;gt;&amp;lt;/lightning-badge&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Assignment Action
&lt;/h2&gt;

&lt;p&gt;Managers can assign employees directly from the dashboard.&lt;/p&gt;

&lt;h4&gt;
  
  
  HTML
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lightning-button
 label={employee.assigned ? 'Assigned' : 'Assign'}
 variant="brand"
 disabled={employee.assigned}
 onclick={assignEmployee}&amp;gt;
&amp;lt;/lightning-button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If an employee is already assigned, the button is automatically disabled. This prevents &lt;strong&gt;duplicate assignments&lt;/strong&gt; and keeps the workflow simple.&lt;/p&gt;




&lt;h2&gt;
  
  
  Styling
&lt;/h2&gt;

&lt;p&gt;For styling, I used &lt;strong&gt;SLDS (Salesforce Lightning Design System)&lt;/strong&gt; along with custom CSS where necessary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building the Project Staffing Assistant taught me an important lesson:&lt;/p&gt;

&lt;p&gt;A system is not only about &lt;strong&gt;correct logic&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is also about &lt;strong&gt;presenting information&lt;/strong&gt; in a way that &lt;em&gt;&lt;strong&gt;helps people understand, trust, and act on it effectively.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Glossary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.salesforce.com/developer-centers/lightning-web-components" rel="noopener noreferrer"&gt;LWC (Lightning Web Components)&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A modern JavaScript framework used in Salesforce to build fast and reusable UI components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.lightningdesignsystem.com/2e1ef8501/p/85bd85-lightning-design-system-2" rel="noopener noreferrer"&gt;SLDS (Salesforce Lightning Design System)&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A design framework that provides prebuilt styles and components for building consistent Salesforce user interfaces.&lt;/p&gt;

</description>
      <category>salesforce</category>
      <category>sideprojects</category>
      <category>ui</category>
      <category>lwc</category>
    </item>
    <item>
      <title>Building Skill Align - Part 6 - Project Staffing Assistant(Backend)</title>
      <dc:creator>Rubasri Srikanthan</dc:creator>
      <pubDate>Thu, 26 Feb 2026 10:20:07 +0000</pubDate>
      <link>https://dev.to/rubasri_srikanthan/building-skill-align-part-6-project-staffing-assistantbackend-1bnn</link>
      <guid>https://dev.to/rubasri_srikanthan/building-skill-align-part-6-project-staffing-assistantbackend-1bnn</guid>
      <description>&lt;p&gt;I started with the first feature in this project: &lt;strong&gt;Project Staffing Assistant&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Project Staffing Assistant helps managers decide which candidates are suitable for a project based on actual project requirements.&lt;/p&gt;

&lt;p&gt;I began with the backend, building the intelligence layer in Apex.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Core Service – SkillEvaluatorService
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public with sharing class SkillEvaluatorService 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Two important design decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;public&lt;/em&gt; → Required because LWC will call this Apex class&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;with sharing&lt;/em&gt; → Ensures record-level security is respected&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I had previously configured roles, OWD, and sharing rules (Refer &lt;a href="https://dev.to/rubasri_srikanthan/building-skill-align-part-4-roles-owd-sharing-record-level-governance-4n61"&gt;here&lt;/a&gt;). &lt;br&gt;
Using &lt;strong&gt;with sharing&lt;/strong&gt; ensures this evaluation logic follows those configurations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apex Sharing Behavior:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Apex runs in system context by default. Object-level and field-level permissions are not automatically enforced.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;with sharing enforces record-level sharing rules only, ensuring queries and DML respect the current user’s access.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;with sharing does &lt;strong&gt;not&lt;/strong&gt; enforce object or field permissions. You must explicitly handle CRUD/FLS (e.g., &lt;em&gt;WITH SECURITY_ENFORCED&lt;/em&gt; or &lt;em&gt;Security.stripInaccessible()&lt;/em&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If no sharing keyword is defined, the class inherits sharing from its caller, so behavior may vary depending on depending on how it is invoked.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Triggers run in system context. Even if a helper class is marked with sharing, the trigger executes in system mode.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Designing Data Transfer Objects
&lt;/h1&gt;

&lt;p&gt;Instead of returning raw &lt;strong&gt;Employee__c&lt;/strong&gt; or &lt;strong&gt;Employee_Skill__c&lt;/strong&gt; records, I created Data Transfer Objects or DTOs.&lt;/p&gt;

&lt;p&gt;DTOs define the structured connection between backend and UI. They wrap only the fields required by the frontend, preventing unnecessary exposure of internal data.&lt;/p&gt;

&lt;p&gt;For this feature, the UI needed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Detailed skill gap information (for manager-level decision making)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Candidate-level summary information&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: @AuraEnabled is required for LWC(UI) to access Apex properties and methods.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Skill-Level DTO
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class SkillGapDetail {
    @AuraEnabled public String skillName;
    @AuraEnabled public Integer requiredLevel;
    @AuraEnabled public Integer impact;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Represents a single skill gap for a candidate.&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;All evaluation logic runs in Apex, so UI performs no calculations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Business logic stays in the backend&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UI remains lightweight&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Future logic changes don’t affect frontend code&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Candidate-Level DTO
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class CandidateResult {
    @AuraEnabled public String employeeName;
    @AuraEnabled public Decimal gapScore;
    @AuraEnabled public Boolean isProjectReady;
    @AuraEnabled public SkillGapDetail detail;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For each evaluated employee, the UI receives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Employee name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final gap score&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ready / Not Ready flag&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Skill Gap Detail&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps the response clean and structured.&lt;/p&gt;




&lt;h1&gt;
  
  
  Entry Point – evaluateProject()
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@AuraEnabled
public static List&amp;lt;CandidateResult&amp;gt; evaluateProject(Id projectId, Integer topN)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Accept a Project Id&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Evaluate unallocated employees&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rank them&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Return top N candidates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Persist evaluation results&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Guard Clause
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Guard clauses help prevent unnecessary processing and avoid unexpected or confusing UI behavior.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (projectId == null) return new List&amp;lt;CandidateResult&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If no project is provided, evaluation stops.&lt;/p&gt;

&lt;p&gt;Prevents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Null pointer exceptions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unexpected UI errors&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wasted governor limits&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Load Project Requirements
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List&amp;lt;Project_Skill_Requirement__c&amp;gt; reqs = [
    SELECT Skill__c, Required_Level__c,
           Importance__c, Weight__c
    FROM Project_Skill_Requirement__c
    WHERE Project__c = :projectId
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Each requirement contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Skill&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Required Level&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Importance (Required / Nice-to-have)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Weight&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After fetching, I converted them into Maps for fast access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Maps?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Governor limits restrict queries per transaction. Querying inside loops risks hitting limits. By storing data in Maps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Avoid repeated SOQL calls&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure constant-time lookups (O(1))&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep code bulk-safe&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maps are essential in Apex for this reason.&lt;/p&gt;




&lt;h1&gt;
  
  
  Weighted Impact Formula
&lt;/h1&gt;

&lt;p&gt;This is the heart of the evaluation engine.&lt;/p&gt;

&lt;p&gt;I first compute the deficit to rank candidates:&lt;br&gt;
    deficit = requiredLevel - employeeLevel;&lt;/p&gt;

&lt;p&gt;By itself, this treats all skills equally. To make evaluations more realistic, I introduced weighted scoring:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Integer impact = deficit * importanceMultiplier * weight;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Required skill&lt;/strong&gt; → multiplier = 2&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Nice-to-have&lt;/em&gt; → multiplier = 1&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Weight&lt;/strong&gt; → configurable per skill&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From this I ensured, &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Missing a critical skill has higher impact&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Minor skills don’t disproportionately penalize a candidate&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a system that is realistic and flexible rather than rigid.&lt;/p&gt;




&lt;h1&gt;
  
  
  Effective Level – Making It Smarter
&lt;/h1&gt;

&lt;p&gt;Raw skill levels aren’t always reliable. To improve accuracy, I introduced two adjustments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confidence adjustment
&lt;/li&gt;
&lt;li&gt;Staleness adjustment&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Confidence Adjustment
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Boolean isTrusted = (src == 'Manager-assessed');
Integer confidenceAdjust = isTrusted ? 0 : 1;
Integer afterConfidence = rawLevel - confidenceAdjust;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Self-assessed → reduce slightly
&lt;/li&gt;
&lt;li&gt;Manager-assessed → keep unchanged&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Staleness Adjustment
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Date staleCutoff = Date.today().addMonths(-12);

if (lastVerified == null) {
    stalenessAdjust = 2;
} else if (lastVerified &amp;lt;= staleCutoff) {
    stalenessAdjust = 1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Never verified → larger reduction&lt;br&gt;&lt;br&gt;
Verified &amp;gt;12 months ago → slight reduction&lt;br&gt;
Finally, the &lt;strong&gt;effective level&lt;/strong&gt; is computed as:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Integer effectiveLevel = afterConfidence - stalenessAdjust;
if (effectiveLevel &amp;lt; 0) effectiveLevel = 0;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This makes the evaluation &lt;strong&gt;time and credibility aware&lt;/strong&gt;, preventing outdated or inflated skill ratings from misleading staffing decisions.&lt;/p&gt;




&lt;h1&gt;
  
  
  Ranking Candidates
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;results.sort(new CandidateComparator());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Custom comparator:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private class CandidateComparator implements System.Comparator&amp;lt;CandidateResult&amp;gt; {
    public Integer compare(CandidateResult x, CandidateResult y) {
        if (x.gapScore != y.gapScore) {
            return (x.gapScore &amp;lt; y.gapScore) ? -1 : 1;
        }
        return x.employeeName.toLowerCase()
               .compareTo(y.employeeName.toLowerCase());
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Sorting priority :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Lowest gap score&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Alphabetical order as tie-breaker&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using this comparator ensures deterministic sorting, providing consistent results across repeated evaluations.&lt;/p&gt;




&lt;h1&gt;
  
  
  Project Ready Logic
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cr.isProjectReady = (requiredImpact == 0);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If all required skills have zero impact, the candidate is ready.&lt;/p&gt;

&lt;p&gt;Nice-to-have gaps don’t block readiness, preventing unnecessary hiring when existing employees are suitable.&lt;/p&gt;




&lt;h1&gt;
  
  
  Persisting Recommendations
&lt;/h1&gt;

&lt;p&gt;The evaluation results are stored in the Project_Candidate__c object.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;composite key&lt;/strong&gt; is used to uniquely identify each candidate for a project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pc.Project_Employee_Key__c =
    String.valueOf(projectId) + '|' + String.valueOf(employeeId);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: - The Project_Employee_Key__c is a Text field marked &lt;strong&gt;Unique&lt;/strong&gt; and &lt;strong&gt;Required&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The records are then saved using:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upsert candidates Project_Employee_Key__c;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;upsert ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Insert if record doesn’t exist&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Updates the record if it already exists&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prevents duplicate records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allows re-evaluation to update previous scores&lt;/p&gt;


&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>salesforce</category>
      <category>sideprojects</category>
      <category>apex</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building Skill Align - Part 5 - Field-Level Security, Page Layout Strategy &amp; Lightning Pages</title>
      <dc:creator>Rubasri Srikanthan</dc:creator>
      <pubDate>Mon, 23 Feb 2026 14:16:02 +0000</pubDate>
      <link>https://dev.to/rubasri_srikanthan/building-skill-align-part-5-field-level-security-page-strategy-lightning-pages-1fjj</link>
      <guid>https://dev.to/rubasri_srikanthan/building-skill-align-part-5-field-level-security-page-strategy-lightning-pages-1fjj</guid>
      <description>&lt;p&gt;After establishing record-level governance in Skill Align, I focused on deeper system control.&lt;/p&gt;

&lt;p&gt;Not just: &lt;em&gt;Who can see records.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But:&lt;/strong&gt;&lt;br&gt;
Who can influence lifecycle.&lt;br&gt;&lt;br&gt;
Who can modify state.&lt;br&gt;&lt;br&gt;
Who can shape operational flow.&lt;/p&gt;

&lt;p&gt;Today I focused on :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Field-Level Security (FLS)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Page Layout Strategy&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lightning Record Page Architecture&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Related List Governance&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Object Governance Model
&lt;/h2&gt;

&lt;p&gt;Each object in Skill Align serves a deliberate architectural purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catalog Objects – Stable Reference Data
&lt;/h2&gt;

&lt;p&gt;These define standardized reference information used across the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Object:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Skill&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Define approved skills.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Support matching logic and reporting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remain stable over time.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OWD: Public Read Only&lt;br&gt;&lt;br&gt;
Editable only by Managers.&lt;/p&gt;

&lt;p&gt;Catalog stability ensures reporting consistency and matching accuracy.&lt;/p&gt;




&lt;h2&gt;
  
  
  State Objects – Represent Business Definition State
&lt;/h2&gt;

&lt;p&gt;State objects define structured requirements or capability conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objects:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Employee Skill&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Project Skill Requirement&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These objects represent defined capability conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;  Employee Skill → Recorded capability currently held.&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;  Project Skill Requirement → Capability required by a project.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Both represent structured state definitions rather than lifecycle movement.&lt;/p&gt;

&lt;p&gt;OWD: Private&lt;/p&gt;

&lt;p&gt;Ownership:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Employee Skill → Employee&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Project Skill Requirement → Manager&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;State objects define what is required or what currently exists — not process transitions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Transactional Objects – Represent Lifecycle Movement
&lt;/h2&gt;

&lt;p&gt;These represent allocation and operational flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objects:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Project&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Project Candidate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Project Assignment&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These objects move through lifecycle stages and require controlled transitions.&lt;/p&gt;

&lt;p&gt;OWD: Private&lt;/p&gt;

&lt;p&gt;Ownership:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Project → Manager&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Project Candidate → Manager&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Project Assignment → Manager&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ownership is not decorative metadata — it drives responsibility, visibility, and control.&lt;/p&gt;




&lt;h1&gt;
  
  
  Field-Level Security : Lifecycle Authority
&lt;/h1&gt;

&lt;p&gt;In Skill Align, FLS maps directly to lifecycle power - It determines who can alter system state.&lt;/p&gt;

&lt;h1&gt;
  
  
  Controlled Lifecycle Field
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Status (Project Assignment)
&lt;/h3&gt;

&lt;p&gt;Editable only by Manager.&lt;/p&gt;

&lt;p&gt;Employees can view assignment status but cannot modify it.&lt;/p&gt;

&lt;p&gt;This centralizes allocation authority and prevents lifecycle bypass.&lt;/p&gt;




&lt;h2&gt;
  
  
  Catalog Stability Control
&lt;/h2&gt;

&lt;p&gt;On the Skill object:&lt;/p&gt;

&lt;p&gt;Editable only by Managers.&lt;/p&gt;

&lt;p&gt;Employees cannot redefine skill definitions.&lt;/p&gt;

&lt;p&gt;Catalog consistency protects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Matching accuracy&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reporting integrity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;System trust&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Page Layout Strategy
&lt;/h1&gt;

&lt;h2&gt;
  
  
  What is Page Layout?
&lt;/h2&gt;

&lt;p&gt;Page Layout defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Which fields appear in the Record Detail section&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which Related Lists are displayed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which standard/custom buttons are available&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Basic section grouping&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does NOT enforce security.&lt;/p&gt;

&lt;p&gt;Security is enforced by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;OWD&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Role Hierarchy&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Permission Sets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FLS&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Page Layout provides structural consistency — not access control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Page Layout Architecture by Object
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Object Type&lt;/th&gt;
&lt;th&gt;Object Name&lt;/th&gt;
&lt;th&gt;Layout Strategy&lt;/th&gt;
&lt;th&gt;Rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Catalog&lt;/td&gt;
&lt;td&gt;Skill&lt;/td&gt;
&lt;td&gt;Single Layout&lt;/td&gt;
&lt;td&gt;Stable reference data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Employee Skill&lt;/td&gt;
&lt;td&gt;Single Layout&lt;/td&gt;
&lt;td&gt;Governance via FLS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Project Skill Requirement&lt;/td&gt;
&lt;td&gt;Single Layout&lt;/td&gt;
&lt;td&gt;Structured requirement definition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transaction&lt;/td&gt;
&lt;td&gt;Project Candidate&lt;/td&gt;
&lt;td&gt;Manager-only Layout&lt;/td&gt;
&lt;td&gt;Employees have no access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transaction&lt;/td&gt;
&lt;td&gt;Project Assignment&lt;/td&gt;
&lt;td&gt;Neutral Layout&lt;/td&gt;
&lt;td&gt;Lifecycle control via FLS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core&lt;/td&gt;
&lt;td&gt;Employee&lt;/td&gt;
&lt;td&gt;Single Layout&lt;/td&gt;
&lt;td&gt;Persona differentiation via Lightning Page&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Layouts are intentionally minimal and consistent.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Page Layout Is Still Required (Even in Lightning)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  What is a Lightning Record Page?
&lt;/h2&gt;

&lt;p&gt;A Lightning Record Page is built using Lightning App Builder.&lt;/p&gt;

&lt;p&gt;It controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Page structure (tabs, sections, columns)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Component placement&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Conditional visibility&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;App/Profile activation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It defines how the page is arranged.&lt;/p&gt;




&lt;h1&gt;
  
  
  Lightning Record Page vs Page Layout
&lt;/h1&gt;

&lt;p&gt;A common misconception is that Lightning replaces Page Layout. It does not.&lt;/p&gt;

&lt;p&gt;Think of it this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Page Layout defines the field structure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lightning Record Page defines the page framework.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Record Detail component pulls fields from the assigned Page Layout.&lt;/p&gt;

&lt;p&gt;Even if the Lightning page is redesigned visually, the structural baseline remains the Page Layout.&lt;/p&gt;

&lt;p&gt;This is why Page Layout remains foundational.&lt;/p&gt;




&lt;h1&gt;
  
  
  Dynamic Forms: Presentation Shift, Not Governance Shift
&lt;/h1&gt;

&lt;p&gt;Dynamic Forms allows fields to be placed directly onto the Lightning Record Page instead of using the Record Detail component.&lt;/p&gt;

&lt;p&gt;When enabled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Field placement is controlled in Lightning App Builder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Page Layout no longer controls field positioning for that page.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Related Lists still depend on Page Layout (unless using Dynamic Related List).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FLS continues to enforce edit access.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Page Layout remains as structural fallback.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dynamic Forms shifts presentation control — not governance control.&lt;/p&gt;

&lt;p&gt;Security still depends on FLS.&lt;/p&gt;




&lt;h1&gt;
  
  
  Activation Strategy: Assigning Lightning Record Pages
&lt;/h1&gt;

&lt;p&gt;After designing a Lightning Record Page, activation determines who sees it.&lt;/p&gt;

&lt;p&gt;Salesforce provides three primary activation models.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Org Default
&lt;/h2&gt;

&lt;p&gt;The page becomes the default for all users across the organization for that object.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Experience does not vary by persona.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Structural consistency is required.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Context specialization is unnecessary.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Skill Align, Org Default activation is used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Employee&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Skill&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Project&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Employee Skill&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These objects remain stable and do not require contextual UI variation.&lt;/p&gt;

&lt;p&gt;Org Default ensures experience consistency across apps.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. App Default
&lt;/h2&gt;

&lt;p&gt;The page is activated only within a specific App.&lt;/p&gt;

&lt;p&gt;Users accessing the object from that App will see the assigned Lightning Record Page.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The same object behaves differently across Apps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Contextual experience is needed without altering governance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Responsibility differs by operational environment.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Skill Align, App Default activation is used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Project Assignment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Project Candidate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Project Skill Requirement&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These objects are responsibility-sensitive and benefit from contextual interaction.&lt;/p&gt;

&lt;p&gt;App-level activation enables UX specialization while preserving the same data model and security structure.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. App + Profile (Granular Activation)
&lt;/h2&gt;

&lt;p&gt;This is the most granular model.&lt;/p&gt;

&lt;p&gt;You can activate a page for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Specific App&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Specific Profile&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optional Record Type&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Persona-based UI differences are essential.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The same object must look different for different roles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authority and responsibility significantly differ.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This model is powerful — but must be used deliberately.&lt;/p&gt;

&lt;p&gt;Overusing profile-based activation fragments user experience and complicates governance.&lt;/p&gt;

&lt;p&gt;Architecturally, it should be applied only when functional differentiation justifies it.&lt;/p&gt;




&lt;h1&gt;
  
  
  Related List Governance
&lt;/h1&gt;

&lt;p&gt;Related Lists reinforce lifecycle clarity.&lt;/p&gt;

&lt;p&gt;Employee displays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Skills&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Project Assignments&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Project displays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Skill Requirements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Candidates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Assignments&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lifecycle flow becomes visible:&lt;/p&gt;

&lt;p&gt;Skill Requirement → Candidate → Assignment&lt;/p&gt;

&lt;p&gt;Objects without outward responsibility do not expose unnecessary related lists.&lt;/p&gt;




&lt;h1&gt;
  
  
  Security Layer Recap
&lt;/h1&gt;

&lt;p&gt;Skill Align security is layered deliberately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;OWD → Record restriction&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Role Hierarchy → Organizational oversight&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Permission Sets → Persona capability control&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FLS → Field-level lifecycle authority&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Page Layout → Structural consistency&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lightning Record Page → Experience personalization&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No single layer enforces governance - Governance emerges from layered architectural design.&lt;/p&gt;

</description>
      <category>sideprojects</category>
      <category>salesforce</category>
      <category>beginners</category>
      <category>security</category>
    </item>
    <item>
      <title>Building Skill Align - Part 4 - Roles, OWD, Sharing &amp; Record-Level Governance</title>
      <dc:creator>Rubasri Srikanthan</dc:creator>
      <pubDate>Sun, 22 Feb 2026 12:54:03 +0000</pubDate>
      <link>https://dev.to/rubasri_srikanthan/building-skill-align-part-4-roles-owd-sharing-record-level-governance-4n61</link>
      <guid>https://dev.to/rubasri_srikanthan/building-skill-align-part-4-roles-owd-sharing-record-level-governance-4n61</guid>
      <description>&lt;p&gt;After defining objects, users, profiles, and permission sets in Skill Align, the next critical layer was not functionality — but visibility.&lt;/p&gt;

&lt;p&gt;Because defining what users can &lt;em&gt;do&lt;/em&gt; without defining what they can &lt;em&gt;see&lt;/em&gt; leads to overexposure.&lt;/p&gt;

&lt;p&gt;In Salesforce, record-level security operates in layered form:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Organization-Wide Defaults (OWD)&lt;/strong&gt; → Baseline visibility&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Role Hierarchy&lt;/strong&gt; → Vertical access expansion&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sharing Rules&lt;/strong&gt; → Controlled horizontal exceptions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Profiles and Permission Sets define object-level permissions.&lt;br&gt;&lt;br&gt;
This phase I focused purely on &lt;strong&gt;record-level governance&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Important: Role Hierarchy and Sharing Rules can only expand access. They can never be more restrictive than OWD. OWD is always the foundation.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Organization-Wide Defaults (OWD)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  What is OWD?
&lt;/h2&gt;

&lt;p&gt;OWD defines the most restrictive level of access to records.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Private&lt;/strong&gt; → Only the record owner can access&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Public Read Only&lt;/strong&gt; → Everyone can view, only owner can edit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Public Read/Write&lt;/strong&gt; → Everyone can view and edit&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security begins here.&lt;/p&gt;




&lt;h2&gt;
  
  
  OWD Configuration in Skill Align
&lt;/h2&gt;

&lt;p&gt;I intentionally started restrictive and expanded access only where required.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Object&lt;/th&gt;
&lt;th&gt;OWD&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Employee&lt;/td&gt;
&lt;td&gt;Private&lt;/td&gt;
&lt;td&gt;Sensitive employee data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project&lt;/td&gt;
&lt;td&gt;Private&lt;/td&gt;
&lt;td&gt;Allocation decisions must remain controlled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skill&lt;/td&gt;
&lt;td&gt;Public Read Only&lt;/td&gt;
&lt;td&gt;Shared reference data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Employee Skill&lt;/td&gt;
&lt;td&gt;Private&lt;/td&gt;
&lt;td&gt;Skill ownership tied to employee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project Skill Requirement&lt;/td&gt;
&lt;td&gt;Private&lt;/td&gt;
&lt;td&gt;Controlled requirement metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project Candidate&lt;/td&gt;
&lt;td&gt;Private&lt;/td&gt;
&lt;td&gt;Shortlisting is sensitive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project Assignment&lt;/td&gt;
&lt;td&gt;Private&lt;/td&gt;
&lt;td&gt;Final allocation must be governed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What This Means Practically
&lt;/h3&gt;

&lt;p&gt;Because most objects are &lt;strong&gt;Private&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Users can only see records they own.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access expands only through hierarchy or administrative override.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Exposure is controlled by design — not by accident.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Role Hierarchy
&lt;/h1&gt;

&lt;h2&gt;
  
  
  What is Role Hierarchy?
&lt;/h2&gt;

&lt;p&gt;Role Hierarchy provides &lt;strong&gt;vertical record visibility&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;OWD = Private&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“Grant Access Using Hierarchies” is enabled&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users higher in the hierarchy automatically gain access to records owned by users below them.&lt;/p&gt;

&lt;p&gt;Important distinction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Profiles&lt;/strong&gt; → Control object permissions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Roles&lt;/strong&gt; → Control record visibility&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Skill Align:&lt;/p&gt;

&lt;p&gt;Managers and Employees share the same profile.&lt;br&gt;&lt;br&gt;
They differ only by role — and that drives visibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  Role Structure
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   HR
    ↑
Project Manager
    ↑
Employee
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Visibility follows structure.&lt;/p&gt;




&lt;h1&gt;
  
  
  How Visibility Works in Skill Align
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Scenario 1: Employee Skill Record
&lt;/h2&gt;

&lt;p&gt;An Employee updates their Skill record.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Employee can see and edit their record (owner).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Project Manager can see it because they sit above the Employee in the hierarchy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HR can see it due to administrative permissions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This enables skill verification without exposing peer data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scenario 2: Project Assignment
&lt;/h2&gt;

&lt;p&gt;Project Assignment records are owned by the Project Manager to reflect accountability.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Project Manager can view and manage the record (record owner).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HR can see and modify it due to administrative permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Employees cannot automatically see it because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  OWD is Private.&lt;/li&gt;
&lt;li&gt;  Their role sits below the Manager role.&lt;/li&gt;
&lt;li&gt;  Ownership represents responsibility.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Governance is embedded into ownership.&lt;/p&gt;




&lt;h1&gt;
  
  
  HR and Administrative Override
&lt;/h1&gt;

&lt;p&gt;HR uses the System Administrator profile, which includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;View All Data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Modify All Data&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These permissions override OWD and hierarchy.&lt;/p&gt;

&lt;p&gt;HR visibility is administrative — not inherited.&lt;/p&gt;

&lt;p&gt;This separation ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Structural governance through roles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Oversight through administrative control&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Sharing Rules
&lt;/h1&gt;

&lt;h2&gt;
  
  
  What Are Sharing Rules?
&lt;/h2&gt;

&lt;p&gt;Sharing Rules allow access to be extended horizontally — outside reporting lines.&lt;/p&gt;

&lt;p&gt;Used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cross-team allocation reviews&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Governance committees&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Audit visibility&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They expand access but never restrict it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Did Skill Align Use Sharing Rules?
&lt;/h2&gt;

&lt;p&gt;Not in this phase.&lt;/p&gt;

&lt;p&gt;Because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Upward visibility is already handled via Role Hierarchy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manager-owned records are intentionally restricted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Governance is preserved through restrictive OWD.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If employee visibility into Project Assignments becomes necessary, a controlled sharing rule can be introduced — without redesigning the architecture.&lt;/p&gt;

&lt;p&gt;The foundation is scalable.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Visibility Flow
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Employee
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Can see only records they own.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cannot see peer records.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cannot see Manager-owned Project Assignments.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Manager
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Can see Employee-owned records below them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Owns and governs Project Assignment records.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Controls allocation lifecycle.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  HR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Full record visibility.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Administrative oversight.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can verify skills and confirm allocations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>salesforce</category>
      <category>beginners</category>
      <category>sideprojects</category>
      <category>security</category>
    </item>
    <item>
      <title>Building Skill Align - Part 3 - Users, Apps, Profiles &amp; Permission Sets</title>
      <dc:creator>Rubasri Srikanthan</dc:creator>
      <pubDate>Sat, 21 Feb 2026 12:10:08 +0000</pubDate>
      <link>https://dev.to/rubasri_srikanthan/building-skill-align-part-3-users-apps-profiles-permission-sets-1pc2</link>
      <guid>https://dev.to/rubasri_srikanthan/building-skill-align-part-3-users-apps-profiles-permission-sets-1pc2</guid>
      <description>&lt;p&gt;After designing the data model for Skill Align, I moved to the next critical layer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who will use this system — and what should they see?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Salesforce, defining users and access is just as important as defining objects.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why I Created Three Users
&lt;/h1&gt;

&lt;p&gt;Skill Align mirrors a real organization. So I created three users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HR&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Project Manager&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Employee&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each represents a distinct business role with different responsibilities and levels of control.&lt;/p&gt;




&lt;h2&gt;
  
  
  HR – The Controller
&lt;/h2&gt;

&lt;p&gt;HR is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Creating Employee records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Managing Skills&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confirming allocations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HR requires full system-level access.&lt;/p&gt;

&lt;p&gt;Since the Developer Org already provides a default &lt;strong&gt;System Administrator&lt;/strong&gt; user, I used that to represent HR. This avoids consuming additional licenses while still maintaining complete administrative control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Manager – The Decision Maker
&lt;/h2&gt;

&lt;p&gt;Project Managers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create Project records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Define required skills&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Review candidates&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They do not need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;System configuration access&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Administrative privileges&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unrestricted data visibility&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Their access must remain business-focused and project-scoped.&lt;/p&gt;




&lt;h2&gt;
  
  
  Employee – The Contributor
&lt;/h2&gt;

&lt;p&gt;Employees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;View their skills&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update confidence levels&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;View project assignments&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They should not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Allocate themselves&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Modify other employees&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Delete important records&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Their access must be limited and self-contained.&lt;/p&gt;




&lt;h1&gt;
  
  
  License Limitation &amp;amp; Profile Strategy
&lt;/h1&gt;

&lt;p&gt;Initially, I assigned both Project Manager and Employee to the &lt;strong&gt;Standard User&lt;/strong&gt; profile.&lt;/p&gt;

&lt;p&gt;However, I quickly realized something important.&lt;/p&gt;

&lt;p&gt;Editing Standard User directly is not a clean architectural practice.&lt;/p&gt;

&lt;p&gt;It is a generic baseline profile provided by Salesforce. Heavily modifying it can create maintenance complications later — especially as the org grows or additional use cases are introduced.&lt;/p&gt;

&lt;p&gt;Instead of altering a shared foundation, I created a dedicated custom profile: &lt;strong&gt;Skill Align User&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This profile became the controlled baseline for both Project Manager and Employee users.&lt;/p&gt;

&lt;p&gt;(We will understand Profiles in detail shortly.)&lt;/p&gt;




&lt;h3&gt;
  
  
  Why create a new profile?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;To isolate all project-specific configurations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To maintain clean and scalable architecture&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To retain flexibility for future enhancements&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final structure became:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;HR → System Administrator profile&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manager → Skill Align User profile&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Employee → Skill Align User profile&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Creating Three Separate Lightning Apps
&lt;/h1&gt;

&lt;p&gt;To improve usability and simulate real enterprise structure, I created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HR App&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manager App&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Employee App&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each app contains only relevant tabs and objects.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is a Tab?
&lt;/h2&gt;

&lt;p&gt;A Tab in Salesforce is a navigation element that provides access to an object or feature.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Employee Tab → Opens Employee records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Project Tab → Opens Project records&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tabs determine what users can access within an app interface.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Next Challenge
&lt;/h1&gt;

&lt;p&gt;At this point, both Manager and Employee shared the same profile:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skill Align User&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If multiple apps are assigned at the profile level, both users would see all assigned apps.&lt;/p&gt;

&lt;p&gt;That defeats role-based separation.&lt;/p&gt;

&lt;p&gt;The architectural question then became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I differentiate access without creating multiple similar profiles?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where understanding Profiles and Permission Sets becomes critical.&lt;/p&gt;




&lt;h1&gt;
  
  
  Profiles vs Permission Sets
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Profile
&lt;/h2&gt;

&lt;p&gt;A Profile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Is mandatory (every user must have one)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Defines baseline permissions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Controls object access, field-level security, and app visibility&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of a profile as the foundation of a building.&lt;/p&gt;

&lt;p&gt;It should remain stable and minimal.&lt;/p&gt;

&lt;p&gt;One user → One profile.&lt;/p&gt;




&lt;h2&gt;
  
  
  Permission Set
&lt;/h2&gt;

&lt;p&gt;A Permission Set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Is optional&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adds additional permissions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can be assigned selectively&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as a modular extension added to the foundation.&lt;/p&gt;

&lt;p&gt;The base stays clean. Capabilities are extended only where required.&lt;/p&gt;

&lt;p&gt;One user → Multiple Permission Sets (if needed).&lt;/p&gt;




&lt;h1&gt;
  
  
  The Final Solution
&lt;/h1&gt;

&lt;p&gt;I kept the &lt;strong&gt;Skill Align User&lt;/strong&gt; profile intentionally generic.&lt;/p&gt;

&lt;p&gt;Then I created two Permission Sets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manager App Access&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Employee App Access&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside each Permission Set, I configured:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Lightning App assignment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Required object permissions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I assigned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Manager Permission Set → Only to Project Manager&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Employee Permission Set → Only to Employee&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allowed both users to share the same profile while still seeing completely different apps and access levels.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Design Matters
&lt;/h1&gt;

&lt;p&gt;This approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Avoids modifying Standard User&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prevents unnecessary profile duplication&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Works within license constraints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Follows Salesforce security best practices&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enterprise-level Salesforce design prefers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Minimal profiles &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maximum flexibility using Permission Sets&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>salesforce</category>
      <category>development</category>
      <category>beginners</category>
      <category>project</category>
    </item>
    <item>
      <title>Building Skill Align - Part 2 - Objects, Relationships &amp; Fields in Salesforce</title>
      <dc:creator>Rubasri Srikanthan</dc:creator>
      <pubDate>Fri, 20 Feb 2026 14:56:11 +0000</pubDate>
      <link>https://dev.to/rubasri_srikanthan/building-skill-align-part-2-objects-relationships-fields-in-salesforce-4kn</link>
      <guid>https://dev.to/rubasri_srikanthan/building-skill-align-part-2-objects-relationships-fields-in-salesforce-4kn</guid>
      <description>&lt;p&gt;When I started building &lt;strong&gt;Skill Align&lt;/strong&gt;, the first step was to lay down the foundation: defining its &lt;strong&gt;core objects, relationships, and key fields&lt;/strong&gt;. Think of this as creating the blueprint that makes the system actually work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Salesforce Terms for RDBMS Fans
&lt;/h2&gt;

&lt;p&gt;If you’re coming from a relational database background, here’s a simple mapping:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Objects&lt;/strong&gt; → store data, like &lt;strong&gt;tables&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Standard Objects&lt;/strong&gt; – provided by Salesforce&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Custom Objects&lt;/strong&gt; – created by you&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Fields&lt;/strong&gt; → define what each record contains, like &lt;strong&gt;columns&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Supports data types like &lt;strong&gt;Text, Picklist (select a single predefined value from multiple options), Number, Date/Time&lt;/strong&gt;, and more&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Records&lt;/strong&gt; → the actual data, like &lt;strong&gt;rows&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Relationships Connect the Dots
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lookup&lt;/strong&gt; → flexible reference; parent has limited control over child&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Master-Detail&lt;/strong&gt; → tightly linked; parent controls ownership of child&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are ideal for &lt;strong&gt;one-to-many relationships&lt;/strong&gt;, but what about &lt;strong&gt;many-to-many&lt;/strong&gt;?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Junction objects&lt;/strong&gt; → solve M:N relationships by connecting two objects, usually via master-detail links to both objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 &lt;strong&gt;Best practice:&lt;/strong&gt; Use standard objects whenever possible. Create custom objects only when necessary.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Idea to Implementation
&lt;/h2&gt;

&lt;p&gt;For Skill Align, the goal was clear: assign &lt;strong&gt;Employees&lt;/strong&gt; with certain skills to &lt;strong&gt;Projects&lt;/strong&gt; requiring those skills. That led to defining the core objects:&lt;/p&gt;

&lt;h3&gt;
  
  
  Employee
&lt;/h3&gt;

&lt;p&gt;Represents people in the company.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Note: Normally, Employee would just be the &lt;strong&gt;User&lt;/strong&gt; standard object. But in a Dev org, creating users requires licenses, so I created a separate Employee object.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Key fields:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;Is Allocated&lt;/code&gt; (Checkbox) → tracks whether the employee is assigned to a project&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Primary Manager&lt;/code&gt; (Lookup to Employee) → tracks reporting lines and helps with allocation&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Skill
&lt;/h3&gt;

&lt;p&gt;Stores all organizational skills and categorizes them for reporting and clarity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project
&lt;/h3&gt;

&lt;p&gt;Stores project information. Linked to a manager via a lookup to Employee.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Candidate
&lt;/h3&gt;

&lt;p&gt;Tracks potential employees for a project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Key fields:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;Gap Score&lt;/code&gt; → difference between required skill level and employee’s actual level&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Is Project Ready&lt;/code&gt; (Checkbox) → tracks readiness&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Assignment
&lt;/h3&gt;

&lt;p&gt;Records final allocation of an employee to a project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Key field:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;Status&lt;/code&gt; (Picklist: Proposed, Confirmed, Released) → tracks employee stage in the project&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Many-to-Many Relationships Matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Employees ↔ Skills
&lt;/h3&gt;

&lt;p&gt;An employee can have many skills, and a skill can belong to many employees. This is solved using the &lt;strong&gt;Employee Skill junction object&lt;/strong&gt;, with key fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Level&lt;/code&gt; → employee’s proficiency in the skill&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Confidence&lt;/code&gt; → how confident the employee feels using this skill&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Verification&lt;/code&gt; → whether the skill has been formally verified&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Projects ↔ Skills
&lt;/h3&gt;

&lt;p&gt;A project may require multiple skills, and a skill can belong to multiple projects. This is solved using the &lt;strong&gt;Project Skill Requirement junction object&lt;/strong&gt;, with key fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Required Level&lt;/code&gt; → the proficiency level needed for the project&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Weight&lt;/code&gt; → the relative importance of this skill for the project&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Importance&lt;/code&gt; → the priority of this skill in decision-making&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Fun Fact:&lt;/strong&gt; Even using &lt;strong&gt;lookup relationships&lt;/strong&gt;, Salesforce can treat the object like a junction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Each requirement has independent ownership&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Records can exist even if the Project or Skill is deleted&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Metadata like Required Level or Weight is managed flexibly&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach gives you &lt;strong&gt;junction-like behavior without strict master-detail constraints&lt;/strong&gt;, perfect when you want flexibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;After planning, I created these objects and connected the relationships in Salesforce. This structure now allows Skill Align to &lt;strong&gt;match employees to projects&lt;/strong&gt; based on skills and readiness, while remaining flexible for future growth.&lt;/p&gt;

&lt;p&gt;Of course, as new features come in, more objects and fields may be required—but Salesforce makes it easy to extend the data model whenever needed.&lt;/p&gt;

&lt;p&gt;💬 &lt;strong&gt;Question for you:&lt;/strong&gt; How do you manage data architecture in your projects?&lt;/p&gt;

</description>
      <category>salesforce</category>
      <category>crm</category>
      <category>beginners</category>
      <category>development</category>
    </item>
    <item>
      <title>Starting My First Salesforce Project - What Is Salesforce?</title>
      <dc:creator>Rubasri Srikanthan</dc:creator>
      <pubDate>Thu, 19 Feb 2026 13:30:05 +0000</pubDate>
      <link>https://dev.to/rubasri_srikanthan/starting-my-first-salesforce-project-what-is-salesforce-1kda</link>
      <guid>https://dev.to/rubasri_srikanthan/starting-my-first-salesforce-project-what-is-salesforce-1kda</guid>
      <description>&lt;p&gt;When I started my job as a Salesforce Developer, my Amma asked:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“So Salesforce is the technology you're working on… is it a sales job? Do you need to sell?😅”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I paused. Then I laughed.&lt;/p&gt;

&lt;p&gt;I told her:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“No Amma 😭 It’s not about selling products. It’s about building systems.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let me explain the same in a simple way.&lt;/p&gt;




&lt;p&gt;Imagine a big theatre with multiple &lt;strong&gt;customers, employees, movies, and tickets&lt;/strong&gt; — all managed using Excel sheets and emails.&lt;/p&gt;

&lt;p&gt;In today’s fast-paced world?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chaos. Absolute chaos.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s where 👉 Salesforce comes in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.salesforce.com/in/" rel="noopener noreferrer"&gt;Salesforce&lt;/a&gt; is a cloud-based CRM (Customer Relationship Management) platform that helps companies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;📦 Store and manage data - Customers, Movies, Bookings, Payments — everything in one structured system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;⚙️ Automate business processes - Automatically allocate movies to Mini or Maxi theatre.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🔐 Control user access - Theatre Manager and Employee should not see the same data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🏗️ Build custom applications - Entire Movie Ticket Management App — from booking to feedback.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So no… it’s not a sales job.&lt;/p&gt;

&lt;p&gt;It’s about designing and developing structured, scalable business systems, automating business logic and solving real-world operational problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;It’s system architecture + development + business understanding combined&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But my Amma's reaction - Oh, then you help them to increase their sales... Fine. &lt;/p&gt;

&lt;p&gt;Even though I was unable to explain her fully, she got the basics right 😊&lt;/p&gt;

&lt;p&gt;Because at the end of the day, well-built systems do help businesses grow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Decided to Build Something Real
&lt;/h2&gt;

&lt;p&gt;Once I truly understood what Salesforce enables, I didn’t want to just learn it from documentation or Trailhead (Official learning documentation of Salesforce).&lt;/p&gt;

&lt;p&gt;I wanted to &lt;strong&gt;build a real system from scratch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s how &lt;strong&gt;Skill Align&lt;/strong&gt; was born.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Skill Align? 💡
&lt;/h2&gt;

&lt;p&gt;In many organizations, employee allocation to projects often depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Availability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manager assumptions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Informal discussions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Quick decisions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But not always on structured skill evaluation.&lt;/p&gt;

&lt;p&gt;This affects almost every employee - without structured skill tracking, talent is often underutilized or misaligned.&lt;/p&gt;

&lt;p&gt;Skill Align is my attempt to design a system where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Employees have clearly defined skill records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Projects specify required skills and expected proficiency levels&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Allocation decisions are based on data, not perception&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Skill gaps can be identified before assigning someone&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of asking,  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Who seems suitable?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The system should answer,  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Who is the best match based on skills and requirements?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s the vision.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this Series Is About 🚀
&lt;/h2&gt;

&lt;p&gt;Instead of building this silently, I’ve decided to document the journey.&lt;/p&gt;

&lt;p&gt;Starting now, I will be sharing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;What I build each day&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why I design it that way&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Architecture decisions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data modeling logic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Challenges and learnings&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improvements and redesigns&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a step-by-step tutorial.&lt;/p&gt;

&lt;p&gt;This is my real-time journey of building a Salesforce application from scratch.&lt;/p&gt;

&lt;p&gt;If you’re learning Salesforce, transitioning into development, or curious about how real systems are structured — this series might give you practical insight beyond theory.&lt;/p&gt;




&lt;p&gt;Would love to hear your parents’ views about your job too 😄&lt;/p&gt;




&lt;h2&gt;
  
  
  About Me
&lt;/h2&gt;

&lt;p&gt;I’m a curious Salesforce Developer who loves turning complex ideas into simple, practical solutions.&lt;/p&gt;

</description>
      <category>salesforce</category>
      <category>beginners</category>
      <category>crm</category>
      <category>career</category>
    </item>
    <item>
      <title>Using Apex to Parse AI Outputs safely in Salesforce</title>
      <dc:creator>Rubasri Srikanthan</dc:creator>
      <pubDate>Fri, 13 Feb 2026 17:10:07 +0000</pubDate>
      <link>https://dev.to/rubasri_srikanthan/using-apex-to-parse-ai-outputs-safely-in-salesforce-5cmn</link>
      <guid>https://dev.to/rubasri_srikanthan/using-apex-to-parse-ai-outputs-safely-in-salesforce-5cmn</guid>
      <description>&lt;p&gt;While building my project, I came across a use case where I needed to capture &lt;strong&gt;AI-generated data&lt;/strong&gt; using &lt;strong&gt;Flow&lt;/strong&gt; and store it directly in Salesforce object fields. The goal was to store this data for later use. &lt;/p&gt;

&lt;p&gt;Although Flow is powerful for automations, AI outputs are not formatted in a way that Flow can reliably use. This challenge led me to explore how &lt;strong&gt;Apex and Flow could work together&lt;/strong&gt; to safely process AI outputs before storing them in Salesforce records.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why a Parser is Needed
&lt;/h3&gt;

&lt;p&gt;Even when AI can be instructed to return structured JSON, Flow can struggle with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Nested or inconsistent data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extracting specific values reliably&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without a parser, automation can &lt;strong&gt;fail or store incomplete data&lt;/strong&gt;. An Apex parser safely extracts the required fields and makes them available to Flow &lt;strong&gt;without modifying the AI-generated values&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Workflow: Step by Step
&lt;/h3&gt;

&lt;p&gt;Here’s how I handled the use case in my project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Generate AI Output Using Prompt Template&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  To make parsing simple, it’s important to instruct the AI to return &lt;strong&gt;structured JSON&lt;/strong&gt;, for example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{  
   "score": 85, 
   "confidenceLevel": "high" 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Clearly specify the fields you need and which values are optional.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In Flow, I used the &lt;strong&gt;Prompt Template action&lt;/strong&gt; to get the generated AI output. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;💡 Tip:&lt;/strong&gt; Effective prompts generate predictable JSON, making it much easier for the parser to extract the values accurately.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;2. Capture AI Output in Flow&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Flow stores the AI response in a variable, ready to be passed to Apex for parsing.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;3. Call Apex Parser from Flow&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Flow sends the AI output variable to an &lt;strong&gt;Apex action&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The parser &lt;strong&gt;safely extracts the required fields&lt;/strong&gt; and handles any missing or optional data.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Understanding the Apex Parser
&lt;/h3&gt;

&lt;p&gt;Here’s the &lt;strong&gt;core of the parser&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Map&amp;lt;String, Object&amp;gt; data = (Map&amp;lt;String, Object&amp;gt;) JSON.deserializeUntyped(aiJson);

Decimal score = data.containsKey('score') 
    ? Decimal.valueOf(String.valueOf(data.get('score'))) 
    : null;

String confidence = data.containsKey('confidenceLevel') 
    ? String.valueOf(data.get('confidenceLevel')) 
    : null;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JSON.deserializeUntyped&lt;/strong&gt; converts the JSON string into a map.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The map lets us safely check for missing fields.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Values are converted to the correct type, or set to null if missing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The parser &lt;strong&gt;does not modify the AI-generated values&lt;/strong&gt;; it only extracts them safely for Flow.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Key points for calling Apex from Flow&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
    * &lt;code&gt;@InvocableMethod&lt;/code&gt; lets Flow call the Apex parser&lt;br&gt;
    * &lt;code&gt;@InvocableVariable&lt;/code&gt; allows data to pass in and out.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;4. Flow Receives Parsed Data and Updates Records&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Flow receives the extracted fields as variables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;These variables are mapped directly to Salesforce object fields.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flow can now &lt;strong&gt;create or update records safely&lt;/strong&gt;, without worrying about missing or malformed AI output.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Best Practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Start by parsing a few key fields before handling everything.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep a log of raw AI outputs for debugging purposes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test the AI prompts and parser together to ensure consistent and predictable results.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  About Me
&lt;/h3&gt;

&lt;p&gt;I’m a curious Salesforce Developer who loves turning complex ideas into simple, practical solutions — and sharing what I learn while building projects.&lt;/p&gt;

</description>
      <category>salesforc</category>
      <category>apex</category>
      <category>ai</category>
      <category>flow</category>
    </item>
    <item>
      <title>Trigger.new vs Trigger.old — Understanding Apex Triggers the Right Way</title>
      <dc:creator>Rubasri Srikanthan</dc:creator>
      <pubDate>Fri, 06 Feb 2026 15:06:00 +0000</pubDate>
      <link>https://dev.to/rubasri_srikanthan/triggernew-vs-triggerold-understanding-apex-triggers-the-right-way-58b8</link>
      <guid>https://dev.to/rubasri_srikanthan/triggernew-vs-triggerold-understanding-apex-triggers-the-right-way-58b8</guid>
      <description>&lt;p&gt;When I first started learning Apex triggers, I wasn’t always sure when to use &lt;strong&gt;Trigger.new&lt;/strong&gt; versus &lt;strong&gt;Trigger.old&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By focusing on how they actually work, I was able to grasp it clearly and apply it effectively.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Idea
&lt;/h2&gt;

&lt;p&gt;Trigger.new → the data Salesforce is trying to save right now.&lt;br&gt;&lt;br&gt;
Trigger.old → the data that existed before the change.&lt;/p&gt;

&lt;p&gt;Keeping this in mind, most usage scenarios become logical.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. When to Use Trigger.new
&lt;/h2&gt;

&lt;p&gt;What it represents:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Trigger.new&lt;/strong&gt; contains the current or incoming values that Salesforce is attempting to insert or update.&lt;/p&gt;

&lt;p&gt;Common use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Validations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting or modifying field values&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reading updated values&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating related records based on new data&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Available in these contexts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Available?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Insert&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Undelete&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Undelete&lt;/strong&gt;: This trigger context runs when a record is restored from the Recycle Bin, and Trigger.new contains the record values being recovered.&lt;/p&gt;

&lt;p&gt;Example: Validation&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for (Contact con : Trigger.new) {
    if (con.Email == null) {
        con.Email.addError('Email is required');
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This works because Trigger.new represents the data being saved right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. When to Use Trigger.old
&lt;/h2&gt;

&lt;p&gt;What it represents:&lt;br&gt;&lt;br&gt;
Trigger.old is a snapshot of the record before the change.&lt;/p&gt;

&lt;p&gt;Common use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Detecting field changes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Conditional logic based on previous values&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Preventing duplicate actions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Available in these contexts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Available?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Update&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Example: Detecting a change&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for (Opportunity newOpp : Trigger.new) {
    Opportunity oldOpp = Trigger.oldMap.get(newOpp.Id);
    if (newOpp.StageName != oldOpp.StageName) {
        // Stage has changed
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Trigger.old is useful because it gives you the previous state of the record for comparison.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: Trigger.oldMap and Trigger.newMap provide fast access to records by Id (Id → Record). Using maps is preferred over looping when comparing old and new values in bulk operations.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. Using Trigger.new and Trigger.old Together
&lt;/h2&gt;

&lt;p&gt;Whenever your logic depends on detecting a change, not just the current value, you need both.&lt;/p&gt;

&lt;p&gt;Example: Detecting a meaningful update&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for (Opportunity newOpp : Trigger.new) {
    Opportunity oldOpp = Trigger.oldMap.get(newOpp.Id);
    if (newOpp.StageName == 'Closed Won' &amp;amp;&amp;amp;
        oldOpp.StageName != 'Closed Won') {
        // Stage just changed to Closed Won
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Using both helps prevent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Duplicate execution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Logic running on unrelated updates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Incorrect automation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Context-Based Decision Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Validate input&lt;/td&gt;
&lt;td&gt;Trigger.new&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modify fields&lt;/td&gt;
&lt;td&gt;Trigger.new&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compare old vs new&lt;/td&gt;
&lt;td&gt;Trigger.new + Trigger.old&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cleanup before delete&lt;/td&gt;
&lt;td&gt;Trigger.old&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block record save&lt;/td&gt;
&lt;td&gt;Trigger.new.addError()&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  5. Before vs After Triggers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trigger Type&lt;/th&gt;
&lt;th&gt;What to Do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Before&lt;/td&gt;
&lt;td&gt;Modify Trigger.new&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After&lt;/td&gt;
&lt;td&gt;Read Trigger.new, compare with Trigger.old&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: You cannot modify records using Trigger.new in after triggers because the record has already been committed to the database. At this stage, Salesforce marks Trigger.new as read-only to preserve data integrity. Any further changes require a separate DML operation.&lt;/p&gt;

&lt;p&gt;Example : Updating in after trigger using DML&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List&amp;lt;Account&amp;gt; accsToUpdate = new List&amp;lt;Account&amp;gt;();
for (Account acc : Trigger.new) {
    accsToUpdate.add(new Account(
        Id = acc.Id,
        Name = 'Updated Name'
    ));
}
update accsToUpdate;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  6. Trigger Timeline
&lt;/h2&gt;

&lt;p&gt;Think about triggers in terms of whether a record already exists and what Salesforce is doing with it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trigger Context&lt;/th&gt;
&lt;th&gt;Available Data&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Before Insert&lt;/td&gt;
&lt;td&gt;Trigger.new&lt;/td&gt;
&lt;td&gt;Record does not exist yet, modify fields before save&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before Update&lt;/td&gt;
&lt;td&gt;Trigger.new + Trigger.old&lt;/td&gt;
&lt;td&gt;Compare old vs new, modify fields before save&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After Update&lt;/td&gt;
&lt;td&gt;Trigger.new + Trigger.old&lt;/td&gt;
&lt;td&gt;Record saved, read-only, detect changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before Delete&lt;/td&gt;
&lt;td&gt;Trigger.old&lt;/td&gt;
&lt;td&gt;Record about to be deleted, cleanup logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After Delete&lt;/td&gt;
&lt;td&gt;Trigger.old&lt;/td&gt;
&lt;td&gt;Record deleted, read-only, audit or related logic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Visualizing the timeline of data makes using the correct context natural.&lt;/p&gt;




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

&lt;p&gt;Instead of memorizing rules, I always ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Am I working with new data, old data, or a change between them?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you answer that, the correct context (Trigger.new, Trigger.old, or both) becomes obvious.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pro Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Always &lt;strong&gt;bulkify&lt;/strong&gt; your triggers — loop over collections like &lt;code&gt;Trigger.new&lt;/code&gt; instead of single records to avoid governor limits.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Trigger.newMap and Trigger.oldMap for &lt;strong&gt;efficient lookups by Id&lt;/strong&gt; instead of nested loops.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  About Me
&lt;/h2&gt;

&lt;p&gt;I’m a curious Salesforce Developer who enjoys breaking down complex concepts into simple, practical explanations.&lt;/p&gt;

</description>
      <category>salesforce</category>
      <category>apex</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
