<?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, 25 Jun 2026 12:56:01 +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>Master the Agentic Development Lifecycle for Agentforce</title>
		<link>https://developer.salesforce.com/blogs/2026/06/master-the-agentic-development-lifecycle-for-agentforce</link>
		<comments>https://developer.salesforce.com/blogs/2026/06/master-the-agentic-development-lifecycle-for-agentforce#respond</comments>
		<pubDate>Wed, 24 Jun 2026 15:00:04 +0000</pubDate>
		<dc:creator><![CDATA[Mohith Shrivastava]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Agentforce Vibes]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Headless 360]]></category>
		<category><![CDATA[Agent Skills]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206559</guid>
		<description><![CDATA[<p>Learn how to design, build, test, and deploy Agentforce agents using plain language, Agent Skills, and a design-first development lifecycle.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/master-the-agentic-development-lifecycle-for-agentforce">Master the Agentic Development Lifecycle for Agentforce</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">Building with </span><a href="https://developer.salesforce.com/docs/ai/agentforce/guide"><span style="font-weight: 400">Agentforce</span></a><span style="font-weight: 400"> rarely involves a single task. An AI agent can span a data model, actions (flows or Apex), agent definition, and permissions. Each piece has its own setup screens, so you have to spend time clicking to wire it all together. With </span><a href="https://developer.salesforce.com/blogs/2026/05/headless-360-what-it-means-for-developers"><b>Salesforce Headless 360</b></a><span style="font-weight: 400">, the platform now exposes every one of those capabilities as an application programming interface (API), a Model Context Protocol (MCP) tool, or a command-line interface (CLI) command. Because the whole platform can be accessed by coding agents, they can take your intent and handle much of that work — which opens a new, agentic way to build.</span></p>
<p><span style="font-weight: 400">Many Salesforce Developers want a repeatable, design-first lifecycle for agent development. The Headless 360 approach pairs a coding agent like Claude Code, Codex, or </span><a href="https://developer.salesforce.com/docs/platform/einstein-for-devs/guide/einstein-overview.html"><span style="font-weight: 400">Agentforce Vibes</span></a><span style="font-weight: 400"> with </span><a href="https://agentskills.io/home"><span style="font-weight: 400">Agent Skills</span></a><span style="font-weight: 400">, so you can design, build, deploy, test, and debug from a single plain-language conversation. You stay the designer; the coding agent does the development. </span></p>
<p><span style="font-weight: 400">In this post, we&#8217;ll look at how to set up Salesforce Agent Skills, scaffold a project and connect an org, design before you build, generate your metadata, validate and test, and debug with traces. Everything we’ll discuss here works today through the open-source </span><a href="https://github.com/forcedotcom/sf-skills"><span style="font-weight: 400">Salesforce Skills Library</span></a><span style="font-weight: 400"> and the </span><a href="https://developer.salesforce.com/tools/salesforcecli"><span style="font-weight: 400">Salesforce CLI</span></a><span style="font-weight: 400">. </span></p>
<h2><strong>Teach your coding agent Salesforce with Agent Skills</strong></h2>
<p><span style="font-weight: 400">Agent Skills are how your coding assistant learns Salesforce. A skill is a small bundle of instructions and commands that teaches the assistant a specific task, such as building an Apex class or a Lightning web component (LWC), building a flow, or developing Agentforce agents. Without skills, the assistant guesses. With them, it runs the right steps and necessary CLI commands or MCP tools and follows conventions.</span></p>
<p><span style="font-weight: 400">You should install these skills either globally or within your project directory for any coding agents besides Agentforce Vibes, as the latter includes them by default. The Salesforce Skills Library ships dozens of skills covering Apex, LWC, Agentforce, Data 360, and metadata deployment. </span></p>
<p>Three skills drive the agent lifecycle specifically:</p>
<ul>
<li><code>developing-agentforce</code>: Design, build, deploy, debug (see <a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/developing-agentforce"><u>docs</u></a>)</li>
<li><code>testing-agentforce</code>: Test specs and batch runs (see <a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/testing-agentforce"><u>docs</u></a>)</li>
<li><code>observing-agentforce</code>: Inspect production traces from Data 360 (see <a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/observing-agentforce"><u>docs</u></a>)</li>
</ul>
<p>Once installed, in most coding agents you should be able to type <code>‘/’</code> in your assistant to browse them. You&#8217;ll see a skill load by name as the assistant works, for example, <code>developing-agentforce</code> activates the moment you ask for an agent scaffold.</p>
<p><span style="font-weight: 400">You&#8217;ll also need a few common tools on your machine first: </span><a href="https://nodejs.org"><span style="font-weight: 400">Node.js</span></a><span style="font-weight: 400"> and the </span><a href="https://developer.salesforce.com/tools/salesforcecli"><span style="font-weight: 400">Salesforce CLI</span></a><span style="font-weight: 400">. And here&#8217;s the best part — if something is missing, use your assistant to get help. And if the assistant has permissions, it can also install these for you.</span></p>
<p><span style="font-weight: 400">To install Salesforce Agent Skills, run the below command from your terminal or ask your assistant to run the below command.</span></p>
<pre language="sh">npx skills add forcedotcom/sf-skills
</pre>
<p><span style="font-weight: 400">After executing the command, you will navigate through a few quick configuration choices. First, you’ll pick the specific skills from the library to equip your assistant. Then, designate your preferred coding agent and decide if these instructions should be available across all your projects or just for the current local directory.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206576" >
			    <img fetchpriority="high" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260623124718/image2-e1782244050713.png?w=1000" class="postimages" width="1000" height="618" alt="A terminal showing the Agent Skills install command asking to select list of installed skills" />
			  </span>
			</p>
<h2><strong>Scaffold the project and connect your org</strong></h2>
<p><span style="font-weight: 400">Every Salesforce project starts with a scaffold — a folder holding all your metadata as files. This is the same project structure you already pull into an integrated development environment (IDE) like VS Code. Your assistant works against those local files, then deploys them to an org.</span></p>
<p><span style="font-weight: 400">Here&#8217;s the shift that matters. You no longer memorize CLI flags or look up command syntax. You describe what you want in plain language, and the coding agent runs the right Salesforce CLI command for you. The CLI does the actual work — the agent just knows which command to call and translates your words into it.</span></p>
<p><span style="font-weight: 400">For example, scaffolding a project used to mean typing the exact command and its flags:</span></p>
<pre language="sh">sf project generate --name agent-script-demo
</pre>
<p><span style="font-weight: 400">Now, you simply describe the goal in plain language, and the agent runs that command for you:</span></p>
<blockquote><p><i><span style="font-weight: 400">&#8220;Create a Salesforce project scaffold named agent-script-demo&#8221;</span></i></p></blockquote>
<p><span style="font-weight: 400">Keep each project in its own folder, so the agent only touches what it should. From there, the same pattern repeats for every step. For example, when you ask it to set up your org, the agent checks the current default and sets your scratch org or sandbox as the default for this project using the commands below.</span></p>
<pre language="sh">sf config get target-org --json
sf config set target-org agent-build --json
</pre>
<p><span style="font-weight: 400">You can also ask the coding agent to open your default org, and the coding agent will use the command below to open it.</span></p>
<pre language="sh">sf org open
</pre>
<p><span style="font-weight: 400">A best practice is to build in scratch orgs or sandboxes and never give your agent access to the production environment. Also, never paste secrets like passwords into a prompt. A handy workflow is to run a second terminal </span><i><span style="font-weight: 400">without</span></i><span style="font-weight: 400"> the assistant. That way you can copy, edit, and run sensitive commands yourself. </span></p>
<h2><strong>Design before you build</strong></h2>
<p><span style="font-weight: 400">This is the most important habit in the whole lifecycle. If you let an agent build on its own, it may produce something you have to throw away. So, slow down and design first.</span></p>
<p><span style="font-weight: 400">Two things make this easy. First, switch your assistant into </span><b>plan mode</b><span style="font-weight: 400"> (in Claude Code, press Shift+Tab to cycle to &#8220;plan mode&#8221;). In plan mode, the assistant proposes a plan instead of changing files. Second, ask the assistant to interview you before it builds. A prompt like this works well:</span></p>
<blockquote><p><i><span style="font-weight: 400">&#8220;Before you build anything, interview me to design this agent. Ask one question </span></i><i><span style="font-weight: 400">at a time and recommend a sensible default for each. Treat the design as a </span></i><i><span style="font-weight: 400">decision tree: when my answer opens new choices, follow that branch and keep </span></i><i><span style="font-weight: 400">asking until it&#8217;s fully resolved, then move to the next branch. Cover the data </span></i><i><span style="font-weight: 400">model, actions, permissions, and agent structure. Don&#8217;t stop or start building </span></i><i><span style="font-weight: 400">until every decision needed to build the agent is made.&#8221;</span></i></p></blockquote>
<p><span style="font-weight: 400">That single instruction turns the assistant into a design partner. It walks down every branch of the decision tree — following each answer to the sub-decisions it raises — and only stops once nothing is left to decide. Nothing gets built on a guess. For our example, an employee To-Do Manager agent, it walked through real architectural choices:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Where should to-dos live: in a custom object, or the standard Task object?</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Which fields and status values does the agent need?</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Should the agent run as the logged-in user?</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Use one sub-agent with three flow actions, or a hub-and-spoke model with a sub-agent per task?</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Should the backing logic be Salesforce Flows or Apex?</span></li>
</ul>
<p><span style="font-weight: 400">Note that the above is an example prompt, and based on your organization’s best practices, you can </span><a href="https://developer.salesforce.com/blogs/2026/05/build-a-salesforce-agent-skill-with-claude-code"><span style="font-weight: 400">build an agent skill of your own</span></a><span style="font-weight: 400"> to help you design this, so you do not have to repeat this prompt.</span></p>
<p><span style="font-weight: 400">The Claude Code coding assistant interviews the developer one question at a time, recommending a default for each design decision.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206577" >
			    <img decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260623124808/image3-e1782244100641.png?w=1000" class="postimages" width="1000" height="608" alt="The Claude Code assistant interviewing the developer" />
			  </span>
			</p>
<p><span style="font-weight: 400">Visualizing your agent as a graph is helpful. Each sub-agent acts as a node representing a specific domain: a single, focused job with its own dedicated instructions and actions. A router node at the top orchestrates these sub-agents based on the conversation. Designing the agent means defining these nodes and providing clear instructions and tools to ensure that the agent achieves the user&#8217;s goals.</span></p>
<p><span style="font-weight: 400">So, the rule of thumb is about domains, not difficulty. Keep a single sub-agent when the whole job lives in one domain, for example, frequently asked questions (FAQ) or a status lookup. Reach for multiple sub-agents in a hub-and-spoke shape when the work spans distinct domains that each deserve their own instructions, actions, or security gate. Most agents land at one to five domain sub-agents. You make these calls, not your coding agent. That&#8217;s the point — you end up more confident because you designed the graph.</span></p>
<p><span style="font-weight: 400">The AI-generated info graphic below conveys these ideas.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206578" >
			    <img decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260623124842/image1-e1782244136116.png?w=1000" class="postimages" width="1000" height="546" alt="Visualizing agents on Agentforce as a graph" />
			  </span>
			</p>
<h2><b>Generate, deploy, and let the agent fix and retry</b></h2>
<p><span style="font-weight: 400">Once the plan is approved, the assistant builds everything: the custom object and fields, the flows, and the agent definition file written in Agent Script (the declarative format that specifies your agent&#8217;s sub-agents, routing logic, instructions, and action bindings). The output is an authoring bundle: the deployable package containing the agent&#8217;s complete definition as local metadata files. You can watch it happen live in VS Code as files appear.</span></p>
<p><span style="font-weight: 400">Be sure to scope the work explicitly in your plan. A common choice is &#8220;build, deploy, and validate&#8221; before you publish. You can also add reminders that the assistant might otherwise skip, for instance, &#8220;make sure the new object has the right permissions; create a permission set.&#8221; Field-level security (FLS) and create-read-update-delete (CRUD) permissions matter, so call them out.</span></p>
<p><span style="font-weight: 400">The standout behavior is an automated </span><b>fix-and-retry loop</b><span style="font-weight: 400">. When a deploy fails, the assistant reads the compiler or deploy error, traces it to the offending metadata, applies a fix, and redeploys, repeating that cycle until the deploy succeeds. It&#8217;s the same edit-compile-debug loop you&#8217;d run by hand, just driven by the agent. If you want to go faster, you can fan out the work, so parallel agents build several flows at once instead of one after another. When you go for parallel work, make sure you understand dependencies and group them, so they can execute without stepping on each other.</span></p>
<p>You drive this in plain language: <i>“Build a single sub-agent To-Do Manager using Salesforce Flows, build, deploy, and validate it, and create a permission set for the new object”</i>. Behind that prompt, the <code>developing-agentforce</code> skill runs a precise sequence. It first generates the authoring bundle (the agent scaffold), then validates that Agent Script compiles with a local check, deploys the backing flows or Apex the actions reference, and finally deploys the authoring bundle itself. Knowing the sequence helps you follow along and step in when needed.</p>
<pre language="sh">sf agent generate authoring-bundle --json --no-spec --name "To-Do Manager" --api-name To_Do_Manager
sf agent validate authoring-bundle --json --api-name To_Do_Manager
sf project deploy start --json --metadata Flow
sf project deploy start --json --metadata AiAuthoringBundle:To_Do_Manager
</pre>
<p>Notice two best practices baked into the skill. Every command leads with <code>--json</code> to make the output machine-readable, and every deploy names its metadata explicitly. A bare <code>sf project deploy start</code> ships everything that changed, and scoping each deploy keeps agent metadata from going out by accident.</p>
<h2><strong>Validate, then test in two modes</strong></h2>
<p><span style="font-weight: 400">When the build finishes, the assistant validates the agent&#8217;s behavior paths automatically, checking far more paths than you&#8217;d test by hand. Validation is required before the agent can be published, so it&#8217;s built into the flow.</span></p>
<p>Still, don&#8217;t skip your own testing. Automated validation is a safety net, not a substitute for judgment. This is where the <code>testing-agentforce</code> skill comes in. It gives you two modes: quick smoke tests while you iterate, and a saved batch suite for regression testing and continuous integration. Use the first to move fast, and the second to keep the agent honest as it grows.</p>
<h3><strong>Quick smoke tests with preview sessions</strong></h3>
<p>The quick mode is a live preview session. You start a session, send a real utterance, and end the session when you&#8217;re done — each preview sends a trace you can read afterward. Note that <code>--authoring-bundle</code> must appear on all three subcommands.</p>
<pre language="sh">sf agent preview start --json --authoring-bundle To_Do_Manager

sf agent preview send --json --authoring-bundle To_Do_Manager --session-id SESSION_ID --utterance "Create a to-do to write a blog"\

sf agent preview end --json --authoring-bundle To_Do_Manager --session-id SESSION_ID
</pre>
<h3><b>Batch regression testing with test specs</b></h3>
<p><span style="font-weight: 400">For repeatable testing, you describe the cases you care about and the skill writes a test spec. The spec is a YAML file that lists each utterance alongside what should happen. Three assertions matter per case:</span></p>
<ul>
<li><code>expectedTopic</code>: The sub-agent that the conversation should route to</li>
<li><code>expectedActions</code>: The actions that should fire</li>
<li><code>expectedOutcome</code>: A plain-language description of the right result</li>
</ul>
<p><span style="font-weight: 400">That last one is graded by a large language model (LLM) acting as a judge, so it reads the response the way a person would. It&#8217;s the most reliable assertion, so include it on every case.</span></p>
<pre language="YAML">name: To_Do_Manager_Tests
subjectType: AGENT
subjectName: To_Do_Manager
testCases:
  - utterance: "Create a to-do to write a blog"
    expectedTopic: To_Do_Management
    expectedActions:
      - Create_To_Do
    expectedOutcome: "A new to-do titled 'write a blog' is created and confirmed to the user."
</pre>
<p><span style="font-weight: 400">You then create the test definition from that spec and run it as a batch, waiting for the results.</span></p>
<pre language="sh">sf agent test create --json --spec specs/To_Do_Manager-testSpec.yaml --api-name To_Do_Manager_Tests --force-overwrite

sf agent test run --json --api-name To_Do_Manager_Tests --wait 10
</pre>
<p><span style="font-weight: 400">When a case fails, the skill diagnoses it straight from the trace — a sub agent that didn&#8217;t match, an action that never fired, or an ungrounded response. It then applies a targeted fix and retries for a few iterations before asking for your help. You can also run the live preview in the new </span><b>Agent Builder</b><span style="font-weight: 400"> for an admin-friendly view, or use the </span><b>Agentforce DX</b><span style="font-weight: 400"> panel in VS Code to start a test session.</span></p>
<p><span style="font-weight: 400">Note: We are enhancing the automated testing experience to support multi-turn conversation, injection of state variables, and verify action validations. Keep an eye on the </span><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_einstein_platform.htm&amp;release=262&amp;type=5"><span style="font-weight: 400">release notes</span></a><span style="font-weight: 400">.</span></p>
<h2><strong>Publish and activate your agent</strong></h2>
<p><span style="font-weight: 400">When it works, publish and activate the agent. Publishing turns the deployed bundle into a runnable agent version as </span><i><span style="font-weight: 400">Inactive</span></i><span style="font-weight: 400">; activating turns it on so users and tests can reach it. You can publish now and activate later if you prefer. Below are commands that the agent runs for publish and activation.</span></p>
<pre language="sh">sf agent publish authoring-bundle --json --api-name To_Do_Manager
sf agent activate --json --api-name To_Do_Manager
</pre>
<p><span style="font-weight: 400">And if you forgot a piece of metadata, just ask the agent to build it for you. The whole point is that you describe the gap and the assistant fills it. Maintain a repeatable history by committing your Agent Script and associated metadata to a source control system like </span><a href="https://git-scm.com/"><span style="font-weight: 400">Git</span></a><span style="font-weight: 400">. This practice ensures you can revert to a known functional version whenever necessary, keeping your development environment stable and secure.</span></p>
<h2><strong>Debug with traces, not guesswork</strong></h2>
<p><span style="font-weight: 400">The agent trace is the primary debugging signal for Agentforce agents. It’s a JSON file generated after each conversation turn that shows which sub-agent handled the request, what the LLM received, and which actions fired. Agents rarely come out perfect on the first try, so you&#8217;ll keep tweaking them. You&#8217;ll work with traces in two places, and they look slightly different in each.</span></p>
<h3><strong>Local traces during development</strong></h3>
<p>While you build, the traces are local. After each <code>preview send</code>, the runtime writes one JSON file per conversation turn. Each trace shows the full execution path: which sub-agent handled the turn, what variables were set, what the LLM saw, and which actions it called. You&#8217;ll find these files in your project.</p>
<pre language="sh">.sfdx/agents/To_Do_Manager/sessions//traces/.json
</pre>
<p><span style="font-weight: 400">These local traces are instant and complete, but they only cover your own preview sessions. The fix loop is simple: open the trace, paste it back into your assistant, and explain what you expected versus what happened. In the Agent DX panel, you can grab the same trace with one click. The trace is detailed enough that the assistant usually pinpoints and fixes the issue — you can even ask it to &#8220;self-test using the trace and fix it.&#8221; This resolves the large majority of problems before the agent ever ships.</span></p>
<h3>Production traces with the <code>observing-agentforce</code> skill</h3>
<p>Once real users are on the agent, you need a different lens. That&#8217;s the job of the <code>observing-agentforce</code> skill. Instead of one local file, it queries the <a href="https://help.salesforce.com/s/articleView?id=ai.generative_ai_session_trace_data_model.htm&amp;type=5"><u>Session Trace Data Model (STDM) </u></a>— the production session data that Agentforce stores in Data 360 — so you can see how the agent behaves across real conversations at volume.</p>
<p><span style="font-weight: 400">The skill follows a three-step loop: </span><b>observe, reproduce, improve</b><span style="font-weight: 400">. It queries production sessions to surface the failures that matter — sub-agent misroutes, action errors, low adherence, slow actions, and abandoned sessions. It then reproduces a suspect case in a local preview to confirm the root cause, and only then edits the agent to fix it. You ask in plain language, </span><i><span style="font-weight: 400">&#8220;find the worst-performing sessions from the last day and tell me why&#8221;</span></i><span style="font-weight: 400">, and the skill runs the queries and reads the results for you.</span></p>
<p><span style="font-weight: 400">For either kind of trace, a visualizer helps when the routing itself is hard to follow. The agent is a graph: a router hands each conversation to a sub-agent, which builds variables and prompts, sends them to the LLM, and calls tools until it has an answer. A tool like </span><a href="https://developer.salesforce.com/blogs/2026/05/agentlens-debug-agentforce-with-interactive-visualizations"><span style="font-weight: 400">AgentLens</span></a><span style="font-weight: 400"> lets you walk that graph step by step, seeing each sub-agent handoff, the tools sent to the LLM, and the response that ends the flow.</span></p>
<p><span style="font-weight: 400">Note: for agent observability at scale for production agents, it is recommended to set up dashboards and metrics using </span><a href="https://help.salesforce.com/s/articleView?id=005226932&amp;type=1"><span style="font-weight: 400">Agent Observability </span></a></p>
<h2><b>Extend the lifecycle with custom agent skills</b></h2>
<p><span style="font-weight: 400">Adopt this lifecycle and your day-to-day changes. You stop clicking through Salesforce setup for routine work and start describing outcomes instead. When something isn&#8217;t right, the fix almost always lives in one of four places: your prompt, your context, the tools you built, or the skills you installed.</span></p>
<p><span style="font-weight: 400">That last one is the real unlock. When a skill produces metadata you don&#8217;t like, improve the skill and contribute it back. Your fixes compound for everyone, and the assistant gets better at Salesforce over time. Pairing that with a design-first interview keeps you in control — you slow down just enough to learn and to own the design.</span></p>
<h2><b>The agentic development lifecycle: Recap</b></h2>
<p><span style="font-weight: 400">The agentic development lifecycle gives you a faster, more reliable way to build Agentforce agents. Install Agent Skills from the Salesforce Skills Library so your assistant knows Salesforce, scaffold and connect your project, then design before you build with plan mode and a design-first interview. Let the assistant generate the metadata and fix-and-retry failed deployments, validate and test the result, and debug with traces when something&#8217;s off. You move quicker and stay the designer the entire time.</span></p>
<p><span style="font-weight: 400">Want to go deeper or share what you&#8217;ve built? 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">.</span></p>
<h2><b>Resources</b></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/developer-centers/agentforce"><span style="font-weight: 400">Agentforce Developer Center</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/forcedotcom/sf-skills"><span style="font-weight: 400">Salesforce Skills Library on GitHub (forcedotcom/sf-skills)</span></a></li>
<li style="font-weight: 400"><a href="https://marketplace.visualstudio.com/items?itemName=salesforce.salesforcedx-vscode-agents"><span style="font-weight: 400">Agentforce DX Visual Studio Code extension</span></a></li>
</ul>
<h2><b>About the author</b></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>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/master-the-agentic-development-lifecycle-for-agentforce">Master the Agentic Development Lifecycle for Agentforce</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/master-the-agentic-development-lifecycle-for-agentforce/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206559</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260618092236/SingleHeadshot-5-e1781799775222.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260618092236/SingleHeadshot-5-e1781799775222.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Analyze and Consolidate Apex Triggers via Agentforce Vibes</title>
		<link>https://developer.salesforce.com/blogs/2026/06/analyze-and-consolidate-apex-triggers-via-agentforce-vibes</link>
		<comments>https://developer.salesforce.com/blogs/2026/06/analyze-and-consolidate-apex-triggers-via-agentforce-vibes#respond</comments>
		<pubDate>Tue, 23 Jun 2026 15:00:56 +0000</pubDate>
		<dc:creator><![CDATA[Lakshmi Anusha Myneni]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Agentforce Vibes]]></category>
		<category><![CDATA[Apex]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Apex Best Practices]]></category>
		<category><![CDATA[Apex triggers]]></category>
		<category><![CDATA[Code Refactoring]]></category>
		<category><![CDATA[Developer Tools]]></category>
		<category><![CDATA[Salesforce CLI]]></category>
		<category><![CDATA[salesforce dx]]></category>
		<category><![CDATA[technical debt]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206556</guid>
		<description><![CDATA[<p>Learn how to use Agentforce Vibes to automatically audit, risk-scan, and safely consolidate multiple Apex triggers into a single, clean, and maintainable framework.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/analyze-and-consolidate-apex-triggers-via-agentforce-vibes">Analyze and Consolidate Apex Triggers via Agentforce Vibes</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">Every org has a story, and it usually begins the same way: one Apex trigger here, another there, each solving a problem in the moment. But over time, orgs quietly accumulate multiple Apex triggers on the same object, written by different developers with no unified strategy. Before long, you&#8217;re staring down unpredictable execution orders, governor limit landmines, logic conflicts, and recursion nightmares. Sound familiar?</span></p>
<p><span style="font-weight: 400">Of course, managed package triggers are a black box. We don&#8217;t own them, and we can&#8217;t touch them. But our custom triggers? That&#8217;s our house, and we get to set the rules. The answer isn&#8217;t more triggers. It&#8217;s one, done right.</span></p>
<p><span style="font-weight: 400">That&#8217;s where </span><a href="https://www.salesforce.com/agentforce/developers/vibe-coding/"><span style="font-weight: 400">Agentforce Vibes</span></a><span style="font-weight: 400"> becomes your best pair programmer. It reads through your triggers, tells you exactly what each one is doing, spots the landmines, and helps you consolidate everything into a clean, scalable pattern iejggcbhdbklunjvfhejciflcuchnvrn the way it should have been built from the start. What would typically take days of manual code review and architectural planning gets done in a fraction of the time, so your team can focus on what actually matters building great solutions.</span></p>
<p><span style="font-weight: 400">In my org, I had multiple triggers firing on the Opportunity object, each one written at a different point in time, by different developers, for different reasons. In this post, I’ll walk you through the approach I used to bring all of that chaos under one roof, into a single, clean trigger while intentionally leaving any managed package triggers untouched.</span></p>
<p><span style="font-weight: 400">The workflows, skills, prompts, and steps you&#8217;ll see throughout this post are based on my own org, and your org will certainly look different. Your trigger count, logic complexity, and dependency map are uniquely yours. That&#8217;s completely normal, and honestly, that&#8217;s the point.</span></p>
<h2><span style="font-weight: 400">From concept to practice: Skills and workflows</span></h2>
<p><span style="font-weight: 400">What makes this approach powerful is that you can turn it into a reusable</span><a href="https://developer.salesforce.com/docs/platform/einstein-for-devs/guide/skills.html"> <span style="font-weight: 400">skill</span></a><span style="font-weight: 400"> or</span><a href="https://developer.salesforce.com/docs/platform/einstein-for-devs/guide/devagent-workflows.html"> <span style="font-weight: 400">workflow</span></a><span style="font-weight: 400"> — a framework that you can run on any object, share across your team, or plug into other agents. The principles are universal; the execution is yours to shape.</span></p>
<h3><span style="font-weight: 400">Skills: Your on-demand code analyst</span></h3>
<p><span style="font-weight: 400">Think of a skill as a specialist you can call on instantly. Point it at your triggers and it surfaces what actually matters: recursion traps, SOQL inside loops, conflicting field assignments, or gaps in test coverage. Analysis that used to take hours of careful reading? It now takes minutes.</span></p>
<h3><span style="font-weight: 400">Workflows: Safe, repeatable refactoring at scale</span></h3>
<p><span style="font-weight: 400">A workflow takes that analysis and turns it into a structured, sequenced process: audit first, then refactor what needs fixing, then generate the consolidated trigger, handler, and tests in the right order. No steps skipped, no code merged before it&#8217;s ready. Just a clean, predictable path from messy to maintainable.</span></p>
<h2><span style="font-weight: 400">From chaos to a single, clean trigger</span></h2>
<p><span style="font-weight: 400">Below is the a generic workflow that I implemented to take multiple Apex triggers on a single object and consolidate them into one clean, maintainable handler.</span></p>
<table>
<tbody>
<tr>
<td><b>Step</b></td>
<td><b>Description</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">1. Workflow start</span></td>
<td>You run the <code>/trigger-consolidation</code> command to start the trigger consolidation workflow.</td>
</tr>
<tr>
<td><span style="font-weight: 400">2. Metadata retrieval</span></td>
<td><span style="font-weight: 400">The trigger consolidation workflow uses the Salesforce CLI to query the org’s metadata and pull all trigger and class sources.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">3. Trigger analysis</span></td>
<td><span style="font-weight: 400">The workflow runs the 15-dimension risk assessment on your code thanks to the two trigger analysis skills.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">4. Plan creation</span></td>
<td>The workflow writes the audit plan to <code>docs/trigger-audit-plan.md</code>.</td>
</tr>
<tr>
<td><span style="font-weight: 400">5. Plan review</span></td>
<td><span style="font-weight: 400">You read, edit, adjust, and approve the plan.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">6. Plan execution</span></td>
<td><span style="font-weight: 400">The workflow reads your plan, and generates files that implement the trigger consolidation.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">7. Manual review</span></td>
<td>You review generated files and resolve the <code>// CONFLICT</code> and <code>// ASYNC REQUIRED</code> comments.</td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">This workflow works on any standard or custom object as it contains no hardcoded object names. You can run it multiple times on different objects. You’ll get the same process and the same quality of analysis without the need for reconfiguration.</span></p>
<p><span style="font-weight: 400">Let’s take a look at the workflow steps in more detail.</span></p>
<h3><span style="font-weight: 400">Step 0: Project setup</span></h3>
<p><span style="font-weight: 400">Before you get started, there are a few things you&#8217;ll need in place.</span></p>
<p><span style="font-weight: 400">This walkthrough assumes that you&#8217;re working in VS Code with the</span><a href="https://marketplace.visualstudio.com/items?itemName=salesforce.salesforcedx-vscode"> <b>Salesforce Extension Pack</b></a><span style="font-weight: 400"> installed, your org is authenticated via the</span><a href="https://developer.salesforce.com/tools/salesforcecli"> <b>Salesforce CLI</b></a><span style="font-weight: 400">, and the</span><a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_mcp_server.htm"> <b>Salesforce DX MCP Server</b></a><span style="font-weight: 400"> is enabled in Agentforce Vibes. If you&#8217;re already set up, feel free to skip ahead. Otherwise, each link above will get you there quickly.</span></p>
<p><span style="font-weight: 400">I&#8217;ve published a base version of the skills and workflows for trigger consolidation on</span><a href="https://github.com/anushamyneni1/apexTriggerConsolidation"> <b>GitHub</b></a><span style="font-weight: 400">. Think of it as your ready-to-run starting point: fork it, adapt it to your org&#8217;s objects and conventions, and make it your own.</span></p>
<p>Now, let&#8217;s install the skills and workflows. Head over to this<a href="https://github.com/anushamyneni1/apexTriggerConsolidation#setup"> <b><u>README</u></b></a> for setup instructions, it walks you through copying the <code>.a4drules/</code> directory into your Salesforce DX project root.</p>
<p>This copies three files into your project: <code>apex-trigger-risk-scan.md</code>,<code> </code><code>pex-trigger-consolidation-analysis.md</code>, and <code>trigger-consolidation.md</code>.</p>
<pre>your-sfdx-project/
├─ .a4drules/
│   ├─ skills/
│   │   └─ apex-trigger-risk-scan.md                  ← single trigger analysis skill
│   │      └─ apex-trigger-consolidation-analysis.md  ← cross trigger analysis skill
│   └─ workflows/
│       └─ trigger-consolidation.md    ← trigger consolidation workflow
</pre>
<p><span style="font-weight: 400">That&#8217;s the entire install — no restart or configuration required. Agentforce Vibes picks up the new skills and workflow immediately.</span></p>
<h3><span style="font-weight: 400">Step 1: Workflow start</span></h3>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Click the </span><b>Agentforce Vibes</b><span style="font-weight: 400"> icon in the VS Code Activity Bar to open the chat panel.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400"><span style="font-weight: 400">Type the following command to start the trigger consolidation workflow.</span></span>
<pre>/trigger-consolidation
</pre>
</li>
<li style="font-weight: 400"><span style="font-weight: 400"><span style="font-weight: 400">The workflow kicks off by asking which object to work on.</span></span>
<pre>Which Salesforce object do you want to consolidate triggers for?
(e.g. Opportunity, Case, Account, Invoice__c)
</pre>
<p><span style="font-weight: 400"></span></li>
<li>Type an object name like <code>Opportunity</code> and press Enter.</li>
</ol>
<h3><span style="font-weight: 400">Step 2: Metadata retrieval</span></h3>
<p><span style="font-weight: 400">The workflow uses the Salesforce CLI to query your org’s metadata to retrieve Apex triggers and the related helper classes.</span></p>
<p><span style="font-weight: 400">You can watch it work in the chat panel with an output similar to this:</span></p>
<pre>Running: sf data query --query "SELECT Name, ... FROM ApexTrigger
WHERE TableEnumOrId = 'Opportunity' AND Status = 'Active'"

## Summary of what was retrieved:
**Triggers (9 total, 5 readable):**
1. **OpportunityBefore** (x2) - Body hidden (managed package / org-installed, unreadable)
2. **OpportunityAfter** - Body hidden (unreadable)
3. **OpportunityTrigger** - Body hidden (unreadable)
4. **OppDeleteTrigger** - before delete — readable
5. **OppCreateTrigger** - before insert, after insert — readable
6. **OpportunityNotifyTrigger** - after insert, after update — readable
7. **OppStageTrigger** - before update — readable
8. **OpportunityUpdateTrigger** - before update, after update — readable
The dependent classes are all accessible: OpportunityUtils, OpportunityHelper, and NotificationService. No test classes exist for this trigger suite. Let me load the apex-trigger-analysis skill to conduct the risk assessment.
</pre>
<h3><span style="font-weight: 400">Step 3: Trigger analysis</span></h3>
<p><span style="font-weight: 400">With the source in hand, the workflow loads the two trigger analysis skills and runs every trigger through all risk dimensions.</span></p>
<h4><span>Per-trigger scan: </span><code>apex-trigger-risk-scan</code><span> skill (seven dimensions)</span></h4>
<table>
<tbody>
<tr>
<td><b>Dimension</b></td>
<td><b>What It Catches</b></td>
</tr>
<tr>
<td><b>Execution order risk</b></td>
<td><span style="font-weight: 400">Flags assumptions that the trigger makes about running before or after another specific trigger, e.g., comments or field reads that depend on values another trigger should set first.</span></td>
</tr>
<tr>
<td><b>Recursion traps</b></td>
<td><span style="font-weight: 400">Flags absence of a static Boolean recursion guard when DML is present — CRITICAL if no guard and DML exists, and HIGH if no guard only.</span></td>
</tr>
<tr>
<td><b>Governor limit exposure</b></td>
<td><span style="font-weight: 400">Scans for SOQL inside loops (CRITICAL), DML inside loops (CRITICAL), synchronous HTTP callouts in trigger context (HIGH), and aggregate queries without LIMIT (MEDIUM).</span></td>
</tr>
<tr>
<td><b>Before/after context boundary</b></td>
<td><span style="font-weight: 400">Maps which contexts that the trigger fires in and flags misplaced logic, e.g., field assignments in after context (silently lost) or DML on triggering record in before context (causes recursion).</span></td>
</tr>
<tr>
<td><b>Bypass mechanism</b></td>
<td><span style="font-weight: 400">Looks for kill-switch patterns, such as custom setting/metadata checks, permission-based skips, or static Boolean flags set externally, and notes absence as well as presence.</span></td>
</tr>
<tr>
<td><b>Static variable inventory</b></td>
<td><span style="font-weight: 400">Lists every static variable in handler/helper classes and flags generic names (isRunning, processed) that collide with the same name in another trigger&#8217;s class after consolidation.</span></td>
</tr>
</tbody>
</table>
<h4><span>Cross-trigger analysis: </span><code>apex-trigger-consolidation-analysis</code><span> skills (eight dimensions)</span></h4>
<table>
<tbody>
<tr>
<td><b>Dimension</b></td>
<td><b>What It Checks</b></td>
</tr>
<tr>
<td><b>Logic overlap</b></td>
<td><span style="font-weight: 400">Compares all triggers and flags the same field assigned in multiple triggers, identical validation logic, and the same helper method called from multiple triggers.</span></td>
</tr>
<tr>
<td><b>Conflicting logic</b></td>
<td><span style="font-weight: 400">Flags contradictions across triggers on the same event, such as opposing field values, one trigger nulling a field another reads, mutually exclusive conditionals, or read-after-write dependencies.</span></td>
</tr>
<tr>
<td><b>Helper class coupling</b></td>
<td><span style="font-weight: 400">Maps helper/utility classes to dependent triggers, i.e., any class used by more than two triggers is a consolidation risk since modifying it can break all callers.</span></td>
</tr>
<tr>
<td><b>Test coverage gaps</b></td>
<td><span style="font-weight: 400">Determines per trigger whether or not a test class exists, coverage is below 85%, or there’s an absence of a bulk test with 200+ records.</span></td>
</tr>
<tr>
<td><b>Static variable collision</b></td>
<td><span style="font-weight: 400">Cross-references static variable inventories from all per-trigger scans and identifies name collisions, such as two classes with the same static variable name, corrupting recursion guards.</span></td>
</tr>
<tr>
<td><b>Bypass consolidation strategy</b></td>
<td><span style="font-weight: 400">Assesses whether bypass mechanisms can be unified: compatible bypasses merge into one; inconsistent bypasses require per-method flags; or triggers with no bypass that inherit a unified bypass unintentionally are flagged.</span></td>
</tr>
<tr>
<td><b>Automation re-entry risk</b></td>
<td>Identifies flows, process builders, or workflow field updates that can commit DML re-firing Apex triggers mid-transaction, and flags <code>@future/Queueable/Platform Event</code> calls that could loop back.</td>
</tr>
<tr>
<td><b>Cumulative governor limit budget</b></td>
<td><span style="font-weight: 400">Sums SOQL, DML, and heap consumption across all triggers to surface compound risk invisible per-trigger, and flags when the combined estimate approaches Salesforce limits even if no single trigger is problematic alone.</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">You’ll see findings stream into the panel in real time.</span></p>
<pre>Score Meaning
1–3      Low — safe to consolidate with minimal prep
4–6      Medium — refactor required before merge
7–9      High — urgent action recommended
10       Critical — production incident likely without immediate action
Overall risk score: 7/10
</pre>
<h3><span style="font-weight: 400">Step 4: Plan creation</span></h3>
<p>Here&#8217;s the part that matters: <b>the workflow does not generate a single line of code yet.</b><br />
Instead, it writes a detailed plan file to <code>docs/trigger-audit-plan.md</code> and pauses.</p>
<pre>Audit complete. I've written the full plan to docs/trigger-audit-plan.md.
Overall risk score: 7/10
Please review the findings and edit anything before I generate
the consolidated scaffold.
When you're ready, reply YES to proceed. Reply NO to stop here.
</pre>
<h3><span style="font-weight: 400">Step 5: Plan review</span></h3>
<p><span style="font-weight: 400">Open the plan file in VS Code. It contains:</span></p>
<ul>
<li style="font-weight: 400"><b>Trigger inventory</b><span style="font-weight: 400">: Every trigger, its events, line count, risk score, and top finding</span></li>
<li style="font-weight: 400"><b>Risk register</b><span style="font-weight: 400">: Every finding with severity, plain-English description, and recommendation</span></li>
<li style="font-weight: 400"><b>Dependency graph</b><span style="font-weight: 400">: Which triggers share which helper classes, as a plain-text diagram</span></li>
<li style="font-weight: 400"><b>Logic merge map</b><span style="font-weight: 400">: Exactly which logic from which trigger goes into each consolidated context, with conflicts flagged explicitly</span></li>
<li style="font-weight: 400"><b>Best practices compliance:</b><span style="font-weight: 400"> Where current triggers violate Apex standards and what the consolidated code will fix</span></li>
</ul>
<p><b>This file is yours to edit.</b><span style="font-weight: 400"> You can change the instructions, add team notes, remove a trigger from the plan, etc. When you’re ready to proceed, the workflow reads your edited version before it writes any code. What you see in the plan is what gets built.</span></p>
<p><span style="font-weight: 400">When the plan looks right, go back to the Agentforce Vibes chat panel and type:</span></p>
<pre>YES
</pre>
<h3><span style="font-weight: 400">Step 6: Plan execution</span></h3>
<p><span style="font-weight: 400">The workflow reads your (possibly edited) plan and generates consolidated files directly into your Salesforce DX project.</span></p>
<p><span style="font-weight: 400">For instance, this is what you could see when consolidating an opportunity trigger:</span></p>
<pre>✓ force-app/main/default/triggers/OpportunityTrigger.trigger        (28 lines)
✓ force-app/main/default/classes/OpportunityTriggerHandler.cls      (74 lines)
✓ force-app/main/default/classes/OpportunityTriggerHelper.cls       (32 lines)
✓ force-app/main/default/classes/OpportunityTriggerTest.cls         (58 lines)

Consolidation complete. Search for // CONFLICT and // ASYNC REQUIRED
comments — these are the only places that need manual review.
</pre>
<p><span style="font-weight: 400">In this example, these four files are generated:</span></p>
<ul>
<li><b><code>OpportunityTrigger.trigger</code></b>: This is a single trigger covering only the events that existed across the originals. No business logic in the body, everything delegates to the handler.</li>
<li><b><code>OpportunityTriggerHandler.cls</code></b>: One method per trigger context has a static Boolean recursion guard at the top, and every method is annotated with its consolidated source triggers. Logic is merged in the correct sequence per the Logic Merge Map you reviewed.</li>
<li><b><code>OpportunityTriggerHelper.cls</code></b>: This is the actual business logic, fully ported from the original triggers. All SOQL is bulkified outside loops, and duplicate logic from multiple triggers appears exactly once.</li>
<li><b><code>OpportunityTriggerTest.cls</code></b>: Test methods for single insert, bulk insert (200 records), update, bulk update, and delete are each traceable back to the source trigger.</li>
</ul>
<h3><span style="font-weight: 400">Step 7: Manual review</span></h3>
<p><span style="font-weight: 400">Once the workflow terminates, the last manual task is to review the comments that were left during the execution. There are two types of comments that you should address:</span></p>
<ul>
<li><code>// CONFLICT</code>: Where two triggers set the same field to different values. You decide which wins</li>
<li><code>// ASYNC REQUIRED</code>: Where a sync callout needs to move to <code>@future</code> or <code>Queueable</code> before deploying</li>
</ul>
<p><span style="font-weight: 400">Finally, the trigger jungle is a solved problem. Once a trigger is optimized, you can leverage prompts to deactivate the old trigger and, once tested and validated, promote the new implementation to a higher environment.</span></p>
<h2><span style="font-weight: 400">Considerations</span></h2>
<ul>
<li><b><span>Before you adapt this framework, make it yours.</span></b><span> The workflow and skill are starting points, not scripts. Your org&#8217;s complexity, naming conventions, and business logic are unique to everything generated here, so edit the audit plan before you commit to it.</span></li>
<li><b><span>Spend real time in plan mode before generating any code.</span></b><span> Open </span><code><span>docs/trigger-audit-plan.md</span></code><span>, work through the phases, flag the conflicts you already know about, and align the merge order with your team&#8217;s plan. The more thinking you do upfront, the cleaner the output.</span></li>
<li><b><span>Treat generated code as a recommendation, not a final answer.</span></b><span> Always review and validate before you deploy. These are smart suggestions, not certified production-ready commits.</span></li>
<li><b><span>Fix High severity issues before you consolidate anything.</span></b><span> If the audit surfaces recursion risks, SOQL or DML in loops, or field conflicts, stop and resolve those first. Merging broken logic into a single trigger doesn&#8217;t fix it and just makes it harder to debug later.</span></li>
<li><b><span>Version control everything.</span></b><span> This includes triggers, handler classes, service classes, audit files, test classes — all of it. If it gets generated or modified, it belongs in source control.</span></li>
<li><b><span>Always test in a sandbox first and never run directly against production.</span></b><span> The workflow connects to whichever org you&#8217;re authenticated to, so before running </span><code><span>/trigger-consolidation</span></code><span>, verify that </span><code><span>sf org list</span></code><span> shows a sandbox or scratch org as your default. Consolidating triggers in production without testing is how outages happen.</span></li>
<li><b><span>Run one object at a time, but think about the whole org.</span></b><span> This isn&#8217;t a one-and-done fix. Once Opportunity is clean, move to Case, then Account, then your custom objects. The phased approach is intentional: each consolidation builds confidence for the next.</span></li>
</ul>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">Trigger sprawl is common and costly, and with the right approach, it&#8217;s completely solvable.</span></p>
<p><b>Ready to clean up your org?</b><span style="font-weight: 400"> Start with your simplest triggers, follow the steps in this guide, build confidence, then tackle the high-traffic objects. Run the Agentforce Vibes analysis skill and let the audit canvas show you exactly what you&#8217;re dealing with. One trigger, one handler, zero surprises.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<p><b>Agentforce Vibes</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Developer Guide: </span><a href="https://developer.salesforce.com/docs/platform/einstein-for-devs/overview"><span style="font-weight: 400">Agentforce Vibes Overview</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Developer Guide: </span><a href="https://developer.salesforce.com/docs/platform/einstein-for-devs/guide/devagent-getstarted.html"><span style="font-weight: 400">Agentforce Vibes Quick Start</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Install:</span> <a href="https://marketplace.visualstudio.com/items?itemName=salesforce.salesforcedx-vscode"><span style="font-weight: 400">Salesforce Extension Pack for VS Code</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation:</span> <a href="https://developer.salesforce.com/docs/platform/einstein-for-devs/guide/devagent-mcp.html"><span style="font-weight: 400">Agentforce Vibes MCP Server </span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Developer Guide: </span><a href="https://developer.salesforce.com/docs/platform/einstein-for-devs/guide/skills.html"><span style="font-weight: 400">Skills in Agentforce Vibes</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Developer Guide: </span><a href="https://developer.salesforce.com/docs/platform/einstein-for-devs/guide/devagent-workflows.html"><span style="font-weight: 400">Workflows in Agentforce Vibes</span></a></li>
</ul>
<p><b>Salesforce CLI</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Setup Guide: </span><a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm"><span style="font-weight: 400">Salesforce CLI </span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Command Reference:</span> <a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_project_retrieve_start.htm"><span style="font-weight: 400">SF Project Retrieve</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Command Reference: </span><a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_data_query.htm"><span style="font-weight: 400">SF Data Query </span></a></li>
</ul>
<p><b>Apex Trigger Best Practices</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Developer Guide: </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers.htm"><span style="font-weight: 400">Apex Triggers </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.apexcode.meta/apexcode/apex_best_practices.htm"><span style="font-weight: 400">Apex Best Practices</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Trailhead:</span> <a href="https://trailhead.salesforce.com/content/learn/modules/apex_triggers/apex_triggers_intro"><span style="font-weight: 400">Apex Trigger Badge</span></a></li>
</ul>
<p><b>Metadata &amp; Deployment</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Developer Guide: </span><a href="https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_intro.htm"><span style="font-weight: 400">Metadata API </span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Reference:</span> <a href="https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/manifest_samples.htm"><span style="font-weight: 400">Package.xml Manifest</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Developer Guide:</span> <a href="https://help.salesforce.com/s/articleView?id=platform.custommetadatatypes_overview.htm&amp;type=5"><span style="font-weight: 400">Custom Metadata Types </span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Lakshmi Anusha Myneni</b><span style="font-weight: 400"> is a Principal Technical Architect at Salesforce with 15 years of experience in the financial services industry. She partners closely with customers during the most critical stages of their journey, translating complex business challenges into compelling, technically sound Salesforce solutions. </span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/06/analyze-and-consolidate-apex-triggers-via-agentforce-vibes">Analyze and Consolidate Apex Triggers via Agentforce Vibes</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/analyze-and-consolidate-apex-triggers-via-agentforce-vibes/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206556</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260622144837/Generic-B-2-e1782164932989.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260622144837/Generic-B-2-e1782164932989.png?w=1000" medium="image" />
	</item>
		<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 loading="lazy" 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 loading="lazy" 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 loading="lazy" 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>
	</channel>
</rss>
