The Salesforce Developer's Guide to the Spring '26 Release
Blog
Mar 16, 2026
0 comments
The Salesforce Developer’s Guide to the Spring ’26 Release

Content

What's inside

2 sections

Need help with your next build?

Talk to our team

Quick Overview:

This guide covers all major Salesforce Spring '26 developer updates, from LWC template expressions and Apex Cursors to Agentforce Builder, mandatory security migrations, Flow logging, GraphQL mutations, and more.

  • Complex Template Expressions in LWC (Beta), write JS inline in templates
  • Apex Cursors are now Generally Available, process up to 50M records
  • Agentforce Builder with Canvas + Script views for AI agent development
  • RunRelevantTests for faster, dependency-aware deployments (API v66)
  • Agent Script, YAML-like DSL for deterministic agent behavior
  • TypeScript support for LWC via @salesforce/lightning-types
  • External Client Apps replacing Connected Apps (mandatory migration)
  • Flow Logging, Kanban boards, and inline-editable data tables
  • Triple DES retirement deadline, Summer '26 (urgent action required)
  • GraphQL mutations are now supported in API v66

Why Spring '26 Is a Milestone Release for Developers

Every Salesforce release matters.

But the Salesforce Spring '26 update feels different. This isn't just a collection of quality-of-life improvements, it's a definitive shift in how the platform thinks about the developer experience, AI integration, and long-term architectural strategy.

As one Salesforce Developer Advocate noted, Spring '26 is "a gobstopper of a release for developers", packed with GA features that remove years-old friction points, Beta features that preview the agentic future, and mandatory security changes that demand immediate attention.

image.png

The defining theme of this release is the move toward what Salesforce calls the "Agentic Enterprise", a model where AI agents and human developers co-build, co-test, and co-deploy across every layer of the platform.

If you've been watching Agentforce evolve since its introduction, Spring '26 is where it starts to feel like native infrastructure rather than a bolted-on product.

In this guide, we'll break down every developer-relevant feature across LWC, Apex, Agentforce 360 platform, Flow, APIs, and security, so you can prioritize what to test first, what to plan for, and what to act on immediately.

Whether you're a solo Salesforce developer, part of an enterprise team, or partnering with a specialist provider of Salesforce development services, this guide has you covered.

Pro Tip: Sandbox preview for Spring '26 began January 9, 2026. If you haven't yet spun up a pre-release org, do it now, some Beta features require explicit activation in Setup before they appear.

Release Dates & Rollout Timeline

The Spring '26 release follows Salesforce's standard phased rollout. Here's the definitive timeline every developer should have bookmarked:

Date

Milestone

Details

Dec 18, 2025Pre-Release Dev EditionSign up for standalone Developer Edition with all Spring '26 features.
Jan 9–10, 2026Sandbox Preview BeginsEarly access in sandboxes for safe exploration and validation.
Jan 16, 2026Production Wave 1Initial production instances upgraded. Check Salesforce Trust for your org.
Feb 13, 2026Production Wave 2Majority of production orgs upgraded in this wave.
Feb 20, 2026Production Wave 3 (Final)All remaining production environments upgraded. Spring '26 fully live globally.

Important: Several Spring '26 changes are enforced, not opt-in. Review the security migration section carefully. The Connected Apps retirement and Triple DES deprecation have hard deadlines with real consequences.

Mandatory Changes: Action Required Now

Session IDs in outbound messages removed Feb 16, 2026. New Connected Apps are disabled by default. Triple DES SAML retires Summer '26. Full details in Section 9.

Lightning Web Components (LWC) Updates in Spring’26

Spring '26 delivers a strong batch of LWC improvements, a new dynamic event listener directive, complete GraphQL CRUD support, a completed TypeScript rollout, a cleaner way to launch flows, and a long-overdue developer debugging tool

1. Dynamic Event Listeners.

Before Spring '26, every event handler had to be declared statically in your HTML template. If you wanted to swap behavior at runtime you needed messy conditional rendering or manual addEventListener calls.

The new lwc:on directive solves this cleanly. You pass a plain JavaScript object where each key is an event name and each value is the handler function. LWC automatically rewires the listeners whenever the object reference changes, no template edits required.

  • How it works: Object keys are event names (click, mouseenter…). Values are the functions that run when that event fires.
  • The benefit: Swap an entire set of event behaviors at runtime from JavaScript alone, without ever touching the HTML template.
dynamicEventListener.html
<template>
<div class="slds-card slds-p-around_medium">

<!--
lwc:on accepts a plain JS object.
Keys = event names, values = handler functions.
LWC rewires listeners when the object reference changes.
-->
<div class="interactive-box" lwc:on={boxEventHandlers}>
<span>{message}</span>
<span>{hint}</span>
</div>

<lightning-radio-group
name="mode"
label="Interaction Mode"
options={modeOptions}
value={currentMode}
type="button"
onchange={handleModeChange}>
</lightning-radio-group>

</div>
</template>
dynamicEventListener.js
import { LightningElement, track } from 'lwc';

export default class DynamicEventListener extends LightningElement {
@track currentMode = 'click';
@track message = 'Interact with the box!';

modeOptions = [
{ label: 'Click Mode', value: 'click' },
{ label: 'Hover Mode', value: 'hover' },
];

// Returning a different object causes LWC to swap event listeners.
get boxEventHandlers() {
switch (this.currentMode) {
case 'click':
return { click: this.handleClick };
case 'hover':
return {
mouseenter: this.handleMouseEnter,
mouseleave: this.handleMouseLeave,
};
default:
return {};
}
}

get hint() {
return this.currentMode === 'click' ? '👆 Click here' : '🖐️ Hover here';
}

handleClick = () => { this.message = '🖱️ You clicked!'; };
handleMouseEnter = () => { this.message = '✨ Mouse entered!'; };
handleMouseLeave = () => { this.message = '👋 Mouse left!'; };

handleModeChange(event) {
this.currentMode = event.detail.value;
this.message = 'Switched to ' + this.currentMode + ' mode';
}
}

Key insight

The getter returns a completely new object whenever currentMode changes. LWC's reactivity system detects the new reference and seamlessly updates which events the element listens to, no removeEventListener calls, no if-blocks in the template. Source: LWC Recipes PR #1081.

2. GraphQL Mutations in LWC

Spring '26 completes GraphQL CRUD in LWC. The existing @wire(graphql) adapter handles reactive reads. The new executeMutation method provides an imperative API for creating, updating, and deleting data; use it anywhere you need to write data from JavaScript.

Define your mutation once using the gql template literal, then call executeMutation with the query and variables. The response returns exactly the fields you requested.

graphqlMutationCreate.js
import { LightningElement } from 'lwc';
import { gql, executeMutation } from 'lightning/graphql';

// Define the mutation once at module level
const CREATE_ACCOUNT = gql`
mutation CreateAccount($input: AccountCreateInput!) {
uiapi {
AccountCreate(input: $input) {
Record {
Id
Name { value }
Type { value }
}
}
}
}
`;

export default class GraphqlMutationCreate extends LightningElement {
isLoading = false;
createdId = '';
errorMsg = '';

async createAccount(name, type) {
this.isLoading = true;
try {
// Note: returns { data, errors }, errors is an ARRAY
const { data, errors } = await executeMutation(this, {
query: CREATE_ACCOUNT,
variables: {
input: {
Name: { value: name },
Type: { value: type },
}
},
operationName: 'CreateAccount',
});
if (errors?.length) {
this.errorMsg = errors.map(e => e.message).join(', ');
return;
}
this.createdId = data?.uiapi?.AccountCreate?.Record?.Id;
} finally {
this.isLoading = false;
}
}
}
graphqlMutationUpdate.js
import { gql, executeMutation } from 'lightning/graphql';

const UPDATE_CONTACT = gql`
mutation UpdateContact($id: ID!, $input: ContactUpdateInput!) {
uiapi {
ContactUpdate(id: $id, input: $input) {
Record { Id FirstName { value } LastName { value } Email { value } }
}
}
}
`;

async updateContact(recordId, firstName, lastName, email) {
const { data, errors } = await executeMutation(this, {
query: UPDATE_CONTACT,
variables: {
id: recordId,
input: {
FirstName: { value: firstName },
LastName: { value: lastName },
Email: { value: email },
}
},
operationName: 'UpdateContact',
});
if (!errors?.length) {
console.log('Updated:', data?.uiapi?.ContactUpdate?.Record?.Id);
}
}
graphqlMutationDelete.js
import { LightningElement, wire } from 'lwc';
import { gql, executeMutation, refreshGraphQL } from 'lightning/graphql';

const DELETE_CONTACT = gql`
mutation DeleteContact($id: ID!) {
uiapi {
ContactDelete(id: $id) { Record { Id } }
}
}
`;

async deleteContact(recordId) {
const { errors } = await executeMutation(this, {
query: DELETE_CONTACT,
variables: { id: recordId },
operationName: 'DeleteContact',
});
if (!errors?.length) {
// Refresh the @wire read query to reflect the deletion
await refreshGraphQL(this.contactsWireResult);
}
}

errors (array) - not error (singular)

executeMutation returns { data, errors } where errors is an array. Always check errors?.length. Child relationship fields are not supported in mutation response queries. See LWC Recipes PR #1083 for complete implementation with tests.

3. TypeScript Support for Base Components

The TypeScript rollout for Lightning base components is now complete. Install @salesforce/lightning-types to get IntelliSense, autocomplete, and compile-time error detection for every base component. Catch bugs at edit time, not after deployment.

Setup in 4 steps:

  • Step 1: Install: npm install --save-dev typescript @salesforce/lightning-types
  • Step 2: Enable: Add "salesforcedx-vscode-lwc.typescript": true to .vscode/settings.json
  • Step 3: Compile: npx tsc --project ./force-app/main/default/lwc
  • Step 4: Deploy: sf project deploy start --source-dir force-app
typeSafeForm.ts
import { LightningElement } from 'lwc';
import type {
LightningInput,
LightningButton,
LightningCombobox,
} from '@salesforce/lightning-types';

export default class TypeSafeForm extends LightningElement {
firstName: string = '';
lastName: string = '';
status: string = '';

// Cast event.target for full IntelliSense on .value, .validity, .label
handleFirstNameChange(event: Event): void {
const input = event.target as LightningInput;
if (input.validity.valid) {
this.firstName = input.value;
}
}

handleLastNameChange(event: Event): void {
const input = event.target as LightningInput;
this.lastName = input.value;
}

handleSubmit(event: Event): void {
const btn = event.target as LightningButton;
console.log('Button label:', btn.label); // fully typed, no 'any'
}

handleStatusChange(event: Event): void {
const combo = event.target as LightningCombobox;
this.status = combo.value;
}
}

Pro tip

The platform stores only JavaScript .ts files that are compiled locally, and the .js output is deployed. Never ship uncompiled TypeScript to a package. If you have custom type stubs for base components, switch to @salesforce/lightning-types for better coverage.

4. Navigate to Flows from LWC

Launching a flow from a component now takes one navigate call. Use the standard__flow PageReference type with the flow's API name, and pass input data through the state object, it maps directly to the flow's input variables.

navToFlow.js, LWC Recipes PR #1082
import { LightningElement, api } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';

export default class NavToFlow extends NavigationMixin(LightningElement) {
@api recordId;
@api flowApiName = 'Contact_Onboarding_Flow';

handleLaunchFlow() {
this[NavigationMixin.Navigate]({
type: 'standard__flow',
attributes: {
flowApiName: this.flowApiName,
},
state: {
// state keys prefixed with flow__ become flow input variables
'flow__contactId': this.recordId,
'flow__source': 'LWC',
}
});
}
}

5. Error Console

Tired of red-box popups interrupting your testing? The Error Console silently logs non-fatal errors to a dedicated panel you can review whenever you want. Fatal errors that do require a popup are also captured here for a full debug history.

Enable it at: Setup > User Interface > Advanced Settings > 'Use Error Console for error reporting in Lightning Experience'.

image.png

Error Console, non-fatal errors logged silently, fatal errors captured for full history | Source: Salesforce Developer Blog

6. Complex Template Expressions

Write JavaScript expressions directly in LWC templates, template literals, ternary operators, optional chaining, null coalescing, array methods, and inline event handlers. No more boilerplate getter methods for simple display logic.

Before Spring '26
<!-- BEFORE, getter methods required for all display logic -->

// contactCard.js
get fullName() { return `${this.firstName} ${this.lastName}`; }
get statusLabel() { return this.isActive ? 'Active' : 'Inactive'; }
get stockCount() { return this.items.filter(i => i.inStock).length; }

// contactCard.html
<p>{fullName}</p>
<span>{statusLabel}</span>
<p>{stockCount} in stock</p>
After, Spring '26 Complex Template Expressions
<!-- AFTER, inline expressions, no getters needed -->

<!-- Template literal -->
<p>{`Hello, ${firstName} ${lastName}!`}</p>

<!-- Ternary -->
<span>{isActive ? 'Active' : 'Inactive'}</span>

<!-- Array method -->
<p>{items.filter(item => item.inStock).length} in stock</p>

<!-- Optional chaining + null coalescing -->
<p>Theme: {user?.profile?.theme ?? 'default'}</p>

<!-- Inline handler -->
<lightning-button label="Add" onclick={()=>{ quantity++ }}></lightning-button>

Beta: use in sandbox first

Complex Template Expressions are subject to Salesforce Beta Services Terms. The feature direction is committed but syntax may change before GA. Test in sandbox and developer orgs before any production rollout.

7. Lightning Empty State & Illustration Component

Two new base components deliver SLDS-compliant empty states with zero custom CSS. Both automatically adapt to dark mode themes. Drop them in and they look polished immediately.

<!-- Full empty state with illustration, title, description, and CTA -->
<lightning-empty-state
illustration-name="cart:noitems"
title="No Records Found">

<p slot="description">
No contacts match your current filters.
</p>

<lightning-button
slot="actions"
label="Create New Contact"
onclick={handleCreate}>
</lightning-button>

</lightning-empty-state>

<!-- Illustration only, no surrounding text needed -->
<lightning-illustration illustration-name="access:request" size="large">
</lightning-illustration>

<!-- Available names: cart:noitems, access:request, empty:desert,
error:pageNotFound, and more from the SLDS illustration library -->

image.png

lightning-empty-state, SLDS illustration + title + description + CTA, auto dark-mode | Source: Salesforce Developer Blog

Apex Updates in Spring '26

Apex gets three meaningful improvements this release: Cursors move to GA for large-dataset processing, RunRelevantTests speeds up CI/CD deployments, and a new ConnectApi method eliminates callouts for picklist data.

1. Apex Cursors: Now Generally Available

Apex Cursors let you work with large SOQL result sets in manageable chunks without the rigidity of Batch Apex. Create a cursor once, the result set is cached server-side, then fetch records from any position, forward or backward. Ideal for processing millions of records without re-running the query.

Basic usage

Database.Cursor locator = Database.getCursor(
'SELECT Id, Name, Status__c FROM Case WHERE Status__c = \'Open\' ORDER BY CreatedDate'
);

Integer total = locator.getNumRecords();

// Fetch any chunk from any offset, bidirectional
List<Case> first = locator.fetch(0, 200);
List<Case> second = locator.fetch(200, 200);
List<Case> last = locator.fetch(total - 50, 50);

// Track governor limit usage
System.debug('Cursor rows used: ' + Limits.getApexCursorRows());

Pattern 1: Queueable chaining for millions of records

Cursors are serialisable, they survive intact between Queueable job executions, so you can chain through arbitrarily large datasets without ever re-running the query.

QueryChunkingQueueable.cls
public class QueryChunkingQueueable implements Queueable {

private Database.Cursor locator;
private Integer position;

// Entry point, create the cursor once, it is cached server-side
public QueryChunkingQueueable() {
this.locator = Database.getCursor(
'SELECT Id FROM Contact WHERE LastActivityDate = LAST_N_DAYS:400'
);
this.position = 0;
}

public void execute(QueueableContext ctx) {
// Chunk size is flexible, Batch Apex fixes it at job start
List<Contact> scope = locator.fetch(position, 200);
position += scope.size();

List<Contact> toUpdate = new List<Contact>();
for (Contact c : scope) {
c.Description = 'Archived: ' + Date.today();
toUpdate.add(c);
}
if (!toUpdate.isEmpty()) update toUpdate;

// Re-enqueue while records remain, chains automatically
if (position < locator.getNumRecords()) {
System.enqueueJob(this);
}
}

public static void start() {
System.enqueueJob(new QueryChunkingQueueable());
}
}

Pattern 2: PaginationCursor for LWC page navigation

PaginationCursor is a variant built for user-facing lists. It is @AuraEnabled-serialisable, pass it between your LWC and Apex for smooth server-side paging with no OFFSET queries.

PaginationController.cls
public with sharing class PaginationController {

public class PageResult {
@AuraEnabled public List<Contact> records;
@AuraEnabled public Database.PaginationCursor cursor;
@AuraEnabled public Integer total;
@AuraEnabled public Boolean hasMore;
}

@AuraEnabled(cacheable=false)
public static PageResult getFirstPage(Integer pageSize) {
Database.PaginationCursor cursor = Database.getPaginationCursor(
'SELECT Id, FirstName, LastName, Email FROM Contact ORDER BY LastName'
);
return build(cursor, pageSize);
}

// Reuse the cursor, no new SOQL issued on the server
@AuraEnabled(cacheable=false)
public static PageResult getNextPage(Database.PaginationCursor cursor, Integer pageSize) {
return build(cursor, pageSize);
}

private static PageResult build(Database.PaginationCursor cursor, Integer pageSize) {
PageResult r = new PageResult();
r.records = cursor.fetchPage(pageSize);
r.cursor = cursor;
r.total = cursor.getNumRecords();
r.hasMore = cursor.getNumRecords() > 0;
return r;
}
}
paginatedList.js
import { LightningElement, track } from 'lwc';
import getFirstPage from '@salesforce/apex/PaginationController.getFirstPage';
import getNextPage from '@salesforce/apex/PaginationController.getNextPage';

const PAGE_SIZE = 20;

export default class PaginatedList extends LightningElement {
@track records = [];
@track hasMore = false;
cursor = null;
total = 0;

async connectedCallback() {
const r = await getFirstPage({ pageSize: PAGE_SIZE });
this.records = r.records;
this.cursor = r.cursor; // serialised cursor returned to Apex on next call
this.total = r.total;
this.hasMore = r.hasMore;
}

async loadNextPage() {
if (!this.hasMore) return;
const r = await getNextPage({ cursor: this.cursor, pageSize: PAGE_SIZE });
this.records = [...this.records, ...r.records];
this.cursor = r.cursor;
this.hasMore = r.hasMore;
}
}

Cursor vs Batch Apex vs PaginationCursor

Dimension

Batch Apex

Cursor + Queueable

PaginationCursor

Max records50M50M per cursor100K per cursor
Query re-executionYes (per chunk)No, server-cachedNo, server-cached
DirectionForward onlyBidirectionalBidirectional
Chunk sizeFixed at job startFlexible per fetch()Fixed per fetchPage()
LWC paginationComplex (polling)@AuraEnabled serialisable@AuraEnabled serialisable
Best forScheduled DML-heavy jobsAsync large-volume processingUser-facing list pagination

Cursor governor limits

Cursors help you use limits smarter, they don't bypass them. Each fetch() counts against the SOQL row limit. Max: 50M records/cursor, 10K cursor instances/24hrs, 200K PaginationCursor instances/24hrs.

2. RunRelevantTests: Faster Deployments

A new deployment test level that analyses the dependency graph of your payload and runs only the tests relevant to the changed code. For large organizations with thousands of test methods, this can dramatically reduce CI/CD deployment time.

# Use RunRelevantTests, dependency analysis is automatic
sf project deploy start --test-level RunRelevantTests

# Scoped to a specific file
sf project deploy start \
--source-dir force-app/main/default/classes/OrderService.cls \
--test-level RunRelevantTests
// @IsTest(testFor), link a test to specific components.
// Only runs when OrderService or OrderTrigger is in the deployment.
@IsTest(testFor='ApexClass:OrderService,ApexTrigger:OrderTrigger')
public class OrderServiceTest {
@isTest static void testOrderCreation() { /* ... */ }
}

// @IsTest(critical=true), this test ALWAYS runs regardless of payload.
// Use for smoke tests, compliance checks, cross-system integration tests.
@IsTest(critical=true)
public class CriticalSmokeTest {
@isTest static void runAlways() { /* ... */ }
}

3. Picklist Values by Record Type: New Apex Method

Retrieving record-type-specific picklist values in Apex used to require UI API callouts. The new ConnectApi.RecordUi.getPicklistValuesByRecordType() method eliminates that, one native Apex call returns all picklist fields for a given record type, including dependent relationships and default values.

@AuraEnabled(cacheable=true)
public static Map<String, Object> getPicklistValues(
String objectApiName,
String recordTypeId
) {
ConnectApi.PicklistValuesCollection col =
ConnectApi.RecordUi.getPicklistValuesByRecordType(
objectApiName,
recordTypeId
);

Map<String, Object> result = new Map<String, Object>();
for (String field : col.picklistFieldValues.keySet()) {
ConnectApi.PicklistValuesRepresentation pvr = col.picklistFieldValues.get(field);
List<String> vals = new List<String>();
for (ConnectApi.PicklistValueRepresentation pv : pvr.values) {
vals.add(pv.value);
}
result.put(field, vals);
}
return result;
}

4. Blob.toPdf(): New Rendering Engine

Blob.toPdf() now uses the same rendering engine as Visualforce PDFs, consistent output, expanded font support, and full CJK (Chinese, Japanese, Korean), Thai, and Arabic character rendering. The API is unchanged; the results are better.

public class CertificateGenerator {
public static Blob generate(String recipient, String course, Date completed) {
String html = '<!DOCTYPE html><html><head>'
+ '<meta charset="UTF-8">'
+ '<style>'
+ 'body { font-family: Arial, sans-serif; margin: 60px; }'
// Backward compat: if your PDFs use old serif font, add:
// font-family: serif;
+ '.cert { padding: 60px; border: 3px solid #032D60; }'
+ 'h1 { color: #032D60; text-align: center; }'
+ '.name { color: #00A1E0; font-size: 28px; text-align: center; }'
+ '</style></head><body><div class="cert">'
+ '<h1>Certificate of Completion</h1>'
+ '<p class="name">' + recipient + '</p>'
+ '<p>Completed: ' + course + '</p>'
// Spring '26 engine renders CJK / Arabic / Thai natively
+ '<p>完了 · مكتمل · เสร็จ</p>'
+ '</div></body></html>';

return Blob.toPdf(html); // same API, upgraded engine under the hood
}
}

Check existing PDFs before Summer '26

If your PDFs rely on the old serif default font, line breaks and spacing may change. Add font-family: serif to maintain backward compatibility. Review via Setup > Release Updates and test in sandbox before the Summer '26 enforcement date.

Agentforce Developer Features

Spring '26 brings the most significant Agentforce developer tooling update to date, a rebuilt Builder in–terface, a new DSL for authoring agent logic, a spreadsheet-style testing environment, and pro-code VS Code tooling. Updates ship monthly within the release window, so watch the release notes.

1. New Agentforce Builder: Canvas & Script Views

The new Agentforce Builder replaces the previous interface with a text editor-inspired IDE, complete with a file explorer, two distinct working modes, and an upgraded graph-based Atlas Reasoning Engine that lets you mix deterministic logic with natural language prompts.

  • Canvas view: Drag-and-drop visual editor for assembling if-then logic flows, action sequences, and topic transitions.
  • Script view: Author Agent Script DSL directly in a text editor, version-controllable and diffable.
  • Deterministic logic: Chain actions in a guaranteed sequence. Control topic transitions precisely, without relying solely on LLM judgment.
  • Deep debug: Trace + reasoning summary for every agent message. See exactly why the agent took each action.

image.png

Agentforce Builder: Canvas mode with visual logic flow and Atlas Reasoning Engine | Salesforce Developer Blog

2. Agent Script DSL

Agent Script is a new DSL that combines natural language instructions with deterministic code-like constructs, if-else conditions, variable bindings, and action sequencing. You curate exactly what instructions and state variables the LLM receives, giving it a high-signal prompt and predictable behavior.

escalation.agent-script
# Agent Script, Escalation Handler
# Mixes natural language with deterministic conditionals.
# LLM handles conversation; Agent Script enforces business rules.

agent: EscalationAgent
description: Handles customer service escalation requests

topics:
- name: HandleEscalation
instructions: |
Greet the customer and acknowledge their frustration.
Use calm, empathetic language throughout.
Never promise refunds, only promise follow-up.

# These conditionals always execute, not left to LLM judgment
conditions:
- if: $context.caseType == 'Billing'
then:
action: TransferToBillingTeam
message: 'Connecting you to our billing specialists now.'

- if: $context.priority == 'Critical'
then:
action: EscalateToManager
variables:
urgencyLevel: 'HIGH'

- else:
action: CreateCaseAndFollowUp
instructions: |
Summarise the issue clearly for the case notes.
Set expectations: 24-hour follow-up timeline.

Why Agent Script?

Unlike freeform system prompts, Agent Script is version-controllable, diff-able, and reviewable in pull requests. By curating the state variables and instructions the LLM sees, you reduce ambiguity and hallucinations. Explore the Agent Script Recipes sample app at developer.salesforce.com/sample-apps/agent-script-recipes.

3. Agent Grid

Agentforce Grid is a spreadsheet-like environment for testing AI workflows against real CRM data. Every row is a CRM record, every column is an agent action, every cell shows immediate AI output. No deployment cycle needed to validate logic changes.

image.png

Agentforce Grid: rows are CRM records, columns are agent actions, cells show instant AI output | Salesforce Developer Blog

4. Agentforce DX: Pro-Code in VS Code

Agentforce DX is the pro-code equivalent of the Builder UI. Build, test, and deploy agents in VS Code and the Salesforce CLI without context switching. Retrieve an admin-built agent from Agentforce Builder, pull it locally, and refine it with complex logic.

  • Language Server: Syntax highlighting, red squiggles for errors, internal validation, and outline support for Agent Script files.
  • Simulated preview: Test agent logic with mocked actions. Fast iteration, no org resources consumed.
  • Live preview: Connect to real Apex, flows, and org data for end-to-end testing.

image.png

Agentforce DX in VS Code: Agent Script editor with language server + Agent Preview | Salesforce Developer Blog

5. Agentforce Vibes: AI Developer Tooling

Agentforce Vibes is an AI-powered developer assistant available as a VS Code extension and in the Agentforce Vibes IDE. Spring '26 delivers the improvements from versions 3.3.0 through 3.8.0, smarter planning, better context management, and a streamlined MCP experience.

1. /deep-planning, Structured Complex Workflows

Type /deep-planning in the Agentforce Vibes chat to invoke a structured four-step approach for complex tasks:

  • Scan: Agent scans your codebase for relevant context and existing patterns.
  • Clarify: Agent asks targeted questions before writing any code.
  • Plan: A comprehensive implementation plan is generated for your review.
  • Execute: Task launches with all context loaded. A persistent Focus Chain keeps the agent on target across context resets.

2. MCP Auto-Approval & Performance

  • Auto-approved: All read-only Salesforce DX MCP server tools, no more prompts for safe queries.
  • Still requires approval: All write operations, insert, update, delete, and deploy.
  • Performance: Toggling auto-approve no longer triggers server reconnections.
  • UX: MCP tab renamed from "Installed" to "Configure".

Salesforce CLI Updates

Spring '26 adds three notable CLI improvements: unified logic testing, package source retrieval, and quality-of-life improvements for login timeouts and test polling.

# Unified Logic Testing (Beta), run Apex + Flow tests in one request
sf apex run test --test-level RunAllTestsInOrg --unified-logic-testing

# Retrieve 2GP or unlocked package source metadata locally
sf package version retrieve \
--package 04tXXXXXXXXXXXXXXX \
--output-dir ./retrieved-package \
--target-dev-hub devhub@example.com

# Set custom OAuth login timeout (prevents indefinite hang)
export SF_WEB_OAUTH_SERVER_TIMEOUT=180000 # 3 minutes in milliseconds

# Adjust test result polling interval to reduce API calls
sf apex run test \
--class-names MyClassTest \
--poll-interval 10 \
--target-org my-org

Data 360 Monthly Updates

Data 360 ships on a monthly cadence. The headline addition for developers this release is new ConnectApi.CdpQuery methods for querying data graphs directly from Apex.

// Query a data graph by record ID
ConnectApi.CdpQueryResult byId =
ConnectApi.CdpQuery.getDataGraphData(
'myDataspace', // dataspace API name
'UnifiedIndividual__dlm', // data model object
'001Ws000001234' // record ID
);

// Query by lookup key (e.g. an email address)
ConnectApi.CdpQueryResult byKey =
ConnectApi.CdpQuery.getDataGraphData(
'myDataspace',
'UnifiedIndividual__dlm',
'email',
'user@example.com'
);

// Get data graph metadata and schema
ConnectApi.DataGraphMetadata meta =
ConnectApi.CdpQuery.getDataGraphMetadata(
'myDataspace', 'UnifiedIndividual__dlm'
);

API Updates

1. Named Query API: Generally Available

Define and expose custom SOQL queries as versioned, reusable REST endpoints. Named Query APIs are faster and more efficient than equivalent Flow or Apex processes, once defined, they are callable as standard REST resources.

# Endpoint auto-generated from a Named Query API named "RecentOpenCases"
GET /services/data/v66.0/query/named/RecentOpenCases?flow__accountId=001Ws000001234

# Response
{
"totalSize": 3,
"done": true,
"records": [
{ "Id": "5001234", "CaseNumber": "00001001", "Status": "Open" },
{ "Id": "5001235", "CaseNumber": "00001002", "Status": "Open" },
{ "Id": "5001236", "CaseNumber": "00001003", "Status": "Open" }
]
}

2. Session IDs in Outbound Messages: Removed

Session IDs can no longer be sent in outbound messages as of February 16, 2026. Audit Setup > Integrations > Outbound Messages. Update all outbound message subscribers to authenticate using OAuth instead of reading a session ID from the message payload.

Security: Three Mandatory Changes

Spring '26 brings three mandatory security changes. These are not optional improvements. Every Salesforce development team needs to review and act on each one.

1. External Client Apps Replace Connected Apps

Creating new Connected Apps is disabled by default for all orgs. Existing Connected Apps continue to work normally, nothing breaks immediately. But all new integrations must now use External Client Apps (ECA).

DimensionConnected Apps (Legacy)External Client Apps (New)
Create newDisabled by defaultFully supported
Existing appsContinue workingMigration target, plan your timeline
2GP packagingLimited supportFull support
Migration pathApp Manager > Migrate to ECAOriginal becomes read-only post-migration

2. Triple DES SAML Retirement

SAML SSO using Triple DES will stop functioning entirely in Summer '26. Find affected configs at Setup > Identity > Single Sign-On Settings,look for 'Triple DES' in the algorithm column. Migrate to AES-128 or AES-256 now. User lockouts at the deadline require emergency Salesforce Support.

3. Session ID in Outbound Messages: Already Removed

If you have any outbound messages still attempting to send session IDs, they are already failing. Audit and update to OAuth authentication immediately. See Section 8.2 for details.

Quick Reference, All Spring '26 Features

Feature

Area

Status

Action Required

lwc:on, dynamic event listeners

LWC

GA · API v66

Available now, use freely

GraphQL executeMutation

LWC

GA · API v66

Upgrade to API v66 in sf-project.json

@salesforce/lightning-types TypeScript

LWC

Dev Beta

npm install --save-dev @salesforce/lightning-types

Complex Template Expressions

LWC

Beta

Sandbox first, enable Beta in Setup

standard__flow PageReference

LWC

GA

Available now

Error Console

LWC

GA

Setup > User Interface > Advanced Settings

Apex Cursors + PaginationCursor

Apex

GA · API v66

Available now, no config needed

RunRelevantTests

Apex / Deploy

Beta

--test-level RunRelevantTests in CLI

Picklist values by record type

Apex

GA

ConnectApi.RecordUi.getPicklistValuesByRecordType()

Blob.toPdf() new engine

Apex

GA (Summer '26 enforced)

Test in sandbox, check font rendering

Agentforce Builder (Canvas + Script)

Agentforce

Beta

Enable Beta in Setup

Agent Script DSL

Agentforce

Beta

Install Agent Script VS Code extension

Agent Grid

Agentforce

Beta

Available in Agentforce app

Named Query API

API

GA

Setup > Integrations > Named Query API

External Client Apps

Security

MANDATORY

Use for all new integrations now

Triple DES SAML retirement

Security

Deadline Summer '26

Migrate to AES-128 or AES-256 now

Session IDs in outbound messages

Security

REMOVED Feb 16

Switch to OAuth immediately

Need expert help with the Spring '26 release?

DianApps certified Salesforce developers specialise in LWC modernisation, TypeScript migration, Agentforce implementation, and mandatory security migrations, so your team can move fast with confidence.

Spring '26 Salesforce FAQs

When did Spring '26 go live to production?

Spring '26 rolled out in three waves: January 16, February 13, and February 20, 2026. Sandbox preview started January 9–10, while a pre-release Developer Edition was available from December 18, 2025. For exact instance timing, check status.salesforce.com.

What API version does Spring '26 introduce?

Spring '26 introduces API version 66.0. Key features requiring v66+ include Apex Cursors (GA), GraphQL executeMutation, the lwc:on directive, and RunRelevantTests. Ensure your sf-project.json is updated to "sourceApiVersion": "66.0" and validate integration compatibility.

What is the difference between Apex Cursor and PaginationCursor?

Database.Cursor is built for async background processing and supports up to 50 million records with 10,000 instances per day—ideal for Queueable chaining. Database.PaginationCursor is optimized for user-facing pagination, handling up to 100,000 records and 200,000 instances per day, with consistent page sizes for LWC and @AuraEnabled use cases.

Do existing Connected Apps need to be migrated now?

No immediate action is required. Existing Connected Apps will continue functioning. However, new Connected Apps can no longer be created without Salesforce Support. Plan migration at your own pace via App Manager → select your app → 'Migrate to External Client App'.

What is Agent Script?

Agent Script is a new DSL (Beta) that blends natural language with structured logic like conditionals and variable bindings. Unlike traditional prompts, it is version-controlled, diff-friendly, and delivers more predictable AI agent behavior.

What is the Triple DES deadline?

The deadline is Summer '26. Any SAML SSO configuration using Triple DES will stop working completely. Failure to migrate can cause login lockouts requiring urgent Salesforce Support. Transition to AES-128 or AES-256 via Setup → Identity → Single Sign-On Settings.

Written by Harshita Sharma

A competent and enthusiastic writer, having excellent persuasive skills in the tech, marketing, and event industry. With vast knowledge about the late...

Leave a Comment

Your email address will not be published. Required fields are marked *

Comment *

Name *

Email ID *

Website