<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet title="XSL_formatting" type="text/xsl" href="https://developer.salesforce.com/blogs/wp-content/themes/dfctheme/includes/feed_styles.xsl" ?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
xmlns:podcast="https://podcastindex.org/namespace/1.0"
xmlns:rawvoice="https://blubrry.com/developer/rawvoice-rss/"
xmlns:media="http://search.yahoo.com/mrss/"
	xmlns:dscblog="https://developer.salesforce.com/blog/dscblog/"
>


<channel>
	<title>Salesforce Developers Blog</title>
	<atom:link href="https://developer.salesforce.com/blogs/feed" rel="self" type="application/rss+xml" />
	<link>https://developer.salesforce.com/blogs</link>
	<description>Elevating developer skills and connecting with the Salesforce Developers community</description>
	<lastBuildDate>Thu, 18 Jun 2026 17:06:54 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	
	<atom:link rel="hub" href="https://pubsubhubbub.appspot.com/" />
	<itunes:author>Salesforce Developers Blog</itunes:author>
	<itunes:explicit>false</itunes:explicit>
	<itunes:image href="https://developer.salesforce.com/blogs/wp-content/plugins/powerpress/itunes_default.jpg" />
	<itunes:owner>
		<itunes:name>Salesforce Developers Blog</itunes:name>
	</itunes:owner>
	<podcast:medium>podcast</podcast:medium>
	<image>
		<title>Salesforce Developers Blog</title>
		<url>https://developer.salesforce.com/blogs/wp-content/plugins/powerpress/rss_default.jpg</url>
		<link>https://developer.salesforce.com/blogs</link>
	</image>
	<podcast:podping usesPodping="true" />
<site xmlns="com-wordpress:feed-additions:1">244780846</site>	<item>
		<title>The Big Objects Playbook: Payload Capture and Replay</title>
		<link>https://developer.salesforce.com/blogs/2026/06/big-objects-playbook-payload-capture-and-replay</link>
		<comments>https://developer.salesforce.com/blogs/2026/06/big-objects-playbook-payload-capture-and-replay#respond</comments>
		<pubDate>Thu, 18 Jun 2026 15:00:28 +0000</pubDate>
		<dc:creator><![CDATA[Laxman Vattam]]></dc:creator>
				<category><![CDATA[Apex]]></category>
		<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Big Objects]]></category>
		<category><![CDATA[Composite Primary Index]]></category>
		<category><![CDATA[Data Cloud]]></category>
		<category><![CDATA[Database.insertImmediate]]></category>
		<category><![CDATA[Payload Capture]]></category>
		<category><![CDATA[Platform Events]]></category>
		<category><![CDATA[scheduled apex]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206431</guid>
		<description><![CDATA[<p>Salesforce Big Objects explained: when to use them, how to design composite indexes, and how to build a payload capture and replay system for high-volume integrations.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/big-objects-playbook-payload-capture-and-replay">The Big Objects Playbook: Payload Capture and Replay</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><span style="font-weight: 400">You’ve likely hit this performance wall before or know someone who has. Standard object storage starts creeping toward its limits. Queries slow down. Compliance asks for seven years of audit history. An Internet of Things (IoT) integration wants to push ten thousand events a minute. The platform wasn’t designed to take that load on transactional objects, and you start wondering if you’re supposed to move everything off-platform just to keep up.</span></p>
<p><span style="font-weight: 400">Well, you don’t have to. </span><a href="https://developer.salesforce.com/docs/atlas.en-us.bigobjects.meta/bigobjects/"><span style="font-weight: 400">Big Objects</span></a><span style="font-weight: 400"> were built for exactly this scenario. In this post, we’ll look at what makes them tick and explore how to avoid the design traps that bite most developers. </span></p>
<h2><span style="font-weight: 400">What are big objects?</span></h2>
<p>A big object is a specialized Salesforce object designed to store hundreds of millions, or even billions of records, without degrading query performance. Unlike custom objects, they don’t count against your org’s <a href="https://help.salesforce.com/s/articleView?id=xcloud.overview_storage.htm&amp;type=5"><u>standard storage limits</u></a>. They end in <code>__b</code> instead of <code>__c</code>, and that one character signals a fundamentally different contract with the Salesforce Platform.</p>
<p><span style="font-weight: 400">With the growing conversation around Data 360, some developers assume that the Big Objects feature is on its way out. That is not the case. Big Objects continues to be the right tool for high-volume, append-heavy, write-once-query-later workloads, and it is still the only native tool that can handle that level of scale.</span></p>
<p>There are two types of big objects:</p>
<ul>
<li><b>Standard Big Objects:</b> These are predefined by Salesforce and cannot be customized. An example is <code>FieldHistoryArchive</code> used by Field Audit Trail.</li>
<li><b>Custom Big Objects:</b> These are defined by developers through the Setup UI or the <a href="https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/metadata.htm"><u>Metadata API</u></a>. That’s where most of the interesting work happens.</li>
</ul>
<p><span style="font-weight: 400">Adopting big objects means accepting strict trade-offs. For example, you lose platform automation (no triggers or flows) and out-of-the-box UI (no standard reports, list views, or sharing rules), and frontends require custom Lightning web components (LWC). Big objects are not transactional database tables, instead they are a durable, queryable ledger. Once you adopt that framing, they become an incredibly powerful tool.</span></p>
<p><span style="font-weight: 400">Big Objects can be used to implement advanced architectural patterns. Let’s take a look at a pattern that captures and logs your org&#8217;s platform events using a big object, for improved traceability and error handling.</span></p>
<h2><span style="font-weight: 400">Architectural pattern with big objects: Payload capture and replay</span></h2>
<p><span style="font-weight: 400">Modern Salesforce orgs act as integration hubs. Platform events stream in from Enterprise Resource Planning (ERP) systems. Outbound callouts push data to marketing platforms, and webhooks arrive from payment gateways. Platform events are retained only briefly — up to 72 hours for high-volume platform events. When things fail, payloads can disappear silently as the platform event bus moves on. The data is effectively gone.</span></p>
<p><span style="font-weight: 400">In this pattern, you write a copy of every platform event that your org publishes to a big object </span><i><span style="font-weight: 400">before</span></i><span style="font-weight: 400"> any business logic runs, so the big object becomes a durable ledger of every event that has crossed the platform event bus. A platform event Apex trigger subscribes to the event (or a platform event–triggered flow) and persists it to the big object. There&#8217;s no extra publishing step, you simply log the event that you already received. After an event is captured and processed, if anything fails, the error details are written to a lightweight custom object. This improves error visibility and lets failures be replayed automatically.</span></p>
<p>The flowchart below shows integration traffic captured to the <code>IntegrationPayloadLog__b</code> big object before processing, failures logged to the <code>IntegrationError__c</code> custom object, and a daily batch job replaying failed payloads. The two objects are linked by <code>EventUuid__c</code>.</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206553" >
			    <img fetchpriority="high" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260617120126/Flowchart-showing-integration-traffic-e1781722900206.png?w=1000" class="postimages" width="1000" height="768" alt="Flowchart showing integration traffic." />
			  </span>
			</p>
<p><span style="font-weight: 400">Let’s take a look at how to build this flow in six steps: define each of the two objects, capture every payload, log failures, automate retries, and add visibility.</span></p>
<h3><span style="font-weight: 400">Step 1: Define the Integration Payload Log big object</span></h3>
<p>A robust payload log requires a well-structured big object schema. For instance, <b>a big object</b> like <span>IntegrationPayloadLog__b</span><span> can capture essential data. Key fields include:</span></p>
<ul>
<li><b><code><span>IntegrationName__c</span></code><span>:</span></b><span> </span><span>A label for the integration or channel (for example, ERP_Inbound) and part of the composite index</span></li>
<li><b><code><span>TransactionId__c</span></code><span>:</span></b><span> </span><span>The source system&#8217;s correlation or transaction ID, when one is available</span></li>
<li><b><code><span>EventUuid__c</span></code><span>:</span></b><span> </span><span>The platform event&#8217;s globally unique ID, linking the record back to the original event and leading the composite index</span></li>
<li><b><code><span>Timestamp__c</span></code><span>:</span></b><span> </span><span>When the payload was captured</span></li>
<li><b><code><span>Direction__c</span></code><span>:</span></b><span> </span><span>Whether the payload was Inbound or Outbound</span></li>
<li><b><code><span>PayloadBody__c</span></code><span>:</span></b><span> </span><span>The raw request or response body, stored as a Long Text Area</span></li>
</ul>
<p>Because this pattern logs platform event traffic, we store the platform event&#8217;s <code>EventUuid__c</code>. <code>EventUuid__c</code> is a system-generated, globally unique identifier that Salesforce assigns to each platform event upon publishing. Storing it on your big object gives you an unbreakable link between the durable payload record and the original event on the platform event bus.</p>
<p><span style="font-weight: 400">Something key in this architectural pattern is to design your </span><a href="https://help.salesforce.com/s/articleView?id=platform.custom_bo_index.htm&amp;language=en_US&amp;type=5"><span style="font-weight: 400">composite primary index</span></a><span style="font-weight: 400"> right. The composite primary index is the only entry point into your big object. Queries must filter on indexed fields as full table scans don’t work at this scale. The mistake that most developers make is designing the index around what the data looks like. Instead, design it around the question you’ll ask most often. </span></p>
<p>For instance, if you want to build a system in which the core question is, &#8220;Give me the exact payloads for these specific failed events,&#8221; then the composite index should become <code>EventUuid__c</code><code> → </code><code>IntegrationName__c</code>. Set the most highly selective identifier first (the UUID), followed by the integration name. This order can map directly to the <code>WHERE</code> clause in SOQL queries to retrieve specific records.</p>
<p><span style="font-weight: 400">Designing the composite primary index is the most important design decision that you’ll make, and unlike almost everything else on the platform, you can’t change it after deployment.</span></p>
<h3><span style="font-weight: 400">Step 2: Define the Integration Error custom object</span></h3>
<p>Next, you define a lightweight custom object — for example, <code>IntegrationError__c</code> — specifically for capturing platform event processing failures. To ensure that your support team has exactly what they need to troubleshoot and retry the payload, we recommend creating the following fields on this custom object:</p>
<ul>
<li><b><code>IntegrationName__c</code></b><b>:</b> Name of the integration</li>
<li><b><code>EventUuid__c</code></b><b>:</b> The shared ID linking directly to the <code>EventUuid__c</code> on the big object record</li>
<li><b><code>Exception_Type__c</code></b><b>:</b> The specific class of error (e.g., <code>CalloutException</code>, <code>DmlException</code>)</li>
<li><b><code>Error_Message__c</code></b><b>:</b> The detailed system error message</li>
<li><b><code>Stack_Trace__c</code></b><b>:</b> The developer stack trace for deep debugging</li>
<li><b><code>Failed_Record_IDs__c</code></b><b>:</b> A text field capturing the specific Salesforce record IDs that failed during processing</li>
<li><b><code>Status__c</code></b><b>:</b> A picklist (e.g., Open, Retrying, Resolved) to manage the recovery workflow</li>
<li><b><code>RetryCount__c</code></b><b>:</b> Number of times the error is reprocessed</li>
</ul>
<p>At first glance, creating two separate objects might seem redundant. Why not just track errors in the big object? The answer comes down to the fundamental differences between big objects and standard custom objects. Think of <code>IntegrationPayloadLog__b</code> as your <b>Universal Ledger</b> (the filing cabinet that permanently stores everything) and <code>IntegrationError__c</code> as your <b>Action Queue</b> (the daily to-do list for your support team).</p>
<p><span style="font-weight: 400">Here is the breakdown of when to use what:</span></p>
<ul>
<li><b><span>IntegrationPayloadLog__b</span><span> (The Ledger):</span></b><span> </span>
<ul>
<li><b>What it stores:</b> <i>Every</i> payload, regardless of success or failure. It holds the heavy, bulky JSON/XML data.</li>
<li><b>Volume:</b> Millions to billions of records.</li>
<li><b>Platform features:</b> No standard UI, no list views, no declarative automation (flows/triggers). It is strictly for massive-scale storage and code-based retrieval.</li>
</ul>
</li>
<li><b><span>IntegrationError__c</span><span> (The Action Queue):</span></b><span> </span>
<ul>
<li><b>What it stores:</b> <i>Only</i> the failures. It is extremely lightweight and does not store the heavy payload, it only stores error metadata and a pointer (<code>EventUuid__c</code>) back to the big object.</li>
<li><b>Volume:</b> Hundreds to thousands of records (only your active, unresolved errors).</li>
<li><b>Platform features:</b> Full Salesforce Platform power. You get standard list views, reportable error trends, and flow-driven escalations.</li>
</ul>
</li>
</ul>
<p><span style="font-weight: 400">By splitting the architecture, the custom object gives you everything that big objects lack (visibility and automation). When reprocessing is needed, your retry logic queries the custom object for open errors, uses the UUID to retrieve the massive payload from the big object, and replays it. This separation keeps your human-facing error management inside the platform’s declarative tooling while preserving Big Objects as your high-volume storage engine. </span></p>
<h3><span style="font-weight: 400">Step 3: Capture the payload (the code)</span></h3>
<p><span style="font-weight: 400">Here&#8217;s the capture step for an inbound platform event. The trigger writes each event to the big object before any downstream logic runs.</span></p>
<pre language="apex">trigger OrderEventCaptureTrigger on Order_Event__e (after insert) {

    List logsToInsert = new List();

    // 1. Map each incoming platform event to a big object record
    for (Order_Event__e event : Trigger.new) {
        logsToInsert.add(new IntegrationPayloadLog__b(
            EventUuid__c       = event.EventUuid,    // Index field 1
            IntegrationName__c = 'ERP_Inbound',      // Index field 2
            Direction__c       = 'Inbound',
            Timestamp__c       = System.now(),
            PayloadBody__c     = event.Payload__c    // The raw JSON/XML message
        ));
    }

    // 2. Persist to the big object BEFORE any business logic runs.
    //    insertImmediate commits immediately, so the payload is safely
    //    stored even if processing later fails.
    if (!logsToInsert.isEmpty()) {
        Database.insertImmediate(logsToInsert);
    }

    // 3. Now run the business logic. The processor handles each event in a
    //    try/catch and writes any failures to IntegrationError__c,
    //    linked back to the stored payload by EventUuid__c.
    OrderEventProcessor.process(Trigger.new);
}

</pre>
<p>The trigger does two things in order: it captures every event to the big object, then hands the same events to <code>OrderEventProcessor.process()</code> (shown in the next step) to run the business logic. Because <code>Database.insertImmediate()</code> commits the big object record immediately — outside the normal Apex transaction, so it can&#8217;t be rolled back — the payload survives even if processing throws later. That&#8217;s the whole point of capturing <i>before</i> the logic runs.</p>
<p>When managing records across both standard custom objects and big objects, it is important to understand their different DML requirements. While standard objects rely on typical DML operations like <code>update</code>, Big Objects does not support the <code>update</code> statement at all. Instead, they require the specialized <code>Database.insertImmediate()</code> method. To modify an existing record in a big object ledger, you simply insert a new record with the exact same Composite Primary Key. The system treats this as an upsert, overwriting the non-indexed fields with your new values without raising a duplicate error.</p>
<h3><span style="font-weight: 400">Step 4: Log the errors</span></h3>
<p>When processing of a platform event fails, you write the error details to <code>IntegrationError__c</code>. The shared <span>EventUuid__c</span><span> lets your team trace the error to the exact platform event, and you can then pull the exact payload from the big object for reprocessing.</span><br />
<span>How you detect a failure depends on where the processing runs:</span></p>
<ul>
<li><b>Apex:</b> Wrap the processing logic in <code>try/catch</code> and create the <code>IntegrationError__c</code> record in the catch block</li>
<li><b>Flow:</b> Add a Fault path to any element that can fail, and create the error record there</li>
</ul>
<p>In every case, store the same <code>EventUuid__c</code> so the error points back to the exact payload in the ledger. Here&#8217;s the Apex version, where a shared <code>IntegrationReplayService.handle()</code> method holds the actual processing logic:</p>
<pre language="apex">public class OrderEventProcessor {

    // Runs AFTER the payload has been captured to the big object
    public static void process(List events) {
        List errors = new List();

        for (Order_Event__e event : events) {
            try {
                // Business logic that may fail: parsing, DML, callouts, etc.
                IntegrationReplayService.handle(event.Payload__c);
            } catch (Exception ex) {
                // Record the failure, linked to the payload by EventUuid__c
                errors.add(new IntegrationError__c(
                    IntegrationName__c = 'ERP_Inbound',
                    EventUuid__c       = event.EventUuid,
                    Exception_Type__c  = ex.getTypeName(),
                    Error_Message__c   = ex.getMessage(),
                    Stack_Trace__c     = ex.getStackTraceString(),
                    Status__c          = 'Open',
                    RetryCount__c      = 0
                ));
            }
        }

        if (!errors.isEmpty()) {
            insert errors;   // Standard DML on the custom object
        }
    }
}
</pre>
<h3><span style="font-weight: 400">Step 5: Automate error recovery and retries</span><span style="font-weight: 400"><br />
</span></h3>
<p>To maintain data integrity, resilient systems rely on automated mechanisms to periodically query for failures and attempt reprocessing. Now that our failures are neatly tracked in <span>IntegrationError__c</span><span>, we can use Batch Apex to drive our automated retry logic.</span></p>
<p><span style="font-weight: 400">Driving the retry process from your custom error object, rather than querying the big object directly, is highly scalable. Standard custom objects support clean Batch Apex chunking and governor limit management. Your batch job simply queries the Action Queue for open errors and only queries the massive big object ledger when a payload actually needs to be reprocessed.</span></p>
<p>Schedule <code>PayloadRetryBatch</code> to run once a day — say, at 2:00 a.m — using a scheduled Apex class. Each run picks up every error still marked <code>Open</code> with fewer than three attempts, pulls the matching payload from the ledger by <code>EventUuid__c</code>, and replays it. If a payload succeeds, its error is marked <code>Resolved</code>. If it fails again, <code>RetryCount__c</code> is incremented and the record stays <code>Open</code>, so the <i>next day&#8217;s</i> run retries it. After three failed attempts it&#8217;s marked <code>PermanentFailure</code> and skipped, so a persistently broken payload can&#8217;t loop forever.</p>
<p><span style="font-weight: 400">Here is what the Batch Apex pattern looks like: </span></p>
<pre language="apex">public class PayloadRetryBatch implements Database.Batchable {

    public Database.QueryLocator start(Database.BatchableContext BC) {
        // 1. Drive the logic from the standard custom object (The Action Queue)
        return Database.getQueryLocator([
            SELECT Id, EventUuid__c, RetryCount__c
            FROM IntegrationError__c
            WHERE Status__c = 'Open'
            AND RetryCount__c &lt; 3
        ]);
    }

    public void execute(Database.BatchableContext BC, List scope) {
        List errorsToUpdate = new List();

        // 2. Gather the single key field (EventUuid__c) to retrieve the heavy payloads
        Set eventUuids = new Set();

        for (IntegrationError__c err : scope) {
            eventUuids.add(err.EventUuid__c);
        }

        // Retrieve the payloads from the Ledger using the EventUuid__c index
        List payloads = [
            SELECT EventUuid__c, PayloadBody__c
            FROM IntegrationPayloadLog__b
            WHERE EventUuid__c IN :eventUuids
        ];

        // Map payloads by EventUuid__c for easy access
        Map&lt;String, IntegrationPayloadLog__b&gt; payloadMap = new Map&lt;String, IntegrationPayloadLog__b&gt;();
        for (IntegrationPayloadLog__b p : payloads) {
            payloadMap.put(p.EventUuid__c, p);
        }

        // 3. Attempt to reprocess each failed payload
        for (IntegrationError__c err : scope) {
            IntegrationPayloadLog__b originalPayload = payloadMap.get(err.EventUuid__c);
            if (originalPayload == null) {
                continue; // Payload not found; leave the error Open for investigation
            }

            try {
                // Re-run the SAME logic the original handler uses, with the stored payload
                IntegrationReplayService.handle(originalPayload.PayloadBody__c);
                err.Status__c = 'Resolved';
            } catch (Exception ex) {
                // Failed again — increment and let the next daily run pick it up
                err.RetryCount__c += 1;
                err.Error_Message__c = ex.getMessage();
                if (err.RetryCount__c &gt;= 3) {
                    err.Status__c = 'PermanentFailure';
                }
            }
            errorsToUpdate.add(err);
        }


        // 4. Commit changes to both systems
        if (!errorsToUpdate.isEmpty()) {
            update errorsToUpdate;     // Standard DML for the custom object
        }
    }

    public void finish(Database.BatchableContext BC) {
        // Optional: Send notifications for any records that reached PermanentFailure
    }
}
</pre>
<p>Because big object queries rely entirely on your composite primary index to handle massive data volumes, your <code>WHERE</code> clauses are strictly governed. Standard SOQL only permits direct matches or range filters (<code>=</code>, <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code>, <code>&gt;=</code>, and <code>IN</code>). By structuring the batch job to collect a consolidated list of identifiers into sets, we can fully leverage the <code>IN</code> operator to query our <code>EventUuid__c</code> index field efficiently.</p>
<p>It&#8217;s worth understanding how this rule scales when your index has two or more fields. You don&#8217;t have to filter on every index field in every query; you can use any leading subset of your index fields, as long as you respect the order in which they were defined. In this pattern, the composite index is <code>EventUuid__c → IntegrationName__c</code>. That means two valid query shapes are supported:</p>
<pre language="apex">// Valid — filters on the first index field only
WHERE EventUuid__c IN :eventUuids

// Also valid — filters on both index fields, in order
WHERE EventUuid__c IN :eventUuids AND IntegrationName__c = 'ERP_Inbound'

// Invalid — skips EventUuid__c and jumps straight to IntegrationName__c
WHERE IntegrationName__c = 'ERP_Inbound'

</pre>
<p>The retry batch uses the first shape — filtering on <code>EventUuid__c</code> alone — because the UUID is precise enough to locate the exact records needed. If you ever needed to narrow a query further (for example, to isolate a specific integration channel), you could add <code>IntegrationName__c</code> as a second filter and the query would remain valid. What you can never do is filter on a later index field while omitting an earlier one — the platform enforces strict left-to-right ordering, and any violation returns an error at runtime.</p>
<h3><span style="font-weight: 400">Step 6: Ensuring system observability</span></h3>
<p>For daily monitoring, use standard list views on your <code>IntegrationError__c</code> object. Build an LWC to add deep payload inspection and one-click replay capabilities. The LWC should list open failures from <code>IntegrationError__c</code> and fetch the full payload from the big object by <code>EventUuid__c</code> on demand, with a Retry Now button. To track operational metrics like failure rates, build reports and dashboards on <code>IntegrationError__c</code>. Finally, fire a proactive notification when a record reaches <code>PermanentFailure</code>, so your team doesn&#8217;t discover dropped payloads by accident.</p>
<h2><span style="font-weight: 400">The same pattern, across every domain</span></h2>
<p><span style="font-weight: 400">After you’ve built this once, you’ll start seeing the same shape everywhere. Use a big object wherever you need to write at high volume, hold data durably, and query on a predictable access pattern.</span></p>
<ul>
<li style="font-weight: 400"><b>Telecom / call detail records (CDRs): </b><span style="font-weight: 400">Carriers use big objects for call detail records, where billions of immutable, subscriber-keyed events need to be queryable for billing and retained for regulatory compliance</span></li>
<li style="font-weight: 400"><b>Financial services / transaction history: </b><span style="font-weight: 400">Write-once records, queried by account and date range, are retained for years without nightly batch jobs copying data into standard objects</span></li>
<li style="font-weight: 400"><b>IoT / device telemetry: </b><span style="font-weight: 400">High-frequency sensor readings, indexed by device ID and timestamp, are fed into CRM Analytics to surface health trends to service teams</span></li>
<li style="font-weight: 400"><b>SaaS metered billing: </b><span style="font-weight: 400">Every API call and feature activation written to a durable ledger is rolled up on a schedule, with full history available if a charge is ever disputed</span></li>
<li style="font-weight: 400"><b>Marketing engagement history: </b><span style="font-weight: 400">Email sends, opens, and clicks are stored at scale and queryable by campaign or subscriber, without bloating transactional objects, and are ready to feed into Data 360 for attribution modeling</span></li>
</ul>
<p><span style="font-weight: 400">In all of these cases, the index question is the same: what filter will you apply most often? Start there, and the schema design follows naturally.</span></p>
<h2><span style="font-weight: 400">Big object limits and constraints</span></h2>
<p><span style="font-weight: 400">Note, in addition to the behaviour we already described, Big Objects have the following limits to take into account: </span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Each org supports up to 100 big objects</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Field types are limited to Text, Number, DateTime, Lookup, Email, Phone, URL and Long Text Area — no picklists, formulas, or checkboxes</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Fields cannot be deleted after creation </span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Triggers, flows, and Process Builder don’t fire on big object records </span></li>
</ul>
<p><span style="font-weight: 400">These aren’t blockers, but they make upfront planning non-negotiable.</span></p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">Big Objects isn’t a legacy feature waiting to be replaced. It’s a precision tool for a specific class of problem, and that problem keeps getting bigger. The payload capture system above is a starting point, but the same principles apply anywhere you need platform-native, durable, high-scale data storage.</span></p>
<p><span style="font-weight: 400">Salesforce is also working on deeper integration between Big Objects and Data 360, which will make high-volume, on-platform data an even more powerful foundation for real-time analytics and AI-driven workflows. Keep an eye on the </span><a href="https://www.salesforce.com/blog/category/product-roadmaps/"><span style="font-weight: 400">Salesforce Product Roadmap</span></a><span style="font-weight: 400"> for upcoming Big Object and Data 360 integration capabilities.</span></p>
<p><span style="font-weight: 400">Have questions or want to share how you’re using big objects? Join the conversation in the </span></p>
<p><a href="https://trailhead.salesforce.com/trailblazer-community/groups/0F93A000000DJbJSAW?tab=discussion&amp;sort=LAST_MODIFIED_DATE_DESC"><span style="font-weight: 400">Salesforce Developer Community</span></a><span style="font-weight: 400"> or on </span><a href="https://salesforce.stackexchange.com/"><span style="font-weight: 400">Salesforce Stack Exchange</span></a><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Implementation Guide: </span><a href="https://developer.salesforce.com/docs/atlas.en-us.bigobjects.meta/bigobjects/"><span style="font-weight: 400">Big Objects</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Apex Reference: </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_database.htm"><span style="font-weight: 400">Database Class</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Trailhead: </span><a href="https://trailhead.salesforce.com/content/learn/modules/large-data-volumes"><span style="font-weight: 400">Large Data Volumes</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Developer Guide: </span><a href="https://developer.salesforce.com/docs/atlas.en-us.bi_dev_guide_rest.meta/bi_dev_guide_rest/"><span style="font-weight: 400">CRM Analytics</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Developer Guide: </span><a href="https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_intro.htm"><span style="font-weight: 400">Platform Events</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Laxman Vattam</b><span style="font-weight: 400"> is a Senior Technical Architect at Salesforce focused on AI enablement, digital transformation, and large-scale platform modernization. He has spent over a decade designing scalable architectures for regulated industries. You can follow Laxman on </span><a href="https://www.linkedin.com/in/laxman-vattam-296a8014/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/big-objects-playbook-payload-capture-and-replay">The Big Objects Playbook: Payload Capture and Replay</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/06/big-objects-playbook-payload-capture-and-replay/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206431</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260617114627/Generic-A-4-e1781722002836.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260617114627/Generic-A-4-e1781722002836.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Build a Decision-Scoring Skill for Any Agent</title>
		<link>https://developer.salesforce.com/blogs/2026/06/build-a-decision-scoring-skill-for-any-agent</link>
		<comments>https://developer.salesforce.com/blogs/2026/06/build-a-decision-scoring-skill-for-any-agent#respond</comments>
		<pubDate>Tue, 16 Jun 2026 15:00:00 +0000</pubDate>
		<dc:creator><![CDATA[Dave Norris]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Agentforce Vibes]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Agent Skills]]></category>
		<category><![CDATA[Architectural Decision Record]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[Code Guardrails]]></category>
		<category><![CDATA[Decision Record]]></category>
		<category><![CDATA[developer tooling]]></category>
		<category><![CDATA[Model context protocol]]></category>
		<category><![CDATA[Well-Architected Framework]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206534</guid>
		<description><![CDATA[<p>Learn how to build agent skills for a structured decision pipeline that covers discovery, risk scoring and a decision record you can give to stakeholders.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/build-a-decision-scoring-skill-for-any-agent">Build a Decision-Scoring Skill for Any Agent</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><span style="font-weight: 400">Technical decisions on a project rarely get documented at the point when they are made. By the time someone asks &#8220;why did we choose X?&#8221;, the reasoning lives in a Slack thread, a meeting memory, or the head of someone who may not be in the team anymore.  </span></p>
<p><span>Instead of letting critical project context vanish, you can leverage AI to enforce architectural rigour. By implementing a decision-scoring skill, project teams can transform their coding agents into guardrails for engineering design. In this post, you’ll learn how to build the </span><code>/decide</code><span> pipeline, a structured framework that moves you from broad discovery to verifiable decision record.</span></p>
<h2><span style="font-weight: 400">A skill to guide technical decisions</span></h2>
<p><a href="https://github.com/deejay-hub/salesforce-ada-agent-skills"><u>Salesforce Ada Agent Skills</u></a><span> is an open source skill repository for agent skills beyond writing code. </span><code>/decide</code><span> is a structured pipeline that demonstrates how to use AI to drive technical decisions. Type </span><code>‘/decide managed package vs. unlocked package vs. unpackaged metadata for distributing utilities across 3 orgs’</code><span> and your coding agent runs you through discovery, options grounded in official documentation, a risk matrix you challenge based on real-world experience, and a scored recommendation written to disk as a decision record. </span></p>
<p><span style="font-weight: 400">The output is a file: versioned, reviewable, and available to the next person who asks.</span></p>
<p><span style="font-weight: 400">The underlying pattern of discovery, debating assessment criteria, and scoring works for any technical decision for </span><b>a</b><span style="font-weight: 400">dmins, </span><b>d</b><span style="font-weight: 400">evelopers and </span><b>a</b><span style="font-weight: 400">rchitects — hence the </span><b><i>ada</i></b><span style="font-weight: 400"> acronym. I’ll be using Claude Code in the examples below, but it has been designed to be used in any agent you choose with scaffolding in GitHub for Gemini and Codex CLIs.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206538" >
			    <img decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260615100514/The-decide-skill-in-Claude-Code-e1781543129720.png?w=1000" class="postimages" width="1000" height="637" alt="The decide skill in Claude Code" />
			  </span>
			</p>
<h2><span style="font-weight: 400">How the pipeline works</span></h2>
<p><span style="font-weight: 400">Each step of the pipeline further refines the scope and context of a decision. Each step&#8217;s output constrains the next step&#8217;s input, narrowing from broad research to a single verifiable number. </span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206539" >
			    <img decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260615100544/The-decision-pipeline-e1781543158547.png?w=1000" class="postimages" width="1000" height="283" alt="The decision pipeline" />
			  </span>
			</p>
<table>
<tbody>
<tr>
<td><b>Step</b></td>
<td><b>Description</b></td>
</tr>
<tr>
<td><b>1. Discovery</b></td>
<td><span>Gather what makes this decision unique, things like data volumes, timelines, team skills, and constraints. The agent asks structured questions and skips anything already answered in the slash command argument.</span></td>
</tr>
<tr>
<td><b>2. Solution options</b></td>
<td><span>Research official documentation in a single broad search, then present it as a table. A person confirms, adds, removes, or modifies before proceeding.</span></td>
</tr>
<tr>
<td colspan="2"><b>Check and review</b></td>
</tr>
<tr>
<td><b>3. Assessment criteria</b></td>
<td><span>Define what &#8220;better&#8221; means for this specific decision, generated from the research results, confirmed by you. If a criterion is missing, the final score may be lacking context to that dimension.</span></td>
</tr>
<tr>
<td colspan="2"><b>Check and review</b></td>
</tr>
<tr>
<td><b>4. Risk assessment</b></td>
<td><span>Rate each option against each criterion as Low, Medium, or High risk with cited rationale. This is the data that drives the decision — a matrix that a person challenges before scoring happens.</span></td>
</tr>
<tr>
<td colspan="2"><b>Check and review</b></td>
</tr>
<tr>
<td><b>5. Decision scoring</b></td>
<td><span>Create a weighted sum of the risk ratings across well-architected pillars. The highest score wins. No narrative override.</span></td>
</tr>
<tr>
<td colspan="2"><b>Create a persistent document (optional)</b></td>
</tr>
</tbody>
</table>
<h2><span style="font-weight: 400">Key design patterns</span></h2>
<p><span style="font-weight: 400">The pipeline above is what the skill does. The patterns below are how it’s implemented and the design choices to make a skill like this reliable, adaptable and worth trusting with a technical decision. Together, they give you output that you can verify, share and build on.</span></p>
<h3><span style="font-weight: 400">The folder structure</span></h3>
<p><span style="font-weight: 400">The skill is a directory of markdown files.</span></p>
<pre language="text">salesforce-ada-agent-skills/
  ├── CLAUDE.md                            	# Global rules
  ├── .mcp.json                            	# MCP server config
  ├── .claude/
  │   └── skills/
  │       ├── learn/
  │       │   └── SKILL.md                 # Learning workflow
  │       └── decide/
  │           └── SKILL.md                 	# Pipeline steps, constraints
  ├── knowledge/
  │   ├── formats/                         	# Shared output templates
  │   │   ├── options-format.md            	# Template for solution options table
  │   │   ├── criteria-format.md           	# Template for assessment criteria
  │   │   ├── risk-matrix-format.md
  │   │   └── decision-format-*.md         	# Templates for decision record output
  │   ├── scoring/                         	# Decision score methodology
  │   └── modifiers/                       	# Composable lenses
  └── output/
      └── decisions/                       	# Generated decision records
</pre>
<p><b><code>CLAUDE.md</code></b> at the repo root defines the scoring pillar weights, MCP source-selection rules, and hard constraints that apply across the decision skill. The skill file (<code>SKILL.md</code>) handles pipeline sequencing; <code>CLAUDE.md</code> handles the rules that govern every step.</p>
<p><b><code>SKILL.md</code></b><span> loads when you type </span><code>/decide</code><span> and controls the pipeline sequence. </span><b><span>Format files</span></b><span> are templates that the agent copies rather than interprets, producing consistent output regardless of conversation length. The </span><b><span>scoring methodology</span></b><span> loads only at the final step. This is about 350 lines of rubric detail that would compete for attention during discovery if inlined earlier.</span></p>
<p><span style="font-weight: 400">Splitting files this way means you can iterate on a format without touching pipeline logic, swap scoring dimensions without changing steps, and keep the context window lean at each stage.</span></p>
<h3><span style="font-weight: 400">You are the expert</span></h3>
<p><span style="font-weight: 400">At every transition the agent pauses and asks whether you&#8217;re happy to proceed. You can reshape the analysis at any gate: add an option, remove a criterion, or challenge a rating. The pipeline prevents a flawed assumption in step one from propagating unchallenged to the recommendation. It makes the most of your real-world experiences and helps produce a higher quality output.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206540" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260615100634/Discovery-questions-to-elicit-details-about-the-decision-e1781543208226.png?w=1000" class="postimages" width="1000" height="392" alt="Discovery questions to elicit details about the decision" />
			  </span>
			</p>
<h3><span style="font-weight: 400">Research early and broadly, then verify claims</span></h3>
<p><span style="font-weight: 400">The initial research in the options step uses a broad query, so competing approaches surface together rather than being cherry-picked individually. Each proposed option is validated against documentation before being presented, and every factual claim in the risk matrix is verified with a targeted search before a rating is assigned. This layered approach balances breadth (discover all options fairly) with depth (no ungrounded claim reaches the final score).</span></p>
<p><span style="font-weight: 400">The skill works without any MCP (Model Context Protocol) server that can search documentation.  It falls back to web search or the agent&#8217;s training knowledge and clearly states which grounding method it&#8217;s using. But documentation quality drives decision quality, so I built </span><a href="https://github.com/deejay-hub/salesforce-ada-content-mcp-server"><span style="font-weight: 400">salesforce-content</span></a><span style="font-weight: 400">, a custom MCP server that provides keyword and vector search across recent official documentation from the admin, developer, and architect ecosystems. Any documentation on MCP will work (the repo ships with a Context7 configuration as a zero-setup alternative), but salesforce-content is preferred because it indexes trusted Salesforce sources directly rather than relying on web search, which often misses key information when the source is hard to parse.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206541" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260615100710/Options-delivered-based-on-trusted-sources-e1781543249950.png?w=1000" class="postimages" width="1000" height="849" alt="Options delivered based on trusted sources" />
			  </span>
			</p>
<h3><span style="font-weight: 400">Separate format files for every output step</span></h3>
<p><span style="font-weight: 400">Each step has its own template file specifying the exact table structure. The agent follows a concrete template more reliably than prose instructions. Splitting them from the pipeline logic means you can iterate on a format without touching the steps.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206542" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260615100755/Criteria-for-assessment-delivered-as-a-table-e1781543290779.png?w=1000" class="postimages" width="1000" height="690" alt="Criteria for assessment delivered as a table" />
			  </span>
			</p>
<h3><span style="font-weight: 400">A mechanical score the agent can self-verify</span></h3>
<p><span style="font-weight: 400">Risk ratings map to fixed confidence values (Low risk = 85, Medium risk = 55, High risk = 25), weighted across pillars (Trust 20%, Reliability 20%, Operational Excellence 20%, Resource Optimization 15%, Cost Optimization 15%, Fairness 10%), and summed up into a single decision score per option. The invariant: the highest score must be the recommendation. The agent verifies this by comparing two numbers and prompt instructions to make sure there is no judgement or reasoning applied. Fixed values remove ambiguity. Pinning each level to a single value makes the score fully deterministic from the risk matrix.</span></p>
<p><span style="font-weight: 400">I am using optimized bands based on the direction being taken by the </span><a href="https://www.youtube.com/watch?v=Gt9tMPl_Kf8&amp;list=PLn15mOuXqGJWg9YJNnAqO-Rv-dN3MngoT"><span style="font-weight: 400">Well-Architected framework</span></a><span style="font-weight: 400">, but these can be changed based on your preference.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206543" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260615100835/An-example-confidence-score-from-a-decision-based-on-user-guidance-e1781543330817.png?w=1000" class="postimages" width="1000" height="532" alt="An example confidence score from a decision based on user guidance" />
			  </span>
			</p>
<h3><span style="font-weight: 400">An output that can be shared</span></h3>
<p><span style="font-weight: 400">Key decisions should be documented. New team members should understand the reasons behind a technical choice and allowing the output to be persistently stored was a key design choice. For this skill, I chose an </span><a href="https://github.com/adr"><span style="font-weight: 400">Architectural Decision Record (ADR)</span></a><span style="font-weight: 400"> in markdown using the option for a minimal or verbose output.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206544" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260615100911/A-decision-summary-and-output-markdown.png?w=1999" class="postimages" width="1999" height="619" alt="A decision summary and output markdown" />
			  </span>
			</p>
<h2><span style="font-weight: 400">Build your own decision-scoring skill</span></h2>
<p><span style="font-weight: 400">The skill is a transferable pattern. It can be adapted to your preferred steps, scoring mechanism, and agent.</span></p>
<table>
<tbody>
<tr>
<td><b>Step</b></td>
<td><b>What to consider</b></td>
</tr>
<tr>
<td><b>1. Define pipeline steps</b></td>
<td><span style="font-weight: 400">What context do you need before listing options? What dimensions matter for scoring?</span></td>
</tr>
<tr>
<td><b>2. Choose dimensions and weights</b></td>
<td><span style="font-weight: 400">What does your organization optimize for? Assign percentages that sum up to 100.</span></td>
</tr>
<tr>
<td><b>3. Write format files</b></td>
<td><span style="font-weight: 400">For each step that produces output, create a template showing the exact structure. Include a confirmation prompt.</span></td>
</tr>
<tr>
<td><b>4. Write your scoring mechanism</b></td>
<td><span style="font-weight: 400">Define risk-to-score bands. State the invariant: highest score wins.</span></td>
</tr>
<tr>
<td><b>5. Ground in external data</b></td>
<td><span style="font-weight: 400">Connect a documentation MCP server, or let the agent fall back to web search. Any MCP that returns docs works; the skill describes what to search for, not which tool to call. The quality of decisions depends on the quality of the sources.</span></td>
</tr>
<tr>
<td><b>6. Repeat rules that matter</b></td>
<td><span style="font-weight: 400">Your most important constraint should appear in at least two files. Agents lose track over long conversations.</span></td>
</tr>
</tbody>
</table>
<h2><span style="font-weight: 400">Get started</span></h2>
<p><span>Clone the </span><a href="https://github.com/deejay-hub/salesforce-ada-agent-skills"><u>repo</u></a><span>, follow the instructions from the readme, and type </span><code>/decide</code><span> followed by your question. </span></p>
<p><span style="font-weight: 400">I built this because feedback loops matter. You can see the reasoning decomposed into a matrix of specific, citable claims, rather than a wall of persuasive prose. With a skill like this, you make better decisions, catch assumptions faster, and leave a record of the decision that the next person can actually use.</span></p>
<p><b>Resources</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Docs: </span><a href="https://code.claude.com/docs/en/skills.md"><span style="font-weight: 400">Anthropic skills documentation</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Docs: </span><a href="https://code.claude.com/docs/en/best-practices.md"><span style="font-weight: 400">Anthropic skills best practices</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Docs: </span><a href="https://architect.salesforce.com/docs/architect/well-architected/guide/overview"><span style="font-weight: 400">Salesforce Well-Architected</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Video: </span><a href="https://www.youtube.com/watch?v=Gt9tMPl_Kf8"><span style="font-weight: 400">The Next Chapter of the Well-Architected Framework</span></a></li>
</ul>
<h2><b>About the author</b></h2>
<p><b>Dave Norris</b><span style="font-weight: 400"> is a Developer Advocate at Salesforce. He’s passionate about making technical subjects broadly accessible to a diverse audience. Dave has been with Salesforce for over a decade, has over 40 Salesforce and MuleSoft certifications, and became a Salesforce Certified Technical Architect in 2013.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/build-a-decision-scoring-skill-for-any-agent">Build a Decision-Scoring Skill for Any Agent</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/06/build-a-decision-scoring-skill-for-any-agent/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206534</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260615102947/SingleHeadshot-3-1-e1781544863838.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260615102947/SingleHeadshot-3-1-e1781544863838.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Understanding the Data 360 Web SDK: From Website Event to Data Model</title>
		<link>https://developer.salesforce.com/blogs/2026/06/understanding-the-data-360-web-sdk</link>
		<comments>https://developer.salesforce.com/blogs/2026/06/understanding-the-data-360-web-sdk#respond</comments>
		<pubDate>Thu, 11 Jun 2026 19:12:42 +0000</pubDate>
		<dc:creator><![CDATA[Chris Charalambous]]></dc:creator>
				<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Data 360]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Behavioral Events]]></category>
		<category><![CDATA[Data Cloud]]></category>
		<category><![CDATA[Data Model Objects (DMO)]]></category>
		<category><![CDATA[Identity resolution]]></category>
		<category><![CDATA[Salesforce Interactions SDK]]></category>
		<category><![CDATA[sendEvent]]></category>
		<category><![CDATA[Sitemap]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206515</guid>
		<description><![CDATA[<p>Learn how your sitemap code becomes actionable data through transformation, validation, routing, and mapping in this guide to the full event pipeline.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/understanding-the-data-360-web-sdk">Understanding the Data 360 Web SDK: From Website Event to Data Model</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p>If you&#8217;ve explored the <a href="https://developer.salesforce.com/docs/data/salesforce-interactions-sdk/guide/c360a-api-salesforce-interactions-web-sdk.html"><u>Salesforce Interactions SDK</u></a>, you likely understand how to configure a website connector, initialize the SDK, and trigger <code>sendEvent()</code>. However, implementing this in a production environment often leads to a common scenario: events are being dispatched, but where is the data actually landing — and why is it not appearing as expected?</p>
<p><span style="font-weight: 400">This post is designed to bridge the gap between basic setup and a production-ready deployment. We will trace the end-to-end journey of an event — from your sitemap code, through the SDK&#8217;s internal transformation layer, and into Data 360&#8217;s schema, data streams, and data model objects (DMOs). By the end, you will understand not just </span><i><span style="font-weight: 400">how to dispatch</span></i><span style="font-weight: 400"> events, but </span><i><span style="font-weight: 400">how they’re structured, partitioned, validated, and mapped</span></i><span style="font-weight: 400"> as they evolve into actionable data.</span></p>
<h2><span style="font-weight: 400">How the Data 360 module transforms your events</span></h2>
<p>When you invoke <code>SalesforceInteractions.sendEvent()</code> (see <a href="https://developer.salesforce.com/docs/data/salesforce-interactions-sdk/guide/c360a-api-event-structure.html#example"><u>docs</u></a>), your data undergoes a significant evolution before reaching Data 360. Between sending an event and the final destination sits the Data 360 module of the Salesforce Interactions SDK. This is a transformation layer within the SDK that converts your hierarchical interaction events into the flattened schema required by Data 360.</p>
<p><b>Key insight: </b><span style="font-weight: 400">A single event dispatched from your sitemap can actually trigger </span><i><span style="font-weight: 400">multiple</span></i><span style="font-weight: 400"> Data 360 events under the hood.</span></p>
<p>To illustrate this, consider a standard <code>sendEvent()</code> call designed to capture a specific button interaction along with the user&#8217;s identity.</p>
<pre language="javascript">SalesforceInteractions.sendEvent({
    interaction: {
        eventType: 'userEngagement',
        name: 'button_click'
    },
    user: {
        attributes: {
            eventType: 'contactPointEmail',
            email: 'joe.smith@domain.com'
        }
    }
})
</pre>
<p><span style="font-weight: 400">We designed the Data 360 module to split this into two separate events before sending them to Data 360.</span></p>
<pre language="json">// Event 1: Profile data
{
    "eventType": "contactPointEmail",
    "email": "joe.smith@domain.com",
    "category": "Profile",
    // ... auto-populated fields
}

// Event 2: Engagement data
{
    "eventType": "userEngagement",
    "interactionName": "button_click",
    "category": "Engagement",
    // ... auto-populated fields
}
</pre>
<p>The <code>eventType</code> defined within the <code>interaction</code> block determines which engagement schema — and ultimately which DMO — captures behavioral data. Conversely, the <code>eventType</code> specified under <code>user.attributes</code> dictates the destination for identity data within the profile schema. This mechanism allows a single user interaction to simultaneously refresh a customer&#8217;s engagement timeline <i>and</i> update their contact details, routing data to distinct segments of your data model.</p>
<h2><span style="font-weight: 400">The end-to-end data flow</span></h2>
<p><span style="font-weight: 400">Understanding the full pipeline makes debugging significantly easier. Here&#8217;s how data flows from your website to your data model:</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206519" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260610124702/How-data-flows-from-the-sitemap-on-the-website-to-Data-360s-data-model-e1781120845323.png?w=1000" class="postimages" width="1000" height="280" alt="How data flows from the sitemap on the website to Data 360’s data model" />
			  </span>
			</p>
<p><span style="font-weight: 400">Every stage of the pipeline presents unique failure modes. If your data isn&#8217;t appearing as expected, consider these common troubleshooting areas:</span></p>
<ul>
<li><b><span>Schema validation: </span></b><span>Verify that your event payload contains all of the schema fields flagged as </span><code><span>required</span></code><span>, otherwise Data 360 will reject the event. Any field in your event that is not defined in the schema will be ignored within the target schema. Pay close attention to data types and ensure that you are using the </span><i><span>exact</span></i><span> field names.</span></li>
<li><b><span>Data stream coverage: </span></b><span>Confirm that your data stream is configured to include the specific event type you&#8217;re dispatching. Remember, if you introduce new schema objects after deployment, you must manually update the stream to incorporate them.</span></li>
<li><b><span>DMO mapping: </span></b><span>Ensure that the </span><code>eventType</code> field from your behavioral data stream is correctly mapped to the Engagement Type field on your target DMO. For a generic event type data point, the <code>eventType</code> field in the Browse section on the left should be used instead of the <code>eventType</code> field in the “All Event Data” when mapping to the Website Engagement DMO. We recommend leveraging Data Explorer to view the various <code>eventType</code> fields in the Behavioural Events DLO to make an informed decision on which DMO they should map to.</li>
</ul>
<p><span style="font-weight: 400">Setting the right level of </span><a href="https://developer.salesforce.com/docs/data/salesforce-interactions-sdk/guide/c360a-api-debugging.html"><span style="font-weight: 400">debugging</span></a><span style="font-weight: 400"> is important to not only give you full visibility over failures, but to also help troubleshoot the end-to-end data flow. You can set a numerical logging level while also specifying your own custom messages from your site’s code.</span></p>
<pre language="javascript">// Example: Enable debug messages
SalesforceInteractions.setLoggingLevel("debug");

// Same as above, using the numeric value
SalesforceInteractions.setLoggingLevel(4);

//Output your own custom messages
SalesforceInteractions.log.debug("here")
</pre>
<h2><span style="font-weight: 400">Standard vs. custom interaction names: Choose carefully</span></h2>
<p>The Interactions SDK utilizes a suite of standard interaction names, such as <code>View Catalog Object</code>, <code>Add To Cart</code>, and <code>Purchase</code>. These are far more than mere labels; they serve as triggers for pre-configured transformation logic residing within the Data 360 module.</p>
<p>When employing a standard interaction name, the resulting <code>eventType</code> in the Data 360 event is automatically assigned and cannot be modified. For instance, any event containing <code>name: View Catalog Object</code> will invariably generate <code>eventType: catalog</code> during the Data 360 translation process, effectively overriding any custom <code>eventType</code> you might have specified within the interaction block.</p>
<pre language="json">// Both of these produce eventType: "catalog" in the Data 360 event
{
    interaction: {
        name: "View Catalog Object",
        catalogObject: {
            type: "Product",
            id: "product-xyz"
        }
    }
}

{
    interaction: {
        name: "View Catalog Object",
        catalogObject: {
            type: "Article",
            id: "article-abc"
        }
    }
}
</pre>
<p><span style="font-weight: 400">This distinction is critical because all events sharing a common </span><span style="font-weight: 400">eventType</span><span style="font-weight: 400"> are constrained to a single DMO mapping. If your website users are engaging with diverse entities, such as insurance policies versus knowledge articles, relying on the standard </span><span style="font-weight: 400">View Catalog Object</span><span style="font-weight: 400"> interaction for both will force them into the same DMO. This overlap complicates the creation of object-specific engagement models and dilutes your data granularity.</span></p>
<p><span style="font-weight: 400">To circumvent this, you should consider custom interaction names and event types.</span></p>
<pre language="json">// Product engagement → maps to Product Browse Engagement DMO
{
    interaction: {
        name: "View",
        eventType: "productEngagement",
        catalogObject: {
            type: "Product",
            id: "product-xyz"
        }
    }
}

// Article engagement → maps to Article Engagement DMO
{
    interaction: {
        name: "Download",
        eventType: "articleEngagement",
        catalogObject: {
            type: "Article",
            id: "article-abc"
        }
    }
}
</pre>
<p><span style="font-weight: 400">If your implementation aligns with standard patterns, then leverage the standard schema, for example, catalog interactions, cart activity, or identity updates. This approach simplifies your architecture by utilizing out-of-the-box DMO mappings via Data Kit packages. You should only implement </span><i><span style="font-weight: 400">custom event types</span></i><span style="font-weight: 400"> when your specific business logic diverges from these supported interaction models.</span></p>
<p><b>Key insight:</b> We have the SDK automatically assign a <code>pageView</code> flag to every dispatched event. A page view event (<code>pageView: 1</code>) is triggered when a <code>pageConfig</code> matches during initial page load, driven by the <code>isMatch</code> logic within your sitemap&#8217;s <code>pageTypes</code> array. Conversely, an interaction-only event (<code>pageView: 0</code> or absent) is dispatched via <code>sendEvent()</code> through an <code>interaction</code> object, typically following a user action such as a click or form submission. Mastering this distinction is critical to ensuring the integrity of your engagement analytics.</p>
<h3><span style="font-weight: 400">How nested event payloads translate to flat Data 360 rows</span></h3>
<p><span style="font-weight: 400">Data 360 events are flat, but the SDK payloads you send are nested. When the Data 360 module ingests an event, it walks the nested structures and flattens them into a single row of columns on the target DMO. Understanding these flattening rules is critical to getting your schema and data stream to line up.</span></p>
<p><span style="font-weight: 400">Looking at a standard interaction first:</span></p>
<pre language="json">// Product engagement → maps to Product Browse Engagement DMO
{
    interaction: {
        name: "View Catalog Object",
        catalogObject: {
            type: "Product",
            id: "product-xyz",
            attributes: {
                name: "Product name",
                description: "Product description",
                inventory: 5,
                price: 123.45
            }
        }
    }
}
</pre>
<p><span style="font-weight: 400">For this sample of a standard event payload sent by the SDK, the Data 360 event that lands in your Behavioural Events data stream DLO will look like this:</span></p>
<table>
<tbody>
<tr>
<td><b>Behavioural Events DLO Field Name</b></td>
<td><b>Value</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">eventType</span></td>
<td><span style="font-weight: 400">catalog</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">catalog.interactionName</span></td>
<td><span style="font-weight: 400">View Catalog Object</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">catalog.type</span></td>
<td><span style="font-weight: 400">Product</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">catalog.id</span></td>
<td><span style="font-weight: 400">product-xyz</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">catalog.attributeName</span></td>
<td><span style="font-weight: 400">Product name</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">catalog.attributeDescription</span></td>
<td><span style="font-weight: 400">Product description</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">catalog.attributeInventory</span></td>
<td><span style="font-weight: 400">5</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">catalog.attributePrice</span></td>
<td><span style="font-weight: 400">123.45</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">When using custom properties at the interaction level, these are added directly to the payload. For custom objects nested under interaction, these are flattened using lowerCamelCase.</span></p>
<p><span style="font-weight: 400">For example:</span></p>
<pre language="json">// Custom interaction
{
    interaction: {
        name: "Special Form Click",
        customProperty: "value"
    }
}
</pre>
<p><span style="font-weight: 400">They will land in the Behavioural Events data stream DLO like this:</span></p>
<table>
<tbody>
<tr>
<td><b>Behavioural Events DLO Field Name</b></td>
<td><b>Value</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">interactionName</span></td>
<td><span style="font-weight: 400">Special Form Click</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">customProperty</span></td>
<td><span style="font-weight: 400">value</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">For custom objects nested under interaction, these are flattened using lowerCamelCase. For example:</span></p>
<pre language="json">// Custom interaction
{
    interaction: {
        name: "Special Form Click",
        customProperties: {
            anotherCustomProperties: {
                customField: "value"
            }
        }
    }
}
</pre>
<p><span style="font-weight: 400">These will land in the Behavioural Events data stream DLO like this:</span></p>
<table>
<tbody>
<tr>
<td><b>Behavioural Events DLO Field Name</b></td>
<td><b>Value</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">interactionName</span></td>
<td><span style="font-weight: 400">Special Form Click</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">customPropertiesAnotherCustomPropertiesCustomField</span></td>
<td><span style="font-weight: 400">value</span></td>
</tr>
</tbody>
</table>
<h2><span style="font-weight: 400">Web schema: What must match, and what&#8217;s flexible</span></h2>
<p><span style="font-weight: 400">Your web schema is a JSON document uploaded to Data 360 that defines the allowed event structure. The Interactions SDK will silently drop any event that doesn&#8217;t conform to it. Understanding what&#8217;s strictly enforced versus what&#8217;s flexible saves hours of debugging.</span></p>
<p><span style="font-weight: 400">The following elements are </span><i><span style="font-weight: 400">strictly bound</span></i><span style="font-weight: 400"> to your schema keys:</span></p>
<ul>
<li><code><span>eventType</span></code><span>, </span><code><span>category</span></code><span>, and </span><code><span>interactionName</span></code></li>
<li><span>Field names utilized within </span><code><span>interaction</span></code><span>, </span><code><span>catalogObjects</span></code><span>, and </span><code><span>user.identitie</span></code></li>
<li><span style="font-weight: 400">Note that these identifiers must provide an </span><i><span style="font-weight: 400">exact match</span></i><span style="font-weight: 400"> to those defined in your uploaded schema</span></li>
</ul>
<p><span style="font-weight: 400">Conversely, these elements remain </span><i><span style="font-weight: 400">flexible</span></i><span style="font-weight: 400"> or free-form:</span></p>
<ul>
<li><code><span>pageConfig.name</span></code><span> (the page type name employed during sitemap matching)</span></li>
<li><span>Values populated within </span><code><span>interaction.attribute</span></code><span> fields</span></li>
<li><span>Custom payload strings, page name identifiers, and any derived values calculated via JavaScript</span></li>
</ul>
<p><b>Key insight:</b><span style="font-weight: 400"> Consider schema keys as the column names in a database. While the column headers must align with your schema perfectly, the specific values you use to populate them remain highly flexible.</span></p>
<p><b>Critical naming conventions</b></p>
<ul>
<li><span>Adhere to strict </span><i><span>lowerCamelCase</span></i><span> for all field names (e.g., use </span><code><span>pageUrl</span></code><span> rather than </span><code><span>page_url</span></code><span> or </span><code><span>PageUrl</span></code><span>)</span></li>
<li><span>Underscores in column or field names are </span><i><span>unsupported</span></i><span> and will cause data ingestion to fail</span></li>
<li><span>Avoid consecutive uppercase characters (e.g., prefer </span><code>urlPath</code> over <code>URLPath</code>)</li>
</ul>
<h2><span style="font-weight: 400">Three essential data streams</span></h2>
<p><span style="font-weight: 400">Upon deploying data streams via your website connector, the resulting architecture is partitioned into three distinct categories, each serving a specialized role in your data ecosystem.</span></p>
<h3><span style="font-weight: 400">1. Identity stream</span></h3>
<p>Automatically generated for the <code>identity</code> event type, this stream captures the <code>deviceId </code>— a unique identifier assigned by the SDK to every anonymous visitor.</p>
<p>This <code>deviceId</code> persists within the same browser typically via Local Storage or a first-party cookie. On every subsequent visit from the same browser, the same <code>deviceId</code> is issued. It does not, however, persist across browsers or different domains. For example, if a user visits the website in Chrome and then Safari, or on a different device, they get a different <code>deviceId</code>.</p>
<p>To ensure proper ingestion, map this to the Individual DMO using the key relationship: <code>deviceId</code> → <code>Individual Id</code>. This mapping is how Data 360 instantiates a new Individual record for every unique website visitor. The table below shows how these fields should be mapped from the identity data stream.</p>
<table>
<tbody>
<tr>
<td><b>DLO Field (from Interactions SDK)</b></td>
<td><b>Maps to DMO</b></td>
<td><b>DMO Field</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">deviceId</span></td>
<td><span style="font-weight: 400">Individual</span></td>
<td><span style="font-weight: 400">Individual Id</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">deviceId</span></td>
<td></td>
<td><span style="font-weight: 400">Party</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">firstName</span></td>
<td></td>
<td><span style="font-weight: 400">First Name</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">lastName</span></td>
<td></td>
<td><span style="font-weight: 400">Last Name</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">isAnonymous</span></td>
<td></td>
<td><span style="font-weight: 400">isAnonymous</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">dateTime</span></td>
<td></td>
<td><span style="font-weight: 400">Created Date</span></td>
</tr>
</tbody>
</table>
<h3><span style="font-weight: 400">2. Contact point / party identification streams</span></h3>
<p>These streams manage profile-level events, including <code>contactPointEmail</code>, <code>contactPointPhone</code>, and <code>partyIdentification</code>. They function as the critical nexus for identity resolution, bridging the gap between an anonymous <code>deviceId</code> and a verified customer identity. For instance, when a user authenticates and you dispatch a <code>partyIdentification</code> event containing their CRM ID, this data populates the Party Identification DMO, enabling Identity Resolution rulesets to unify disparate records.</p>
<p><span style="font-weight: 400">Each stream maps to a different Profile-category DMO, and each has a specific field mapping that you need to get right before Identity Resolution rulesets will fire. The table below shows generic mappings. </span></p>
<table>
<tbody>
<tr>
<td><b>Data Stream</b></td>
<td><b>DLO Field (from Web SDK)</b></td>
<td><b>Maps to DMO</b></td>
<td><b>DMO Field</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">contactPointEmail</span></td>
<td><span style="font-weight: 400">deviceId</span></td>
<td><span style="font-weight: 400">Contact Point Email</span></td>
<td><span style="font-weight: 400">Party</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">deviceId</span></td>
<td></td>
<td><span style="font-weight: 400">Contact Point Email Id</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">email</span></td>
<td></td>
<td><span style="font-weight: 400">Email Address</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">dateTime</span></td>
<td></td>
<td><span style="font-weight: 400">Created Date</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">contactPointPhone</span></td>
<td><span style="font-weight: 400">deviceId</span></td>
<td><span style="font-weight: 400">Contact Point Phone</span></td>
<td><span style="font-weight: 400">Party</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">deviceId</span></td>
<td></td>
<td><span style="font-weight: 400">Device</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">deviceId</span></td>
<td></td>
<td><span style="font-weight: 400">Contact Point Id</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">phoneNumber</span></td>
<td></td>
<td><span style="font-weight: 400">Phone Number</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">phoneNumber</span></td>
<td></td>
<td><span style="font-weight: 400">Formatted E164 Phone Number</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">dateTime</span></td>
<td></td>
<td><span style="font-weight: 400">Created Date</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">partyIdentification</span></td>
<td><span style="font-weight: 400">deviceId</span></td>
<td><span style="font-weight: 400">Party Identification</span></td>
<td><span style="font-weight: 400">Party</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">deviceId</span></td>
<td></td>
<td><span style="font-weight: 400">Party Identification Id</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">IDName </span><i><span style="font-weight: 400">(a constant, like “Web ID”)</span></i></td>
<td></td>
<td><span style="font-weight: 400">Identification Name</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">IDType</span></td>
<td></td>
<td><span style="font-weight: 400">Party Identification Type</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">userId</span><i><span style="font-weight: 400"> (your known customer unique identifier like a CRM/loyalty ID)</span></i></td>
<td></td>
<td><span style="font-weight: 400">Identification Number</span></td>
</tr>
<tr>
<td></td>
<td><span style="font-weight: 400">dateTime</span></td>
<td></td>
<td><span style="font-weight: 400">Created Date</span></td>
</tr>
</tbody>
</table>
<p>The <code>deviceId</code> → <code>Party</code> mapping on the Contact Point DMOs is there because the SDK&#8217;s <code>deviceId</code> is what unifies the Contact Point record with the Individual DMO record (which itself has <code>deviceId</code> →<code> Individual Id</code>). They share the <code>deviceId</code> value as the join key. Party Identification DMO keeps <code>deviceId</code> as the anchor. Note the convention: <code>deviceId</code> goes into the <code>Party</code> field, and the known customer identifier (Lead ID, Contact ID, loyalty number) goes into Identification Number. The literal string &#8220;Web ID&#8221; (or similar) goes into<code> Identification Name</code>, so IR rulesets can target this identification type by name.</p>
<p><b>Why</b> <b>all</b> <b>three</b> <b>matter</b> <b>for</b> <b>Identity</b> <b>Resolution</b></p>
<p><span style="font-weight: 400">Each of these streams feeds a different identity rule:</span></p>
<ul>
<li><span>The </span><b><span>email</span></b><span> and </span><b><span>phone</span></b><span> streams give IR fuzzy-match anchors</span></li>
<li><span>The </span><b><span>partyIdentification</span></b><span> stream gives IR an </span><i><span>exact-match</span></i><span> anchor — when a user logs in or submits an authenticated form and you fire a partyIdentification event with their CRM ID for example, this is the deterministic bridge that says: &#8220;this anonymous </span><code>deviceId</code> is definitely this Lead/Contact.&#8221;</li>
</ul>
<h3><span style="font-weight: 400">3. Behavioral events stream</span></h3>
<p><span>This consolidated stream acts as a universal receiver for all engagement event types, including page views, product interactions, cart activity, and successful purchases. Because this stream is shared across multiple event types, the </span><code>eventType</code> field becomes the essential discriminator. It is the primary attribute used to route specific interaction models to their respective destination DMOs during the mapping phase.</p>
<h4><span style="font-weight: 400">Consent within the behavioral events stream</span></h4>
<p><span style="font-weight: 400">Before any meaningful tracking happens, the SDK needs a recorded consent decision for the visitor. Consent events are captured as a standard engagement event type and land in the Behavioral Events stream alongside every other engagement event. </span></p>
<p><span style="font-weight: 400">Within that stream, consent data appears as three prefixed fields:</span></p>
<ul>
<li><code><span>consentLog.provider</span></code><span>: The system that captured the consent decision (e.g. &#8220;Salesforce Interactions&#8221; when captured by the SDK directly)</span></li>
<li><code><span>consentLog.purpose</span></code><span>: What the visitor has consented to (e.g., tracking, personalization, targeting)</span></li>
<li><code>consentLog.status</code>: The decision itself (opt-in, opt-out)</li>
</ul>
<p><span style="font-weight: 400">If your website connector is deployed and you&#8217;re seeing no data at all in Data Explorer, check consent first. Without a recorded opt-in for the relevant purpose, the SDK will suppress downstream event tracking — which looks identical to a broken sitemap or schema. Always validate that consent is firing before you start debugging anything else.</span></p>
<p><b>Key configuration when deploying all data streams</b></p>
<ul>
<li><b>Refresh mode: </b><span style="font-weight: 400">You must consistently utilize </span><i><span style="font-weight: 400">Partial</span></i><span style="font-weight: 400"> for web data streams. Incremental mode is unsuitable here as it clears or overwrites values not explicitly present in the event payload, which invariably leads to significant data loss.</span></li>
<li><b>Data space: </b><span style="font-weight: 400">Designate the specific data space intended to receive the ingestion. Note that these streams can be extended to additional data spaces post-deployment without incurring redundant ingestion costs.</span></li>
</ul>
<h2><span style="font-weight: 400">Common field mappings</span></h2>
<p><span style="font-weight: 400">Standard field mappings often serve as a significant bottleneck during implementation. To help you avoid common pitfalls, here is a quick reference guide to ensure that your data correctly reaches its intended destination:</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206520" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260610124829/Screenshot-of-a-table-listing-each-website-schema-field-and-which-DMO-field-it-maps-to-e1781120921100.png?w=1000" class="postimages" width="1000" height="717" alt="Screenshot of a table listing each website schema field, and which DMO field it maps to" />
			  </span>
			</p>
<h2><span style="font-weight: 400">From Interactions SDK event to DLO</span></h2>
<p><span style="font-weight: 400">Grasping the end-to-end flow of your website events is fundamental for both debugging and schema design. This section provides a comprehensive reference for the lifecycle of each standard event type: from sending an event, through the Interactions SDK schema validation layer, and into the data stream that ultimately routes data to its target data lake object (DLO).</span></p>
<p><b>Architectural mapping: Schema objects to data streams</b></p>
<ul>
<li><span>Each </span><i><span>Profile</span></i><span> schema definition instantiates its own dedicated individual data stream, adhering to CamelCase naming conventions (e.g., </span><code><span>contactPointEmail</span></code><span> or </span><code><span>contactPointPhone</span></code><span>)</span></li>
<li><span>All </span><i><span>Engagement</span></i><span> schema definitions are consolidated into a universal Behavioral Events data stream, with fields utilizing an object-prefixed notation (e.g., </span><code>catalog.attributeColor</code>)</li>
</ul>
<p><span style="font-weight: 400">The following screenshot shows the architectural mapping for the Catalog interaction.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206521" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260610125025/The-architectural-mapping-for-the-Catalog-interaction-e1781121036970.png?w=1000" class="postimages" width="1000" height="625" alt="The architectural mapping for the Catalog interaction" />
			  </span>
			</p>
<p><span style="font-weight: 400">This screenshot shows the architectural mapping for the Order interaction.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206522" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260610125059/The-architectural-mapping-for-the-Order-interaction-e1781121070545.png?w=1000" class="postimages" width="1000" height="569" alt="The architectural mapping for the Order interaction" />
			  </span>
			</p>
<p><span style="font-weight: 400">This screenshot shows the architectural mapping for the Cart interaction.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206523" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260610125124/The-architectural-mapping-for-the-Cart-interaction-e1781121095830.png?w=1000" class="postimages" width="1000" height="532" alt="The architectural mapping for the Cart interaction" />
			  </span>
			</p>
<p><span style="font-weight: 400">This screenshot shows the architectural mapping for the Website Engagement interaction.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206524" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260610125151/The-architectural-mapping-for-the-Website-Engagement-interaction-e1781121130292.png?w=1000" class="postimages" width="1000" height="311" alt="The architectural mapping for the Website Engagement interaction" />
			  </span>
			</p>
<p><span style="font-weight: 400">And finally, this screenshot shows the architectural mapping for the Identity &amp; Contact Point interactions.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206525" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260610125249/The-architectural-mapping-for-the-Identity-Contact-Point-interactions-e1781121182901.png?w=1000" class="postimages" width="1000" height="707" alt="The architectural mapping for the Identity &amp; Contact Point interactions" />
			  </span>
			</p>
<p><b>Key insight: </b><span style="font-weight: 400">There are significant architectural advantages to leveraging standard, out-of-the-box DMOs for your mapping strategy. For instance, when capturing purchase transactions, line items, and product data, utilizing the Sales Order, Sales Order Product, and Goods Product DMOs ensures that Salesforce Personalization can natively recognize and process your data.</span></p>
<p>The Interactions SDK automatically injects several auto-populated fields into every event payload, including <code>category</code>, <code>dateTime</code>, <code>deviceId</code>, <code>eventId</code>, <code>eventType</code>, and <code>sessionId</code>. While these must be present in every schema object, they are handled entirely by the SDK — no manual configuration within your sitemap is required. The <code>eventType</code> field serves as the critical nexus; it binds your sitemap event to a specific schema definition and acts as the primary discriminator for routing data from the Behavioral Events stream to the appropriate destination DMO.</p>
<h2><span style="font-weight: 400">Deployment models: Sitemap-driven vs. Tag Manager-driven</span></h2>
<p><span style="font-weight: 400">Determining your event dispatch strategy hinges on a fundamental architectural choice: will you manage event capture natively within the sitemap, or leverage your Tag Management System? While both models are fully supported, they operate through distinct mechanisms and should not be implemented concurrently.</span></p>
<h3><span style="font-weight: 400">Option 1: Sitemap-driven architecture</span></h3>
<p>In this model, the sitemap serves as the central orchestrator for all tracking logic. You define <code>interaction</code> objects within your <code>pageTypes</code> array to capture page-load events, while utilizing <code>global.listeners</code> or explicit <code>sendEvent()</code> calls for behavioral interactions like clicks and form submissions. Because the sitemap is bundled into the CDN (Content Delivery Network) script generated by your website connector, your Tag Management System (TMS) simply injects a single <code>&lt;script&gt;</code> tag — eliminating the need for custom JavaScript within the TMS itself.</p>
<p><span style="font-weight: 400">This approach is ideal for maintaining a single source of truth for tracking logic. Furthermore, it is a strict requirement if your implementation includes Salesforce Personalization, as personalization experiences rely on the full sitemap context — including page types and content zones — to render correctly.</span></p>
<h3><span style="font-weight: 400">Option 2: Tag Manager-driven architecture</span></h3>
<p>Conversely, the TMS-led model utilizes a lean sitemap containing only <code>init()</code> and <code>initSitemap()</code>, shifting the responsibility for event dispatching to your TMS tags. Your TMS injects the CDN script and triggers page-level events via Page View triggers and behavioral data via Click or Form triggers. This provides greater flexibility for analytics teams who prefer working within a TMS and allows you to avoid managing JavaScript sitemaps directly within Data 360.</p>
<h4><span style="font-weight: 400">Dispatching events from a TMS</span></h4>
<p>When dispatching events from a TMS, you must explicitly set <code>"pageView": 1</code> in the payload for page-load events, or omit it for interaction-only events. To ensure a successful implementation, adhere to these critical technical requirements:</p>
<ul>
<li><b><span>Initialization sequence: </span></b><span>The </span><code><span>SalesforceInteractions.sendEvent()</span></code><span> method is only accessible after </span><code><span>init()</span></code><span> has resolved and </span><code><span>initSitemap()</span></code><span> has been invoked. Ensure that your </span><code><span>sendEvent</span></code><span> calls reside within the </span><code><span>.then()</span></code><span> callback of </span><code><span>init()</span></code><span>.</span></li>
<li><b><span>Race condition prevention: </span></b><span>When utilizing a TMS, always wrap your </span><code>sendEvent</code> calls in a safety check, such as <code>if (SalesforceInteractions)</code>, to prevent execution errors if the SDK has not finished loading.</li>
</ul>
<pre language="javascript">// Standard initialization pattern for TMS-based sendEvent calls
SalesforceInteractions.init({ ... }).then(() =&gt; {
    SalesforceInteractions.initSitemap(sitemapConfig);
    // Additional sendEvent calls from TMS are safe here
});
</pre>
<p><span style="font-weight: 400">The TMS-led approach may suit your organization if you manage a large number of brands or complex digital properties. Rather than maintaining different JavaScript sitemaps within Data 360 for each unique site, you can have just one lean sitemap for each site, with interactions managed via your Tag Management System. This can reduce the overhead of custom JavaScript maintenance within the Data 360 platform. </span></p>
<p><span style="font-weight: 400">It is best practice to leverage even a lean sitemap rather than trying to bypass this and do everything manually in a TMS. The CDN script that Data Cloud generates per tenant (see </span><a href="http://cdn.c360a.salesforce.com/beacon/c360a/abc123/scripts/c360a.min.js"><span style="font-weight: 400">example</span></a><span style="font-weight: 400">) already bundles the base SDK, the Data 360 module, and the sitemap, which can perform the initialization code above. This allows the sitemap to manage background operations like device ID persistence and consent orchestration.</span></p>
<p><span style="font-weight: 400">In practice, this architecture allows your TMS to orchestrate event capture just as it does for any other vendor. If you have an existing &#8220;form completed&#8221; trigger that currently dispatches data to your analytics and marketing pixels, you simply append a new logic block for Data 360.</span></p>
<pre language="javascript">// Example: TMS fires this snippet when a form-completion event triggers
SalesforceInteractions.sendEvent({
    interaction: {
        name: "Completed Form",
        attributes: {
            type: "Home Loan",
            id: "HomeLoanProduct-1",
            income: dataLayer.loanForm.income,
            loanAmount: dataLayer.loanForm.loanAmount
        }
    },
    user: {
        attributes: {
            id: "1234"
        }
    }
})
</pre>
<h3><span style="font-weight: 400">The personalization caveat: State vs. events</span></h3>
<p><span style="font-weight: 400">A critical distinction often overlooked by implementation teams is that the sitemap performs a dual role: it dispatches events while simultaneously establishing the global </span><i><span style="font-weight: 400">page state</span></i><span style="font-weight: 400">.</span></p>
<p>When you define <code>pageTypes</code> within a sitemap, you are doing more than specifying event triggers. You are initializing the contextual state of the page, identifying the current page type (e.g., a &#8220;Product Detail Page&#8221;), the specific anchor item (e.g., the product SKU), and the content zones eligible for personalization. This state is the foundational telemetry that Salesforce Personalization utilizes to execute logic such as:=</p>
<ul>
<li style="font-weight: 400"><b>&#8220;Inject a product recommendations carousel on all PDP pages&#8221;</b><span style="font-weight: 400">:  This requires explicit knowledge of the current page type</span></li>
<li style="font-weight: 400"><b style="color: #4a4a4a">&#8220;Surface recommendations related to the item currently being viewed&#8221;:</b><span style="font-weight: 400"> This requires an active anchor item ID</span></li>
</ul>
<p>Invoking <code>sendEvent()</code> in isolation, whether via a TMS or native application code, successfully transmits data to the Data 360 backend, but fails to set this local page state. While your events will reach Data 360, refresh customer profiles, and align with your DMO mappings, the personalization engine will lack the necessary context to identify the user&#8217;s current location or focal item. This architectural gap makes it significantly more complex to deploy context-aware, real-time personalized experiences.</p>
<h3><span style="font-weight: 400">The deciding question: </span></h3>
<p>If your Interactions SDK implementation is focused exclusively on data collection (triggering behavioral events), a TMS or application-led strategy is perfectly sufficient. However, if you intend to leverage Salesforce Personalization (either now or in a future phase) to render on-page experiences, the sitemap is required to maintain page state. This means your sitemap must define <code>pageTypes</code>, content zones, and anchor items, even if you supplement the architecture with additional <code>sendEvent()</code> calls from your TMS.</p>
<p><span style="font-weight: 400">Even in a pure TMS-driven data collection scenario, remember that the SDK still manages several background operations: consent orchestration, device ID persistence, and initial identity resolution. While these do not require explicit sitemap configuration, they are dependent on a properly initialized base SDK.</span></p>
<p>Lastly, never implement both approaches for the same set of interactions. If a sitemap triggers a <code>sendEvent</code> on page load and your TMS dispatches a parallel event for the same action, your data streams will suffer from duplication. Select a single architectural source of truth: either (a) centralize all tracking within the sitemap, or (b) utilize a lean sitemap and manage all event dispatching via your TMS.</p>
<h3><span style="font-weight: 400">Performance considerations</span></h3>
<p><span style="font-weight: 400">A frequent point of inquiry regarding the Interactions SDK is its potential impact on page performance. The SDK payload is approximately 100KB (minified), and its impact on page load is typically measured in mere milliseconds. By utilizing a Tag Manager to handle site-specific logic rather than loading it directly from Salesforce, the total script footprint remains highly optimized. We recommend running Lighthouse audits in Google Chrome on Interactions SDK deployments to demonstrate the negligible impact on overall performance scores.</span></p>
<h2><span style="font-weight: 400">Website connector strategy</span></h2>
<p>For most standard deployments, we recommend maintaining a 1:1 relationship between your website connectors and your domains. While the SDK technically permits a single connector to service multiple sites — typically by implementing conditional sitemap branching based on <code>window.location </code>— this architecture introduces unnecessary complexity and operational risk into your pipeline.</p>
<p><span style="font-weight: 400">When managing multiple brands or digital properties, your architectural strategy should hinge on your data governance requirements. If your goal is to enable cross-brand analytics and unified personalization, you should route these connectors into a single data space. Conversely, if your priority is strict data isolation and simplified governance, separate data spaces are the better path. Remember, you can deploy multiple connectors to feed a single data space, allowing you to maintain discrete sitemaps and schemas while still achieving a unified 360-degree view of your customer.</span></p>
<h2><span style="font-weight: 400">Building your schema iteratively</span></h2>
<p><span style="font-weight: 400">Rather than attempting to define your entire schema upfront, we recommend adopting a progressive implementation strategy to minimize operational risk. Deconstruct your web connector schema into discrete files, one for each specific event type. We recommend starting with a foundational behavioral event type, such as standard website engagement for tracking page views. Incorporating this event in your schema will mean that when you deploy the data stream and finalize the DMO mapping, you can verify that events are successfully ingested by validating the payload within Data Explorer. </span></p>
<p>We also strongly recommend an iterative approach: append the next event type and repeat the validation process. This approach allows you to validate each segment of the pipeline before introducing further architectural complexity. Furthermore, it leverages Data 360&#8217;s automated mapping engine: schema fields utilizing <code>masterLabel</code> values that provide an exact match to DMO field labels will be auto-mapped, significantly reducing manual configuration effort.</p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p>Our Interactions SDK appears deceptively simple on the surface — <code>init()</code>, <code>sendEvent()</code>, and you&#8217;re done. However, the internal transformation layer, schema validation, data stream routing, and DMO mapping that occur behind the scenes are the critical junctures where implementations either succeed or fail. Mastering this full pipeline — and being deliberate about your event types, schema keys, and stream configuration — is the difference between simply &#8220;events are firing&#8221; and ensuring that &#8220;data is powering real-time personalization.&#8221;</p>
<p><span style="font-weight: 400">Some personalization examples include an abandoned cart: trigger an email or SMS journey in Marketing Cloud when an AddToCart event isn&#8217;t followed by a Purchase within a defined window. Or on-site article recommendations: use the anchor item from View Catalog Object events to render a &#8220;related articles&#8221; carousel via Salesforce Personalization.</span></p>
<p><span style="font-weight: 400">Wrapping up, we recommend that you construct your sitemap first, then test it thoroughly while monitoring the console logs. Once you validate the events within Data Explorer, build your schema to mirror the actual data payloads you observe. By adopting this progressive implementation strategy, the architectural pieces will fall into place.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation: </span><a href="https://developer.salesforce.com/docs/data/salesforce-interactions-sdk/guide/c360a-api-salesforce-interactions-web-sdk.html"><span style="font-weight: 400">Salesforce Data 360 Web SDK Developer Documentation</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Integration guide: </span><a href="https://developer.salesforce.com/docs/data/data-cloud-int/guide/c360-a-mobile-web-sdk-schema-quick-guide.html"><span style="font-weight: 400">Salesforce Data 360 Web and Mobile SDK</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation: </span><a href="https://developer.salesforce.com/docs/data/salesforce-interactions-sdk/guide/c360a-api-translating-sdk-events-to-web-connector-schemas.html"><span style="font-weight: 400">Translation of SDK Events to Web Connector Schemas</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Blog post: </span><a href="https://developer.salesforce.com/blogs/2024/04/using-data-cloud-web-sdk-to-capture-engagement-on-your-website"><span style="font-weight: 400">Using Data Cloud Web SDK to Capture Engagement on Your Website</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Chris Charalambous</b><span style="font-weight: 400"> is a Distinguished AI &amp; Data Architect for the Salesforce Data 360 &amp; Agentforce Solutions team, helping customers design scalable solutions and better understand how Data 360 and Agentforce can impact their business. His background includes software engineering, data engineering, mobile messaging, marketing automation, and data platforms. Follow Chris on </span><a href="https://www.linkedin.com/in/charalambouschris"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p><b>Acknowledgements: </b><span style="font-weight: 400">A special thank you to Sergey Agadzhanov, Matija Vrzan, </span><a href="https://www.linkedin.com/in/lockryan/"><span style="font-weight: 400">Ryan Lock</span></a><span style="font-weight: 400">, and </span><a href="https://www.linkedin.com/in/felix-agung-4ba07b74/"><span style="font-weight: 400">Felix Agung</span></a><span style="font-weight: 400"> for their invaluable contributions and technical review of this article.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/understanding-the-data-360-web-sdk">Understanding the Data 360 Web SDK: From Website Event to Data Model</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/06/understanding-the-data-360-web-sdk/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206515</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260610110804/Generic-D-3-e1781114897893.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260610110804/Generic-D-3-e1781114897893.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Build Production-Ready Apps in Claude Code with Salesforce Skills</title>
		<link>https://developer.salesforce.com/blogs/2026/06/build-production-ready-apps-in-claude-code-with-salesforce-skills</link>
		<comments>https://developer.salesforce.com/blogs/2026/06/build-production-ready-apps-in-claude-code-with-salesforce-skills#respond</comments>
		<pubDate>Tue, 09 Jun 2026 15:07:12 +0000</pubDate>
		<dc:creator><![CDATA[Akshata Sawant]]></dc:creator>
				<category><![CDATA[Apex]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Lightning Web Components]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[Salesforce CLI]]></category>
		<category><![CDATA[Salesforce Code Analyzer]]></category>
		<category><![CDATA[Salesforce Skills]]></category>
		<category><![CDATA[Software Development]]></category>
		<category><![CDATA[Test Classes]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206498</guid>
		<description><![CDATA[<p>Build production-ready apps in Claude Code using Salesforce Skills to generate bulkified code, Apex controller logic, and test classes.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/build-production-ready-apps-in-claude-code-with-salesforce-skills">Build Production-Ready Apps in Claude Code with Salesforce Skills</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><a href="https://github.com/forcedotcom/sf-skills"><span style="font-weight: 400">Salesforce Skills</span></a><span style="font-weight: 400"> in Claude Code generate production-grade Apex, Lightning web components (LWCs), and test classes from natural language prompts. The code automatically passes </span><a href="https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview"><span style="font-weight: 400">Salesforce Code Analyzer</span></a><span style="font-weight: 400"> and achieves 75%+ test coverage. This means you can describe what you need in plain English, and Claude generates deployment-ready code that follows Salesforce best practices.</span></p>
<p><span style="font-weight: 400">In this post, we&#8217;ll walk through building a complete account creation form with LWC, Apex controller, and test classes. Then we&#8217;ll explain how the skill system works under the hood, and explore advanced patterns like batch jobs, service layers, and debugging workflows.</span></p>
<h2><span style="font-weight: 400">Understanding Salesforce Skills</span></h2>
<p><span style="font-weight: 400">Salesforce Skills are specialized AI capabilities that understand Salesforce architecture, governor limits, security patterns, and deployment requirements. Think of them as expert developers embedded directly into your development workflow.</span></p>
<p>When you ask Claude Code to &#8220;Create an account creation form,&#8221; it doesn&#8217;t just generate generic code. Behind the scenes, the <code>generating-apex</code> skill discovers your project conventions and generates production-grade code with proper error handling and governor-safe queries. It enforces guardrails like preventing SOQL in loops and validates bulkification patterns. The skill then creates test classes with 75%+ coverage using <code>TestDataFactory</code> patterns. Before marking the task complete, it runs Salesforce Code Analyzer and executes your test suites automatically.</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206500" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260608144207/Claude-Code-terminal-showing-the-generating-apex-skill-being-activated--e1780954943727.png?w=1000" class="postimages" width="1000" height="526" alt="Claude Code terminal showing the generating-apex skill being activated" />
			  </span>
			</p>
<h2><span style="font-weight: 400">How skills work under the hood</span></h2>
<p>When you make a request to Claude Code, the system follows a four-stage workflow. First, Claude analyzes your prompt and matches it to the appropriate skills. Keywords like &#8220;Lightning Web Component&#8221; activate <code>generating-lwc-components</code>, while &#8220;Apex class&#8221; triggers <code>generating-apex</code>, and &#8220;test class&#8221; activates <code>generating-apex-test</code>.</p>
<p>Once matched, each skill executes its workflow. The <code>generating-apex</code> skill discovers your project conventions, including naming patterns and existing classes. It chooses the correct pattern, i.e., Service, Controller, Batch, or another architecture, and reviews templates from its assets directory. The skill then generates your class with built-in guardrails that enforce sharing keywords and prevent SOQL in loops. Next, it calls the <code>generating-apex-test</code> skill to create comprehensive test coverage.</p>
<p>Throughout this process, Skills enforce guardrails automatically. They reject code with queries inside loops, force the sharing keyword on all classes, and won&#8217;t complete without generating test classes. For Lightning web components, they validate pattern formatting on both client and server sides. Every skill generates the required metadata files like <code>.cls-meta.xml</code> automatically.</p>
<p>Finally, before reporting success, Skills validate the generated code. They run <code>sf code-analyzer </code>to catch security violations, execute <code>sf apex run test</code> to verify functionality, and ensure 75%+ coverage. This means you get production-ready code, not just boilerplate.</p>
<h2><span style="font-weight: 400">Tutorial: Building an account creation form</span></h2>
<p><span style="font-weight: 400">Let&#8217;s build a real-world feature: a Lightning web component form that creates Account records with full validation, error handling, and test coverage.</span></p>
<h3><span style="font-weight: 400">Overview</span></h3>
<p><span>We&#8217;ll create a complete solution with three components: a Lightning web component with Account Name and Phone fields, an Apex Controller with an </span><code><span>@AuraEnabled</span></code><span> method and error handling, and a test class with 75%+ coverage and bulk testing for 251+ records. The form will include inline validation, toast notifications, and will be ready for deployment to App Builder and Home pages.</span></p>
<h3><span style="font-weight: 400">Prerequisites</span></h3>
<p><span>Before we dive in, make sure you have </span><a href="https://claude.ai/code"><u>Claude Code</u></a> installed (free for individual developers), Node.js for installing skills, and the <a href="https://developer.salesforce.com/tools/salesforcecli"><u>Salesforce CLI</u></a> authenticated to your org. You&#8217;ll need a Developer Edition org, sandbox, or scratch org. Run <code>sf org login web</code> to authenticate if you haven&#8217;t done so already.</p>
<h3><span style="font-weight: 400">Installing Salesforce Skills</span></h3>
<p><span style="font-weight: 400">Setting up Salesforce Skills takes just one command. Navigate to your Salesforce project directory and run:</span></p>
<pre language="text">npx skills add forcedotcom/sf-skills
</pre>
<p><span style="font-weight: 400">This pulls the official Salesforce Skills library from GitHub.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206501" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260608144246/Interactive-skill-selection-interface-showing-various-Salesforce-skills.png?w=968" class="postimages" width="968" height="994" alt="Interactive skill selection interface showing various Salesforce skills" />
			  </span>
			</p>
<p><span style="font-weight: 400">Use the </span><b>spacebar</b><span style="font-weight: 400"> to select the skills you want. For this tutorial, select:</span></p>
<ul>
<li><code>generating-apex</code>: For Apex class generation</li>
<li><code>generating-apex-test</code>: For Apex test class generation</li>
<li><code>generating-lwc-components</code>: For Lightning web component generation</li>
<li><code>debugging-apex-logs</code>: To debug your Apex classes</li>
</ul>
<p><span style="font-weight: 400">Press </span><b>Enter</b><span style="font-weight: 400"> to install. </span></p>
<p><span style="font-weight: 400">You can select the installation scope: </span></p>
<ul>
<li style="font-weight: 400"><b>Project:</b><span style="font-weight: 400"> Install in current directory (committed with you projects) </span></li>
<li style="font-weight: 400"><b style="color: #4a4a4a">Global:</b><span style="font-weight: 400">  Install in home directory (available for all your projects)</span></li>
</ul>
<p>The skills are now available in your Salesforce Project under <code>your-salesforce-project/skills-lock.json</code>.<code> </code></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206502" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260608144334/Screenshot-of-a-Salesforce-Project-folder-showing-installed-skills.png?w=678" class="postimages" width="678" height="405" alt="Screenshot of a Salesforce Project folder showing installed skills" />
			  </span>
			</p>
<p><span style="font-weight: 400">You can also take a look at all the skills in the </span><span style="font-weight: 400">skills-lock.json</span><span style="font-weight: 400"> file to view your installed skills.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206503" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260608144404/Screenshot-of-the-skills-lock.json-file-showing-installed-skills-e1780955067628.png?w=1000" class="postimages" width="1000" height="855" alt="Screenshot of the skills-lock.json file showing installed skills" />
			  </span>
			</p>
<p><span style="font-weight: 400">Currently, the forcedotcom/sf-skills library includes 60 + skills — and they’re growing. View the full list at the </span><a href="https://github.com/forcedotcom/sf-skills"><span style="font-weight: 400">official sf-skills repository</span></a><span style="font-weight: 400">.</span></p>
<p>To install all available skills, add the <code>--all</code> flag</p>
<pre language="text">npx skills add forcedotcom/sf-skills --all
</pre>
<p><span style="font-weight: 400">Note: </span><span style="font-weight: 400">Salesforce skills are NOT packaged as a npm package. We also do not own the </span><a href="https://www.npmjs.com/package/skills"><span style="font-weight: 400">&#8220;skills&#8221; npm package</span></a><span style="font-weight: 400">. We use it as a convenient way to install our skills from our GitHub repo.</span></p>
<h3><span style="font-weight: 400">Step 1: Start Claude Code</span></h3>
<p><span style="font-weight: 400">Launch Claude Code in your project directory.</span></p>
<pre language="text">claude
</pre>
<p><span style="font-weight: 400">You&#8217;ll see the Claude Code interactive prompt. This is where the magic happens.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206504" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260608144445/Claude-Code-command-line-interface-showing-the-welcome-message-and-prompt-ready-for-input-e1780955128999.png?w=1000" class="postimages" width="1000" height="246" alt="Claude Code command-line interface showing the welcome message and prompt ready for input" />
			  </span>
			</p>
<h3><span style="font-weight: 400">Step 2: Make your request</span></h3>
<p><span style="font-weight: 400">Type or paste the following prompt.</span></p>
<pre language="text">Create a complete Salesforce solution with the following components:

1. Lightning Web Component (LWC)
Build a form component named accountCreationForm with two input fields:
- Account Name (required)
- Phone (required, 10-15 digits)
- Include a Save button disabled until both fields are valid
- Display inline validation error messages
- Show success toast on save, error toast on failure
- Handle loading state with spinner

2. Apex Controller
- Create AccountCreationController class
- Method: @AuraEnabled(cacheable=false) saveAccount(String accountName, String phone)
- Use standard Phone field (Account has no standard Email field)
- Proper error handling with AuraHandledException
- Test class with ≥85% coverage

3. App Builder Integration
- Configure *.js-meta.xml for App Page and Home Page
- Provide deployment instructions

Constraints:
- Use imperative Apex (not @wire) since this is DML
- Follow Salesforce LWC best practices
- Standard objects and fields only
</pre>
<p><span style="font-weight: 400">Here’s what the complete prompt for creating an Account Creation form, including all the requirements listed, looks like in the terminal. </span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206511" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260609002034/image5-e1780989648382.png?w=1000" class="postimages" width="1000" height="526" alt="Terminal showing the complete prompt for creating an Account Creation form" />
			  </span>
			</p>
<h3><span style="font-weight: 400">Step 3: Watch Skills in action</span></h3>
<p>After you submit the prompt, Claude Code automatically activates the right skills: <code>generating-lwc-components</code> for the UI, <code>generating-apex</code> for the controller, and <code>generating-apex-test</code> for tests.</p>
<p>It discovers your project patterns, including existing naming conventions and architectural layers. Then it generates all required files:</p>
<ul>
<li><code>accountCreationForm.html: </code>Lightning Web Component template</li>
<li><code>accountCreationForm.js: </code>Component logic with imperative Apex calls</li>
<li><code>accountCreationForm.js-meta.xml</code>: Metadata for App/Home page exposure</li>
<li><code>AccountCreationController.cls:</code> Apex controller with error handling</li>
<li><code>AccountCreationController.cls-meta.xml:</code><code> </code>Apex metadata file</li>
<li><code>AccountCreationControllerTest.cls:</code> Test class with test methods</li>
<li><code>AccountCreationControllerTest.cls-meta.xml:</code> Test metadata file</li>
</ul>
<h3><span style="font-weight: 400">Step 4: Review the generated code and test classes</span></h3>
<p><span style="font-weight: 400">Review the generated code and the test classes. You’ll notice that they are production-ready with the help of Salesforce Skills, and we’ve achieved 100% test coverage for the Apex Class.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206505" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260608145328/Salesforce-CLI-output-showing-test-execution-results.png?w=624" class="postimages" width="624" height="934" alt="Salesforce CLI output showing test execution results" />
			  </span>
			</p>
<h3><span style="font-weight: 400">Step 5: Deploy and test your solution</span></h3>
<p><span style="font-weight: 400">Since we’re using Claude, it can help us deploy and test our solution, or you can use Salesforce CLI commands. </span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206506" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260608145356/Salesforce-CLI-showing-successful-deployment-of-Lightning-web-component-Apex-controller-and-test-class-files.png?w=614" class="postimages" width="614" height="975" alt="Salesforce CLI showing successful deployment of Lightning web component, Apex controller, and test class files" />
			  </span>
			</p>
<h3><span style="font-weight: 400">Step 6: Add component to your org’s Home page</span></h3>
<p><span style="font-weight: 400">Navigate to </span><b>Setup → Lightning App Builder</b><span style="font-weight: 400"> and click </span><b>New → Select Home Page</b><span style="font-weight: 400">. Choose a template like &#8220;Two Regions.&#8221; From the Custom components section, drag </span><b>Account Creation Form</b><span style="font-weight: 400"> onto the page. Click </span><b>Save → Activation → Assign</b><span style="font-weight: 400"> as Org Default.</span></p>
<p>The screenshot below shows the Lightning App Builder interface with the <code>accountCreationForm</code> component visible in the custom components panel and being placed in a Home Page layout.</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206507" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260608145422/Lightning-App-Builder-interface-with-the-accountCreationForm-component-visible-e1780955674602.png?w=1000" class="postimages" width="1000" height="511" alt="Lightning App Builder interface with the accountCreationForm component visible" />
			  </span>
			</p>
<h3><span style="font-weight: 400">Step 7: Test in Salesforce UI</span></h3>
<p><span style="font-weight: 400">Navigate to your Salesforce Home page and test the component. Leave Account Name empty and an error message appears. Enter an invalid phone number with nine digits and an error message appears. Enter valid data and the Save button enables. </span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206509" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260609001730/Salesforce-Home-page-displaying-the-Account-Creation-Form-componentAdvanced-use-cases-e1780989466621.png?w=1000" class="postimages" width="1000" height="228" alt="Salesforce Home page displaying the Account Creation Form componentAdvanced use cases" />
			  </span>
			</p>
<p>This tutorial covered a single LWC plus Apex controller pattern, but Skills handle much more complex scenarios, including batch jobs with <code>Database.Stateful</code>, Service-Selector-Domain layered architectures, screen flows with decision routing, and debug log analysis.</p>
<p><span style="font-weight: 400">Here are five patterns that you can build with Salesforce Skills:</span></p>
<h3><span style="font-weight: 400">1. Generate a batch job for data processing</span></h3>
<p><span style="font-weight: 400">Ask Claude to &#8220;Create a batch Apex class to archive Cases older than two years named CaseArchivalBatch. Query Cases where ClosedDate &lt; LAST_N_YEARS:2, update Status__c to &#8216;Archived&#8217;, and include proper error handling and logging. Generate test class with 75%+ coverage.&#8221;</span></p>
<p>Claude activates <code>generating-apex</code> and generates <code>CaseArchivalBatch.cls</code> with <code>Database.Stateful</code> for error tracking, proper start/execute/finish methods, governor-safe queries with no SOQL in loops, and <code>CaseArchivalBatchTest.cls</code> with bulk testing for 251+ records.</p>
<h3><span style="font-weight: 400">2. Build a service layer class</span></h3>
<p><span style="font-weight: 400">Request &#8220;Create an AccountService class with a method to update Account billing addresses in bulk using the Service-Selector-Domain pattern. The method signature should be updateBillingAddresses(Map&lt;Id, Address&gt; addressByAccountId). Include proper error handling and generate test class with 90%+ coverage.&#8221;</span></p>
<p>Claude generates <code>AccountService.cls</code> with the with sharing keyword and bulkified DML, delegates queries to <code>AccountSelector.cls</code>, returns <code>List&lt;Database.SaveResult&gt;</code> for partial-success handling, and creates a test class using the <code>TestDataFactory</code> pattern.</p>
<h3><span style="font-weight: 400">3. Create a screen flow</span></h3>
<p><span style="font-weight: 400">Prompt &#8220;Create a screen flow for lead qualification named Lead_Qualification. Add screens for Company Name, Industry, and Annual Revenue. Include a decision element that routes to appropriate queues based on revenue. Assign to Enterprise Queue if revenue &gt; $1M, else assign to SMB Queue.&#8221;</span></p>
<p>Claude activates <code>generating-flow</code> and generates Flow metadata following Salesforce best practices.</p>
<h3><span style="font-weight: 400">4. Debug Apex logs</span></h3>
<p><span style="font-weight: 400">Tell Claude &#8220;Analyze the Apex logs in debug.log to find why the batch job is hitting governor limits.&#8221; </span></p>
<p>Claude uses the <code>debugging-apex-logs</code> skill to parse log files for SOQL and DML statements, identify SOQL-in-loop violations, report CPU time and heap size consumption, and suggest optimizations.</p>
<h2><span style="font-weight: 400">Best practices for working with Skills</span></h2>
<p><span style="font-weight: 400">The more context you provide in your prompts, the better the output. Instead of &#8220;Create an Apex class,&#8221; try &#8220;Create an Apex controller for my AccountForm LWC that saves Account records with Name and Phone validation.&#8221; Include field API names, object relationships, and any specific business logic requirements.</span></p>
<p><span style="font-weight: 400">Claude Code is conversational, so if the first pass isn&#8217;t quite right, just ask for changes. You can say &#8220;Add bulk error handling to that controller&#8221; or &#8220;Change the test class to use TestDataFactory patterns.&#8221; Skills maintain context across the conversation and update the code accordingly.</span></p>
<p><span style="font-weight: 400">While Skills generate production-grade code, always review the output before deploying to production. Verify that field API names match your org schema, and check that custom objects and fields exist in your target org. Review security settings including sharing rules and field-level security. Test in sandbox first before production deployment, and run Salesforce Code Analyzer manually as an extra safety check.</span></p>
<p>Even if you&#8217;re an experienced developer, Skills can teach you something new. Try using them once to discover best practices that you might have missed, see how test classes should be structured with <code>Given/When/Then</code> patterns, and learn which Code Analyzer flags are most important for security and performance. Skills are not just automation — they&#8217;re also a teaching tool.</p>
<h2><span style="font-weight: 400">Expanding your Skills toolkit</span></h2>
<p><span style="font-weight: 400">Now that you&#8217;ve seen Skills in action, explore the </span><a href="https://github.com/forcedotcom/sf-skills"><span style="font-weight: 400">full Skills library</span></a><span style="font-weight: 400"> to see all 60+ available skills. You can build OmniStudio solutions with Flexcards and Integration Procedures, connect Data Cloud data sources, create B2B commerce stores, and generate screen flows with orchestrations.</span></p>
<p>Try advanced patterns like <code>Trigger Frameworks</code> using the Trigger Actions Framework (TAF), <code>Queueable with Finalizers</code> for async operations with cleanup logic, and <code>Custom REST Resources</code> to build versioned APIs with proper error handling.</p>
<p><span style="font-weight: 400">Skills are open source, which means you can fork the repository and customize patterns, add your team&#8217;s naming conventions to templates, and create organization-specific skills. See the </span><a href="https://github.com/forcedotcom/sf-skills/blob/main/CONTRIBUTING.md"><span style="font-weight: 400">contribution guide</span></a><span style="font-weight: 400"> to get started.</span></p>
<p><span style="font-weight: 400">Finally, integrate Skills with your existing workflow. They work seamlessly with the </span><a href="https://marketplace.visualstudio.com/items?itemName=Anthropic.claude-code"><span style="font-weight: 400">Claude Code VS Code extension</span></a><span style="font-weight: 400">, CI/CD pipelines for generating code in automated workflows, and code review processes as a first-pass reviewer before PR submission.</span></p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">Salesforce Skills in Claude Code transform AI from a code generator into a development expert. Instead of managing boilerplate and governor limits manually, you describe your goals and Claude implements them using production-grade patterns.</span></p>
<p><span style="font-weight: 400">From generating Apex and LWCs to debugging logs, Skills accelerate your workflow while reinforcing best practices. This allows you to focus on solving business problems rather than repetitive implementation tasks.</span></p>
<p>Ready to get started? <a href="https://claude.ai/code"><u>Download Claude Code for free</u></a> and run <code>npx skills</code> to add <code>forcedotcom/sf-skills</code> in your Salesforce Project. Within minutes, you&#8217;ll be generating production-ready code from natural language prompts.</p>
<p><span style="font-weight: 400">Have questions or want to share what you&#8217;ve built with Skills? Join the conversation in the </span><a href="https://developer.salesforce.com/forums"><span style="font-weight: 400">Salesforce Developer Community</span></a><span style="font-weight: 400"> or connect with us on </span><a href="https://www.linkedin.com/showcase/salesforce-developers/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400"> and </span><a href="https://twitter.com/SalesforceDevs"><span style="font-weight: 400">Twitter/X</span></a><span style="font-weight: 400">. We&#8217;d love to hear about your experience.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><a href="http://github.com/forcedotcom/sf-skills"><span style="font-weight: 400">Salesforce Skills Library</span></a><b> </b></li>
<li style="font-weight: 400"><a href="http://claude.ai/code"><span style="font-weight: 400">Claude Code Documentation</span></a><b> </b></li>
<li style="font-weight: 400"><a href="http://docs.anthropic.com/claude-code"><span style="font-weight: 400">Claude Code Getting Started Guide</span></a><b> </b></li>
<li style="font-weight: 400"><a href="http://agentskills.io"><span style="font-weight: 400">Agent Skills Specification</span></a><b> </b></li>
<li style="font-weight: 400"><a href="http://developer.salesforce.com/tools/salesforcecli"><span style="font-weight: 400">Salesforce CLI Reference</span></a><b> </b></li>
<li style="font-weight: 400"><a href="http://trailhead.salesforce.com/content/learn/modules/lightning-web-components-basics"><span style="font-weight: 400">Trailhead: </span><span style="font-weight: 400">LWC Basics</span></a><b> </b></li>
<li style="font-weight: 400"><a href="http://trailhead.salesforce.com/content/learn/modules/apex_testing"><span style="font-weight: 400">Trailhead: Apex Testing</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Akshata Sawant</b><span style="font-weight: 400"> is a Senior Developer Advocate at Salesforce and co-author of a book titled “MuleSoft for Salesforce Developers,” published by Packt Publication. For a more in-depth look at Akshata’s accomplishments, visit her </span><a href="https://www.linkedin.com/in/akshatasawant02/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400"> profile. </span></p>
<p>&nbsp;</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/build-production-ready-apps-in-claude-code-with-salesforce-skills">Build Production-Ready Apps in Claude Code with Salesforce Skills</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/06/build-production-ready-apps-in-claude-code-with-salesforce-skills/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206498</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260608135949/SingleHeadshot-3-e1780952406426.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260608135949/SingleHeadshot-3-e1780952406426.png?w=1000" medium="image" />
	</item>
		<item>
		<title>The Salesforce Developer’s Guide to the Summer ’26 Release</title>
		<link>https://developer.salesforce.com/blogs/2026/06/the-salesforce-developers-guide-to-the-summer-26-release</link>
		<comments>https://developer.salesforce.com/blogs/2026/06/the-salesforce-developers-guide-to-the-summer-26-release#respond</comments>
		<pubDate>Mon, 08 Jun 2026 15:00:21 +0000</pubDate>
		<dc:creator><![CDATA[Mohith Shrivastava]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Apex]]></category>
		<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[Data 360]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Headless 360]]></category>
		<category><![CDATA[Lightning Web Components]]></category>
		<category><![CDATA[Salesforce Releases]]></category>
		<category><![CDATA[Agent Script]]></category>
		<category><![CDATA[Agentforce Builder]]></category>
		<category><![CDATA[Agentforce Mobile SDK]]></category>
		<category><![CDATA[and Accessibility]]></category>
		<category><![CDATA[Code Extension]]></category>
		<category><![CDATA[Data Cloud]]></category>
		<category><![CDATA[developer tooling]]></category>
		<category><![CDATA[GraphQL Mutations]]></category>
		<category><![CDATA[Model context protocol]]></category>
		<category><![CDATA[Salesforce CLI]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[SOAP login retirement]]></category>
		<category><![CDATA[State Managers]]></category>
		<category><![CDATA[trust]]></category>
		<category><![CDATA[User Mode by Default]]></category>
		<category><![CDATA[Web Console Beta]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206465</guid>
		<description><![CDATA[<p>Summer &rsquo;26 developer highlights: Hosted MCP Servers, LWC State Managers, Apex user-mode defaults, Agentforce Mobile SDK, and CLI updates with code examples.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/the-salesforce-developers-guide-to-the-summer-26-release">The Salesforce Developer’s Guide to the Summer ’26 Release</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><span style="font-weight: 400">The </span><a href="https://admin.salesforce.com/blog/2026/admin-summer-26-release-countdown"><span style="font-weight: 400">Summer ’26 release</span></a><span style="font-weight: 400"> is rolling out to your sandbox environments through May and June, and is scheduled to go live in production mid-June. Specifically, the key dates for this release are: May 8, 2026 (sandbox preview), and May 15, June 5, June 12, and June 13, 2026 (production rollouts), depending on your instance. Check the </span><a href="https://status.salesforce.com/products/all/maintenances?_ga=2.91603481.2022017460.1780421510-826685783.1780373061"><span style="font-weight: 400">maintenance calendar</span></a><span style="font-weight: 400"> for your specific org. </span></p>
<p><span style="font-weight: 400">In this post, we’ll take a look at the highlights for developers across Salesforce </span><span style="font-weight: 400">Headless 360, </span><span style="font-weight: 400">Lightning Web Components (LWC), Apex, Agentforce, Data 360, and Agentforce 360 Platform developer tools.</span></p>
<h2><span style="font-weight: 400">Salesforce Headless 360</span></h2>
<p><a href="https://developer.salesforce.com/blogs/2026/05/headless-360-what-it-means-for-developers"><span style="font-weight: 400">Headless 360</span></a><span style="font-weight: 400"> makes every major Salesforce capability available as an API, Model Context Protocol (MCP) tool, or CLI command — accessible to any authenticated caller, whether that&#8217;s an app, a human, or an autonomous AI agent. It&#8217;s the biggest theme of the Summer &#8217;26 release. Headless 360 brings several innovations: hosted MCP servers, new MCP tools, coding skills, and CLI and API enhancements. </span><span style="font-weight: 400">Let’s dive into the primary developer features related to Headless 360 available in this release.</span></p>
<h3><span style="font-weight: 400">Salesforce Hosted MCP Servers</span></h3>
<p><span style="font-weight: 400">Salesforce Hosted MCP Servers let you connect any MCP-compatible AI client, such as Claude, ChatGPT, Cursor, or custom agents, to your Salesforce org and data through the open MCP standard. Every connection uses standard </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/setup-overview.html"><span style="font-weight: 400">OAuth authentication</span></a><span style="font-weight: 400">, so your agents interact with Salesforce data and automation in a secure, governed way. Because Salesforce hosts them, there&#8217;s no additional infrastructure to manage. </span></p>
<p><span style="font-weight: 400">You get two flavors: pre-built </span><b>standard servers</b><span style="font-weight: 400">, and </span><b>custom servers</b><span style="font-weight: 400"> that you define yourself.</span></p>
<h4><span style="font-weight: 400">Standard MCP Servers</span></h4>
<p><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/servers-reference.html#sobject-servers"><span style="font-weight: 400">Salesforce Hosted Standard MCP Servers</span></a><span style="font-weight: 400"> are now generally available. Salesforce provides several pre-built standard hosted MCP servers, including:</span></p>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/servers-reference.html#sobject-servers"><span style="font-weight: 400">SObject Servers</span></a><span style="font-weight: 400">: SObject CRUD, SOQL queries, search</span></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/references/reference/data-cloud-sql.html"><span style="font-weight: 400">Data 360</span></a><span style="font-weight: 400">: Data 360 queries and graph traversal</span></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/references/reference/tableau-next.html"><span>Tableau</span></a><span style="font-weight: 400">: Analytics and visualization</span></li>
</ul>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206473" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260605093402/Salesforce-Hosted-Standard-MCP-Servers-e1780677258898.png?w=1000" class="postimages" width="1000" height="487" alt="Salesforce Hosted Standard MCP Servers" />
			  </span>
			</p>
<h4><span style="font-weight: 400">Custom MCP Servers</span></h4>
<p><span style="font-weight: 400">When standard MCP servers aren&#8217;t enough, you can build </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/custom-servers.html"><span style="font-weight: 400">custom MCP servers</span></a><span style="font-weight: 400"> with granular control over which tools and prompts you expose. Custom MCP servers respect the full sharing and security model you have configured for your Salesforce org. Custom MCP tools can be built from:</span></p>
<ul>
<li><b>Apex Actions:</b> Expose <code>@</code><code>InvocableMethod</code> (see <a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/invocable-actions.html"><u>docs</u></a>) annotated methods as MCP tools</li>
<li><b>Lightning Flows:</b> Expose autolaunched flows as MCP tools</li>
<li><b>Apex REST:</b> Expose custom Apex REST endpoints as MCP tools</li>
<li><b>AuraEnabled:</b> Expose <code>@AuraEnabled</code> annotated Apex methods as MCP tools</li>
<li><b>Named Query API:</b> Expose <a href="https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_named_query_intro.htm"><u>parameterized SOQL</u></a> queries as MCP tools</li>
<li><b>Prompt Builder:</b> Expose <a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/prompt-builder.html"><u>prompts from Prompt Builder</u></a> as MCP prompts</li>
<li><b>Agentforce:</b> Expose <a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/agentforce.html"><u>Agentforce agents</u></a> as MCP tools</li>
<li><b>API Catalog:</b> Map <a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/api-catalog.html"><u>Salesforce API Catalog</u></a> (curated registry of REST API endpoints) to MCP tools</li>
</ul>
<p><span style="font-weight: 400">For a complete walkthrough, including OAuth configuration details and connecting Claude Desktop and Claude Code, read </span><a href="https://developer.salesforce.com/blogs/2026/05/connect-claude-with-salesforce-hosted-mcp-servers"><span style="font-weight: 400">Connect Claude with Salesforce Hosted MCP Servers</span></a><span style="font-weight: 400"> and </span><a href="https://developer.salesforce.com/blogs/2026/05/expose-custom-apex-as-a-hosted-mcp-tool-for-agents"><span style="font-weight: 400">Expose Custom Apex as a Hosted MCP Tool for Agents</span></a><span style="font-weight: 400">.</span></p>
<h3><span style="font-weight: 400">Developer and designer MCP servers and tools</span></h3>
<p><span style="font-weight: 400">Developers and designers get a productivity boost from MCP-powered tools that bring AI assistance directly into the IDE and coding agents.</span></p>
<p><a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_mcp.htm"><b>Salesforce DX MCP server (Beta)</b></a><span style="font-weight: 400">: </span><b> </b><span style="font-weight: 400">T</span><span style="font-weight: 400">wo important tools land here. </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/mcp-slds.html"><span style="font-weight: 400">SLDS Guideline tools</span></a><span style="font-weight: 400"> speeds up UI work with instant Salesforce Lightning Design System (SLDS) styling-hook and component-blueprint guidance. </span><a href="https://developer.salesforce.com/blogs/2026/04/performance-first-apex-development-with-apexguru-in-salesforce-dx-mcp-server"><span style="font-weight: 400">ApexGuru  </span></a><span style="font-weight: 400">brings Apex code review into your coding agent from your org&#8217;s runtime metrics.  It flags and fixes anti-patterns inline, such as SOQL or DML inside loops and redundant SOQL, and its Test Case Insights surface inefficient tests that drag down coverage.</span></p>
<p><a href="https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_salesforce_api_mcp_intro.htm"><b>Metadata API Context MCP Server (Beta)</b></a><span style="font-weight: 400">: This server now ships five granular tools instead of one. These tools provide contextual information about Salesforce metadata types to help generate accurate metadata files, with faster responses and more efficient token usage.</span></p>
<p><a href="https://developer.salesforce.com/blogs/2026/05/introducing-the-data-360-mcp-server-developer-preview"><b>Data 360 MCP Server (Developer Preview)</b></a><span style="font-weight: 400">: </span><span style="font-weight: 400"> </span><span style="font-weight: 400">This open-source server connects your coding agent to Data 360. Instead of exposing roughly 200 REST operations one by one, it fronts them with three facade tools — </span><b>search</b><span style="font-weight: 400"> (find a capability by intent), </span><b>payload_examples</b><span style="font-weight: 400"> (fetch a working request body), and </span><b>execute</b><span style="font-weight: 400"> (run it) — which keep the coding agent from blowing its context window. </span></p>
<p><a href="https://developer.salesforce.com/blogs/2026/01/accelerate-flexcard-development-with-omnistudio-mcp"><b>Omnistudio MCP Server (Beta)</b></a><span style="font-weight: 400">: </span><span style="font-weight: 400"> This server </span><span style="font-weight: 400">bridges agentic and low-code development. Use your coding agent to turn requirements — plain text, screenshots, or UX mockups — into functional </span><a href="https://help.salesforce.com/s/articleView?id=xcloud.os_omnistudio_flexcards_24388.htm&amp;type=5"><span style="font-weight: 400">Flexcard templates</span></a><span style="font-weight: 400">.</span></p>
<p><a href="https://salesforcecommercecloud.github.io/b2c-developer-tooling/mcp/"><b>B2C DX MCP Server</b></a><span style="font-weight: 400">: Modify your Storefront Next components quickly with the Figma-to-Component tool set, converting design files directly into components.</span></p>
<p><a href="https://developer.salesforce.com/blogs/2026/06/the-mcp-server-for-marketing-cloud-engagement-is-now-ga"><b>Marketing Cloud Engagement MCP Server</b></a><span style="font-weight: 400">: </span><span style="font-weight: 400">Securely connect external AI agents to Marketing Cloud Engagement and expose core developer capabilities like data extensions and journeys as natural-language tools.</span></p>
<h3><span style="font-weight: 400">Agent Skills for coding agents</span></h3>
<p><a href="https://agentskills.io/home"><span style="font-weight: 400">Agent Skills</span></a><span style="font-weight: 400"> are a lightweight, open format for extending AI agent capabilities with specialized knowledge and workflows. In this release, we are open-sourcing a </span><a href="https://github.com/forcedotcom/sf-skills"><span style="font-weight: 400">library of Salesforce development skills</span></a><span style="font-weight: 400">. The skills are optimized to work with the Salesforce coding agent Agentforce Vibes, and are also compatible with any other coding agent, such as Claude Code or Codex.</span></p>
<p><span>Skills come pre-packaged with Agentforce Vibes. For any other coding agent, install them using the npx command:</span><span> </span><code>npx skills add forcedotcom/sf-skills.</code></p>
<h3><span style="font-weight: 400">Salesforce CLI updates</span></h3>
<p><a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_top.htm"><span style="font-weight: 400">Salesforce CLI&#8217;s</span></a><span style="font-weight: 400"> 220+ commands are a core part of Salesforce Headless 360. The CLI keeps shipping every week, and recent releases shipped several updates worth knowing about. We&#8217;ll look at the highlights for developers, organized by what they help you build. The emphasis is on </span><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/agent-dx.html"><span style="font-weight: 400">Agentforce DX tooling</span></a><span style="font-weight: 400"> and a major credential security overhaul.</span></p>
<h4><span style="font-weight: 400">Build agents from a working starting point:</span></h4>
<ul>
<li><b>Agent project scaffolding:</b> Spin up a runnable Agentforce sample instead of starting from scratch. The <code>agent</code> template generates a Local Info Agent demonstrating Apex, Prompt Template, and Flow subagents.</li>
</ul>
<pre language="sh">sf template generate project --name my-agent --template agent
</pre>
<ul>
<li><b>One-command agent user:</b><span style="font-weight: 400"> Automate the setup of service agent users, eliminating the need for manual provisioning.</span></li>
</ul>
<pre language="sh">sf org create agent-user --first-name Service --last-name Agent --target-org my-org
</pre>
<h4><span style="font-weight: 400">Test, preview, and debug agents:</span></h4>
<ul>
<li><b>Agent preview is GA:</b> You can now script interactive test sessions end to end with <code>agent preview start</code>, <code>send</code>, <code>sessions</code>, and <code>end</code>.</li>
<li><b>Trace files for diagnosis:</b> Inspect and manage the traces recorded during a preview session to see exactly how your agent routed and acted.</li>
</ul>
<pre language="sh">sf agent trace read --session-id  --format detail --dimension actions
</pre>
<ul>
<li><b>Richer evaluations (Beta):</b><span style="font-weight: 400"> Run YAML- or JSON-defined evaluation tests for higher-quality, repeatable agent testing.</span></li>
</ul>
<pre language="sh">sf agent test run-eval --spec specs/my-agent-testSpec.yaml --target-org my-org
</pre>
<h4><span style="font-weight: 400">Keep credentials out of your logs:</span></h4>
<ul>
<li><b>Secrets redacted by default:</b> Access tokens, SFDX auth URLs, and passwords are now stripped from commands like <code>org display</code> and <code>org list --json</code>, preventing accidental leaks in continuous integration (CI) and logs.</li>
<li><b>Deliberate secret retrieval:</b> When you actually need a credential, ask for it explicitly.</li>
</ul>
<pre language="sh">sf org auth show-access-token --target-org my-org
</pre>
<p><span style="font-weight: 400">As always, the deep-dive details for every command and flag live in the </span><a href="https://github.com/forcedotcom/cli/blob/main/releasenotes/README.md"><span style="font-weight: 400">Salesforce CLI release notes</span></a><span style="font-weight: 400">. Read them to go further.</span></p>
<h3><span style="font-weight: 400">Salesforce Platform API updates</span></h3>
<p><span style="font-weight: 400">The platform&#8217;s APIs are a big part of Headless 360. Summer &#8217;26 ships </span><b>API version 67.0</b><span style="font-weight: 400">, and a few changes are worth knowing about as you build. </span></p>
<h4><span style="font-weight: 400">Plan now: SOAP </span><span style="font-weight: 400">login()</span><span style="font-weight: 400"> is being retired</span></h4>
<p><span>This is the most impactful change in this release for integration owners. SOAP </span><code>login()</code><span> in API versions 31.0–64.0 </span><a href="https://help.salesforce.com/s/articleView?id=005132110&amp;type=1"><u>retires in Summer &#8217;27</u></a><span>. Any integration that authenticates with a username and password over SOAP will stop working. Move those integrations to OAuth — using external client apps with JWT tokens — well ahead of the cutoff. A new </span><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_api_soap_login.htm&amp;release=262&amp;type=5"><u>Any API Auth</u></a><span> user permission lets you control who can authenticate via SOAP </span><code>login()</code><span>, and it&#8217;s enforced by default in newly created orgs.</span></p>
<h4><span style="font-weight: 400">Chain dependent records in one GraphQL request</span></h4>
<p><a href="https://developer.salesforce.com/docs/platform/graphql/guide/mutations-intro.html"><u>GraphQL mutations</u></a> can now reference any field returned by an earlier operation in the same request, not just its record ID. Use <code>@{ref.Record.FieldName.value}</code> for a field value, <code>@{ref.Record.Id}</code> (or shorthand <code>@{ref}</code>) for the ID. This lets you create linked records in a single round trip. Below is an example body for chaining dependent request in one GraphQL</p>
<pre language="js">mutation CreateChain {
  uiapi(input: { allOrNone: false }) {
    AccountCreate(input: { Account: { Name: "Headless 360 Test Co" } }) {
      Record { Id  Name { value } }
    }
    ContactCreate(input: { Contact: {
      LastName: "@{AccountCreate.Record.Name.value}"
      AccountId: "@{AccountCreate}"   # == AccountCreate.Record.Id
    }}) { Record { Id  LastName { value } } }
    OpportunityCreate(input: { Opportunity: {
      Name: "@{ContactCreate.Record.LastName.value}"
      AccountId: "@{AccountCreate.Record.Id}"
      StageName: "Value Proposition"
      CloseDate: "2026-12-31"
    }}) { Record { Id } }
  }
}
</pre>
<p><span style="font-weight: 400">You can use a Beta Salesforce CLI command to execute any GraphQL as shown below.</span></p>
<pre language="sh">sf api request graphql --body  --target-org my-org
</pre>
<h4><span style="font-weight: 400">Use JWT access tokens with SOAP API</span></h4>
<p><a href="https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_quickstart_intro.htm"><u>SOAP API</u></a> now accepts <a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_api_soap_jwt.htm&amp;release=262&amp;type=5"><u>JWT-based</u></a> access tokens from Salesforce OAuth flows in the <code>sessionId</code> header element, reaching parity with REST authentication and making token sharing with external services safer.</p>
<h4><span style="font-weight: 400">Connect REST API rate limits are relaxed</span></h4>
<p><span style="font-weight: 400">Orgs have been migrated off the restrictive per-user, per-application, per-hour </span><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_connect_api.htm&amp;release=262&amp;type=5"><span style="font-weight: 400">Connect REST API limit </span></a><span style="font-weight: 400">and onto the more generous per-org, per-24-hour Salesforce Platform API limit — the same pool that every other API call shares. Only requests that require Chatter keep the old hourly throttle. The identical change applies to Connect in Apex.</span></p>
<h4><span style="font-weight: 400">User Interface API CSRF token</span></h4>
<p><span>A new </span><code>GET /ui-api/session/csrf </code><span>resource (see </span><a href="https://developer.salesforce.com/docs/atlas.en-us.uiapi.meta/uiapi/ui_api_resources_session_csrf.htm"><u>docs</u></a><span>) returns a token that you can use to protect User Interface API requests from third-party forgeries.</span></p>
<h2><span style="font-weight: 400">LWC updates</span></h2>
<p><b>Summer &#8217;26 is a maturity release for LWC.</b><span style="font-weight: 400"> The big themes: </span><b>cleaner architecture</b><span style="font-weight: 400"> (state finally lives outside your components), a </span><b>quicker edit-and-preview cycle</b><span style="font-weight: 400"> (real-time previews in the browser and your IDE), </span><b>better defaults</b><span style="font-weight: 400"> (more virtualization, tighter security), and a </span><b>new bridge to Agentforce</b><span style="font-weight: 400">. </span></p>
<p><span style="font-weight: 400">Here are the five features most likely to change how you build — each with the problem it solves.</span></p>
<ul>
<li><b>State Managers (GA)</b>: These pull data and its logic out of components into a reusable, testable layer.</li>
<li><b>API 67.0 niceties</b>: This includes zero-JS accordions via grouped <span>&lt;details&gt;</span>, plus faster hot reload.</li>
<li><b>Secure downloads</b>: LWS now blocks <span>data:</span> URIs, so generate files the right way.</li>
<li><b>Dynamic lists (in Developer Preview)</b>: This renders thousands of rows smoothly with built-in virtualization.</li>
<li><b><code>lightning/accApi</code></b>:<span> This new module lets your components open and drive the Agentforce panel.</span></li>
</ul>
<h3><span style="font-weight: 400">State Managers for LWC</span></h3>
<p><a href="https://developer.salesforce.com/docs/platform/lwc/guide/state-management.html"><u>State Managers</u></a> is the most consequential LWC feature going GA during this release. State managers move data and the logic that mutates it <i>out</i> of components into a dedicated layer. Build one as a plain JS module with the new<span> </span><code>defineState</code><span> </span><span>primitive from</span><span> </span><code>@lwc/state</code>, which gives you three building blocks:</p>
<ul>
<li><code><span>atom(value)</span></code><span>: Reactive state, read through </span><code><span>.value</span></code><code></code></li>
<li><code><span>computed([deps], fn)</span></code><span>: A derived value that recomputes when a dependency changes</span></li>
<li><code>setAtom(atom, value)</code><span>: The </span><i><span>only</span></i><span> way to update an atom</span></li>
</ul>
<p><code>defineState</code><span> returns a </span><b><span>factory</span></b><span> — each call yields a fresh, independent instance. The essential shape: one atom as the source of truth, a derived computed, and an action that mutates via </span><code>setAtom</code>.</p>
<p><span style="font-weight: 400">Below is example code that demonstrates a state manager in action, handling a cart:</span></p>
<p><b><code>cartStateManager.js</code></b></p>
<pre language="js">import { defineState } from '@lwc/state';
export default defineState(({ atom, computed, setAtom }) =&gt; {
    const items = atom([]);                       // source of truth
    const count = computed([items], (l) =&gt; l.length); // derived
    const addItem = (item) =&gt;                       // action
        setAtom(items, [...items.value, item]);
    return { items, count, addItem };
});
</pre>
<p>A component imports the manager module, calls the factory, then reads reactive state through<span> </span><code>.value</code><span> — </span><span>in JS or the template, which re-renders automatically on change. No data logic, no manual subscription:</span></p>
<p><b><code>cartSummary.js</code></b></p>
<pre language="js">import { LightningElement } from 'lwc';
import createCartStateManager from 'c/cartStateManager';
export default class CartSummary extends LightningElement {
    cart = createCartStateManager();
}
</pre>
<p><b><code>cartSummary.html</code></b></p>
<pre language="html">&lt;template&gt;&lt;h2&gt;Your Cart ({cart.value.count})&lt;/h2&gt;&lt;p&gt;
&lt;/p&gt;&lt;/template&gt;
</pre>
<p><span style="font-weight: 400">For complete, runnable examples, see the open-source</span> <a href="https://github.com/trailheadapps/lwc-recipes/tree/main/force-app/main/default/lwc/opportunitiesStateManager"><span style="font-weight: 400">lwc-recipes repo</span></a><span style="font-weight: 400">. </span><span style="font-weight: 400">Because each instance is isolated, managers are trivially unit-testable</span><span style="font-weight: 400">.</span></p>
<p><span><span>Salesforce also ships </span></span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/reference-state-managers.html"><u>built-in Lightning state managers</u></a><span><span> that wrap Lightning Data Service (LDS) access to the most common UI API data and metadata — for records, object info, page layouts, and related lists (for example, </span></span><code>lightning/stateManagerRecord</code><span><span> and </span></span><code>lightning/stateManagerObjectInfo</code><span><span>). They follow the same pattern as the ones you write and participate fully in LDS caching, normalization, and subscriptions, so reach for them before rolling your own.</span></span></p>
<h4><span>LWC API version 67.0: Group</span><b><span> </span></b><code>&lt;details&gt;</code><b><span> </span></b><span>and faster HMR</span></h4>
<p><span>To enable the features of this release, update your bundle </span><code>.js-meta.xml</code><span> by setting </span><code>&lt;apiVersion&gt;67.0&lt;/apiVersion&gt;</code><span>.</span></p>
<p><span>Two wins by using the 67.0 API version: hot module reloading (HMR) is faster and more memory-efficient, and you can now group native </span><code>&lt;details&gt;</code><span> elements with the </span><span>name</span><span> attribute for a zero-JavaScript exclusive accordion (it threw a compiler warning before 67.0). Same </span><code>name</code><span> = only one open at a time.</span></p>
<p><b><code>faqAccordion.html</code></b></p>
<pre language="html">&lt;details key={faq.id} name=&quot;summer26-faq&quot;&gt;
    &lt;summary&gt;{faq.question}&lt;/summary&gt;
    &lt;p&gt;{faq.answer}&lt;/p&gt;&lt;/details&gt;</pre>
<h3><span>Secure file downloads: LWS blocks </span><code>data:</code><span> URIs</span></h3>
<p><span>Lightning Web Security (LWS) adds a </span><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_lc_lws_distortion_changes.htm&amp;release=262&amp;type=5"><u>batch of API distortions</u></a><span> in this release. The one most likely to break code: </span><code>HTMLAnchorElement.prototype.href</code><span> now </span><b><span>blocks the </span><code>data:</code><span> URI scheme</span></b><span>. If you trigger client-side downloads by setting an anchor&#8217;s </span><code>href</code><span> to a </span><code>data:</code><span> URL, that stops working.</span></p>
<p><span>The fix is the supported pattern anyway — build a </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob"><u>blob</u></a><span> in JavaScript and use a </span><code>blob:</code><span> object URL (origin-bound, and revoked after use).</span></p>
<p><b><code>secureDownload.js</code></b></p>
<pre language="js">// LWS-safe: blob: URL, not data:
const blob = new Blob([this.content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
anchor.href = url;
anchor.download = this.fileName;
anchor.click();
URL.revokeObjectURL(url); // release memory
</pre>
<p>Other new distortions include<code> </code><code>Element.getAttribute</code><code>, </code><code>innerHTML/outerHTML</code> getters, <code>MutationObserver.observe</code>, the <code>IndexedDB</code> factory, <code>Promise.then/catch/finally</code>, and more — with matching ESLint rules. Run the updated <a href="https://developer.salesforce.com/docs/platform/lightning-components-security/guide/lws-tools-lint.html"><u>ESLint package</u></a> and review the <a href="https://developer.salesforce.com/tools/lws-distortion-viewer"><u>LWS Distortion Viewer </u></a>before you upgrade the components to this release.</p>
<h3><span style="font-weight: 400">Load large lists dynamically (Developer Preview)</span></h3>
<p><span>Rendering thousands of rows used to choke the browser, the new </span><code>lightning-dynamic-list-container</code><span> (see </span><a href="https://developer.salesforce.com/docs/platform/lightning-component-reference/guide/lightning-dynamic-list-container.html?type=Example"><u>docs</u></a><span>) and </span><code>lightning-dynamic-list-item</code><span> (see </span><a href="https://developer.salesforce.com/docs/platform/lightning-component-reference/guide/lightning-dynamic-list-item.html"><u>docs</u></a><span>) base components use </span><b><span>virtualization</span></b><span>. They render only the rows in the viewport and stream the rest in as you scroll, from 50 items to 5,000.</span></p>
<p><b><code>dynamicContactList.html</code></b></p>
<pre class="wp-block-code" language="html">&lt;!-- Container needs a bounded height for scroll + virtualization --&gt;
&lt;div style="height: 400px"&gt;
  &lt;lightning-dynamic-list-container
      list-items={allContacts}
      onrenderlistitems={handleRenderListItems}
      onloadmore={handleLoadMore}&gt;
    &lt;template for:each={visibleContacts} for:item="contact"&gt;
      &lt;lightning-dynamic-list-item
          key={contact.id} item-id={contact.id}&gt;
        &lt;div&gt;{contact.name}&lt;/div&gt;
      &lt;/lightning-dynamic-list-item&gt;
    &lt;/template&gt;
  &lt;/lightning-dynamic-list-container&gt;
&lt;/div&gt;
</pre>
<p><span>The container fires </span><code>renderlistitems</code><span> as you scroll (which slice to render) and </span><code>loadmore</code><span> near the end:</span></p>
<p><b><code>dynamicContactList.js</code></b></p>
<pre language="js">handleRenderListItems(event) {
    const { listItemsToRender } = event.detail;
    this.visibleContacts = listItemsToRender;
}
</pre>
<p><span style="font-weight: 400">You also get </span><b>focus preservation</b><span style="font-weight: 400"> and </span><b>built-in accessibility</b><span style="font-weight: 400"> (screen readers, Home/End/Arrow nav, Browse-Mode hint). </span></p>
<p>Here are some recommendations for working with dynamic lists: keep container and item adjacent, give every item a unique <code>item-id</code><span>, and don&#8217;t set </span><code>overflow: scroll</code><span> </span><span>on your own container — the component handles scrolling</span><span>.</span></p>
<h3>Talk to Agentforce from LWC with new LWC module <code>lightning/accApi</code></h3>
<p><span>The standout new module is </span><code>lightning/accApi</code><span> (see </span><a href="https://developer.salesforce.com/docs/platform/accsdk/guide/acc-api.html"><u>docs</u></a><span>) — the </span><b><span>Agentforce Conversation Client API</span></b><span>. This </span><i><span>headless</span></i><span> module lets your LWC components drive the native Agentforce side panel in Lightning Experience: open it, close it, point it at a specific Employee Agent, and send natural-language utterances. Think of a &#8220;Summarize this record&#8221; button, or a context-aware launcher in a console sidebar.</span></p>
<p>The entire API is three async methods:</p>
<table>
<tbody>
<tr>
<td><b>Method</b></td>
<td><b>Purpose</b></td>
</tr>
<tr>
<td><code>open(botId?)</code></td>
<td><span style="font-weight: 400">Open the side panel, optionally to a specific agent</span></td>
</tr>
<tr>
<td><span style="font-weight: 400"><code>close()</code></span></td>
<td><span style="font-weight: 400">Close the side panel</span></td>
</tr>
<tr>
<td><code>execute(utterance, botId)</code></td>
<td><span style="font-weight: 400">Run a natural-language utterance on an agent</span></td>
</tr>
</tbody>
</table>
<p>All three return a <code>Promise</code> and are <b>queued</b>, running in sequence. Note <code>execute</code> does <i>not</i> return the reply — the conversation renders in the panel, not your component. Import the methods and call them.</p>
<p><b><code>agentforceLauncher.js</code></b></p>
<pre language="js">import { open, close, execute } from 'lightning/accApi';

@api botId; // design-time property, set on the page

async handleOpen() {
    await open(this.botId);
}
async handleQuickAction(event) {
    const { utterance } = event.currentTarget.dataset;
    await execute(utterance, this.botId); // wrap in try/catch + toast
}
</pre>
<div style="height: 400px">
<p>Expose <code>botId</code> as a design-time property, so admins can wire up the agent without touching code (a <code>&lt;property name="botId" type="String"&gt;</code> entry in the bundle&#8217;s <code>.js-meta.xml targetConfig</code>). <code>botId</code> can be obtained from the URL in Agentforce Builder.</p>
<h2><span style="font-weight: 400">Apex updates</span></h2>
<p><b>API version 67.0</b> <span style="font-weight: 400">reinforces Apex security with safer defaults, and adds some long-requested ergonomics along the way</span><span style="font-weight: 400">. </span></p>
<h3><span style="font-weight: 400">Database operations run in user mode by default</span></h3>
<p>SOQL, SOSL, DML, and <code>Database</code> (see <a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_database.htm"><u>docs</u></a>) methods now default to <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_enforce_usermode.htm"><u>user mode</u></a> instead of system mode, so ever operation enforces the running user&#8217;s object permissions, field-level security (FLS), and sharing rules. The platform no longer assumes that the surface in front of it has already filtered the data; elevated access is now something you opt into explicitly.</p>
<h3><code>with sharing</code><b> is the new default, and </b><code>WITH SECURITY_ENFORCED</code><b> is retired</b></h3>
<p>Two changes reinforce the same idea. A class compiled at 67.0 with <b>no</b> sharing keyword now defaults to <code>with sharing</code> (previously <code>without sharing</code>), so bypassing sharing is now a deliberate <code>without sharing</code> declaration. And the old <code>WITH SECURITY_ENFORCED</code> clause no longer compiles.</p>
<p><span style="font-weight: 400">Here&#8217;s the exact error from a 67.0 org:</span></p>
<pre language="apex">// Deploying this class at API 67.0 fails with:
//   "WITH SECURITY_ENFORCED is no longer supported, use WITH USER_MODE instead."
[SELECT Id FROM Account WITH SECURITY_ENFORCED LIMIT 1];
</pre>
<p><code>WITH USER_MODE</code> isn&#8217;t just a rename. It handles polymorphic fields (<code>Owner</code>, <code>Task.whatId</code>), checks the <code>WHERE</code> clause and not just the <code>SELECT</code> list, and reports <i>every</i> FLS violation instead of only the first, which you can read off the <code>QueryException</code>.</p>
<pre language="apex">try {
    List a = [SELECT Id, Name, AnnualRevenue FROM Account WITH USER_MODE LIMIT 1];
} catch (QueryException e) {
    // returns Map fieldNames&gt;
    Map&lt;String, Set&gt; blocked = e.getInaccessibleFields();
}
</pre>
<h3>Multiline strings and <code>String.template()</code></h3>
<p>Triple single-quotes (<code>'''</code>) give you real <a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_apex_multiline_string.htm&amp;release=262&amp;type=5"><u>multiline string</u></a> literals — no more <code>+ '\n' +</code> chains for JSON payloads, email bodies, or SOQL. And <code>String.template()</code> (see <a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_string.htm#apex_System_String_template"><u>docs</u></a>) does named interpolation with <code>${variableName}</code> placeholders, replacing the index-juggling of <code>String.format()</code>.</p>
<p>Below is example Apex code showing multiline string and templating in action.</p>
<pre language="apex">String payload = '''
{
    "Account": "${accountName}",
    "Last Updated": "${date}"
}'''.template(new Map&lt;String, Object&gt;{
    'accountName' =&gt; 'My Account',
    'date' =&gt; Datetime.newInstance(2018, 11, 15, 8, 0, 0)
});
</pre>
<p>Two things to keep in mind when running this:</p>
<ul>
<li>The newline right after the opening <code>'''</code> is trimmed, so the literal above starts with <code>{</code>, not a blank line.</li>
<li><code>String.template()</code> renders a <code>Datetime</code> in <b>GMT</b> using <code>yyyy-MM-dd HH:mm:ss</code>, not the user&#8217;s local time in the way that <code>String.valueOf()</code> does. Format it yourself if you need a specific zone.</li>
</ul>
<h3><span style="font-weight: 400">Integration tests for Agentforce and Data 360 (Developer Preview)</span></h3>
<p>Integration tests for Agentforce and Data 360 are currently in Developer Preview and are supported in scratch orgs only. Standard unit tests mock every callout and roll back data, which prevents asserting on real Agentforce or Data 360 interactions. The new <code>@IntegrationTest</code> annotation overcomes these limitations by allowing live callouts and enabling data commits mid-transaction using <code>IntegrationTest.commitTestOnly()</code>, with cleanup handled in a <code>@TearDown</code> method. To enable this, add &#8216;<b>ApexIntegrationTests</b>&#8216; to the features array in your scratch org definition file. These tests run asynchronously, one at a time, via the Tooling API&#8217;s runTestsAsynchronous resource.</p>
<pre language="apex">@IntegrationTest
public with sharing class MyServiceIntegrationTest {
    @IntegrationTest
    public static void testServiceInteraction() {
        Account a = new Account(Name = 'Integration Test Account');
        insert as user a;
        IntegrationTest.commitTestOnly();           // data survives mid-test
        Account r = [SELECT Name FROM Account WHERE Id = :a.Id WITH USER_MODE];
        Assert.areEqual('Integration Test Account', r.Name);
    }
    @TearDown
    public static void tearDown() {
        delete as user [SELECT Id FROM Account WHERE Name = 'Integration Test Account' WITH USER_MODE];
    }
}
</pre>
<h3><span style="font-weight: 400">Elastic limits, trigger system mode, and other Apex changes</span></h3>
<ul>
<li><b>Elastic limits for async jobs (Beta):</b> Enqueue <code>Queueable</code> and @<code>future</code> jobs up to <a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_apex_elastic_async_limit.htm&amp;release=262&amp;type=5"><i><u>twice</u></i><u> your licensed daily limit</u></a> instead of hitting a hard wall; overflow is throttled, not rejected. Track it with the new <code>DailyAsyncApexElasticExecutions</code> and <code>DailyAsyncApexProcessed</code> entries in <code>System.OrgLimits.getMap()</code>.</li>
<li><b>Apex triggers always run in system mode:</b> <a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_apex_triggers_system_mode.htm&amp;release=262&amp;type=5"><u>Triggers now uniformly bypass</u></a> sharing/FLS and can&#8217;t declare sharing or access modes. Push security-sensitive logic into a handler class where you control the access mode.</li>
<li><b>Block anonymous Apex from managed packages:</b> <a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_apex_block_exec_anon_ru.htm&amp;release=262&amp;type=5"><u>Managed package</u></a> session IDs can no longer authenticate anonymous Apex. Enforced Summer &#8217;27 — package authors should move to a shared <code>global</code> interface plus <code>Type.forName()</code>.</li>
<li><b>No-arg constructors required for invocable-action parameter classes:</b> Any custom Apex type used as an invocable action input must expose a<a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_apex_constructor_visibility_invocable_custom_classes_v66.htm&amp;release=262&amp;type=5"><u> visible no-argument constructor</u></a> (public, or global for packaged classes). This applies to Apex at API version 67.0 and beyond.</li>
</ul>
<h2><span style="font-weight: 400">Agentforce monthly updates</span></h2>
<p><span style="font-weight: 400">Agentforce enables you to customize pre-built agents, or create and deploy enterprise-ready agents, that work across Salesforce apps, Slack, and third-party platforms. We&#8217;re adding some important developer features in the upcoming monthly releases.</span></p>
<h3><span style="font-weight: 400">Agentforce Builder and Agent Script are Generally Available (GA)</span></h3>
<p><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/agent-script.html"><span style="font-weight: 400">Agent Script</span></a><span style="font-weight: 400"> — a scripting language for AI agents that gives builders precise control by blending deterministic rules with agentic reasoning — and the new </span><a href="https://help.salesforce.com/s/articleView?id=ai.agent_builder_tour.htm&amp;type=5"><span style="font-weight: 400">Agentforce Builder</span></a><span style="font-weight: 400"> are now generally available.</span></p>
<ul>
<li style="font-weight: 400"><b>The new builder is the default:</b><span style="font-weight: 400"> Starting the week of </span><b>July 13, 2026</b><span style="font-weight: 400">, the New Agent button no longer opens the legacy builder in Setup. New agents are created only in the new Agentforce Builder.</span></li>
<li style="font-weight: 400"><b>One-click upgrade:</b> <a href="https://help.salesforce.com/s/articleView?id=ai.agent_setup_create_upgrade.htm&amp;type=5"><span style="font-weight: 400">Upgrading a legacy agent</span></a><span style="font-weight: 400"> converts all subagents, actions, system messages, data, and connections to Agent Script, then optionally optimizes it for reliability.</span></li>
<li style="font-weight: 400"><b style="color: #4a4a4a">Models are configurable in script:</b><span style="font-weight: 400"> Pin the </span><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/ascript-model.html"><span>model for an agent directly in Agent Script</span></a><span style="font-weight: 400"> rather than relying only on the org-wide model option.</span></li>
</ul>
<h3><span style="font-weight: 400">Agent Script is now open source</span></h3>
<p><span style="font-weight: 400">Salesforce open-sourced the </span><a href="https://github.com/salesforce/agentscript"><span style="font-weight: 400">Agent Script toolchain</span></a><span style="font-weight: 400"> under an Apache 2.0 license: parser, linter, compiler, Language Server Protocol (LSP), and editor integrations.</span></p>
<p><span style="font-weight: 400">This lets developers build custom tools. We&#8217;re excited to see the </span><a href="https://www.linkedin.com/posts/jasonlantz_the-pydantic-ai-harness-for-running-compiled-share-7464850529576357889-FJNi"><span style="font-weight: 400">community</span></a><span style="font-weight: 400"> (shout out to Jason Lantz) building new tools with the open-source Agent Script.</span></p>
<h3><span style="font-weight: 400">Skills for Agentforce development</span></h3>
<p><span style="font-weight: 400">The </span><a href="https://github.com/forcedotcom/sf-skills/tree/main/skills"><span style="font-weight: 400">sf-skills</span></a><span style="font-weight: 400"> GitHub repo covered above under “Agent Skills for Coding Agents” also includes skills that teach AI coding assistants to </span><a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/developing-agentforce"><span style="font-weight: 400">build</span></a><span style="font-weight: 400">, </span><a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/testing-agentforce"><span style="font-weight: 400">test</span></a><span style="font-weight: 400"> and </span><a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/observing-agentforce"><span style="font-weight: 400">observe</span></a><span style="font-weight: 400"> Agentforce.</span></p>
<h3><span style="font-weight: 400">Agentforce Data Library Connect API (Beta)</span></h3>
<p><b>Agentforce Data Libraries (ADL)</b><span style="font-weight: 400"> ground an agent in your trusted content. They index Knowledge articles or uploaded files into a vector search index and expose a retriever for retrieval-augmented generation (RAG). Creating one used to be a manual step in Setup; the new </span><b>ADL Connect API (Beta)</b><span style="font-weight: 400"> makes the whole lifecycle scriptable and ready for continuous integration/continuous delivery (CI/CD). It&#8217;s the data half of Headless 360 — grounding itself becomes an API.</span></p>
<p>All endpoints sit under the base resource: <code>/services/data/v67.0/einstein/data-libraries</code></p>
<p><span style="font-weight: 400">There are five steps to provisioning a file-based library and grounding an agent on it:</span></p>
<p><strong>Step 1: Create the library</strong></p>
<p>A single <code>POST</code> — note <code>sourceType</code> — is <i>nested</i> under <code>groundingSource</code> (<code>SFDRIVE</code> for files, or <code>KNOWLEDGE</code> / <code>RETRIEVER</code>). The response returns the <code>libraryId</code> that every later step needs.</p>
<pre language="sh">sf api request rest "/services/data/v67.0/einstein/data-libraries" \
  --method POST --target-org my-org \
  --body '{
    "masterLabel": "Product Docs Library",
    "developerName": "Product_Docs_Library",
    "groundingSource": { "sourceType": "SFDRIVE" }
  }'
# → { "libraryId": "1JD...", "status": "IN_PROGRESS" }
</pre>
<p><strong>Step 2: Wait for upload readiness</strong></p>
<p>Poll <code>GET …/{libraryId}/upload-readiness</code> until it reports ready. Data 360 is provisioning the objects that hold your file metadata behind the scenes.</p>
<p><strong>Step 3: Upload the file</strong></p>
<p>Request a pre-signed S3 URL from <code>POST …/{libraryId}/file-upload-urls</code>, then <code>PUT</code> the file straight to that URL (forward the returned headers verbatim, or S3 rejects it with a 403).</p>
<p><strong>Step 4: Index it</strong></p>
<p>Trigger <code>POST …/{libraryId}/indexing</code> to chunk, embed, and build the retriever. Then poll <code>GET …/{libraryId}</code> and treat the library as ready when <code>retrieverId</code> goes non-null — <b>not</b> when the top-level <code>status</code> flips, which lags the retriever by 10–30 minutes.</p>
<p><strong>Step 5: Ground the agent</strong></p>
<p><b></b>Wire the finished library into a <code>.agent</code> file&#8217;s <code>knowledge:</code> block, then invoke <code>AnswerQuestionsWithKnowledge</code> from a subagent. The <code>rag_feature_config_id</code> is <code>"ARFPC_"</code> + the <code>libraryId</code> — <i>not</i> the raw ID.</p>
<pre language="yaml">knowledge:
    rag_feature_config_id: "ARFPC_1JDcf0000024ZZ7GAM"
    citations_enabled: True
</pre>
<h3><span style="font-weight: 400">Agentforce Mobile SDK</span></h3>
<p><span style="font-weight: 400">The </span><a href="https://github.com/salesforce/AgentforceMobileSDK-iOS"><span style="font-weight: 400">Agentforce Mobile SDK</span></a><span style="font-weight: 400"> (Software Development Kit) embeds your agents in native </span><b>iOS</b><span style="font-weight: 400">, </span><b>Android</b><span style="font-weight: 400">, and </span><b>React Native</b><span style="font-weight: 400"> apps, as a pre-built chat UI or </span><i><span style="font-weight: 400">headless</span></i><span style="font-weight: 400">, where you own the UI. Three things landed for Summer &#8217;26:</span></p>
<h4><span style="font-weight: 400">React Native: One codebase, both platforms</span></h4>
<p>One TypeScript codebase ships the agent to both iOS and Android. You work through a single object, <code>AgentforceService</code>, and the whole integration is three calls: <b>configure</b> → (optional) <b>add context</b> → <b>launch</b>. First decide which kind of agent you&#8217;re embedding, for example:</p>
<ul>
<li><b>Service Agent</b>: Customer-facing and <i>anonymous</i> (no login). Best for support in a public app.</li>
<li><b>Employee Agent</b>: Internal and <i><span>authenticated</span></i><span>. The SDK gets OAuth tokens from the Salesforce Mobile SDK.</span></li>
</ul>
<p><span>To integrate the Agentforce Mobile SDK into your React native mobile applications, follow these three steps. These steps are essential to establish a secure, authorized connection between your application and your chosen agent.</span></p>
<p><strong>Step 1: Configure the agent</strong></p>
<p><span style="font-weight: 400">First, tell the SDK which agent to connect to. The fields differ slightly by type, so here&#8217;s each one:</span></p>
<pre language="js">import { AgentforceService } from 'react-native-agentforce';
// Option A — Service Agent (anonymous, customer-facing)
await AgentforceService.configure({
    type: 'service',
    serviceApiURL: 'https://service.salesforce.com',
    organizationId: '00DWs00000Ip47F',
    esDeveloperName: 'Order_Support_Agent',        // the agent's API name
    serviceUISettings: { enableLightningType: true }  // render Custom Lightning Types as cards
});

// Option B — Employee Agent (internal, authenticated)
await AgentforceService.configure({
    type: 'employee',
    instanceUrl: 'https://your-domain.my.salesforce.com',
    organizationId: '00DWs00000Ip47F',
    userId: '005xx0000001234',
    agentId: '0XxWs000001DTDJK'                       // Bot Id from publishing the agent
});
</pre>
<p><strong>Step 2: Add session context (optional)</strong></p>
<p><span style="font-weight: 400">Pass typed variables that the agent can use to personalize its replies, for example, the identity of the user.</span></p>
<pre language="js">await AgentforceService.setAdditionalContext({
    variables: [{ name: 'userId', type: 'Text', value: '005xx0000001234' }]
});
</pre>
<p><strong>Step 3: Launch</strong></p>
<p><span style="font-weight: 400">Open the SDK&#8217;s pre-built native chat screen.</span></p>
<pre language="js">await AgentforceService.launchConversation();
</pre>
<h4><span style="font-weight: 400">Embed the Agentforce as a native iOS chat screen</span></h4>
<p><span style="font-weight: 400">The SDK gives you a ready-made chat screen to drop into your iOS app. You write a small bit of code that supplies the logged-in user&#8217;s access token, then point it at your published agent&#8217;s </span><b>Bot Id</b><span style="font-weight: 400"> (the 18-character ID you get when you publish). The SDK returns a complete native chat view that you present like any other screen. The screenshot below is our Order Support Agent answering live inside that app.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206474" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260605093436/Native-iOS-mobile-app-built-with-the-Agentforce-Mobile-SDK-rendering-a-custom-LWC.png?w=460" class="postimages" width="460" height="1000" alt="Native iOS mobile app built with the Agentforce Mobile SDK, rendering a custom LWC" />
			  </span>
			</p>
<h4><span style="font-weight: 400">Make agent replies rich, on every surface, with Custom Lightning Types</span></h4>
<p><span style="font-weight: 400">Notice the reply in the above screenshot is a clean </span><i><span style="font-weight: 400">card</span></i><span style="font-weight: 400"> — an order number, a green </span><i><span style="font-weight: 400">Shipped</span></i><span style="font-weight: 400"> badge, dates, and a total — not a raw list of field names and values. That&#8217;s </span><a href="https://developer.salesforce.com/blogs/2026/05/use-custom-lightning-types-in-agent-script-for-rich-agent-ui"><span style="font-weight: 400">Custom Lightning Types</span></a><span style="font-weight: 400">. When an agent action returns structured data, a custom Lightning type lets you attach a purpose-built UI to that data, so the agent shows a designed component instead of plain text. </span></p>
<p><span style="font-weight: 400">Note that </span><a href="https://developer.salesforce.com/blogs/2026/05/use-custom-lightning-types-in-agent-script-for-rich-agent-ui"><span style="font-weight: 400">Custom Lightning Types</span></a><span style="font-weight: 400"> is a cross-surface feature, not mobile specifically. You define it once against the action&#8217;s output, and it renders idiomatically on every surface where the agent runs — a Lightning web component on desktop and web, and the matching native UI in a mobile app.</span></p>
<h3><span style="font-weight: 400">Multi-Agent Orchestration (Beta)</span></h3>
<p><span style="font-weight: 400">Real workflows rarely fit a standalone agent. With </span><a href="https://help.salesforce.com/s/articleView?id=ai.agent_multi_orch.htm&amp;type=5"><b>Multi-Agent Orchestration</b></a><span style="font-weight: 400">, an orchestrator agent connects to other specialized agents in your org and presents one unified point of contact, so users handle cross-domain tasks without juggling separate sessions.</span></p>
<p>In Agentforce Builder, open a draft agent as your orchestrator, then from the Explorer panel click <b>+ → Connect Agent as Subagent (Beta)</b> and give each connected subagent a description that governs its behavior. With Agent Router, you add each subagent under Actions Available for Reasoning and reference it with <code>@</code>.</p>
<h3>Observability: Custom Scorers (Beta)</h3>
<p><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_einstein_analytics_new_experience.htm&amp;release=262&amp;type=5"><b>Refined Agent Analytics</b></a> unifies Service Agent and Employee Agent analytics into one view with 40+ metrics. On top of that, <a href="https://help.salesforce.com/s/articleView?id=ai.generative_ai_optimize_scorers.htm&amp;type=5"><b>Custom Scorers (Beta)</b></a> lets you grade sessions against your own key performance indicators (KPIs) — Sentiment, Tone of Voice, Product Interest, Escalation Trigger, Politeness — alongside Salesforce&#8217;s standard quality metrics.</p>
<p>For developers, the workflow matters most: build scorers with <a href="https://help.salesforce.com/s/articleView?id=ai.agent_studio_testing_center_setup_tests.htm&amp;type=5"><b><u>Next Gen Testing</u></b></a> in Agentforce Studio or deploy them via the <b>Metadata API (using </b><b><code>aiAgentScorerDefinitions</code></b><b>),</b> so they live in source control, then activate them from the <b>Scorer Hub</b> to run on live sessions. Custom Scorers require the Agentforce Scorer Beta permission set.</p>
<h2><span style="font-weight: 400">Data 360 monthly updates</span></h2>
<p><span style="font-weight: 400">Like Agentforce, Data 360 features are released as often as monthly, so check the </span><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_c360_truth.htm&amp;release=262&amp;type=5"><span style="font-weight: 400">monthly section</span></a><span style="font-weight: 400"> of the release notes often. Here are the developer-relevant highlights currently slated for the Summer ’26 timeframe. </span></p>
<h3><span style="font-weight: 400">Query Data 360 more precisely</span></h3>
<p>Use the new <b>SET OPTIONS</b> clause (see <a href="https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_set_options.htm"><u>docs</u></a>) in SOQL queries to specify Data 360 dataspaces and control how <code>NULL</code> and empty string values are handled. When querying Data 360 data lake objects, add the clause at the end of your SOQL query to get more precise results.</p>
<pre language="sql">SELECT Id, EmailOptIn__c
FROM ContactDLO__dlm
WHERE EmailOptIn__c = ''
SET OPTIONS (dataspace = 'default', honorEmptyStrings = true)
</pre>
<p>The clause goes at the very end. Dataspace is required for DLO queries — omit it and the query returns zero records. <code>honorEmptyStrings = true</code> makes Data 360 treat <code>NULL</code> and <code>''</code> as distinct values; the default, false, collapses them the way Salesforce Platform objects do.</p>
<h3><span style="font-weight: 400">Extend Data 360 with custom code using Code Extension</span></h3>
<p><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/use-custom-code.html"><b>Code Extension</b></a><span style="font-weight: 400"> is a Data 360 feature that allows you to deploy custom Python scripts and functions that run on isolated containers on the platform. Currently, code extensions support deploying functions for complex batch data transformations, such as string manipulation, custom computations, or data cleansing, and deploying scripts that implement custom chunking logic on search index creation. In the future, Code Extension will support other Data 360 capabilities and programming languages.</span></p>
<p><span style="font-weight: 400">You write and debug Python scripts locally using the project scaffold provided by the </span><a href="https://pypi.org/project/salesforce-data-customcode/"><span style="font-weight: 400">Data Custom Code Python SDK</span></a><span style="font-weight: 400"> and the </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/set-up-sdk.html"><span style="font-weight: 400">Salesforce CLI Code Extension plugin</span></a><span style="font-weight: 400">, validating them with the Salesforce CLI against a sandbox. Then, you deploy them to the sandbox and run them. There you can monitor them through the new code extensions log DLO. While developers author the code, users with the Data Cloud Architect permission set run and monitor it. We strongly recommend using the new </span><a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/developing-datacloud-code-extension"><span style="font-weight: 400">code extension skill in afv-library</span></a><span style="font-weight: 400"> to automate building, debugging, and deploying, and the new Data 360 MCP Server to run and monitor them.</span></p>
<p><a href="https://www.youtube.com/watch?v=96PC1KSnmfk"><span style="font-weight: 400">Watch a demo</span></a><span style="font-weight: 400"> of how to work with code extensions.</span></p>
<h3><span style="font-weight: 400">Deploy code extension components using data kits</span></h3>
<p><span style="font-weight: 400">Use a DevOps data kit to move </span><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_cdp_2026_summer_code_extension_data_kit.htm&amp;release=262&amp;type=5"><span style="font-weight: 400">code extensions</span></a><span style="font-weight: 400"> or the data transforms built from them, from sandbox to production. When you add such a data transform to your data kit, its associated code extension is automatically included. This enables headless DevOps workflows; your CI/CD pipeline can promote Data 360 logic the same way it promotes Apex and LWC metadata.</span></p>
<h2><span style="font-weight: 400">Agentforce 360 Platform development tool updates</span></h2>
<p><span style="font-weight: 400">The Summer ’26 pro-code toolchain picked up a few notable upgrades:</span></p>
<ul>
<li style="font-weight: 400"><a href="https://marketplace.visualstudio.com/items?itemName=salesforce.salesforcedx-agentforce-vibes-2"><b>Agenforce Vibes 2.0 (Developer Preview):</b></a> <span style="font-weight: 400">The first public pre-release of Agentforce Vibes 2.0 is here. This agentic development environment does far more than generate code. It reasons through complex tasks, builds structured implementation plans, and asks clarifying questions before acting. You stay in control through approvals, permissions, and native VS Code diff reviews. This release gives you a redesigned multi-tab chat experience and Plan Mode for breaking down complex work. You also get deeper Model Context Protocol (MCP) integration, built-in Skills and Rules, live Lightning Web Component previews, and the latest Claude and GPT models in one unified picker.</span></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/webconsole/guide/get-started"><b>Web Console (Beta)</b><span style="font-weight: 400">:</span></a> <span style="font-weight: 400">This is a full IDE that runs inside your org, right in the browser. Write, debug, and deploy Apex, LWC, and other metadata without leaving Salesforce. You can edit and save classes, run anonymous Apex, and set trace flags and debug log levels in one place. It differs from the </span><a href="https://developer.salesforce.com/docs/platform/code-builder/overview"><span style="font-weight: 400">Agentforce Vibes (AFV) IDE</span></a><span style="font-weight: 400"> in three ways: it&#8217;s available on every org, it loads faster, and it runs entirely in the browser. The trade-off is that it supports only Salesforce-provided extensions, not custom ones. Reach for it for quick, in-org edits, and use the AFV IDE when you need a richer, extensible environment. Enable it in Setup under Development → Web Console (Beta).</span></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/lwc/guide/get-started-test-components.html"><b>Live Preview VS Code Extension</b></a><span style="font-weight: 400">: </span><span style="font-weight: 400">This is the renamed Local Dev. See a single Lightning web component update in real time as you edit it, either in the browser or inside VS Code and the Agentforce Web IDE, using the Live Preview extension.</span></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=salesforce.salesforcedx-metadata-visualizer-vscode"><b>Metadata Visualizer</b></a><span style="font-weight: 400"> vs Code Extension: </span><span style="font-weight: 400">This extension turns raw metadata files into interactive diagrams, so you can see structure and relationships at a glance instead of reading XML. It updates in real time as you edit, and plugs into Agentforce Vibes to visualize AI-generated metadata. This extension is actively developed and currently supports visualizers for objects, permission sets and flexipages (Beta). Additional metadata visualizers are scheduled for delivery.</span></li>
</ul>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">Summer ’26 is the release that makes Salesforce truly headless. Every major capability — data, automation, grounding, and agents — is now reachable from a CLI, an API, your IDE, or an autonomous AI agent, with security enforced by default. For you, that means less glue code, safer defaults, and quicker feedback as you code — whether you build with Apex, LWC, or Agent Script.</span></p>
<p><span style="font-weight: 400">The best way to get ready is to spin up a sandbox, scratch org or a developer edition org and try these features before they reach production. Have questions, or want to share what you&#8217;re building? Join the conversation in the </span><a href="https://trailhead.salesforce.com/trailblazer-community/groups/0F93A000000DJbJSAW?tab=discussion&amp;sort=LAST_MODIFIED_DATE_DESC"><span style="font-weight: 400">Salesforce Developers Trailblazer Community</span></a><span style="font-weight: 400">, or connect with us on the </span><a href="https://developer.salesforce.com/"><span style="font-weight: 400">Salesforce Developers</span></a><span style="font-weight: 400"> channels.</span></p>
<h2><span style="font-weight: 400">More Summer ’26 learning resources</span></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Read the </span><a href="https://help.salesforce.com/s/articleView?id=release-notes.salesforce_release_notes.htm&amp;release=262&amp;type=5"><span style="font-weight: 400">official release notes</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Join the </span><a href="https://trailhead.salesforce.com/trailblazer-community/groups/0F93A000000DJbJSAW?tab=discussion&amp;sort=LAST_MODIFIED_DATE_DESC"><span style="font-weight: 400">Salesforce Developers Trailblazer Community group</span></a><span style="font-weight: 400"> to connect with the global developer community</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Join the </span><a href="https://trailhead.salesforce.com/trailblazer-community/groups/0F9300000001okuCAA?tab=discussion&amp;sort=LAST_MODIFIED_DATE_DESC"><span style="font-weight: 400">Release Readiness Trailblazers Community group</span></a><span style="font-weight: 400"> to get early access to release information and discuss changes with other developers</span></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Mohith Shrivastava</b><span style="font-weight: 400"> is a Principal Developer Advocate at Salesforce with 15 years of experience building enterprise-scale products on the Agentforce 360 Platform. Mohith is currently among the lead contributors on Salesforce Stack Exchange, a developer forum where Salesforce Developers can ask questions and share knowledge. You can follow him on </span><a href="https://www.linkedin.com/in/mohith-shrivastava-9a36464a/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
</div>
<p>&nbsp;</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/the-salesforce-developers-guide-to-the-summer-26-release">The Salesforce Developer’s Guide to the Summer ’26 Release</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/06/the-salesforce-developers-guide-to-the-summer-26-release/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206465</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260605092828/Dev-BlogImage-1000x563-1.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260605092828/Dev-BlogImage-1000x563-1.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Security Anti-Patterns in Lightning Web Components</title>
		<link>https://developer.salesforce.com/blogs/2026/06/security-anti-patterns-in-lightning-web-components</link>
		<comments>https://developer.salesforce.com/blogs/2026/06/security-anti-patterns-in-lightning-web-components#respond</comments>
		<pubDate>Fri, 05 Jun 2026 16:00:37 +0000</pubDate>
		<dc:creator><![CDATA[Tim Dionne]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Lightning Web Components]]></category>
		<category><![CDATA[Trust, Security, and Accessibility]]></category>
		<category><![CDATA[Content Security Policy]]></category>
		<category><![CDATA[document.cookie]]></category>
		<category><![CDATA[lightning web security]]></category>
		<category><![CDATA[localStorage]]></category>
		<category><![CDATA[LWS Distortion Viewer]]></category>
		<category><![CDATA[Proxy objects]]></category>
		<category><![CDATA[Shadow DOM]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206449</guid>
		<description><![CDATA[<p>Learn how the platform&rsquo;s three independent security layers operate, how Lightning Web Security (LWS) distorts common APIs, and how to avoid the cross-namespace mistakes that lead to silent data failures.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/security-anti-patterns-in-lightning-web-components">Security Anti-Patterns in Lightning Web Components</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><span style="font-weight: 400">You write a Lightning web component. It works in your scratch org. You deploy it. And then it silently returns wrong data in production — no errors, no warnings, just the wrong result. If this sounds familiar, you&#8217;ve probably run into </span><a href="https://developer.salesforce.com/docs/platform/lightning-components-security/guide/lws-intro.html"><span style="font-weight: 400">Lightning Web Security</span></a><span style="font-weight: 400"> (LWS). But the real problem isn&#8217;t LWS itself; it&#8217;s misunderstanding what LWS actually does.</span></p>
<p><span style="font-weight: 400">This is the first post in a series on common anti-patterns in Lightning Web Components (LWC). In this post, you&#8217;ll learn how the three security layers in the Lightning platform work, which APIs are namespaced versus blocked, and how to avoid the most common mistakes that lead to silent failures in production. The patterns covered here apply to any custom LWC running on the Salesforce Platform.</span></p>
<p><b>Note: LWS distortions can change between Salesforce releases as the platform evolves. The </b><a href="https://developer.salesforce.com/tools/lws-distortion-viewer"><b>LWS Distortion Viewer</b></a><b> is the live source of truth for the exact behavior of every distorted API. If a pattern in this post no longer matches what you observe in your org, check the Distortion Viewer first before filing a bug.</b></p>
<h2><span style="font-weight: 400">Three security layers instead of one</span></h2>
<p><span style="font-weight: 400">Your LWC code runs inside three independent security layers. Knowing which layer does what saves you hours of debugging.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206452" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260604221827/Diagram-that-illustrates-the-three-independent-security-layers-in-which-your-Lightning-web-component-runs-e1780636721444.png?w=1000" class="postimages" width="1000" height="622" alt="Diagram that illustrates the three independent security layers in which your Lightning web component runs." />
			  </span>
			</p>
<p><span style="font-weight: 400">Lightning Web Security (LWS) is the namespace isolation layer. It places your component&#8217;s JavaScript in a sandboxed environment and applies </span><i><span style="font-weight: 400">distortions</span></i><span style="font-weight: 400"> to browser APIs. Distortions don&#8217;t simply block APIs — they namespace storage, sanitize HTML on shared DOM elements, sandbox code evaluation, and block only a small number of APIs that could escape the sandbox. You can see every distortion and its exact behavior in the </span><a href="https://developer.salesforce.com/tools/lws-distortion-viewer"><span style="font-weight: 400">LWS Distortion Viewer</span></a><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">The LWC framework enforces </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-dom.html"><span style="font-weight: 400">shadow DOM</span></a><span style="font-weight: 400"> scoping, prevents access to legacy Aura globals like </span><span style="font-weight: 400">$A</span><span style="font-weight: 400">, and manages the component lifecycle.</span></p>
<p><a href="https://developer.salesforce.com/docs/platform/lightning-components-security/guide/content-security-policy-intro.html"><span style="font-weight: 400">Content Security Polic</span></a><span style="font-weight: 400">y (CSP) and platform-level restrictions block inline scripts, external CDN loading, and certain URL schemes. These operate independently of LWS.</span></p>
<p><span style="font-weight: 400">When something doesn&#8217;t work, your first question should be: which layer is responsible? Getting this wrong leads to workarounds that solve the wrong problem.</span></p>
<h2><span style="font-weight: 400">Five key anti-patterns</span></h2>
<p><span style="font-weight: 400">The patterns below fall into five categories. The first three describe how LWS distorts APIs — namespacing, sanitizing/sandboxing, and outright blocking. The fourth covers what happens at the boundary between namespaces. The fifth covers patterns that look like LWS issues, but actually come from the LWC framework or CSP. Identifying the right category is usually the fastest path to the right fix.</span></p>
<h3><span style="font-weight: 400">1. Namespaced APIs</span></h3>
<p><span style="font-weight: 400">LWS does not block these APIs — it applies a namespace isolation layer. The APIs function as expected within your own component set, but they remain blind to data established by the platform or by components residing in other namespaces.</span></p>
<h4><span>Assuming </span><code>localStorage</code><span> and </span><code>sessionStorage</code><span> are global</span></h4>
<p><span>LWS does not block </span><code>localStorage</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage"><u>docs</u></a><span>) or </span><code>sessionStorage</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage"><u>docs</u></a><span>). It namespaces them. Every key is transparently prefixed with </span><code>LSKey[namespace]</code><span>, so each namespace gets its own isolated storage. When you call </span><code>localStorage.setItem('myKey', 'value')</code><span> in namespace </span><code>c</code><span>, Salesforce actually stores </span><code>LSKey[c]myKey</code><span>. Your component sees only its own keys.</span></p>
<p><span>This means </span><code>localStorage</code><span> works — but only within your namespace. If you expect to read keys set by the platform or by a component in a different namespace, you&#8217;ll get back </span><code>null</code><span> with no error.</span></p>
<pre language="javascript">// Returns null — the key was set outside this namespace's sandbox
connectedCallback() {
    const token = window.localStorage.getItem('sessionToken');
}
</pre>
<p><span>Use </span><code>localStorage</code><span> for same-namespace persistence like caching user preferences. For cross-namespace data or system-level state, use custom settings, Apex calls, or the </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/use-message-channel.html"><u>Lightning Message Service</u></a><span>.</span></p>
<h4><span>Assuming </span><code>document.cookie</code><span> is global</span></h4>
<p><span>Like storage, </span><code>document.cookie</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie"><u>docs</u></a><span>) is namespaced by LWS, not blocked. The getter returns only cookies belonging to your sandbox, and the setter adds a sandbox prefix to new cookie keys. Platform cookies and cookies from other namespaces are invisible.</span></p>
<p><span>Don&#8217;t rely on </span><code>document.cookie</code><span> to read platform or cross-namespace cookies. Use server-side Apex to access session or authentication state instead.</span></p>
<h3><span style="font-weight: 400">2. Sanitized and sandboxed APIs</span></h3>
<p><span>LWS does not block APIs like </span><code>innerHTML</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML"><u>docs</u></a><span>), </span><code>eval()</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval"><u>docs</u></a><span>), or </span><code>Function()</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function"><u>docs</u></a><span>). Instead, it applies specific distortions based on the execution context. The key to avoiding silent failures is understanding exactly how the platform modifies these behaviors rather than attempting to bypass them entirely.</span></p>
<h4><strong>DOM mutation on shared elements</strong></h4>
<p><span>LWS runs in the main </span><code>window</code><span>, where </span><code>&lt;html&gt;</code><span>, </span><code>&lt;head&gt;</code><span>, and </span><code>&lt;body&gt;</code><span> are shared across all components. LWS protects these shared elements by sanitizing HTML strings and restricting which child elements you can add. For elements inside your component&#8217;s own shadow DOM, these APIs work normally.</span></p>
<p><span>Here&#8217;s the key distinction: </span><code>innerHTML</code><span>, </span><code>outerHTML</code><span>, </span><code>insertAdjacentHTML</code><span>, and related APIs are not blocked. They&#8217;re sanitized when targeting shared elements, and unrestricted on elements your component owns.</span></p>
<pre language="javascript">// innerHTML works on component-owned elements — but this is still an XSS risk
renderedCallback() {
    const container = this.template.querySelector('.container');
    container.innerHTML = `<span>${this.userProvidedValue}</span>`;
}
</pre>
<p><span>LWS will sanitize this if </span><code>container</code><span> is a shared element, but the real issue is the </span><a href="https://owasp.org/www-community/attacks/xss/"><u>Cross Site Scripting</u></a><span> (XSS) risk. Relying on LWS sanitization as a security mechanism is fragile — it protects shared DOM elements, not your component&#8217;s shadow DOM.</span></p>
<p><span>Use LWC&#8217;s declarative template directives (</span><code>lwc:if</code><span>, </span><code>for:each</code><span>, template expressions) for dynamic content. For trusted rich text from a CMS, use </span><code>lightning-formatted-rich-text</code><span> (see </span><a href="https://developer.salesforce.com/docs/platform/lightning-component-reference/guide/lightning-formatted-rich-text.html?type=Example"><u>docs</u></a><span>).</span></p>
<h4><strong>Code evaluation is sandboxed, not blocked</strong></h4>
<p><span>This one surprises many developers: </span><code>eval()</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval"><u>docs</u></a><span>) and the </span><code>Function()</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function"><u>docs</u></a><span>) constructor are not blocked by LWS. They run inside the sandbox — LWS ensures the evaluated code executes in the same sandboxed context as your component. Passing a string to </span><code>setTimeout()</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout"><u>docs</u></a><span>) or </span><code>setInterval()</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval"><u>docs</u></a><span>) works the same way.</span></p>
<p><code>eval('1 + 1')</code><span> returns </span><code>2</code><span> in a Salesforce org. But </span><code>eval()</code><span> is still a bad practice. It makes code harder to analyze, blocks compiler optimizations, and creates injection vectors if the evaluated string includes user input.</span></p>
<pre language="javascript">// eval works in the sandbox — but don't do this
processRule(ruleString) {
    return eval(ruleString);
}</pre>
<pre><span style="font-weight: 400">Replace dynamic code evaluation with explicit logic, a lookup table, or a strategy pattern. If you need to evaluate user-defined expressions, implement a safe parser scoped to the expression language you support.</span></pre>
<h3><span style="font-weight: 400">3. APIs that are actually blocked</span></h3>
<p>&nbsp;</p>
<h4><strong>APIs that throw at runtime</strong></h4>
<p><span style="font-weight: 400">A small number of APIs are genuinely blocked by LWS because they provide direct paths to escape the sandbox. Calling any of these throws a runtime exception.</span></p>
<table>
<tbody>
<tr>
<td><b>API</b></td>
<td><b>Why it&#8217;s blocked</b></td>
</tr>
<tr>
<td><code>document.write()</code><span> / </span><code>writeln()</code></td>
<td><span style="font-weight: 400">Can write arbitrary JavaScript that bypasses the sandbox</span></td>
</tr>
<tr>
<td><code>Worker()</code><span> / </span><code>SharedWorker()</code></td>
<td><span style="font-weight: 400">Script execution outside the sandbox</span></td>
</tr>
<tr>
<td><span style="font-weight: 400"><code>ServiceWorkerContainer</code> </span><span style="font-weight: 400">(all methods)</span></td>
<td><span style="font-weight: 400">Can intercept responses to run unsandboxed code</span></td>
</tr>
<tr>
<td><code>window.find()</code></td>
<td><span style="font-weight: 400">Cross-namespace content access</span></td>
</tr>
<tr>
<td><code>XSLTProcessor.transformToDocument()</code><span> / </span><code>transformToFragment()</code></td>
<td><span style="font-weight: 400">XSLT can generate HTML that bypasses distortions</span></td>
</tr>
<tr>
<td><code>Document.parseHTMLUnsafe()</code><span> / </span><code>Element.setHTMLUnsafe()</code></td>
<td><span style="font-weight: 400">Unsanitized HTML injection</span></td>
</tr>
</tbody>
</table>
<p><span>Use LWC&#8217;s declarative template system for HTML rendering and </span><code>lightning/platformResourceLoader</code><span> (see </span><a href="https://developer.salesforce.com/docs/platform/lightning-component-reference/guide/lightning-platform-resource-loader.html?type=Develop"><u>docs</u></a><span>) for script loading. There is no supported workaround for Workers inside the LWS sandbox. If your use case requires them, consider an iframe-based isolation strategy.</span></p>
<h4><strong>Network requests are not broadly restricted</strong></h4>
<p><code>fetch()</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch"><u>docs</u></a><span>), </span><code>XMLHttpRequest</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest"><u>docs</u></a><span>), </span><code>navigator.sendBeacon()</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon"><u>docs</u></a><span>), and </span><code>fetchLater()</code><span> (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/fetchLater"><u>docs</u></a><span>) work normally under LWS. The only distortion blocks requests to URLs containing </span><code>/aura</code><span> or </span><code>/webruntime</code><span> — these are internal framework endpoints that your components should not access directly. All other network requests work normally, subject to standard </span><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS"><u>Cross-Origin Resource Sharing</u></a><span> (CORS) rules and your org&#8217;s CSP configuration.</span></p>
<pre language="javascript">// Blocked — hitting internal framework endpoints
fetch('/aura?aura.ApexAction.execute=1', { method: 'POST', body: payload });
</pre>
<p><span>Use Apex </span><code>@AuraEnabled</code><span> methods via the wire service or imperative calls instead.</span></p>
<h3><span style="font-weight: 400">4. Cross-namespace boundaries</span></h3>
<p><span style="font-weight: 400">LWS employs a proxy membrane to enforce strict isolation across namespace boundaries. Consequently, objects traversing this boundary exhibit different behaviors compared to those residing within a single namespace. For further details on the performance impacts of this membrane proxying, consult the </span><a href="https://developer.salesforce.com/docs/platform/lightning-components-security/guide/lws-performance.html"><span style="font-weight: 400">LWS Performance documentation</span></a><span style="font-weight: 400">.</span></p>
<h4><strong>Cross-namespace object isolation</strong></h4>
<p><span>When your component receives an object from a different namespace — through an </span><code>@api</code><span> property, event </span><code>detail</code><span>, or a shared service — and you mutate that object in place, the change is not propagated back outside the sandbox. No error is thrown. The originating component just keeps seeing the original value. This is one of the hardest LWS traits to diagnose because the only symptom is stale data.</span></p>
<p><span style="font-weight: 400">For more on the performance implications of cross-namespace membrane proxying, see the </span><a href="https://developer.salesforce.com/docs/platform/lightning-components-security/guide/lws-performance.html"><span style="font-weight: 400">LWS Performance documentation</span></a><span style="font-weight: 400">.</span></p>
<pre language="javascript">// childComponent.js — namespace 'c'
@api record;

handleEdit() {
    // Mutation is silently absorbed by the LWS proxy — parent never sees it
    this.record.Name = 'Updated Name';
}
</pre>
<p><span>Clone the received object before modifying it, then communicate changes back with a </span><code>CustomEvent</code><span>.</span></p>
<pre language="javascript">handleEdit() {
    const updated = { ...this.record, Name: 'Updated Name' };
    this.dispatchEvent(new CustomEvent('recordchange', {
        detail: { ...updated }
    }));
}
</pre>
<h4><strong>Passing objects across namespace boundaries</strong></h4>
<p><span>Objects passed between components in different namespaces are wrapped in LWS </span><code>Proxy</code><span> objects (see </span><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy"><u>docs</u></a><span>). Proxy unwrapping can fail silently, giving you </span><code>null</code><span> or incorrect values on the receiving end. Browser extensions that listen to custom events also receive </span><code>null</code><span> for </span><code>detail</code><span> when it contains a proxied object.</span></p>
<pre language="javascript">// Namespace 'foo' — dispatching
this.dispatchEvent(new CustomEvent('selected', {
    detail: { record: this.selectedRecord }  // may arrive as null in another namespace
}));
</pre>
<p><span style="font-weight: 400">Ensuring your object can be </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone"><span style="font-weight: 400">structured cloned</span></a><span style="font-weight: 400"> is the key. Or, serialize the payload to JSON before dispatching and parse it on receipt. A plain string crosses the namespace boundary without proxy wrapping.</span></p>
<pre language="javascript">// Namespace 'foo'
this.dispatchEvent(new CustomEvent('selected', {
    detail: JSON.stringify(this.selectedRecord)
}));

// Namespace 'bar'
handleSelected(event) {
    const record = JSON.parse(event.detail);
}
</pre>
<h3><span style="font-weight: 400">5. Misattributed restrictions</span></h3>
<p><span style="font-weight: 400">These patterns fail at runtime, but the cause is not LWS. Blaming LWS leads to the wrong fix.</span></p>
<h4><strong>Referencing legacy Salesforce globals</strong></h4>
<p><span>The </span><b><span>LWC framework</span></b><span> (not LWS) blocks access to </span><code>$A</code><span>, </span><code>Aura</code><span>, </span><code>Sfdc</code><span>, and </span><code>sforce</code><span> in Lightning web components. These belong to the Aura framework or are deprecated platform globals. Any reference to them in LWC code fails at runtime.</span></p>
<p><span>This is the most common mistake when migrating Aura components to LWC.</span></p>
<pre language="javascript">// Pattern carried over from Aura — fails at runtime
connectedCallback() {
    const userId = $A.get('$SObjectType.CurrentUser.Id');
}
</pre>
<p><span style="font-weight: 400">Use the LWC platform equivalents instead.</span></p>
<pre language="javascript">import userId from '@salesforce/user/Id';
import { getRecord } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/User.Name';
</pre>
<p><span style="font-weight: 400">For a complete mapping, see the </span><a href="https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.migrate_intro"><span style="font-weight: 400">Migrate Aura Components to LWC</span></a><span style="font-weight: 400"> documentation.</span></p>
<h4><strong>Loading third-party scripts from external CDNs</strong></h4>
<p><span>Loading a JavaScript library with a </span><code>&lt;script&gt;</code><span> tag pointing to an external CDN URL violates Salesforce&#8217;s CSP. The platform blocks it before LWS even comes into play. Beyond the CSP violation, external CDN loading introduces a versioning risk: the CDN may update the library at any time, silently introducing sandbox incompatibilities.</span></p>
<pre language="javascript">connectedCallback() {
    const script = document.createElement('script');
    script.src = 'https://cdn.example.com/somelib/v3.min.js'; // CSP violation
    document.head.appendChild(script);
}
</pre>
<p><span>Download the library, upload it as a Static Resource, and load it with </span><code>lightning/platformResourceLoader</code><span>.</span></p>
<pre language="javascript">import { loadScript } from 'lightning/platformResourceLoader';
import SOMELIB from '@salesforce/resourceUrl/somelib';

async renderedCallback() {
    if (this._libLoaded) return;
    this._libLoaded = true;
    await loadScript(this, SOMELIB);
    this.initializeLib();
}
</pre>
<p><span>Test the static-resource version in a sandbox before promoting to production. If the library requires APIs that LWS blocks (like Workers or </span><code>document.write</code><span>), it won&#8217;t work inside Salesforce regardless of how you load it.</span></p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">These anti-patterns share a root cause: misunderstanding which security layer does what. LWS doesn&#8217;t block most things — it namespaces, sanitizes, and sandboxes. The APIs it actually blocks are a small, specific set. Meanwhile, the LWC framework and CSP enforce their own independent restrictions.</span></p>
<p><span style="font-weight: 400">The mental model to remember: LWS is a namespace isolation layer, not a blanket firewall. When an API seems &#8220;blocked,&#8221; it&#8217;s usually namespaced — you&#8217;re looking at an empty namespace, not a wall. When in doubt, check the </span><a href="https://developer.salesforce.com/tools/lws-distortion-viewer"><span style="font-weight: 400">LWS Distortion Viewer</span></a><span style="font-weight: 400"> for the exact behavior.</span></p>
<p><span style="font-weight: 400">In the next post in this series, we&#8217;ll cover data and communication: the Wire Service, </span><span style="font-weight: 400"><code>@api</code></span><span style="font-weight: 400"> properties, and event handling. These are the three surfaces where most component-to-component bugs originate, and where a few common misunderstandings cause the most trouble.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/tools/lws-distortion-viewer"><span style="font-weight: 400">LWS Distortion Viewer</span></a><span style="font-weight: 400"> — the definitive reference for every LWS distortion and its behavior</span></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/lightning-components-security/guide/lws-architecture.html"><span style="font-weight: 400">Lightning Web Security architecture</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/lightning-components-security/guide/lws-performance.html"><span style="font-weight: 400">LWS Performance</span></a><span style="font-weight: 400"> — performance implications of cross-namespace membrane proxying</span></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/lwc/guide/security-lwsec-intro"><span style="font-weight: 400">Lightning Web Security Developer Guide</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/lwc/guide/migrate-introduction.html"><span style="font-weight: 400">Migrate Aura Components to LWC</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Tim Dionne</b><span style="font-weight: 400"> is a PMTS on the Customer Centric Engineering team. He’s worked on many UI features of Salesforce over the years, starting with VisualForce, Aura Components, and Lightning Web Components with an emphasis on Lightning Web Security and Lightning Data Service.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/security-anti-patterns-in-lightning-web-components">Security Anti-Patterns in Lightning Web Components</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/06/security-anti-patterns-in-lightning-web-components/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206449</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260608003817/6_5-Security-Anti-Patterns-in-Lightning-Web-Components-e1780904306103.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260608003817/6_5-Security-Anti-Patterns-in-Lightning-Web-Components-e1780904306103.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Simplify Your SOQL Queries Using SOQL FORMULA() in WHERE</title>
		<link>https://developer.salesforce.com/blogs/2026/06/simplify-your-soql-queries-using-soql-formula-in-where</link>
		<comments>https://developer.salesforce.com/blogs/2026/06/simplify-your-soql-queries-using-soql-formula-in-where#respond</comments>
		<pubDate>Thu, 04 Jun 2026 16:35:02 +0000</pubDate>
		<dc:creator><![CDATA[Dikshita Patel]]></dc:creator>
				<category><![CDATA[Apex]]></category>
		<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Salesforce Releases]]></category>
		<category><![CDATA[Data Modeling]]></category>
		<category><![CDATA[Database Performance]]></category>
		<category><![CDATA[ISV App Development]]></category>
		<category><![CDATA[Query Optimization]]></category>
		<category><![CDATA[SOQL]]></category>
		<category><![CDATA[Summer 26]]></category>
		<category><![CDATA[WHERE Clause]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206439</guid>
		<description><![CDATA[<p>Turn formula style expressions into first class WHERE filters, so other integrations and Apex can query computed business rules without extra formula fields or in-memory filtering.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/simplify-your-soql-queries-using-soql-formula-in-where">Simplify Your SOQL Queries Using SOQL FORMULA() in WHERE</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><span style="font-weight: 400">With the Summer ’26 release, Salesforce is piloting <code>FORMULA()</code> in <code>SOQL WHERE</code></span><span style="font-weight: 400"> clauses, enabling you to filter rows using compiled formula expressions instead of adding one-off formula fields or post-processing in Apex. This helps developers reduce schema clutter, simplify queries, and write less “query everything, filter later” code, particularly for ISVs working across customer schemas. </span></p>
<p><span style="font-weight: 400">In this post, you’ll learn the basic syntax and mental model, typical use cases (including e‑commerce examples), and pilot gates and current constraints, so you can try it safely in a sandbox.</span></p>
<p><span style="font-weight: 400">The pilot is open to any Salesforce customer — contact your Salesforce Account Executive or Customer Success Manager to request access.</span></p>
<h2><span style="font-weight: 400">Why use FORMULA() in SOQL WHERE?</span></h2>
<p><span style="font-weight: 400">When a filter depends on something that you calculate from fields, such as margin, revenue per head, days idle, or days late to ship, the usual choices are: add a formula field, maintain a duplicate rule in Apex, or query too many rows and throw away what you do not need. </span></p>
<p><span style="font-weight: 400"><code>FORMULA('…')</code> in <code>WHERE</code> is for the case where the rule belongs with the query: you describe the computed condition once, the platform evaluates it as part of filtering, and callers (Apex, APIs, downstream jobs) get a smaller, already qualified result set. This is especially valuable when you can not or do not want to change the data model for every subscriber org, a common scenario for ISVs or developers who want to keep operational segmentation logic visible in SOQL instead of buried in loops.</span></p>
<p><span style="font-weight: 400">For example, say you&#8217;re working with an</span> <span style="font-weight: 400">Order__c</span><span style="font-weight: 400"> object and need to find every order where profit (revenue minus cost) exceeds $150. Without </span><span style="font-weight: 400">FORMULA()</span><span style="font-weight: 400">, the rule lives in Apex after a broader query; with </span><span style="font-weight: 400">FORMULA()</span><span style="font-weight: 400">, the rule moves into the </span><span style="font-weight: 400">WHERE</span><span style="font-weight: 400"> clause itself.</span></p>
<p>For example, say you&#8217;re working with an<span> </span><code>Order__c</code> object and need to find every order where profit (revenue minus cost) exceeds $150. Without <code>FORMULA()</code>, the rule lives in Apex after a broader query; with <code>FORMULA()</code>, the rule moves into the <code>WHERE</code> clause itself.</p>
<pre language="apex">// Without FORMULA(): business rule lives in Apex after a wider query

 List rows = [SELECT Id, Name, Revenue__c, Cost__c FROM Order__c];
  List highMargin = new List();
  for (Order__c o : rows) {
      if (o.Revenue__c != null &amp;&amp; o.Cost__c != null
          &amp;&amp; (o.Revenue__c - o.Cost__c) &gt; 150) {
          highMargin.add(o);
      }
  }
</pre>
<pre language="soql">--With FORMULA(): same rule expressed in the WHERE clause 

SELECT Id, Name FROM Order__c WHERE FORMULA('Revenue__c - Cost__c') &gt; 150
</pre>
<p>The formula expression supports addition and subtraction (+ and -). Expressions can evaluate to <code>DOUBLE, INTEGER, DATETIME, DATE &amp; CURRENCY</code>.</p>
<p>In practice:</p>
<ul>
<li><code>INTEGER</code> behaves like <code>DOUBLE</code></li>
<li><code>DATE</code> behaves like <code>DATETIME</code></li>
</ul>
<p>So the rest of this post focuses on the three illustrative types: <code>DOUBLE</code>, <code>DATETIME</code>, and <code>CURRENCY</code>.</p>
<p><span style="font-weight: 400">This pattern fits Apex and SOQL developers, ISV teams shipping logic across subscriber orgs where schema changes are hard, and anyone trying to move off the &#8220;query a large candidate set, filter in code&#8221; approach.</span></p>
<p>Beyond fitting that audience, <code>FORMULA()</code> in <code>WHERE</code> matters today because it:</p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Eliminates unnecessary formula fields</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Reduces Apex complexity</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Improves performance by filtering at the query level</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Is especially valuable for ISVs who can’t modify customer schemas</span></li>
</ul>
<h2><span style="font-weight: 400">Syntax: FORMULA() in WHERE</span></h2>
<p>Today, <code>FORMULA()</code> in SOQL is available as a pilot capability: it is only supported in the <code>WHERE</code> clause (not <code>HAVING</code>).</p>
<p>The general shape is:</p>
<p><code>WHERE FORMULA('…') &lt;operator&gt; &lt;literal&gt;</code></p>
<p><span style="font-weight: 400">The string is a formula expression evaluated for each row as part of filtering.</span></p>
<h2><span style="font-weight: 400">Example: E-commerce order management </span></h2>
<p><span style="font-weight: 400">Imagine that you&#8217;re building an order analytics dashboard for a growing e-commerce platform.</span></p>
<p>Your custom <code>Order__c</code> object tracks:</p>
<ul>
<li><code>Revenue__c</code> (Currency): Total order value</li>
<li><code>Cost__c</code> (Currency): Fulfillment cost</li>
<li><code>OrderDate__c</code> (Date): When customer placed order</li>
<li><code>ShipDate__c</code> (Date): When order shipped</li>
<li><code>Status__c</code> (Text): Order status</li>
</ul>
<p>Previously, filtering orders by calculated metrics meant either:</p>
<ol>
<li>Creating formula fields like <code>Profit__c</code>, <code>ShippingDelay__c</code>, <code>ProfitMargin__c</code></li>
<li>Querying all orders and filtering in Apex loops</li>
</ol>
<p>Let&#8217;s see how <code>FORMULA() </code>eliminates both.</p>
<p>The screenshot below shows a <code>Sample Order__c</code> records table that includes seven orders (ORD-001 through ORD-007) with Revenue, Cost, OrderDate, ShipDate, and Status columns used throughout the <code>FORMULA()</code> examples.</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206443" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260604091700/Screenshot-of-Sample-Order__c-records-table-showing-seven-orders-e1780589845175.png?w=1000" class="postimages" width="1000" height="140" alt="Screenshot of Sample Order__c records table showing seven orders." />
			  </span>
			</p>
<h3><span style="font-weight: 400">Three key use cases</span></h3>
<p><span style="font-weight: 400">For our e-commerce example, </span><span style="font-weight: 400"><code>FORMULA() </code></span><span style="font-weight: 400">is particularly helpful in the following three use cases:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Find high-profit orders using currency arithmetic.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Detect late shipments using datetime arithmetic.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Filter by premium order criteria using combined conditions.</span></li>
<li style="font-weight: 400"></li>
</ol>
<h4><span style="font-weight: 400">1. Find high-profit orders (<code>CURRENCY</code></span><span style="font-weight: 400"> arithmetic)</span></h4>
<p><span style="font-weight: 400">Our business requirement for this first use case is to identify orders with profit &gt; $250 for priority fulfillment review.</span></p>
<p><b>The old way</b></p>
<pre language="apex"> // Query everything, filter in memory
  List allOrders = [
      SELECT Id, Name, Revenue__c, Cost__c
      FROM Order__c
      WHERE Status__c = 'Shipped'
  ];

  List highProfitOrders = new List();
  for (Order__c order : allOrders) {
      if (order.Revenue__c != null &amp;&amp;
          order.Cost__c != null &amp;&amp;
          (order.Revenue__c - order.Cost__c) &gt; 250) {
          highProfitOrders.add(order);
      }
  }

// Result: 3 orders (ORD-001, ORD-003, ORD-005)
</pre>
<p><b>The new way</b></p>
<pre language="sql">SELECT Id, Name, Revenue__c, Cost__c
FROM Order__c
WHERE Status__c = 'Shipped'
AND FORMULA('Revenue__c - Cost__c') &gt; 250
</pre>
<p>The following screenshot shows a SOQL query using the FORMULA() function to filter orders by profit: <code>SELECT </code><code>Id</code>, <code>Name</code>, <code>Revenue__c</code>, <code>Cost__c FROM Order__c WHERE Status__c</code> equals “Shipped” AND <code>FORMULA('Revenue__c - Cost__c')</code> is greater than 250.Query results show three records: ORD-001 with 500 revenue and 200 cost, ORD-003 with 800 revenue and 300 cost, and ORD-005 with 1200 revenue and 400 cost.</p>
<p><strong>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206444" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260604091804/Screenshot-of-a-SOQL-query-using-the-FORMULA-function-to-filter-orders-by-profit-e1780589901667.png?w=1000" class="postimages" width="1000" height="107" alt="Screenshot of a SOQL query using the FORMULA() function to filter orders by profit." />
			  </span>
			 </strong></p>
<h4><span style="font-weight: 400">2. Detect late shipments (<code>DATETIME</code></span><span style="font-weight: 400"> arithmetic)</span></h4>
<p><span style="font-weight: 400">Our business requirement here is to find orders that shipped more than three days after order date for SLA analysis. </span></p>
<pre language="sql">SELECT Id, Name, OrderDate__c, ShipDate__c
FROM Order__c
WHERE FORMULA('ShipDate__c - OrderDate__c') &gt; 3
</pre>
<p>The screenshot below shows a SOQL query using the <code>FORMULA()</code> function to calculate shipping delays: <code>SELECT Id</code>, <code>Name</code>, <code>OrderDate__c</code>, <code>ShipDate__c</code> <code>FROM Order__c WHERE FORMULA('ShipDate__c minus OrderDate__c')</code> greater than three. Query results display two records: ORD-002 ordered April 5 and shipped April 12, and ORD-005 ordered April 15 and shipped April 25, both exceeding the three-day shipping threshold.</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206445" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260604091902/Screenshot-of-a-SOQL-query-using-the-FORMULA-function-to-calculate-shipping-delays-e1780589954238.png?w=1000" class="postimages" width="1000" height="93" alt="Screenshot of a SOQL query using the FORMULA() function to calculate shipping delays." />
			  </span>
			</p>
<h4>3. <span style="font-weight: 400">Premium order criteria (combined conditions)</span></h4>
<p><b>Our </b><span style="font-weight: 400">business requirement for the third use case is to</span> <span style="font-weight: 400">find &#8220;premium&#8221; orders: high revenue (&gt; $600) AND fast shipping (≤ 2 days).                   </span><b>                                                                                        </b></p>
<pre language="sql">SELECT Id, Name, Revenue__c, OrderDate__c, ShipDate__c
FROM Order__c
WHERE Revenue__c &gt; 600
AND FORMULA('ShipDate__c - OrderDate__c') &lt;= 2
</pre>
<p>The screenshot below shows a SOQL query combining standard field filter with the <code>FORMULA() </code>function: <code>SELECT Id</code>, <code>Name</code>, <code>Revenue__c</code>, <code>OrderDate__c</code>, <code>ShipDate__c FROM Order__c WHERE Revenue__c</code> is greater than 600 AND <code>FORMULA('ShipDate__c minus OrderDate__c')</code> is less than or equal to two. Query results show two records: ORD-003 with 800 revenue, ordered April 10 and shipped April 11, and ORD-007 with 650 revenue, ordered April 20 and shipped April 21, with both records meeting the high-revenue and fast-shipping criteria.</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206446" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260604091932/Screenshot-of-a-SOQL-query-combining-standard-field-filter-with-FORMULA-function-e1780589984880.png?w=1000" class="postimages" width="1000" height="87" alt="Screenshot of a SOQL query combining standard field filter with FORMULA function." />
			  </span>
			</p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p>By applying the ideas in this post, you can express computed filters directly in SOQL. <code>FORMULA()</code> in <code>WHERE</code> turns the question from “what rows might be relevant?” into “what rows already match my computed rule?” with less schema churn. This tends to shrink Apex branching, reduce one-off formula fields for query-only rules, and make the business condition readable next to the <code>SELECT</code>, which helps both integrations and teams reviewing SOQL in code review and operations.</p>
<p><span style="font-weight: 400">Beyond individual queries, the same pattern reinforces a broader habit: keep data access and the rule that defines “the right rows” together, so batch jobs, services, and packaged logic stay easier to maintain, especially when subscriber org schemas are not yours to reshape at will. As always, treat what you validate in a non-production org as the contract for syntax, supported types, and supported operators.</span></p>
<p><span style="font-weight: 400">We would love your feedback on the capabilities of this pilot, and your comments will influence the GA release. Please post your questions and/or feedback on the Pilot to the </span><a href="https://trailhead.salesforce.com/trailblazer-community/groups/0F93A000000DJbJSAW?tab=discussion&amp;sort=LAST_MODIFIED_DATE_DESC"><span style="font-weight: 400">Salesforce Developers Trailblazer Community</span></a><span style="font-weight: 400">. Just let us know!</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Pilot enrollment: Contact your Salesforce Account Executive or Customer Success Manager to request access to the SOQL <code>FORMULA() </code></span><span style="font-weight: 400">pilot</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Trailhead: </span><a href="https://trailhead.salesforce.com/content/learn/modules/soql-for-admins"><span style="font-weight: 400">SOQL for Admins</span></a><span style="font-weight: 400"> &#8211; Create SOQL Queries to get data from your Salesforce org</span></li>
</ul>
<h2><b>About the author</b></h2>
<p><b>Dikshita Patel</b><span style="font-weight: 400"> is a Software Engineer on Salesforce’s Enterprise API team, where she builds Platform APIs allowing developers, partners, and customers to query and access Salesforce data without using the Salesforce user interface. You can follow and connect with her on </span><a href="https://www.linkedin.com/in/dikshita-patel/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/simplify-your-soql-queries-using-soql-formula-in-where">Simplify Your SOQL Queries Using SOQL FORMULA() in WHERE</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/06/simplify-your-soql-queries-using-soql-formula-in-where/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206439</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260604082729/Generic-B-1-e1780586873385.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260604082729/Generic-B-1-e1780586873385.png?w=1000" medium="image" />
	</item>
		<item>
		<title>The MCP Server for Marketing Cloud Engagement is Now GA</title>
		<link>https://developer.salesforce.com/blogs/2026/06/the-mcp-server-for-marketing-cloud-engagement-is-now-ga</link>
		<comments>https://developer.salesforce.com/blogs/2026/06/the-mcp-server-for-marketing-cloud-engagement-is-now-ga#respond</comments>
		<pubDate>Tue, 02 Jun 2026 15:00:08 +0000</pubDate>
		<dc:creator><![CDATA[Swetha Pinninti]]></dc:creator>
				<category><![CDATA[Announcements]]></category>
		<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[Automation]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[New Developments]]></category>
		<category><![CDATA[AI agent]]></category>
		<category><![CDATA[automations]]></category>
		<category><![CDATA[data extensions]]></category>
		<category><![CDATA[Installed Package]]></category>
		<category><![CDATA[journeys]]></category>
		<category><![CDATA[Marketing Cloud Engagement]]></category>
		<category><![CDATA[Model context protocol]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206424</guid>
		<description><![CDATA[<p>Learn how to securely connect external AI agents to Marketing Cloud Engagement and expose core capabilities like data extensions and journeys as natural language tools.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/the-mcp-server-for-marketing-cloud-engagement-is-now-ga">The MCP Server for Marketing Cloud Engagement is Now GA</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><span style="font-weight: 400">Model Context Protocol (MCP) is an open standard that lets any LLM-powered AI agent — Claude, ChatGPT, Cursor, Gemini, and more — connect to and control an external platform using a standardized protocol. Think of it like a universal adapter for AI. Now, Salesforce is releasing a generally available MCP interface for Marketing Cloud Engagement APIs.</span></p>
<p><span style="font-weight: 400">The </span><a href="https://developer.salesforce.com/docs/marketing/mce-mcp/guide/mce-mcp.html"><span style="font-weight: 400">MCE MCP Server</span></a><span style="font-weight: 400"> is Salesforce&#8217;s first-party, enterprise-grade, hosted MCP server for Marketing Cloud Engagement. It’s a first step towards a truly headless experience, exposing MCE&#8217;s core marketing capabilities as tools that any MCP-compatible AI agent can call — in plain language, without writing code or navigating the UI. </span></p>
<p><span style="font-weight: 400">The server allows almost any external agent that supports MCP to manage data extensions, journeys, automations, and more. Note that Marketing Cloud API limits and guidelines apply (see </span><a href="https://help.salesforce.com/s/articleView?id=mktg.mc_overview_limits_api.htm&amp;type=5"><span style="font-weight: 400">documentation</span></a><span style="font-weight: 400"> for more information).</span></p>
<p><span style="font-weight: 400">In this post, we’ll walk you through the MCE MCP Server setup, tools, and typical use cases.</span></p>
<h2><b>How to set up the MCE MCP Server</b></h2>
<p><span style="font-weight: 400">An agent’s permissions are a combination of the scopes in a dedicated, installed package and your own user permissions. If your installed package has access to read data extensions, but you (as a user) do not, then the MCP server does not have access. Likewise, you can limit what it can do by not granting permissions to the installed package, even if the user still has them. </span></p>
<p><span style="font-weight: 400">From the install package, you’ll be able to get an MCP URL that you and potentially other teammates can use. This is specific to your organization and the install package. It’s not sensitive, but it’s worth keeping in a safe place for convenience. Registering it with Claude Code, for example, is simple.</span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<pre language="shell">claude mcp add --transport http <a name=""></a> https://
</pre>
<p><span style="font-weight: 400">Other agents will be very similar. You should only need to do this once, and then authenticate from inside the agent. The details on how this works will vary depending on how the agent implements OAuth login flows, but you will be prompted to log into your Marketing Cloud account if you are not already logged in. This gives the agent a token that it can use to act on your behalf, but only for a limited time. </span></p>
<p><span style="font-weight: 400">For more information on MCE MCP Server setup, please see the </span><a href="https://developer.salesforce.com/docs/marketing/mce-mcp/guide/mce-mcp-setup.html"><span style="font-weight: 400">documentation</span></a><span style="font-weight: 400">.</span></p>
<h2><b>MCE MCP Server capabilities and tool reference</b></h2>
<p>The MCE MCP Server is designed to simply wrap existing functionality provided by the Marketing Cloud Engagement APIs. For example, to get lists of data extensions, we offer a tool called <code>sfmc_get_data_extensions</code>. This is nearly 1:1 with a request to <code>data/v1/customobjects</code> on the original REST route, but made more easily discoverable for an agent. It can read the embedded documentation and discover that it needs to provide a <code>$search</code> variable with the search string.</p>
<p><span style="font-weight: 400">As an example, this is how this tool is shown to the agent:</span></p>
<pre language="json">{
  "name": "sfmc_get_data_extensions",
  "description": "Retrieve a list of data extensions. GET /data/v1/customobjects.\n\nIMPORTANT: The $search query parameter is REQUIRED and must be a valid search term (no wildcards like * or %).\nYou must provide query_json with the structure: {\"$search\": \"term\"}\n\nExamples:\n- Search for 'customer': {\"$search\": \"customer\"}\n- Search for 'email': {\"$search\": \"email\"}\n\nSee resource 'sfmc://docs/data/v1/customobjects' for full API documentation.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query_json": {
        "type": "string",
        "description": "JSON object string containing $search parameter. REQUIRED format: {\"$search\": \"search_term\"}. The search term cannot be empty or contain wildcards (* or %)."
      }
    },
    "required": [
      "query_json"
    ]
  },
  "annotations": {
    "title": "sfmc get data extensions",
    "readOnlyHint": true,
    "destructiveHint": false,
    "idempotentHint": true,
    "openWorldHint": true
  }
}
</pre>
<p><span style="font-weight: 400">Just as our documentation promised, the resulting tool call is simple. </span></p>
<pre language="json">{ "$search": "test" }
</pre>
<p><span style="font-weight: 400">The results are also pretty straightforward, even for a human to read.</span></p>
<pre language="json">Status: 200 OK
URL: https://mcfdpyw7c97kdckv65lk2j1vspd0.rest.marketingcloudapis.com/data/v1/customobjects?%24search=test

{
  "count" : 8,
  "page" : 1,
  "pageSize" : 25,
  "links" : { },
  "items" : [ {
    "id" : "bdc04e14-0209-f111-a5e8-5cba2c701e38",
    "name" : "test_7",
    "key" : "test_7",
    "description" : "Subscribers who purchased in 2018",
    "isActive" : true,
    "isSendable" : false,
    "isTestable" : false,
    "categoryId" : 1998298,
    "ownerId" : 11280369,
    "isObjectDeletable" : true,
    "isFieldAdditionAllowed" : true,
    "isFieldModificationAllowed" : true,
    "createdDate" : "2026-02-13T11:33:22.133",
    "createdById" : 11280369,
    "createdByName" : "GI Admin",
    "modifiedDate" : "2026-02-13T11:33:22.133",
    "modifiedById" : 11280369,
    "modifiedByName" : "GI Admin",
    "ownerName" : "GI Admin",
    "partnerApiObjectTypeId" : 310,
    "partnerApiObjectTypeName" : "DataExtension",
    "rowCount" : 0,
    "dataRetentionProperties" : {
      "isDeleteAtEndOfRetentionPeriod" : false,
      "isRowBasedRetention" : false,
      "isResetRetentionPeriodOnImport" : false
    },
    "fieldCount" : 15
  }, ...
</pre>
<p><span style="font-weight: 400">It’s worth noting that the result is NOT JSON as far as the agent is concerned. It’s simply text, which in this case represents the result of making an API call to a REST endpoint. It is up to the agent to interpret this information and use it, and that’s what provides the maximum flexibility.</span></p>
<p>While the original intent of this API was to integrate with custom software, an AI agent can instead make it available interactively and with minimal friction. You don’t need to know about <code>$search</code> (did you forget the dollar sign?) or its syntax, the agent can deal with that. If the tool fails, it has access to the error message and relevant documentation to try again with different arguments without requiring a human to copy and paste an error code into a search engine. In this case, the tool offered <code>sfmc://docs/data/v1/customobjects</code> as a resource to selectively load and find out more about custom object definitions if necessary.</p>
<p><span style="font-weight: 400">Another advantage is that if you are making a complex request, the AI agent (like Claude Client) can sequence tools that need to be executed in order to achieve the given functionality. For example, If you just tell the AI client to “Update the loyalty status of subscriberId 5544 to gold in XYZ DE,” the agent first gets the XYZ DE, fetches the row in DE, and updates the row without user intervention.</span></p>
<p><span style="font-weight: 400">See the </span><a href="https://developer.salesforce.com/docs/marketing/mce-mcp/references/mce-mcp-tools/mce-mcp-tools.html"><span style="font-weight: 400">MCE MCP tool reference</span></a><span style="font-weight: 400"> for more information.</span></p>
<h2><b>What you can build with MCP + Marketing Cloud</b></h2>
<p><span style="font-weight: 400">We tested a range of use cases, from creating a new campaign journey using a single prompt to sending transactional messages in real time. AI agents are not necessarily the most creative things around, but our test agent was able to put most of the pieces we needed together, and it prompted for more context if it needed clarity on intent. Typically, you really want to hand off mundane tasks that you’d rather not deal with to an agent, such as the following.</span></p>
<ul>
<li style="font-weight: 400"><b>Create a Data Extension from a plain English description</b><span style="font-weight: 400">: Ask your AI agent, &#8220;Create a Data Extension for Holiday Shoppers with Email, First Name, Last Name, and Purchase Date.&#8221; The agent maps field descriptions to Marketing Cloud datatypes, sets SubscriberKey as the primary key, and returns the CustomerKey and a direct link to the new Data Extension — no Contact Builder navigation required.</span></li>
<li style="font-weight: 400"><b>Launch a basic automated campaign without touching Journey Builder:</b><span style="font-weight: 400"> Ask, &#8220;Create a Welcome Email journey that sends immediately when someone joins the Welcome List DE.&#8221; The agent verifies the entry source DE, builds the Entry → Email → Exit structure, resolves the email asset, and creates the journey in Draft. It then asks if you want to activate it.</span></li>
<li style="font-weight: 400"><b>Build a nurture series conversationally:</b><span style="font-weight: 400"> Ask, &#8220;Create a 3-email Welcome Series: send Email 1 immediately, wait 3 days, send Email 2, wait 7 days, send Email 3.&#8221; The agent parses the wait durations, names each activity descriptively, resolves all email assets, and returns the Journey Builder URL ready to review and activate.</span></li>
<li style="font-weight: 400"><b>Add Einstein optimization to any journey step</b><span style="font-weight: 400">: Ask, &#8220;Add Einstein Send Time Optimization before the promotional email in my Black Friday Journey.&#8221; The agent verifies that Einstein STO is provisioned for your Business Unit, and inserts the activity immediately before the specified email.</span></li>
<li style="font-weight: 400"><b>Propagate a new data field across every dependent DE and query in one command</b><span style="font-weight: 400">: Ask, &#8220;Add Propensity_Score (Number) from the Customer_Signals DE to all downstream Data Extensions and queries.&#8221; The agent traces transitive dependencies across all Query Activities and target DEs, shows you a full impact report (X DEs, Y query steps across Z automations), and — on your confirmation — adds the field to every DE, and updates every SELECT clause in dependency order. It then returns a summary of what changed and any errors encountered. This supports a dry-run mode to preview all changes before touching anything.</span></li>
</ul>
<p><span style="font-weight: 400">Having said that, we encourage you to try all your use cases with this MCP server and let us know if we are missing any crucial or nice-to-have tools on </span><a href="https://ideas.salesforce.com/s/"><span style="font-weight: 400">IdeaExchange</span></a><span style="font-weight: 400">.</span></p>
<h2><b>Permissions, destructive operations, and safety guardrails</b></h2>
<p><span style="font-weight: 400">We’ve done our best to make this service as flexible and useful as possible, for as many people as possible. We decided early on to even support “destructive” operations like deleting data, even though that’s almost always a bad idea to hand off to an agent. Operations like this are annotated as destructive in a way that the agent can read, but the agent is still capable of choosing to execute them if it has the permissions to do so. It’s crucial to think through the “worst case” when assigning permissions during setup, because an agent can make mistakes. Customers are also sensitive to reputational harm if an unintentional send is triggered, so bear that in mind as well! Please make sure that you review your Installed Package scopes before using them. More details in this </span><a href="https://developer.salesforce.com/docs/marketing/mce-mcp/guide/mce-mcp-setup.html"><span style="font-weight: 400">documentation</span></a><span style="font-weight: 400">.</span></p>
<h2><b>Future growth</b></h2>
<p><span style="font-weight: 400">The MCE MCP Server is designed to be extended in the future, so be sure to check back often. We plan to extend coverage to other important Marketing Cloud APIs and improve what we’ve already built. Agents and the LLMs that power them are also continuously releasing and improving, so workflows that weren’t possible before might get more practical over time. </span></p>
<p><span style="font-weight: 400">We plan on continually improving the server and extending it to other Marketing Cloud APIs over time. Please keep an eye on our release notes, and provide feedback through your account manager or provide on </span><a href="https://ideas.salesforce.com/s/"><span style="font-weight: 400">IdeaExchange</span></a><span style="font-weight: 400">.</span></p>
<h2><b>Resources</b></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation: </span><a href="https://developer.salesforce.com/docs/marketing/mce-mcp/guide/mce-mcp.html"><span style="font-weight: 400">MCE MCP Server Setup Guide</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation</span><span style="font-weight: 400">: </span><a href="https://developer.salesforce.com/docs/marketing/mce-mcp/references"><span style="font-weight: 400">MCE MCP Server Tool Reference</span></a></li>
</ul>
<h2><b>About the author</b></h2>
<p><b>​​Swetha Pinninti</b><span style="font-weight: 400"> is a Director of Engineering at Salesforce on the Marketing Cloud Einstein team. </span></p>
<p><span style="font-weight: 400"></span><b>Patrick Frampton</b><span style="font-weight: 400"> is a Lead Member of Technical Staff at Salesforce on the Marketing Cloud Einstein team. </span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/the-mcp-server-for-marketing-cloud-engagement-is-now-ga">The MCP Server for Marketing Cloud Engagement is Now GA</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/06/the-mcp-server-for-marketing-cloud-engagement-is-now-ga/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206424</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260601114100/Generic-A-2-e1780339272284.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260601114100/Generic-A-2-e1780339272284.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Build a Salesforce Agent Skill with Claude Code</title>
		<link>https://developer.salesforce.com/blogs/2026/05/build-a-salesforce-agent-skill-with-claude-code</link>
		<comments>https://developer.salesforce.com/blogs/2026/05/build-a-salesforce-agent-skill-with-claude-code#respond</comments>
		<pubDate>Fri, 29 May 2026 15:26:35 +0000</pubDate>
		<dc:creator><![CDATA[Dave Norris]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Agentforce Vibes]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[Apex]]></category>
		<category><![CDATA[architecture]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[developer tooling]]></category>
		<category><![CDATA[LLMs]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206402</guid>
		<description><![CDATA[<p>Learn how to build authoring skills for consistently producing high quality code across the Salesforce ecosystem using structured workflows, code templates, and automated validators.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/build-a-salesforce-agent-skill-with-claude-code">Build a Salesforce Agent Skill with Claude Code</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><span style="font-weight: 400">Getting AI to write code is easy. Getting it to write code that passes your security review, respects governor limits, follows your preferred frameworks, and scores well on static analysis, consistently, is often problematic. A good way to solve this is to use an </span><a href="https://agentskills.io/home"><span style="font-weight: 400">agent skill</span></a><span style="font-weight: 400">: a structured prompt package that encodes your team&#8217;s definition of &#8220;production-ready&#8221; into something that an agent follows and a validator enforces. Because LLMs are probabilistic, outliers are inevitable and the architecture assumes this and catches them.</span></p>
<p><span style="font-weight: 400">This post breaks down the structure of a Salesforce agent skill using Apex as an example. For clarity, we’ll use Claude Code to highlight specific examples, but the principles apply to any agent. We’ll cover what a skill is, the project structure, and how to get started creating your first skill.</span></p>
<h2><span style="font-weight: 400">What is an agent skill?</span></h2>
<p><span style="font-weight: 400">An agent skill is a structured prompt package that transforms Claude from a general-purpose assistant into a specialist. Rather than relying on the model&#8217;s training data alone, a skill provides explicit workflow contracts, reference material, code templates, and automated validators that load into context when the skill activates.</span></p>
<p><span style="font-weight: 400">The large language models (LLMs) that power Claude already know the programming languages across the Salesforce ecosystem: Apex, LWC, SOQL, etc. But knowing a language and consistently producing high-quality code that adheres to best practices are two different things. A skill bridges that gap.</span></p>
<p><span style="font-weight: 400">Each skill lives in a folder with a predictable structure:</span></p>
<pre>skills/authoring-apex/
├── SKILL.md              # The execution contract
├── references/           # Decision trees, patterns, guardrails
├── assets/               # Example code
└── hooks/scripts/        # Python validators
</pre>
<p><span>At minimum, you need a </span><code>SKILL.md</code>,<span> which is the entry point that Claude reads when the skill activates. Everything else is scaffolding that you add as the skill grows. Start with one </span><a href="http://skill.md"><u><code>SKILL.md</code></u></a><span> and one reference doc, and add hooks when you want mechanical enforcement rather than relying on instructions alone.</span></p>
<p><span>Skills activate based on frontmatter. Frontmatter is the YAML metadata block between the </span><code>‘---’ </code><span>delimiters at the top of a markdown file. It&#8217;s not rendered as content, but it’s there to be read as machine-readable configuration. In a skill, the frontmatter tells an agent when to activate: the name field identifies the skill, and the description field contains the trigger rules that the agent uses to decide whether this skill is relevant to the current task. </span></p>
<p><span>In the example below, the description uses a </span><code>TRIGGER when</code><span> / </span><code>DO NOT TRIGGER when</code><span> structure that gives the agent both positive and negative match criteria. This eliminates the ambiguous middle ground (like &#8220;is this SOQL query part of Apex authoring or the SOQL skill?&#8221;) that causes misfires when you only tell the model what to activate on.</span></p>
<pre language="yaml">---
name: authoring-apex
description: &gt;
  TRIGGER when: user writes, edits, or reviews Salesforce Apex code —
  .cls or .trigger files including service classes, selector classes,
  trigger handlers, test classes, batch jobs, queueable classes,
  invocable methods, or Apex REST endpoints.
  DO NOT TRIGGER when: user works on LWC JavaScript, Flow XML,
  standalone SOQL queries, or running existing tests.
---
</pre>
<p>This is a simplified version. In practice, the description is more comprehensive: listing every class type (<code>schedulable</code>, <code>AuraEnabled</code>, <code>HttpCalloutMock</code>) and naming which skill handles each excluded case (e.g. &#8220;use authoring-lwc&#8221; for LWC JavaScript). The more specific the boundary, the fewer misfires.</p>
<p>When a matching task is detected, Claude loads the <code>SKILL.md</code> and follows the workflow. You can also trigger a skill manually with <code>/skill-name</code> in the prompt. Either way, the full skill context — workflow, references, templates — is injected automatically. You never paste the <code>SKILL.md</code> content into the conversation yourself.</p>
<p><span>The skill format itself (a</span><span> </span><code>SKILL.md</code><span> with frontmatter, references, and assets) is an </span><a href="https://agentskills.io/home"><u>open specification</u></a><span> maintained by Anthropic under Apache 2.0 and open to community contributions. The</span><span> </span><code>SKILL.md</code><span>, references, and templates that you write are portable. The hooks mechanism (hooks.yaml with PostToolUse lifecycle events) is Claude Code-specific — if you use another agent runtime, you&#8217;d wire validation differently, but the skill content travels with you. This makes skills headless. Use them with Agentforce Vibes, Claude Code, Cursor, VS Code, Gemini CLI, OpenAI Codex, Windsurf, Roo Code, Goose, or any of the 30+ agents that support the open specification.</span></p>
<h2><span style="font-weight: 400">The structure of an agent skill</span></h2>
<h3><span style="font-weight: 400">SKILL.md: The execution contract</span></h3>
<p><span style="font-weight: 400">This is the key, non-reorderable workflow with hard exit criteria for each phase that will be followed when the skill is used. Here is a simplified example showing the structure. Your real </span><a href="http://skill.md"><span style="font-weight: 400">SKILL.md</span></a><span style="font-weight: 400"> would have more detail in each phase, but the shape is the same: ordered phases with exit criteria.</span></p>
<pre language="markdown">Follow this workflow in order. Do not skip, merge, or reorder steps.
If blocked, stop and ask for missing context. If not applicable, mark `N/A`
with a one-line justification in the report.

## Required Inputs

Gather or infer before authoring:

- Class type (service, selector, batch, queueable, invocable, trigger handler)
- Target object(s) and business goal
- Sharing default (`with sharing` unless justified)
- Trigger framework already in use (or explicit choice)

The phases:

### Phase 1 — Author
1. **Discover project conventions** — scan for existing patterns, trigger
     framework, naming style
2. **Choose the smallest correct pattern**:

  | Need               | Pattern                              |
  | ------------------ | ------------------------------------ |
  | Business logic     | Service class                        |
  | Data access        | Selector class                       |
  | Trigger logic      | Trigger handler (framework required) |
  | Flow integration   | @InvocableMethod                     |
  | Bulk processing    | Batch Apex                           |
  | Async work         | Queueable                            |

3. **Read the matching template** from `assets/` before authoring
4. **Author with guardrails** — apply every rule in the Rules section below
5. **Generate test class** — delegate to testing skill

### Phase 2 — Validate

6. **Run code analyzer** — remediate all blocking violations; re-run until clean
7. **Execute tests** — capture pass/fail and coverage percentage

### Phase 3 — Report

8. **Report** — files, design decisions, analyzer output, test results, deploy note

</pre>
<p><span style="font-weight: 400">If a phase doesn&#8217;t apply, Claude must document it as N/A with justification. This maps directly to Anthropic&#8217;s best practice of providing &#8220;instructions as sequential steps using numbered lists when order or completeness matters.&#8221;</span></p>
<h3><span style="font-weight: 400">references/: The knowledge library</span></h3>
<p><span style="font-weight: 400">These are your reference documents that target a specific failure mode that general training handles inconsistently. Below are some </span><a href="https://platform.claude.com/docs/en/docs/build-with-claude/prompt-engineering/claude-prompting-best-practices"><span style="font-weight: 400">best practices</span></a><span style="font-weight: 400"> from Anthropic to follow since the temptation will be to include too much information.</span></p>
<table>
<tbody>
<tr>
<td><b>Best Practice</b></td>
<td><b>Reasoning</b></td>
<td><b>Example for Apex</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">File size</span></td>
<td><span style="font-weight: 400">Claude prioritizes unevenly as documents grow. Short, focused files get more consistent attention.</span></td>
<td><span style="font-weight: 400">Split by concern: core patterns (Factory, Strategy, Selector, Service) in one file, and advanced patterns (Unit of Work, Domain Model, Facade) in another.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Structured hierarchy</span></td>
<td><span style="font-weight: 400">Table of contents and headers let Claude locate relevant sections without reading everything. </span></td>
<td><span style="font-weight: 400">SKILL.md says &#8220;read best-practices.md before authoring&#8221; and Claude navigates to the heading it needs, not the whole file.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">One rule per section</span></td>
<td><span style="font-weight: 400">One point per section eliminates ambiguity about which rule applies.</span></td>
<td><span style="font-weight: 400">&#8220;SOQL in Loops&#8221; covers SOQL in loops, not DML, sharing, or null safety.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Short sections, many of them</span></td>
<td><span style="font-weight: 400">Self-contained sections (~20-40 lines) let Claude apply one rule without loading the full document.</span></td>
<td><span style="font-weight: 400">One anti-pattern entry = one heading, one &#8220;Why this fails&#8221; paragraph, one BAD block, one &#8220;Fix&#8221; line, and one GOOD block.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Concrete over abstract</span></td>
<td><span style="font-weight: 400">BAD/GOOD pairs are implicit few-shot examples. Anthropic says examples are &#8220;one of the most reliable ways to steer output.&#8221; Abstract rules (&#8220;avoid governor limits&#8221;) get interpreted loosely; concrete pairs anchor the behavior.</span></td>
<td><span style="font-weight: 400">// BAD: 1 SOQL per iteration. Hits 100-query limit at record 101.</span></p>
<p><span style="font-weight: 400">&lt;your demonstrable example&gt;</span></p>
<p><span style="font-weight: 400">// GOOD: 1 SOQL total.</span></p>
<p><span style="font-weight: 400">&lt;your demonstrable example&gt;</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Consequences over commands</span></td>
<td><span style="font-weight: 400">&#8220;Don&#8217;t do X&#8221; is weaker than &#8220;X fails because Y.&#8221; Anthropic notes &#8220;explaining why helps Claude generalize&#8221; to novel cases. If Claude knows the reason, it applies the rule even to patterns that look different from the example.</span></td>
<td><span style="font-weight: 400">&#8220;Salesforce allows only 150 DML statements per transaction. A trigger on 200 records with DML inside the loop hits 150 at record 151 and the entire batch rolls back.&#8221;</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Positive framing</span></td>
<td><span style="font-weight: 400">&#8220;Don&#8217;t do X&#8221; forces Claude to infer what to do. Stating the desired behavior directly gives Claude a single target to hit.</span></td>
<td><span style="font-weight: 400">Declare sharing explicitly on every class, with sharing for user-facing logic, and inherited sharing for utility classes called from both contexts.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Diverse examples</span></td>
<td><span style="font-weight: 400">Cover edge cases so Claude doesn&#8217;t over-fit to one shape. Anthropic recommends 3-5 varied examples to prevent narrow pattern matching.</span></td>
<td><code>selector.cls</code><span> could show four methods: </span><code>getByIds</code><span> (bulk), </span><code>getByName</code><span> (LIKE with sanitization), </span><code>getWithContacts</code><span> (parent-child subquery), </span><code>getContactsWithAccount</code><span> (child-to-parent). Same pattern, four shapes. Claude then learns &#8220;selector&#8221; and not &#8220;single query method.”</span></td>
</tr>
</tbody>
</table>
<p>For an <code>authoring-apex</code> skill let’s explore the reference files you might consider including.</p>
<table>
<tbody>
<tr>
<td><b>Reference</b></td>
<td><b>What the doc contains</b></td>
<td><b>What goes wrong without it</b></td>
</tr>
<tr>
<td><code>best-practices.md</code></td>
<td><span style="font-weight: 400">Your coding conventions (4-space indent, 120-char lines), naming rules, ApexDoc requirements, API version management, and general guidance</span></td>
<td><span style="font-weight: 400">Drift from project conventions &#8211; Claude generates valid code that doesn’t look like </span><i><span style="font-weight: 400">your</span></i><span style="font-weight: 400"> code.</span></td>
</tr>
<tr>
<td><code>design-patterns.md</code></td>
<td><span style="font-weight: 400">Decision trees for patterns (Factory, Strategy, Selector, Service, Batch, Queueable, Unit of Work, Domain Model, etc.). Each pattern includes a &#8220;when to use&#8221; table so Claude doesn&#8217;t default to the most common pattern</span></td>
<td><span style="font-weight: 400">Without this, Claude tends to produce Service classes for everything, even when a Selector or Strategy would be more appropriate.</span></td>
</tr>
<tr>
<td><code>anti-patterns.md</code></td>
<td><span style="font-weight: 400">Common Apex mistakes, each with a BAD/GOOD code pair and an explanation of *</span><i><span style="font-weight: 400">why</span></i><span style="font-weight: 400">* it fails at scale</span></td>
<td><span style="font-weight: 400">Covers common mistakes Claude makes more often than human developers (e.g., generating d</span><span style="font-weight: 400">atabase.query()</span><span style="font-weight: 400"> without </span><span style="font-weight: 400">AccessLevel</span><span style="font-weight: 400"> hints, or using legacy </span><span style="font-weight: 400">System.assertEquals</span><span style="font-weight: 400"> instead of </span><span style="font-weight: 400">Assert.areEqual</span><span style="font-weight: 400">).</span></td>
</tr>
<tr>
<td><code>security-guide.md</code></td>
<td><span style="font-weight: 400">CRUD/FLS enforcement using </span><span style="font-weight: 400">USER_MODE</span><span style="font-weight: 400"> (API 56+) and </span><span style="font-weight: 400">Security.stripInaccessible()</span><span style="font-weight: 400"> for backward compatibility. SOQL injection prevention. XSS protection patterns</span></td>
<td><span style="font-weight: 400">Without this reference, Claude sometimes generates code that works for someone assigned an admin profile but throws </span><span style="font-weight: 400">INSUFFICIENT_ACCESS</span><span style="font-weight: 400"> for standard users</span></td>
</tr>
<tr>
<td><code>transaction-security-policy.md</code></td>
<td><span style="font-weight: 400">Specialized references. This example covers </span><span style="font-weight: 400">TxnSecurity.EventCondition</span><span style="font-weight: 400"> implementations (Enhanced Transaction Security). These classes are </span><span style="font-weight: 400">global</span><span style="font-weight: 400">, run in system context, and evaluate monitoring events</span></td>
<td><span style="font-weight: 400">These are special cases. They break every normal rule about sharing and visibility. Without this reference, Claude applies </span><span style="font-weight: 400">with sharing</span><span style="font-weight: 400"> to these types of classes and breaks them.</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">This leverages what Anthropic calls providing &#8220;context and motivation behind instructions.&#8221; The references don&#8217;t just say </span><i><span style="font-weight: 400">what</span></i><span style="font-weight: 400"> to do, they explain </span><i><span style="font-weight: 400">why</span></i><span style="font-weight: 400">, enabling Claude to generalize correctly to novel situations rather than pattern-matching blindly.</span></p>
<p><span style="font-weight: 400">As an example the <a href="http://anti-patterns.md"><u><code>anti-patterns.md</code></u></a><span> </span></span><span style="font-weight: 400"> could look like this. This structure is designed for practices that are likely to go wrong repeatedly. Every section follows the same BAD/GOOD formula because Claude needs to recognise and avoid independent mistakes. Learn </span><a href="https://architect.salesforce.com/docs/architect/well-architected-tools/guide/anti-patterns.html"><span style="font-weight: 400">more about these anti-patterns</span></a><span style="font-weight: 400"> in the documentation.</span></p>
<pre language="markdown"># Apex Anti-Patterns

## Table of Contents

- [SOQL in Loops](#soql-in-loops)
- 

---

## SOQL in Loops
**Anti-pattern:** Executing SOQL queries inside a `for` or `while` loop.

**Why this fails:** Salesforce enforces a hard limit of 100 SOQL queries
per synchronous transaction. Triggers fire in batches of up to 200 records,
so a single SOQL inside a loop exhausts the limit after just 100 records.

```apex
// BAD: 1 SOQL per iteration — hits 100-query limit at record 101
for (Account acc : accounts) {
    List contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
}

Fix: Query once in bulk, then iterate over results.

// GOOD: 1 SOQL total
Map&lt;Id, List&gt; contactsByAccount = new Map&lt;Id, List&gt;();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
    ...
}
// add other entries to align with the anti-patterns in salesforce well-architected
</pre>
<p><span style="font-weight: 400">For speciality examples, like transaction security policies, the markdown could follow this structure and cover one specific Apex feature that breaks all the normal rules. It needs to teach Claude when the normal rules don&#8217;t apply, what to do instead, and why. There&#8217;s no BAD/GOOD pair because the &#8220;bad&#8221; code (e.g., global without sharing) is actually the correct code in this context. Note that the example in this case refers to a worked example in the assets folder.</span></p>
<pre language="markdown"># Transaction Security Policy (Apex condition)

## Table of Contents

- [Overview](#overview)
- [Class shape](#class-shape)
- [Sharing](#sharing)
- [Guardrails](#guardrails)
- [Example](#example)

---

## Overview

Enhanced Transaction Security policies call Apex when Condition Builder
is not enough. Implement `TxnSecurity.EventCondition` with a single
`evaluate(SObject event)` method.

## Class shape

- Declare the class **`global`** (required for Setup selection)
- Implement `TxnSecurity.EventCondition`
- Signature: `global Boolean evaluate(SObject event)`

## Sharing

These classes are **not** user-facing controllers. Use `without sharing`.
The normal `with sharing` default does NOT apply here -- applying it
will break the policy silently.

## Guardrails

- One bulk SOQL query max (no SOQL in loops -- governor limits still apply)
- Return `true` to block the transaction, `false` to allow

## Example

See assets/transaction-security-policy.cls

</pre>
<h3><span style="font-weight: 400">assets/: Few-shot by example</span></h3>
<p><span style="font-weight: 400">Anthropic highlights that one of the </span><span style="font-weight: 400">most reliable ways to steer Claude&#8217;s output format, tone, and structure is with implicit few-shot examples. Few-shot just means that you provide a few good examples as a template. Each template embeds the non-negotiable requirements: ApexDoc comments, bulk-safe logic, explicit sharing declarations, CRUD/FLS enforcement, and dependency injection for testability. When Claude adapts a template, it inherits these properties by construction rather than needing to recall them from training.</span></p>
<p><span style="font-weight: 400">For Apex specifically, the world is your oyster here, but let’s explore some good use cases.</span></p>
<table>
<tbody>
<tr>
<td><b>Asset</b></td>
<td><b>What the doc contains</b></td>
<td><b>What goes wrong without it</b></td>
</tr>
<tr>
<td><code>service.cls</code></td>
<td><span style="font-weight: 400">Business logic orchestrator. Delegates queries, collects DML, handles errors.</span></td>
<td><span style="font-weight: 400">Claude puts query logic, DML, and orchestration in one huge method instead of looking into a separation of concerns.</span></td>
</tr>
<tr>
<td><code>selector.cls</code></td>
<td><span style="font-weight: 400">Centralized SOQL access. One selector per sObject.</span></td>
<td><span style="font-weight: 400">Claude scatters ad-hoc queries throughout service and trigger code. Makes SOQL difficult to audit for security/performance in code review.</span></td>
</tr>
<tr>
<td><code>batch.cls</code></td>
<td>Large-volume async processing (10,000+ records). <code>Database.Batchable</code> and <code>Database.Stateful</code>.</td>
<td><span style="font-weight: 400">Claude writes batch jobs that swallow errors silently, or using non-stateful batches with no way to report failures.</span></td>
</tr>
<tr>
<td><code>queueable.cls</code></td>
<td><span style="font-weight: 400">Async processing with object passing and job chaining.</span></td>
<td><span style="font-weight: 400">Claude chains without depth limits (infinite recursion), or drops errors because partial DML isn&#8217;t used.</span></td>
</tr>
<tr>
<td><code>transaction-security-policy.cls</code></td>
<td>Enhanced Transaction Security <code>EventCondition</code> implementation.</td>
<td>Claude applies normal rules (with sharing, <code>USER_MODE</code>) to a class that <i>must</i> break them. Transaction Security Policy (TSP) classes are global without sharing by platform requirement.</td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">Each template isn&#8217;t just &#8220;how to write X.&#8221; It&#8217;s a pre-loaded, few-shot example with the non-negotiable requirements baked into the structure.</span></p>
<h3><span style="font-weight: 400">hooks.yaml and validator scripts: The safety net</span></h3>
<p>A hook is a shell command that Claude Code runs automatically at a specific lifecycle moment. You configure them in <code>hooks.yaml</code>. The script is whatever that command executes, typically a Python validator that inspects the tool&#8217;s input/output and returns structured feedback.</p>
<p><span style="font-weight: 400">Hooks close the feedback loop without human intervention. Claude writes something, the hook evaluates it immediately, and Claude sees the result in the same conversation turn. The correction happens in context while Claude still has the full problem loaded.</span></p>
<p><span style="font-weight: 400">The hooks file wires the validator to the right moment. For example, a preflight check runs when the user submits a prompt.</span></p>
<pre language="json">{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 ${CLAUDE_SKILL_DIR}/hooks/scripts/preflight-apex-check.py",
            "timeout": 10000
          }
        ]
      }
    ]
  }
}
</pre>
<p>Or a <code>PostToolUse</code> hook fires after every file write or edit.</p>
<pre language="yaml">---
name: authoring-apex
description: ...
hooks:
  PostToolUse:
    - matcher: "Write|Edit"
      hooks:
        - type: command
          command: "python3 ${CLAUDE_SKILL_DIR}/hooks/scripts/preflight-apex-check.py"
          timeout: 90000
---
</pre>
<p>Claude Code delivers the hook context as JSON on stdin. The payload shape varies by lifecycle event: <code>PostToolUse</code> includes the tool name, input parameters, and output; <code>UserPromptSubmit</code> includes the user&#8217;s prompt text.</p>
<p>Consider a preflight check for your development team. The <code>SKILL.md</code> workflow says &#8220;run Code Analyzer&#8221; in the validate phase. But what if Code Analyzer isn&#8217;t installed or is the wrong version? Without a hook, Claude attempts the command mid-workflow, gets an error, and the developer has to diagnose a missing plugin. With a hook, the check runs the moment Apex work begins.</p>
<pre language="python">import json, subprocess, sys

MINIMUM_VERSION = "5.5.0"

def parse_version(v):
    """Parse version string into comparable tuple."""
    return tuple(int(x) for x in v.split(".")[:3])

def main():
    hook_input = json.load(sys.stdin)
    prompt = hook_input.get("prompt", "").lower()

    # Only check when Apex work is likely
    apex_keywords = ["apex", ".cls", "class", "trigger", "service", "selector", "batch"]
    if not any(kw in prompt for kw in apex_keywords):
        sys.exit(0)

    try:
        result = subprocess.run(
            ["sf", "plugins", "--json"],
            capture_output=True, text=True, timeout=15
        )
        if result.returncode == 0:
            plugins = json.loads(result.stdout)
            for plugin in plugins:
                if plugin.get("name") == "@salesforce/plugin-code-analyzer":
                    version = plugin.get("version", "0.0.0")
                    if parse_version(version) &gt;= parse_version(MINIMUM_VERSION):
                        sys.exit(0)
                    else:
                        print("=== Preflight Check ===")
                        print(f"  [WARNING] Code Analyzer {version} is below minimum {MINIMUM_VERSION}.")
                        print(f"  Install: sf plugins install @salesforce/plugin-code-analyzer@latest")
                        sys.exit(0)
    except (FileNotFoundError, subprocess.TimeoutExpired, json.JSONDecodeError):
        pass

    print("=== Preflight Check ===")
    print("  [WARNING] Salesforce Code Analyzer is not installed.")
    print("  The authoring-apex skill requires it for validation.")
    print("  Install: sf plugins install @salesforce/plugin-code-analyzer")
    sys.exit(0)

if __name__ == "__main__":
    main()

</pre>
<p><span style="font-weight: 400">The developer is told up front that their toolchain is incomplete rather than discovering it mid-workflow when the validate phase fails. </span></p>
<p><span style="font-weight: 400">That&#8217;s what separates an instruction from a hook. The</span><span style="font-weight: 400"> <code>SKILL.md</code></span><span style="font-weight: 400"> says &#8220;run Code Analyzer in the validate phase.&#8221; The hook proves that it can run before the workflow even starts. Instructions are aspirational; hooks are mechanical. You want both capabilities.</span></p>
<h2><span style="font-weight: 400">Why agent skills need human and mechanical validation</span></h2>
<p><span style="font-weight: 400">Skills are not a silver bullet. They dramatically narrow the probability distribution of Claude&#8217;s outputs, but they operate on a fundamentally non-deterministic system. Two things work against you:</span></p>
<p><b>You can&#8217;t fully specify behavior</b><span style="font-weight: 400">. No matter how precise your</span> <span style="font-weight: 400"><code>SKILL.md</code></span><span style="font-weight: 400">, real-world requirements contain novel combinations that aren&#8217;t covered by templates or references. Claude must still make judgement calls and newer models interpret prompts more literally, so slight underspecification produces inconsistent results. If your</span> <span style="font-weight: 400"><code>SKILL.md</code></span><span style="font-weight: 400"> says &#8220;add error handling&#8221; without specifying the pattern, you&#8217;ll get different approaches each time.</span></p>
<p><b>Outputs are probabilistic</b><span style="font-weight: 400">. Even with structured prompts, role assignment, examples, and validation hooks, you&#8217;re optimizing a hit rate. You move from 60% correct to 95% correct — a massive improvement —  but the remaining 5% is why validators exist. This is why the architecture includes both preventive measures (structured prompts, references, templates) and detective measures (automated validators, scoring rubrics, blocking on errors). The skill assumes Claude will sometimes get it wrong and builds correction into the workflow rather than pretending perfection is achievable.</span></p>
<h2><span style="font-weight: 400">Getting started</span></h2>
<p><span style="font-weight: 400">Anthropic&#8217;s golden rule for prompts applies directly: &#8220;Show your prompt to a colleague with minimal context and ask them to follow it. If they&#8217;d be confused, Claude would be too.&#8221; A well-built agent skill is unambiguous to both a human reader and the model.</span></p>
<p><span style="font-weight: 400">You don&#8217;t need to build a skill from scratch. Anthropic provides a </span><span style="font-weight: 400"><code>skill-creator</code></span> <a href="https://github.com/anthropics/skills/blob/main/skills/skill-creator/SKILL.md"><span style="font-weight: 400">skill</span></a><span style="font-weight: 400"> that walks you through the full process: capturing intent, writing the <code>SKILL.md</code></span><span style="font-weight: 400">, creating test cases, running evals, and iterating until the output meets your bar. Install it and tell Claude what you want the skill to do; it handles the scaffolding, interviews you on edge cases, and generates a working draft you can refine.</span></p>
<p><span style="font-weight: 400">If you&#8217;d prefer to work from an existing Salesforce-specific example, the </span><a href="https://github.com/forcedotcom/afv-library"><span style="font-weight: 400">Agentforce Vibes skill library</span></a><span style="font-weight: 400"> includes production-ready skills for Apex, LWC, Flow, and more. Install them, use them, and look at how they&#8217;re structured; they follow similar patterns described in this post. The Agentforce Vibes skill library puts rules and rationale inline in the </span><span style="font-weight: 400"><code>SKILL.md</code></span><span style="font-weight: 400"> and uses complete Apex source files as templates rather than Markdown reference docs. This keeps the skill self-contained in fewer files, though it trades modularity: updating one rule means editing the main workflow file. </span></p>
<p><span style="font-weight: 400">The skill grows with your needs. Start with the </span><span style="font-weight: 400"><code>skill-creator</code></span><span style="font-weight: 400"> or an existing skill, customize what doesn&#8217;t fit your project, and build new skills only when you have a gap not covered by existing ones.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">GitHub: </span><a href="https://github.com/forcedotcom/afv-library"><span style="font-weight: 400">Salesforce’s collection of agent skills</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Trailhead: </span><a href="https://trailhead.salesforce.com/content/learn/modules/prompt-fundamentals"><span style="font-weight: 400">Prompt Fundamentals</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Trailhead: </span><a href="https://trailhead.salesforce.com/content/learn/modules/prompt-engineering-techniques"><span style="font-weight: 400">Prompt Engineering Techniques</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">GitHub: </span><a href="https://github.com/anthropics/skills/blob/main/skills/skill-creator/SKILL.md"><span style="font-weight: 400">Anthropics Skill Creator</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation: </span><a href="https://docs.anthropic.com/en/docs/claude-code/skills"><span style="font-weight: 400">Claude Code</span></a><span style="font-weight: 400"> </span></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Dave Norris</b><span style="font-weight: 400"> is a Developer Advocate at Salesforce. He’s passionate about making technical subjects broadly accessible to a diverse audience. Dave has been with Salesforce for over a decade, has over 40 Salesforce and MuleSoft certifications, and became a Salesforce Certified Technical Architect in 2013.</span></p>
<p>&nbsp;</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/build-a-salesforce-agent-skill-with-claude-code">Build a Salesforce Agent Skill with Claude Code</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/05/build-a-salesforce-agent-skill-with-claude-code/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206402</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260528154202/SingleHeadshot-2-e1780008137933.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260528154202/SingleHeadshot-2-e1780008137933.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Capture Rich Data within Agent Conversations by Using Discovery Framework</title>
		<link>https://developer.salesforce.com/blogs/2026/05/capture-rich-data-within-agent-conversations-by-using-discovery-framework</link>
		<comments>https://developer.salesforce.com/blogs/2026/05/capture-rich-data-within-agent-conversations-by-using-discovery-framework#respond</comments>
		<pubDate>Wed, 27 May 2026 15:00:43 +0000</pubDate>
		<dc:creator><![CDATA[adityabhansali]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Apex]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Lightning Web Components]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Custom LWC]]></category>
		<category><![CDATA[Discovery Framework]]></category>
		<category><![CDATA[Financial Services Cloud]]></category>
		<category><![CDATA[Omniscript]]></category>
		<category><![CDATA[Salesforce Developer]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206387</guid>
		<description><![CDATA[<p>Learn how to integrate Discovery Framework, an Omniscript-based digital form engine in Agentforce for Financial Services, directly into an Agentforce agent.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/capture-rich-data-within-agent-conversations-by-using-discovery-framework">Capture Rich Data within Agent Conversations by Using Discovery Framework</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><span style="font-weight: 400">Complex enterprise workflows, such as dispute intake, require capturing large amounts of structured information accurately. However, relying solely on text-based conversational interfaces creates a suboptimal experience: data entry is error-prone, UI patterns like radio buttons or checkboxes can&#8217;t be rendered in a chat stream, and the risk of misinterpretation increases with every unstructured exchange. Therefore, a structured approach to data capture is essential.</span></p>
<p><span style="font-weight: 400">In this post, we’ll show you how to integrate </span><a href="https://help.salesforce.com/s/articleView?id=ind.fsc_discovery_framework_overview.htm&amp;type=5"><span style="font-weight: 400">Discovery Framework</span></a><span style="font-weight: 400">, an Omniscript-based digital form engine in </span><a href="https://help.salesforce.com/s/articleView?id=ind.fsc_agents_overview.htm&amp;type=5"><span style="font-weight: 400">Agentforce for Financial Services</span></a><span style="font-weight: 400">, directly into an Agentforce agent. </span></p>
<p><span style="font-weight: 400">By the end, you&#8217;ll have a working solution that renders structured forms inside the Agent window, replacing error-prone conversational capture with a reliable, UI-driven experience. </span><span style="font-weight: 400">This solution is for Salesforce Developers and architects building Agentforce experiences for the financial services industry. It requires familiarity with Apex, Lightning Web Components (LWC), and Omniscript basics.</span></p>
<h2><span style="font-weight: 400">The problem: Conversational capture falls short</span></h2>
<p><span style="font-weight: 400">In financial services, a typical transaction dispute intake process has five stages:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Identifying the impacted customer and account</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Identifying the impacted transaction with enrichment for ease of identification</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Capturing the reason for dispute and disputed amount</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Creating a case for dispute intake</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Capturing a dispute questionnaire</span></li>
</ol>
<p><span style="font-weight: 400">For high-volume contact center agents, Stage 5 is the hardest. Questionnaires are dynamic, changing based on payment network rules and transaction type. </span></p>
<p><span style="font-weight: 400">A pure conversational experience creates real problems:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Users can&#8217;t navigate backwards to update responses</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Special UI patterns (radio buttons, checkboxes) don&#8217;t translate to text</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Any AI paraphrase of a question risks capturing the wrong answer</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Questions must appear in strict order, with required/optional labels intact</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Longer questionnaires need save-and-resume support</span></li>
</ul>
<p><span style="font-weight: 400">The screen recording below shows an agent in conversational mode, asking dispute questionnaire questions one at a time.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206404" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260526134558/Screen-recording-of-an-agent-in-conversational-mode-asking-dispute-questionnaire-questions-one-at-a-time_F.gif?w=800" class="postimages" width="800" height="450" alt="Screen recording of an agent in conversational mode, asking dispute questionnaire questions one at a time" />
			  </span>
			</p>
<h2><span style="font-weight: 400">The solution: Discovery Framework in agents</span></h2>
<p><span style="font-weight: 400">Agentforce Financial Services provides Discovery Framework, a feature that creates digital forms to collect and validate data while avoiding time-consuming, error-prone manual methods.</span></p>
<p><span style="font-weight: 400">This framework was originally built for non-agentic, UI-driven scenarios. When you bring it into agents, you give users navigable, structured forms that address all five problems discussed above.</span></p>
<p><b>Key benefits:</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Navigation between questions with ability to update responses</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Proper rendering of radio buttons, checkboxes, and other form controls</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Exact question display without AI interpretation</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Correct ordering and required/optional field marking</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">State persistence for incomplete forms</span></li>
</ul>
<p><span style="font-weight: 400">The framework renders a structured form directly in the agent output, with no conversational questions and no AI paraphrase risk. The screen recording below shows an agent in a Discovery Framework experience with structured form fields, radio buttons, and navigation controls embedded in the Agent interface.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206405" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260526134649/Screen-recording-of-an-agent-in-a-Discovery-Framework-experience-with-structured-form-fields-radio-buttons-and-navigation-controls-embedded-in-the-Agent-interface._F.gif?w=800" class="postimages" width="800" height="450" alt="Screen recording of an agent in a Discovery Framework experience with structured form fields, radio buttons, and navigation controls embedded in the Agent interface." />
			  </span>
			</p>
<p><b>Prerequisites:</b><span style="font-weight: 400"> This experience is available only in UI-based channels — Lightning Experience, Messaging for Experience Sites, and third-party sites. It is not supported in Voice or Short Message Service (SMS) channels, where complex UI elements can&#8217;t be displayed.</span></p>
<h2><span style="font-weight: 400">Technical architecture overview</span></h2>
<p><span style="font-weight: 400">The integration uses Agentforce&#8217;s support for custom Lightning types and custom LWC components. Here&#8217;s the component architecture:</span></p>
<ol>
<li style="font-weight: 400"><b>Discovery Framework Omniscript:</b><span style="font-weight: 400"> The digital form definition with assessment questions</span></li>
<li style="font-weight: 400"><b>Custom LWC component:</b><span style="font-weight: 400"> Wraps the Omniscript and handles data binding</span></li>
<li style="font-weight: 400"><b>Apex class:</b><span style="font-weight: 400"> Executes business logic when the Agent action runs</span></li>
<li style="font-weight: 400"><b>Custom Lightning type:</b><span style="font-weight: 400"> Associates the Apex output type with the LWC renderer</span></li>
<li style="font-weight: 400"><b>Agent action:</b><span style="font-weight: 400"> Uses the Apex class as its reference action and the custom Lightning type as its output renderer</span></li>
</ol>
<p><span style="font-weight: 400">When an Agent action executes via a subagent, the custom Lightning type renders in the output, displaying the Discovery Framework Omniscript directly in the Agent window. </span></p>
<p><span style="font-weight: 400">The architecture diagram below illustrates the runtime flow: a subagent triggers an agent action, which then executes Apex class business logic and renders the custom Lightning type LWC component, wrapping the Discovery Framework Omniscript in the Agent interface.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206390" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260526090803/Architecture-diagram-illustrating-the-Agentforce-and-Discovery-Framework-runtime-flow-e1779811763197.png?w=1000" class="postimages" width="1000" height="546" alt="Architecture diagram illustrating the Agentforce and Discovery Framework runtime flow" />
			  </span>
			</p>
<p><span style="font-weight: 400">To learn more about the underlying capability, see</span><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/lightning-types.html"> <span style="font-weight: 400">Enhance the Agent UI with Custom LWCs and Lightning Types</span></a><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400">Steps to implement Discovery Framework in Agentforce</span></h2>
<p><span style="font-weight: 400">The implementation involves five steps. You start by creating a Discovery Framework Omniscript, then build the Apex classes that define the agent action&#8217;s business logic. Next, you create a Lightning web component to wrap and render the Omniscript inside the Agent window. You then configure a custom Lightning type to associate the Apex output with the LWC, and finally, you set up the agent action, which uses the Apex class as the reference action and the Lightning type as the output render. Let’s take a detailed look.</span></p>
<h3><span style="font-weight: 400">Step 1: Create a Discovery Framework Omniscript</span></h3>
<p><span style="font-weight: 400">First, we’ll need to create a series of assessment questions, then add them as steps in an </span><a href="https://help.salesforce.com/s/articleView?id=xcloud.os_omniscripts_8355.htm&amp;type=5"><span style="font-weight: 400">Omniscript</span></a><span style="font-weight: 400"> (a drag-and-drop digital form builder in Discovery Framework). See the </span><a href="https://help.salesforce.com/s/articleView?language=en_US&amp;id=ind.fsc_discovery_framework_overview.htm&amp;type=5"><span style="font-weight: 400">Discovery Framework documentation</span></a><span style="font-weight: 400"> for a step-by-step walkthrough.</span></p>
<p><span style="font-weight: 400">For this example, we’ll create two sample dispute questions of type </span><b>Radio Button</b><span style="font-weight: 400"> with Yes/No responses.</span></p>
<p><b>Omniscript properties:</b></p>
<ul>
<li style="font-weight: 400"><b>Type:</b><span style="font-weight: 400"> FinancialServices</span></li>
<li style="font-weight: 400"><b>SubType:</b><span style="font-weight: 400"> DisputeQuestionnaire</span></li>
<li style="font-weight: 400"><b>Language:</b><span style="font-weight: 400"> English</span></li>
</ul>
<p><b>Important:</b><span style="font-weight: 400"> Configure the Omniscript for a smaller viewport, so it renders correctly inside the agent window. Open the Omniscript properties panel and set the </span><b>Viewport</b><span style="font-weight: 400"> width to a maximum of 600px. Enable </span><b>Responsive Mode</b><span style="font-weight: 400"> if available. This ensures that the form fits within the agent output area without scrolling issues.</span></p>
<p><b>Navigation configuration:</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Enable </span><b>Allow Save for Later</b><span style="font-weight: 400"> to let users save incomplete forms</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Enable </span><b>Show Progress Indicator</b><span style="font-weight: 400"> to display form completion status</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Configure step-by-step navigation with </span><b>Previous</b><span style="font-weight: 400"> and </span><b>Next</b><span style="font-weight: 400"> buttons</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Select </span><b>Hide step chart</b><span style="font-weight: 400"> in Step Chart Options</span></li>
</ul>
<p><span style="font-weight: 400">For detailed instructions on creating Discovery Framework Omniscripts, see the </span><a href="https://help.salesforce.com/s/articleView?language=en_US&amp;id=ind.fsc_discovery_framework_overview.htm&amp;type=5"><span style="font-weight: 400">Discovery Framework documentation</span></a><span style="font-weight: 400">.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206395" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260526090820/Screenshot-of-the-Omniscript-properties-panel-showing-viewport-configuration-and-navigation-settings.png?w=1999" class="postimages" width="1999" height="620" alt="Screenshot of the Omniscript properties panel showing viewport configuration and navigation settings" />
			  </span>
			</p>
<p><span style="font-weight: 400">To learn more, watch the video: </span><a href="https://salesforce.vidyard.com/watch/twss6ysqr7WQ8yyEi7WfF2"><span style="font-weight: 400">Discovery Framework for Financial Services Cloud</span></a><span style="font-weight: 400"> </span></p>
<h3><span style="font-weight: 400">Step 2: Create Apex classes</span></h3>
<p><span style="font-weight: 400">Next, we’ll create an Apex class that captures the business logic for the agent action. In this example, the class creates a real Case record and returns the case number and ID. You’ll want to adapt the logic to your business needs.</span></p>
<p><span style="font-weight: 400">The following Apex classes define the input, output, and business logic for the agent action. Each class plays a specific role in handling the incoming dispute request, structuring the output data, and executing the case creation logic.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206396" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260526090822/Screenshot-showing-needed-Apex-classes.png?w=716" class="postimages" width="716" height="404" alt="Screenshot showing needed Apex classes" />
			  </span>
			</p>
<p><span style="font-weight: 400">Here is sample code for the Apex classes noted above.</span></p>
<p><b>DisputeQuestionnaireInput.cls</b><br />
This class serves as the input of the agent action and contains a <code>disputeRequest</code> String field.<br />
<a href="https://github.com/AdityaBhansali1/salesforce-integrate-discovery-framework-omniscript-in-agentforce/blob/master/force-app/main/default/classes/DisputeQuestionnaireInput.cls"><u>See sample code.</u></a></p>
<p><b>DisputeQuestionnaireOutput.cls</b><br />
This class serves as the output of the agent action and contains a <code>DisputeQuestionnaireCaseOutput</code> object.<br />
<a href="https://github.com/AdityaBhansali1/salesforce-integrate-discovery-framework-omniscript-in-agentforce/blob/master/force-app/main/default/classes/DisputeQuestionnaireOutput.cls"><u>See sample code</u></a>.</p>
<p><b>DisputeQuestionnaireCaseOutput.cls</b><br />
This class contains the <code>caseNumber</code> and <code>caseId</code> fields as Strings.<br />
<a href="https://github.com/AdityaBhansali1/salesforce-integrate-discovery-framework-omniscript-in-agentforce/blob/master/force-app/main/default/classes/DisputeQuestionnaireCaseOutput.cls"><u>See sample code</u></a>.</p>
<p><b>DisputeQuestionnaireAction.cls</b><br />
This class has the business logic that is executed by the agent to create a case and add it to a <code>DisputeQuestionnaireCaseOutput</code> object which is embedded inside a <code>DisputeQuestionnaireOutput</code> object.<br />
<a href="https://github.com/AdityaBhansali1/salesforce-integrate-discovery-framework-omniscript-in-agentforce/blob/master/force-app/main/default/classes/DisputeQuestionnaireAction.cls"><u>See sample code</u></a>.</p>
<h3><span style="font-weight: 400">Step 3: Create a Lightning web component</span></h3>
<p><span style="font-weight: 400">Next, we’ll create a Lightning Web Component that wraps the Discovery Framework Omniscript from Step 1. </span></p>
<p><span style="font-weight: 400">The <code>displayDisputeQuestionnaire</code></span><span style="font-weight: 400"> Lightning web component wraps the Discovery Framework Omniscript and renders it inside the Agent window. This component includes an HTML, a JavaScript and an XML file. The HTML defines the component structure, the JavaScript handles data binding and event subscription, and the metadata XML registers the component as an Agentforce output target.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206393" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260526090817/Screenshot-of-the-displayDisputeQuestionnaire-Lightning-web-component-configuration.png?w=640" class="postimages" width="640" height="232" alt="Screenshot of the displayDisputeQuestionnaire Lightning web component configuration" />
			  </span>
			</p>
<p><span style="font-weight: 400">Here is a sample code for the <code>displayDisputeQuestionnaire</code></span><span style="font-weight: 400"> Lightning web component.</span></p>
<p><b>displayDisputeQuestionnaire.html<br />
</b>This HTML component wraps the Discovery Framework Omniscript created in Step 1, using <code>&lt;omnistudio-omnistudio-standard-runtime-wrapper&gt;</code><span>.</span></p>
<pre language="html">&lt;template&gt;
   &lt;div class=&quot;slds-card&quot;&gt;
           &lt;div class=&quot;slds-card&quot;&gt;
               &lt;div class=&quot;slds-modal__content&quot;&gt;
                   &lt;omnistudio-omnistudio-standard-runtime-wrapper
                           type=&quot;FinancialServices&quot;
                           subtype=&quot;DisputeQuestionnaire&quot;
                           language=&quot;English&quot;
                           prefill={prefill}&gt;
                  &lt;/omnistudio-omnistudio-standard-runtime-wrapper&gt;
               &lt;/div&gt;
           &lt;/div&gt;
   &lt;/div&gt;
&lt;/template&gt;
</pre>
<p><b>Note:</b><span style="font-weight: 400"> We’ll use the same Type, SubType, and Language values in the HTML as configured in Step 1, as shown in above code snippet.</span></p>
<p><a href="https://github.com/AdityaBhansali1/salesforce-integrate-discovery-framework-omniscript-in-agentforce/blob/master/force-app/main/default/lwc/displayDisputeQuestionnaire/displayDisputeQuestionnaire.html"><span style="font-weight: 400">See sample code.</span></a></p>
<p><b>displayDisputeQuestionnaire.js<br />
</b><span style="font-weight: 400">This is the JavaScript for the LWC, supporting the above HTML.</span></p>
<pre language="javascript">import { LightningElement, api, track } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
import pubsub from 'omnistudio/pubsub';

export default class DisplayDisputeQuestionnaire extends NavigationMixin(LightningElement) {
   _value;
   @api
   get value() {
       return this._value;
   }
   set value(value) {
       this._value = value;
   }
   @track prefill; assessmentId; caseNumber; caseId;
   connectedCallback() {
       this.caseNumber = this.value?.caseOutput?.caseNumber || null;
       this.caseId = this.value?.caseOutput?.caseId || null;
       pubsub.register('omniscript_action', {
           data: this.handleOmniAction.bind(this),
       });
   }
   handleOmniAction(data) {
       this.assessmentId = data?.assessmentId || null;
   }
}
</pre>
<p><a href="https://github.com/AdityaBhansali1/salesforce-integrate-discovery-framework-omniscript-in-agentforce/blob/master/force-app/main/default/lwc/displayDisputeQuestionnaire/displayDisputeQuestionnaire.js"><span style="font-weight: 400">See sample code.</span></a></p>
<p><b>displayDisputeQuestionnaire.js-meta.xml<br />
</b>This is the<code> js-meta.xml</code> for the LWC, which captures the target as <code>AgentforceOutput</code>.<br />
<a href="https://github.com/AdityaBhansali1/salesforce-integrate-discovery-framework-omniscript-in-agentforce/blob/master/force-app/main/default/lwc/displayDisputeQuestionnaire/displayDisputeQuestionnaire.js-meta.xml"><u>See sample code.</u></a><span style="font-weight: 400">    </span></p>
<p><b>Key implementation details:</b></p>
<ul>
<li>The component subscribes to <code>omniscript_action</code> events to capture form completion</li>
<li>The <code>prefill</code> property allows pre-populating form fields with contextual data</li>
<li>The <code>value</code> property receives output from the Apex class</li>
<li>Navigation handles redirecting users to the created Case record after completion</li>
<li>The <code>lightning__AgentforceOutput</code> target in the metadata XML is required for the component to render in Agentforce</li>
</ul>
<h3><b>Step 4: Create a custom Lightning type</b></h3>
<p><span style="font-weight: 400">Next, we’ll create a custom Lightning type to associate the Apex output types from Step 2 with the LWC from Step 3.</span></p>
<p><span style="font-weight: 400">The <code>displayQuestionnaireResponse</code></span><span style="font-weight: 400"> Lightning type defines the custom Lightning type. This Lightning type includes the JSON files, which tells Agentforce how to map your Apex output to the LWC renderer and validates the data structure at runtime.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206394" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260526090818/Screenshot-of-the-displayQuestionnaireResponse-Lightning-type-configuration.png?w=530" class="postimages" width="530" height="230" alt="Screenshot of the displayQuestionnaireResponse Lightning type configuration" />
			  </span>
			</p>
<p><span style="font-weight: 400">Here is a sample code for the <code>displayQuestionnaireResponse</code></span><span style="font-weight: 400"> Lightning type</span><span style="font-weight: 400">.</span></p>
<p><b>renderer.json<br />
</b><span style="font-weight: 400">The<code>renderer.json</code></span><span style="font-weight: 400"> file maps the root output ($) to your LWC component.<br />
</span><a href="https://github.com/AdityaBhansali1/salesforce-integrate-discovery-framework-omniscript-in-agentforce/blob/master/force-app/main/default/lightningTypes/disputeQuestionnaireResponse/lightningDesktopGenAi/renderer.json"><span style="font-weight: 400">See sample code.</span></a></p>
<p><b>schema.json<br />
</b><span style="font-weight: 400">The </span><span style="font-weight: 400"><code>schema.json</code></span><span style="font-weight: 400"> file defines the data type structure matching your Apex output class.<br />
</span><a href="https://github.com/AdityaBhansali1/salesforce-integrate-discovery-framework-omniscript-in-agentforce/blob/master/force-app/main/default/lightningTypes/disputeQuestionnaireResponse/schema.json"><span style="font-weight: 400">See sample code.</span></a></p>
<p><span style="font-weight: 400">For detailed setup instructions, see</span><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/lightning-types.html"> <span style="font-weight: 400">Enhance the Agent UI with Custom LWCs and Lightning Types</span></a><span style="font-weight: 400">.</span></p>
<h3><span style="font-weight: 400">Step 5: Create an agent action</span></h3>
<p><span style="font-weight: 400">Finally, we’ll create an agent action that uses the Apex classes from Step 2 for execution and the custom Lightning type from Step 4 for output rendering.</span></p>
<p><b>Agent action configuration steps:</b></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Navigate to </span><b>Setup</b><span style="font-weight: 400"> &gt; </span><b>Agentforce</b><span style="font-weight: 400"> &gt; </span><b>Agent Actions</b></li>
<li style="font-weight: 400"><span style="font-weight: 400">Click </span><b>New Agent Action</b></li>
<li style="font-weight: 400"><span style="font-weight: 400">Set </span><b>Reference Action Type</b><span style="font-weight: 400"> to Apex</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Select </span><b>DisputeQuestionnaireAction</b><span style="font-weight: 400"> as the Apex class</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Set </span><b>Output Rendering</b><span style="font-weight: 400"> to the </span><b>DisputeQuestionnaireResponse</b><span style="font-weight: 400"> custom Lightning type</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Configure input parameters to match the Agent Topic context</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Add instructions for when the agent should invoke this action</span></li>
</ol>
<p><b>Example subagent instruction:</b></p>
<p><span style="font-weight: 400">When a customer wants to dispute a transaction, execute the Process Dispute Request action to capture detailed dispute information through a structured questionnaire. When this agent action fires as part of a subagent, the Discovery Framework Omniscript renders directly in the Agent window.</span></p>
<p><span style="font-weight: 400">The screenshot below shows a sample agent action configuration in Agent Builder, with the <code>disputeQuestionnaireResponse</code></span><span style="font-weight: 400"> Lightning type created in Step 4 set as the output renderer.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206397" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260526090824/Screenshot-showing-the-sample-agent-action-configuration-in-Agent-Builder-e1779811868668.png?w=1000" class="postimages" width="1000" height="618" alt="Screenshot showing the sample agent action configuration in Agent Builder" />
			  </span>
			</p>
<h3><span style="font-weight: 400">Rendering the Discovery Framework Omniscript in an Agent Script action</span></h3>
<p><span style="font-weight: 400">If your Agent is built using Agent Script, you can also render the Discovery Framework Omniscript in an action which is part of the Agent Script. </span></p>
<p><span style="font-weight: 400">For the dispute example, the code snippet below would help to render the output of the action using the Discovery Framework Omniscript.</span></p>
<pre language="text">outputs:
    caseOutput: object
        label: "Case Output"
        description: "The dispute case details including case number and case Id"
        complex_data_type_name: "c__DisputeQuestionnaireResponse"
        developer_name: "caseOutput"
        is_displayable: True
        filter_from_agent: False
</pre>
<h2><span style="font-weight: 400">Testing the integration</span></h2>
<p><span style="font-weight: 400">The following are steps to test the complete integration.</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Create a subagent that invokes your new agent action</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Start a conversation with the agent</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Trigger the dispute intake flow</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Verify that the Discovery Framework form renders in the Agent interface</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Complete the questionnaire and verify that data is captured correctly</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Confirm that the Case record is created with the captured information</span></li>
</ol>
<p><b>Troubleshooting tips:</b></p>
<ul>
<li>If the form doesn&#8217;t render, verify that the Omniscript Type, SubType, and Language match exactly in both the Omniscript definition and LWC component</li>
<li>If data doesn&#8217;t pass correctly, check that <code>@AuraEnabled</code> annotations are present on all Apex output class properties</li>
<li>If navigation fails, ensure that the LWC component&#8217;s <code>js-meta.xml</code> includes the <code>lightning__AgentforceOutput</code> target</li>
</ul>
<h2><span style="font-weight: 400">Extending this pattern</span></h2>
<p><span style="font-weight: 400">This mechanism isn&#8217;t limited to Discovery Framework Omniscripts; you can apply it to any Omniscript in your org, as long as the user experience fits within a compact viewport.</span></p>
<p><b>Additional use cases:</b></p>
<ul>
<li style="font-weight: 400"><b>Loan application intake</b><span style="font-weight: 400">: Multi-step loan applications with document upload</span></li>
<li style="font-weight: 400"><b>Insurance claims processing</b><span style="font-weight: 400">: Complex claim forms with conditional logic</span></li>
<li style="font-weight: 400"><b>Customer onboarding</b><span style="font-weight: 400">: Know Your Customer (KYC) questionnaires with validation rules</span></li>
<li style="font-weight: 400"><b>Financial planning</b><span style="font-weight: 400">: Goal-setting forms with calculation logic</span></li>
</ul>
<p><span style="font-weight: 400">The key requirement: design Omniscripts for responsive display within the agent interface viewport constraints.</span></p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">With these five steps, you can replace fragile conversational question-and-answer flows with a reliable, structured form experience — directly inside Agentforce. This approach combines the conversational intelligence of Agentforce with the rich data collection capabilities of Discovery Framework, delivering the best of both worlds.</span></p>
<p><span style="font-weight: 400">By following this approach, you maintain accurate data capture without AI hallucination, while giving users intuitive, navigable forms that feel natural within the Agent conversation flow. Try it out and share what you build. Post questions on the </span><a href="https://trailhead.salesforce.com/trailblazer-community/groups/0F93A000000DJbJSAW?tab=discussion&amp;sort=LAST_MODIFIED_DATE_DESC"><span style="font-weight: 400">Salesforce Developer Community</span></a><span style="font-weight: 400"> or Stack Overflow.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation:</span> <a href="https://help.salesforce.com/s/articleView?id=ind.fsc_admin_transaction_dispute_management.htm&amp;type=5"><span style="font-weight: 400">Transaction Dispute Management Intake in Financial Services Cloud</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation: </span><a href="https://help.salesforce.com/s/articleView?id=ind.fsc_discovery_framework_overview.htm&amp;type=5"><span style="font-weight: 400">Discovery Framework</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Video: </span><a href="https://salesforce.vidyard.com/watch/twss6ysqr7WQ8yyEi7WfF2"><span style="font-weight: 400">Discovery Framework for Financial Services Cloud</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Developer Guide: </span><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/lightning-types.html"><span style="font-weight: 400">Enhance the Agent UI with Custom LWCs and Lightning Types</span></a><span style="font-weight: 400"> </span></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Aditya Bhansali</b><span style="font-weight: 400"> is a Lead Member of Technical Staff in the Agentforce Financial Services organization, where he leads Salesforce engineering teams in building enterprise products on the Salesforce Platform. He shares updates on</span><a href="https://www.linkedin.com/in/abhansali/"><span style="font-weight: 400"> LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/capture-rich-data-within-agent-conversations-by-using-discovery-framework">Capture Rich Data within Agent Conversations by Using Discovery Framework</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/05/capture-rich-data-within-agent-conversations-by-using-discovery-framework/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206387</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260526081321/Generic-D-2-e1779808430413.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260526081321/Generic-D-2-e1779808430413.png?w=1000" medium="image" />
	</item>
	</channel>
</rss>
