<?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>Fri, 23 Oct 2026 16:16:13 +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>Agentforce Voice for Agent Script: Route Real Phone Calls to Your Agent</title>
		<link>https://developer.salesforce.com/blogs/2026/10/agentforce-voice-for-agent-script-route-real-phone-calls-to-your-agent</link>
		<comments>https://developer.salesforce.com/blogs/2026/10/agentforce-voice-for-agent-script-route-real-phone-calls-to-your-agent#respond</comments>
		<pubDate>Wed, 21 Oct 2026 15:00:24 +0000</pubDate>
		<dc:creator><![CDATA[Alex Martinez]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Automation]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Flow]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Agent Script]]></category>
		<category><![CDATA[Agentforce Voice]]></category>
		<category><![CDATA[Omni-Channel]]></category>
		<category><![CDATA[Phone Routing]]></category>
		<category><![CDATA[Salesforce CLI]]></category>
		<category><![CDATA[Service Agent]]></category>
		<category><![CDATA[Setup Guide]]></category>
		<category><![CDATA[Voice Channels]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207211</guid>
		<description><![CDATA[<p>Learn how to configure a phone number, set up Omni-Channel routing, and verify calls with Salesforce CLI. Take your voice-enabled Agent Script agent from browser preview to real phone calls without changing your agent's logic.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/agentforce-voice-for-agent-script-route-real-phone-calls-to-your-agent">Agentforce Voice for Agent Script: Route Real Phone Calls to Your 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">Your Agent Script agent can talk in a browser voice preview. The next step is a real phone number that anyone can call. That step doesn&#8217;t touch your script&#8217;s logic.</span></p>
<p><span style="font-weight: 400">If you followed along with my previous post, </span><a href="https://developer.salesforce.com/blogs/2026/10/agentforce-voice-for-agent-script-voice-enabling-best-practices"><span style="font-weight: 400">Agentforce Voice for Agent Script: Voice-Enabling Best Practices</span></a><span style="font-weight: 400">, you added the voice blocks to a script and tuned its instructions for the ear. This post picks up where that one stopped. It&#8217;s for developers who have a voice-ready Agent Script agent and want it to answer real calls. You&#8217;ll need an org with the full Agentforce Voice setup, which a free Developer Edition org doesn&#8217;t include. </span></p>
<p><span style="font-weight: 400">In this post, you’ll look at what your org needs first and how a call reaches your agent. Then you’ll set up the number and routing, and check a call after you hang up.</span></p>
<h2><span style="font-weight: 400">What does my org need before a call can reach the agent?</span></h2>
<p>You&#8217;ll need everything from the previous post: an org with Agentforce Voice turned on, and an Agent Script agent with the <code>language</code>, <code>connection telephony</code>, and <code>modality voice</code> blocks. Phone calls add two more needs: a phone number and an agent user.</p>
<p>You&#8217;ll do most of the setup with the <a href="https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-intro.html"><u>Salesforce CLI</u></a>, from the Salesforce DX project that holds your agent. If you don&#8217;t have one yet, <a href="https://developer.salesforce.com/docs/platform/sfvscode-extensions/guide/create-project.html"><u>create a project in VS Code</u></a> with the <a href="https://developer.salesforce.com/docs/platform/sfvscode-extensions/guide/vscode-overview.html"><u>Salesforce Extensions</u></a>. Then, in the project&#8217;s terminal, log in and make the org your default. Replace <code>&lt;your-domain&gt;</code> with your org&#8217;s My Domain:</p>
<pre language="bash">sf org login web --alias voice-org --set-default --instance-url https://&lt;your-domain&gt;.my.salesforce.com
</pre>
<p>The commands in this post then leave out <code>--target-org</code>. The checks are SOQL (Salesforce Object Query Language) queries, which you run with <code>sf data query</code>.</p>
<h3><span style="font-weight: 400">A way to get a phone number</span></h3>
<p><span style="font-weight: 400">Check for this before you start, not halfway through setup. Some orgs have an entitlement that includes a number and inbound voice credits. Query for those two:</span></p>
<pre language="bash">sf data query -q "SELECT MasterLabel, AmountUsed, CurrentAmountAllowed FROM TenantUsageEntitlement WHERE Setting IN ('setting/force.com/orgValue.CountOfTenDLCNumbers', 'setting/force.com/orgValue.InboundVoiceCredits')"
</pre>
<p>You want two rows, &#8220;Count of Ten-Digit Long Code Numbers&#8221; and &#8220;Inbound Voice Credits&#8221;, each with a <code>CurrentAmountAllowed</code> above <code>AmountUsed</code>. A blank or a null <code>AmountUsed</code> means none are used yet. If the query returns no rows, talk to your Salesforce account team about adding numbers before you go on.</p>
<h3><span style="font-weight: 400">An agent user</span></h3>
<p>A phone agent must be an Agentforce Service Agent. It runs as its own agent user, not as whoever is logged in, because a caller isn&#8217;t logged in to anything. That user has the Einstein Agent User profile, and each one takes an Einstein Agent User license. Make sure you have one available with the following command. <code>UsedLicenses</code> should be lower than <code>TotalLicenses</code>:</p>
<pre language="bash">sf data query -q "SELECT UserLicense.Name, UserLicense.TotalLicenses, UserLicense.UsedLicenses FROM Profile WHERE Name = 'Einstein Agent User'"
</pre>
<p><span style="font-weight: 400">Then create the user with the following command. It prints the new user&#8217;s username:</span></p>
<pre language="bash">sf org create agent-user
</pre>
<p><span style="font-weight: 400">Licenses are limited, so reuse an agent user across rebuilds instead of making a new one each time.</span></p>
<p>In your Agent Script, set the agent type to <code>AgentforceServiceAgent</code>, and put the new user’s username in <code>access.default_agent_user</code>. It&#8217;s the user&#8217;s Salesforce username, which looks like an email address but often isn&#8217;t one you can email.</p>
<pre language="yaml">config:
   developer_name: "VoiceAgent"
   agent_label: "Voice Agent"
   agent_type: "AgentforceServiceAgent"

access:
   default_agent_user: "agent.user.xxxxx@example.com"
</pre>
<h2><span style="font-weight: 400">How does a phone call reach an Agent Script agent?</span></h2>
<p><span style="font-weight: 400">Three pieces of setup sit between a caller and your script. A call reaches a phone number. A voice channel ties that number to an Omni-Channel flow. The flow routes the call to your agent.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-full wp-image-207213" >
			    <img fetchpriority="high" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261020095859/image1_20d56a-e1792515557657.png" class="postimages" width="1000" height="139" alt="Diagram of a call going from a caller&apos;s phone to a phone number, then a PSTN Voice channel, then an Omni-Channel flow with a Route Work element, and finally the Agent Script agent. A dotted line from the flow goes to a fallback queue for when the agent can&apos;t take the call." />
			  </span>
			</p>
<p><span style="font-weight: 400">Each piece has one job. The number is the address callers dial. The flow decides who answers. Today, that&#8217;s always your agent, but a flow is also where you&#8217;d add business hours or more routing rules later. The channel connects the number to the flow, and it&#8217;s the switch that turns calls on and off.</span></p>
<p><span style="font-weight: 400">Salesforce creates a VoiceCall record for every call. That record travels through the whole chain: the flow receives its ID, and so does your agent. It&#8217;s also how you&#8217;ll check the call afterwards.</span></p>
<h2><span style="font-weight: 400">How do I set up the phone number and Omni-Channel routing flow?</span></h2>
<p><span style="font-weight: 400">The number and the channel live under </span><b>Agentforce Voice Setup</b><span style="font-weight: 400"> → </span><b>Voice Channels with Agents</b><span style="font-weight: 400">, and the flow lives in Flow Builder. Do these in order, because each step needs the one before it.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-full wp-image-207215" >
			    <img decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261020100015/image4_c09ff8-e1792515661584.png" class="postimages" width="1000" height="450" alt="The Agentforce Voice Setup page in Setup. The Voice Channels with Agents section has New Number and New Channel buttons, and Channel and Phone Number tabs." />
			  </span>
			</p>
<ol>
<li style="font-weight: 400"><b>Activate your agent.</b><span style="font-weight: 400"> The routing flow can only point at an active agent. If yours isn&#8217;t active, it won&#8217;t show up when you pick where calls go. You can do this from </span><b>Agentforce Builder</b><span style="font-weight: 400"> → Select your Agent → </span><b>Commit</b><span style="font-weight: 400"> → </span><b>Activate</b><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><b>Request a number.</b><span style="font-weight: 400"> Click </span><b>New Number</b><span style="font-weight: 400">. A wizard opens in a new window. Pick a country, a number type, and a number. Once you&#8217;re done, the number shows as </span><b>Requested</b><span style="font-weight: 400">, then </span><b>Live</b><span style="font-weight: 400">. It can take a few minutes. Use </span><b>Refresh Status</b><span style="font-weight: 400"> on the Phone Number tab to check.</span></li>
<li style="font-weight: 400"><b>Build the routing flow.</b><span style="font-weight: 400"> It&#8217;s a small Omni-Channel flow with one element, which you&#8217;ll build in the following section.</span></li>
<li style="font-weight: 400"><b>Create and activate the channel.</b><span style="font-weight: 400"> The channel, which you&#8217;ll set up in the section after that, ties the number to the flow.</span></li>
</ol>
<h3><span style="font-weight: 400">Build the routing flow</span></h3>
<p><span style="font-weight: 400">You will need an </span><b>Omni-Channel flow</b><span style="font-weight: 400">. A few details decide whether calls reach your agent:</span></p>
<ul>
<li><b>Name the input variable exactly </b><b><code>recordId</code></b><b>.</b> Salesforce passes the VoiceCall ID into the flow through that name. Make it a Text variable that&#8217;s available for input, or the flow never receives the call.</li>
<li><b>Keep it to one Route Work element.</b> You need to route a <b>single record</b> (the <code>recordId</code> variable) through a <b>Phone</b> service channel to the <b>Agentforce Service Agent</b> you previously activated.</li>
<li><b>Set a voice queue as the fallback.</b> It&#8217;s where a call lands if the agent can&#8217;t take it, so it must be a queue that supports the VoiceCall object. List your voice queues with the following command:</li>
</ul>
<pre language="bash">sf data query -q "SELECT Queue.Name, QueueId FROM QueueSobject WHERE SobjectType = 'VoiceCall'"
</pre>
<ul>
<li style="font-weight: 400"><b>Activate the flow, and note its API name.</b><span style="font-weight: 400"> You&#8217;ll use the API name to check the flow with a query.</span></li>
</ul>
<p>
			  <span class="postimagessection_specify alignnone size-full wp-image-207216" >
			    <img decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261020100256/image2_755797-e1792515789537.png" class="postimages" width="1000" height="509" alt="The active Voice Agent Routing flow in Flow Builder. The Toolbox lists one variable, recordId, and the canvas has one Route Work element named Router. Its settings route a single record, recordId, over the Phone service channel to the Voice Agent Agentforce Service Agent, with General Voice as the fallback queue." />
			  </span>
			</p>
<p>You can also confirm the flow is active from the Salesforce CLI. Replace <code>Voice_Agent_Routing</code> with your flow&#8217;s API name:</p>
<pre language="bash">sf data query -q "SELECT ApiName, IsActive FROM FlowDefinitionView WHERE ApiName = 'Voice_Agent_Routing'"
</pre>
<h3><span style="font-weight: 400">Create and activate the channel</span></h3>
<p><span style="font-weight: 400">The channel ties your number to your flow. Getting a number doesn&#8217;t create one, so back in </span><b>Agentforce Voice Setup</b><span style="font-weight: 400"> → </span><b>Voice Channels with Agents</b><span style="font-weight: 400">, create a </span><b>New Channel</b><span style="font-weight: 400"> where you select the Phone Number you previously created and the Omni-Channel flow you just created.</span></p>
<p><span style="font-weight: 400">This creates a </span><b>PSTN Voice</b><span style="font-weight: 400"> channel. PSTN stands for </span><i><span style="font-weight: 400">public switched telephone network</span></i><span style="font-weight: 400">: the regular phone network.</span></p>
<p><span style="font-weight: 400">A new channel is created </span><b>inactive</b><span style="font-weight: 400">, and calls won&#8217;t reach your agent until you activate it. The channel list doesn&#8217;t have a toggle. The </span><b>Activate</b><span style="font-weight: 400"> button is on the channel&#8217;s own page. If your first test call goes nowhere, check this first.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-full wp-image-207217" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261020100337/image5_1db8bb-e1792515829127.png" class="postimages" width="1000" height="283" alt="A voice channel&apos;s own page in Agentforce Voice Setup. The channel, Voice Channel, has the type Pstn Voice, and the Activate button is in the top right corner." />
			  </span>
			</p>
<p><span style="font-weight: 400">Once it&#8217;s active, the channel shows a checkmark in the </span><b>Active</b><span style="font-weight: 400"> column of the Voice Channels with Agents list:</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-full wp-image-207218" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261020100536/image3_8688c1-e1792515954183.png" class="postimages" width="1000" height="214" alt="The Channels list in Voice Channels with Agents. A PSTN Voice channel that uses the Voice_Agent_Routing flow has a checkmark in the Active column." />
			  </span>
			</p>
<p>To check the channel from the Salesforce CLI instead, run the following command. <code>IsActive</code> should be true:</p>
<pre language="bash">sf data query -q "SELECT DeveloperName, IsActive FROM MessagingChannel WHERE MessageType = 'PstnVoice'"
</pre>
<h2><span style="font-weight: 400">How do I check a call after I hang up?</span></h2>
<p><span style="font-weight: 400">To check a call, query its VoiceCall and AgentWork records. A live call doesn&#8217;t save a transcript, so these records are how you confirm what happened. First, call the phone number you set up in Agentforce Voice Setup. You should reach your agent.</span></p>
<p><span style="font-weight: 400">Don&#8217;t worry if the first ring takes a while. Some of that time is spent before the call reaches Salesforce. In testing, Salesforce accepted the call around five seconds after it arrived.</span></p>
<p><span style="font-weight: 400">After you hang up, start with the call itself:</span></p>
<pre language="bash">sf data query -q "SELECT Id, FromPhoneNumber, CallAcceptDateTime, CallEndDateTime, CallDurationInSeconds, VendorType FROM VoiceCall ORDER BY CreatedDate DESC LIMIT 1"
</pre>
<p>Check that <code>VendorType</code> is <code>VirtualVoiceAgent</code>, and that <code>FromPhoneNumber</code> is the phone you called from. Then check the routing with the call&#8217;s ID:</p>
<pre language="bash">sf data query -q "SELECT Status, ServiceChannel.DeveloperName, AcceptDateTime FROM AgentWork WHERE WorkItemId = '&lt;VoiceCall Id&gt;'"
</pre>
<p>The work item should be on <code>sfdc_phone</code>, the developer name of the built-in Phone service channel.</p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">Answering real phone calls doesn&#8217;t mean rewriting your Agent Script agent. The work is setup: an agent user, a number, a one-element Omni-Channel flow, and a PSTN Voice channel. Check each call through its VoiceCall and AgentWork records, because a live call doesn&#8217;t save a transcript.</span></p>
<p><span style="font-weight: 400">With those pieces in place, the agent you voice-enabled in </span><a href="https://developer.salesforce.com/blogs/2026/10/agentforce-voice-for-agent-script-voice-enabling-best-practices"><span style="font-weight: 400">my previous post</span></a><span style="font-weight: 400"> answers when someone calls. The VoiceCall record also holds the caller&#8217;s number, which your agent&#8217;s actions can use. That&#8217;s a topic for a later post.</span></p>
<p><span style="font-weight: 400">Have questions? Ask on the </span><a href="https://trailhead.salesforce.com/trailblazer-community"><span style="font-weight: 400">Trailblazer Community</span></a><span style="font-weight: 400"> or reach out to </span><a href="https://x.com/SalesforceDevs"><span style="font-weight: 400">@SalesforceDevs</span></a><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/blogs/2026/10/agentforce-voice-for-agent-script-voice-enabling-best-practices"><span style="font-weight: 400">Agentforce Voice for Agent Script: Voice-Enabling Best Practices</span></a></li>
<li style="font-weight: 400"><a href="https://www.salesforce.com/agentforce/voice/"><span style="font-weight: 400">Agentforce Voice</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/agent-script.html"><span style="font-weight: 400">Agent Script (Agentforce Developer Guide)</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/sfvscode-extensions/guide/create-project.html"><span style="font-weight: 400">Create a Salesforce DX Project in VS Code</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/"><span style="font-weight: 400">Salesforce CLI Command Reference</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=service.how_does_an_omni_channel_flow_work.htm&amp;type=5"><span style="font-weight: 400">Omni-Channel Flows (Salesforce Help)</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/blogs/2026/05/agent-platform-tracing-debug-agentforce-with-trace-trees-soql-and-slack"><span style="font-weight: 400">Agent Platform Tracing: Debug Agentforce with Trace Trees, SOQL, and Slack</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/trailheadapps/agent-script-recipes"><span style="font-weight: 400">Agent Script Recipes (GitHub)</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Alex Martinez</b><span style="font-weight: 400"> was part of the MuleSoft Community before joining Salesforce as a Developer Advocate. Today they help developers build with Agentforce and MuleSoft. You can find more of their content on ProstDev, a site that shares MuleSoft tutorials and walkthroughs. Follow Alex on </span><a href="https://www.linkedin.com/in/alexandra-n-martinez"><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/10/agentforce-voice-for-agent-script-route-real-phone-calls-to-your-agent">Agentforce Voice for Agent Script: Route Real Phone Calls to Your 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/10/agentforce-voice-for-agent-script-route-real-phone-calls-to-your-agent/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207211</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261020100630/SingleHeadshot-10-e1792516002205.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261020100630/SingleHeadshot-10-e1792516002205.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Protect Apex Shares: Custom Row Causes on Standard Objects</title>
		<link>https://developer.salesforce.com/blogs/2026/10/protect-apex-shares-custom-row-causes-on-standard-objects</link>
		<comments>https://developer.salesforce.com/blogs/2026/10/protect-apex-shares-custom-row-causes-on-standard-objects#respond</comments>
		<pubDate>Tue, 20 Oct 2026 15:30:59 +0000</pubDate>
		<dc:creator><![CDATA[Larry Tung]]></dc:creator>
				<category><![CDATA[Apex]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Flow]]></category>
		<category><![CDATA[Salesforce Releases]]></category>
		<category><![CDATA[Trust, Security, and Accessibility]]></category>
		<category><![CDATA[and Accessibility]]></category>
		<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[app development]]></category>
		<category><![CDATA[architecture]]></category>
		<category><![CDATA[new developments]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[trust]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207178</guid>
		<description><![CDATA[<p>Explore two Winter &rsquo;27 enhancements that simplify record sharing in Salesforce. Learn how to preserve manual shares during ownership changes and use custom row causes on standard objects to reduce workarounds and improve sharing management.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/protect-apex-shares-custom-row-causes-on-standard-objects">Protect Apex Shares: Custom Row Causes on Standard Objects</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">For over a decade, standard objects didn’t support custom row causes. When you needed to share a record from Apex, you had to use the Manual row cause. . Since Salesforce deletes Manual shares when a record&#8217;s owner changes, you had to write workarounds.</span></p>
<p><span style="font-weight: 400">Two enhancements remove the need for those workarounds. The </span><i><span style="font-weight: 400">Keep Manual Shares when Transferring Records</span></i><span style="font-weight: 400"> setting is available now. Custom row causes, also called Apex sharing reasons, are <a href="https://help.salesforce.com/s/articleView?language=en_US&amp;id=release-notes.rn_apex_standard_sharing_reasons.htm&amp;release=264&amp;type=5">rolling out for standard objects</a> in the Metadata API and will reach all orgs by the end of October. In this post, we&#8217;ll look at Keep Manual Shares on Record Transfer and Custom Row Causes for Standard Objects.</span></p>
<h2><b>Keep manual shares on record transfer</b></h2>
<p><i><span style="font-weight: 400">Availability</span></i><span style="font-weight: 400">: Available now.</span></p>
<p><span style="font-weight: 400"></span><span style="font-weight: 400">In Sharing Settings, turn on </span><a href="https://help.salesforce.com/s/articleView?id=platform.security_sharing_keep_manual_transfer.htm&amp;type=5"><span style="font-weight: 400">Keep Manual Shares when Transferring Records</span></a><span style="font-weight: 400">. Manual shares then survive ownership changes, and you can remove any workarounds you built for this.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207179" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261013155018/The-Sharing-Settings-page-showing-the-Keep-Manual-Shares-when-Transferring-Records-option-e1791931832523.png?w=1000" class="postimages" width="1000" height="449" alt="The Sharing Settings page showing the Keep Manual Shares when Transferring Records option." />
			  </span>
			</p>
<h2><b>Custom row causes for standard objects</b></h2>
<p><i><span style="font-weight: 400">Availability</span></i><span style="font-weight: 400">: Rolling out now; all orgs will have it by the end of October.</span></p>
<p><span style="font-weight: 400">Apex managed sharing requires an Apex sharing reason. Salesforce stores it in the </span><span style="font-weight: 400">RowCause</span><span style="font-weight: 400"> field of each share record; we call it a </span><i><span style="font-weight: 400">custom row cause</span></i><span style="font-weight: 400"> in this post. You can now define one on a standard object the same way you do on a custom object. For details, see </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_bulk_sharing_creating_with_apex.htm"><span style="font-weight: 400">Sharing a Record Using Apex</span></a><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">The following example defines an </span><i><span style="font-weight: 400">On-Call Expert</span></i><span style="font-weight: 400"> sharing reason on Case, using the Metadata API:</span></p>
<pre language="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;CustomObject xmlns="http://soap.sforce.com/2006/04/metadata"&gt;
    &lt;sharingReasons&gt;
        &lt;fullName&gt;On_Call_Expert__c&lt;/fullName&gt;
        &lt;label&gt;On-Call Expert&lt;/label&gt;
    &lt;/sharingReasons&gt;
&lt;/CustomObject&gt;
</pre>
<p>Deploy it with a package.xml that lists <code>Case.On_Call_Expert__c</code> as a SharingReason member.</p>
<p><span style="font-weight: 400">Then you can use it in Apex:</span></p>
<pre language="apex">CaseShare share = new CaseShare();
share.CaseId = caseId;
share.UserOrGroupId = userId;
share.CaseAccessLevel = 'Edit';
share.RowCause = Schema.CaseShare.RowCause.On_Call_Expert__c;
insert share;
</pre>
<p><span style="font-weight: 400">You can also assign a custom row cause in Flow.</span></p>
<p><span style="font-weight: 400"></span><span style="font-weight: 400">A custom row cause keeps your Apex shares separate from users&#8217; manual shares, and Salesforce doesn&#8217;t delete them when the owner changes. In the share table, admins can see why each user has access. Because each reason is separate, you can update or delete the shares for one reason without touching the others. Unlike the Keep Manual Shares setting, a custom row cause doesn&#8217;t change how users&#8217; manual shares behave. In November, you’ll be able to manage custom row causes in </span><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_headless360.htm&amp;release=264&amp;type=5"><span style="font-weight: 400">AIforce</span></a><span style="font-weight: 400"> and </span><a href="https://help.salesforce.com/s/articleView?id=xcloud.setup_agentforce_overview.htm&amp;type=5"><span style="font-weight: 400">Setup with Agentforce</span></a><span style="font-weight: 400">.</span></p>
<h2><b>Conclusion</b></h2>
<p><span style="font-weight: 400">Winter &#8217;27 gives you two ways to keep shares when a record&#8217;s owner changes.</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">If you don&#8217;t create shares in code, turn on Keep Manual Shares when Transferring Records. It&#8217;s one org-wide setting.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">If code, a Flow, or an integration creates shares on standard objects, switch to a custom row cause. Your shares survive owner changes, stay separate from users&#8217; manual shares, and show why each user has access.</span></li>
</ul>
<p><span style="font-weight: 400">You may need only one of them. Start by reviewing the code that creates CaseShare, AccountShare, or other standard share records.</span></p>
<h2><b>Resources</b></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_bulk_sharing.htm"><span style="font-weight: 400">Understanding Apex Managed Sharing</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/blogs/2013/02/protecting-force-com-custom-sharing-code"><span style="font-weight: 400">Protecting Force.com Custom Sharing Code (2013)</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/blogs/developer-relations/2017/04/salesforce-data-security-model-explained-visually"><span style="font-weight: 400">Salesforce Data Security Model — Explained Visually (2017)</span></a></li>
<li style="font-weight: 400"><a href="https://ideas.salesforce.com/s/idea/a0B8W00000GddnHUAR/allow-custom-rowcause-values-for-standard-objects-ie-accountshare"><span style="font-weight: 400">IdeaExchange: Allow Custom RowCause values for Standard Objects</span></a></li>
</ul>
<h2><b>About the author</b></h2>
<p><span style="font-weight: 400"><strong>Larry Tung</strong> is the Product Manager for the Sharing team, where he’s working on the next generation of user access features and protecting sensitive data. When Larry isn&#8217;t working, he enjoys playing sports and board games with his kids and reading.</span></p>
<p><span style="font-weight: 400"><strong>Peder Rice</strong> is a software engineer on the Sharing team, where he works on record access at scale. He enjoys traveling with his kids in search of hidden corners the guidebooks miss.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/protect-apex-shares-custom-row-causes-on-standard-objects">Protect Apex Shares: Custom Row Causes on Standard Objects</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/10/protect-apex-shares-custom-row-causes-on-standard-objects/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207178</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261020075852/Generic-A-10-e1792508344254.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261020075852/Generic-A-10-e1792508344254.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Building a Claude Code Mod for Salesforce</title>
		<link>https://developer.salesforce.com/blogs/2026/10/building-a-claude-code-mod-for-salesforce</link>
		<comments>https://developer.salesforce.com/blogs/2026/10/building-a-claude-code-mod-for-salesforce#respond</comments>
		<pubDate>Mon, 19 Oct 2026 17:08:09 +0000</pubDate>
		<dc:creator><![CDATA[Dave Norris]]></dc:creator>
				<category><![CDATA[App Development]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Headless 360]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[developer experience]]></category>
		<category><![CDATA[MCP]]></category>
		<category><![CDATA[Mods]]></category>
		<category><![CDATA[plugins]]></category>
		<category><![CDATA[Salesforce CLI]]></category>
		<category><![CDATA[salesforce dx]]></category>
		<category><![CDATA[Terminal]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207189</guid>
		<description><![CDATA[<p>Never wonder which org you're deploying to again: build a Claude Code mod that shows your Salesforce target org above the prompt.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/building-a-claude-code-mod-for-salesforce">Building a Claude Code Mod for Salesforce</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">If you&#8217;ve worked with Salesforce DX for any length of time, you&#8217;ve probably had that moment: you run a deploy, and whilst it’s progressing you wonder: which org was that again? The Salesforce CLI always knows your default org, but you don&#8217;t always see it, and your terminal doesn&#8217;t warn you before you work against production. Add MCP servers to Claude Code, and each one can reach orgs of its own.</span></p>
<p><span style="font-weight: 400">Claude Code now supports </span><a href="https://code.claude.com/docs/en/plugins/mods/overview"><b>mods</b></a><span style="font-weight: 400">: small plugins that run inside Claude Code and can draw directly in its interface. In this post, you&#8217;ll build sf-headless-band, a mod that shows your Salesforce default org above the prompt, including its type and, for a scratch org, how many days it has left. It also adds a line for each Salesforce MCP server, with the org it reaches. Production always appears in red, so you can&#8217;t miss it.</span></p>
<p><span style="font-weight: 400">The full source for this mod is on </span><a href="https://github.com/deejay-hub/sf-headless-band"><span style="font-weight: 400">GitHub</span></a><span style="font-weight: 400">. Clone it to follow along, or create the files as you go.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207203" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261019100407/image4-2-1.gif?w=1778" class="postimages" width="1778" height="1000" alt="" />
			  </span>
			</p>
<h2><span style="font-weight: 400">What is a Claude Code mod?</span></h2>
<p><span style="font-weight: 400">A mod is a Claude Code plugin that can use JavaScript or TypeScript event handlers, called hooks. Claude Code calls a hook when something happens: a session starts, a turn completes, a tool is about to run, or a part of the interface is about to be drawn. A hook can watch the event, change it, or take it over.</span></p>
<p><span style="font-weight: 400">That&#8217;s what makes mods different from settings hooks, skills, and MCP servers, which work from outside Claude Code. A mod runs inside it, so it can draw its own interface: a pane, a toast, a status line, or a band above the prompt, where your org information belongs.</span></p>
<p><span style="font-weight: 400">The Claude Code docs cover the rest in depth: start with the </span><a href="https://code.claude.com/docs/en/plugins/mods/overview"><span style="font-weight: 400">Mods Overview</span></a><span style="font-weight: 400">, then </span><a href="https://code.claude.com/docs/en/plugins/mods/create"><span style="font-weight: 400">Create a Mod</span></a><span style="font-weight: 400"> and </span><a href="https://code.claude.com/docs/en/plugins/mods/interface"><span style="font-weight: 400">Draw in the Interface</span></a><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400">What you&#8217;ll build</span></h2>
<p>The <b><code>sf-headless-band</code></b> mod:</p>
<ul>
<li>Finds your default org the way the Salesforce CLI does: by checking an environment variable (<code>SF_TARGET_ORG</code>) first, then the project&#8217;s config, and finally the global config</li>
<li>Shows the org&#8217;s alias and My Domain, its type on a colored pill (scratch, sandbox, Developer Edition, or production), and, for a scratch org, the days until it expires</li>
<li>Adds a line for each Salesforce MCP server: for the <a href="https://developer.salesforce.com/docs/platform/sfdx-dev/guide/sfdx-dev-mcp.html"><u>Salesforce DX MCP Server</u></a>, the orgs its <code>--orgs</code> flag allows; for a <a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide"><u>Hosted MCP Server</u></a>, the org its calls reach</li>
<li>Shows or hides the band with <code>/sf-headless-band </code>and remembers your choice</li>
<li>Refreshes when you change folders, after each Claude turn, and within three seconds of a config change, so <code>sf config set target-org</code> in another terminal shows up on its own</li>
</ul>
<p>When you&#8217;re done, the plugin folder looks like this:</p>
<pre language="text">sf-headless-band/
├── .claude-plugin/
│   └── plugin.json      # the plugin manifest
├── hooks/
│   ├── hooks.json       # points to your code
│   ├── salesforce.ts    # reads the Salesforce CLI's files
│   ├── org.ts           # finds the default org
│   ├── mcp.ts           # finds the Salesforce MCP servers
│   └── register.tsx     # draws the band
├── tests/
│   ├── org.test.ts
│   ├── mcp.test.ts
│   └── band.test.ts
└── types/
    └── index.d.ts       # declares the mod's state
</pre>
<h2><span style="font-weight: 400">Prerequisites</span></h2>
<ul>
<li style="font-weight: 400"><a href="https://code.claude.com/docs/en/setup"><span style="font-weight: 400">Claude Code</span></a><span style="font-weight: 400"> v2.1.287 or later, in the terminal or the Code tab of the Claude Desktop app</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The </span><a href="https://developer.salesforce.com/tools/salesforcecli"><span style="font-weight: 400">Salesforce CLI</span></a><span style="font-weight: 400"> with at least one authorized org, or a Salesforce MCP server added to Claude Code</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">A Salesforce DX project to try it in</span></li>
</ul>
<h2><span style="font-weight: 400">Step 1: Create the plugin</span></h2>
<p><span style="font-weight: 400">A mod is a plugin with a manifest, a </span><span style="font-weight: 400">hooks.json</span><span style="font-weight: 400"> that points to your code, and the code itself. Create the folders:</span></p>
<pre language="text">mkdir -p sf-headless-band/.claude-plugin sf-headless-band/hooks sf-headless-band/types
</pre>
<p>Save the manifest as <code>sf-headless-band/.claude-plugin/plugin.json</code>. The <code>types</code> field points to the file where you&#8217;ll declare the mod&#8217;s state in the next step:</p>
<pre language="json">{
  "name": "sf-headless-band",
  "version": "0.1.0",
  "description": "Shows the Salesforce default org, its type, and scratch org expiry above the prompt",
  "author": { "name": "Your Name" },
  "types": "./types/index.d.ts"
}
</pre>
<p>Name your entry point in <code>sf-headless-band/hooks/hooks.json</code>. Claude Code loads TypeScript and JSX directly, so there&#8217;s no build step:</p>
<pre language="json">{ 
  "modules": ["./register.tsx"] 
}
</pre>
<h2><span style="font-weight: 400">Step 2: Declare the mod&#8217;s state</span></h2>
<p>The mod keeps what it finds in Claude Code&#8217;s reactive state, <code>$.state</code>. When a value changes, the band redraws on its own, and the values survive the mod reloading.</p>
<p>Declare them in <code>sf-headless-band/types/index.d.ts</code>, under your plugin&#8217;s name. Here&#8217;s the org&#8217;s part; the MCP servers&#8217; types are in the full file on GitHub:</p>
<pre language="typescript">export type OrgType = 'production' | 'developer' | 'sandbox' | 'scratch' | 'unknown'

export type OrgInfo = {
  /** The alias when the target org is one, else the username. */
  name: string
  username: string
  type: OrgType
  /** Its My Domain host, from the CLI's auth file. */
  host?: string
  /** Days until a scratch org expires; undefined for any other type. */
  daysLeft?: number
}

declare module 'claude-code' {
  interface PluginState {
    'sf-headless-band': {
      org: OrgInfo | null
      mcp: McpOrg[]   // the Salesforce Hosted MCP Servers
      dx: DxMcp[]     // the Salesforce DX MCP Servers
      domains: Record&lt;string, { host: string; type: OrgType }&gt;
      hidden: boolean // kept in $.store across sessions
    }
  }
}
</pre>
<h2><span style="font-weight: 400">Step 3: Find the default org</span></h2>
<p>You could run <code>sf org display</code> and parse the output, but it takes a second or two each time: far too slow for something that redraws above your prompt.</p>
<p>Instead, the mod reads the same small files the Salesforce CLI reads:</p>
<table>
<thead>
<tr>
<th><b>What</b></th>
<th><b>Where</b></th>
</tr>
<tr>
<th><span style="font-weight: 400">Is this a Salesforce project?</span></th>
<th><code>sfdx-project.json</code> in the folder or any parent</th>
</tr>
<tr>
<th><span style="font-weight: 400">Default org </span></th>
<th><code>target-org</code> in the project&#8217;s <code>.sf/config.json</code>, then in <code>~/.sf/config.json</code></th>
</tr>
<tr>
<th><span style="font-weight: 400">Alias to username</span></th>
<th><code>~/.sfdx/alias.json</code></th>
</tr>
<tr>
<th><span style="font-weight: 400">Org type and expiry</span></th>
<th><code>~/.sfdx/&lt;username&gt;.json</code> (<code>isScratch</code>, <code>isSandbox</code>, <code>instanceUrl</code>, <code>expirationDate</code>)</th>
</tr>
</thead>
</table>
<p>Outside a Salesforce DX project, the global default still applies, as it does for the CLI. Some older auth files don&#8217;t have the <code>isScratch</code> and <code>isSandbox</code> flags, so the mod falls back to the instance URL: <code>.scratch.</code> and <code>.sandbox.</code> in the My Domain give the type away, and so does a Developer Edition&#8217;s <code>-dev-ed</code>.</p>
<p>Here&#8217;s the heart of that logic. It returns the same type you declared in Step 2, under the name <code>SfOrgType</code>, and <code>DEVELOPER_URL</code> matches a Developer Edition&#8217;s My Domain. As a catch-all, an org that matches nothing counts as production, so if the mod guesses wrong, it errs on the side of caution.</p>
<pre language="typescript">export const authType = (auth: any): SfOrgType =&gt; {
  if (auth === undefined) return 'unknown'
  const url: string = auth.instanceUrl ?? ''
  if (auth.isScratch === true || url.includes('.scratch.')) return 'scratch'
  if (auth.isSandbox === true || url.includes('.sandbox.')) return 'sandbox'
  if (DEVELOPER_URL.test(url)) return 'developer'
  return 'production'
}
</pre>
<p>Copy <code>hooks/salesforce.ts</code>, <code>hooks/org.ts</code>, and <code>hooks/mcp.ts</code> from GitHub. They take a file-reading function as an argument instead of calling the mods API, so tests can pass in fake file contents rather than reading real Salesforce CLI files.</p>
<p><b>Note:</b> The files the mod reads are Salesforce CLI internals, not a public API, so a future CLI release could change them. The auth files also hold access tokens. The mod reads only the type, My Domain, and expiry fields and never displays or sends anything else, but it&#8217;s a good reminder that a mod runs with your permissions. Install mods only from people you trust, and run <code>claude plugin validate</code> on any mod before you install it.</p>
<h2><span style="font-weight: 400">Step 4: Draw the band</span></h2>
<p>Now for the hooks. Copy <code>hooks/register.tsx</code> from GitHub. In the order they appear in the code:</p>
<ul>
<li><code>session.start</code> finds the orgs and servers, watches the CLI&#8217;s config files every three seconds, and registers <code>/sf-headless-band</code></li>
<li><code>tool.call</code> watches each MCP call: a hosted server doesn&#8217;t say which org it reaches, so the mod finds the org&#8217;s My Domain in the call&#8217;s request or result, and remembers it</li>
<li><code>command.run</code> shows or hides the band</li>
<li><code>classic.SessionStart</code> finds them again after <code>/clear</code>, <code>/resume</code>, and <code>/branch</code>, which reset <code>$.state</code></li>
<li><code>classic.CwdChanged</code> finds them again when you change folders</li>
<li><code>turn.complete</code> finds them again after each Claude turn, in case Claude ran <code>sf config set</code> for you</li>
<li><code>ui.render</code> draws the band</li>
</ul>
<p>Here&#8217;s the <code>ui.render</code> hook. To keep it short, it shows only the CLI line and leaves out the MCP server lines:</p>
<pre language="typescript">on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) =&gt; {
  const info = await read($, org)
  if (e.props.hasSurvey || (await read($, hidden)) || info === null) {
    return next(e)
  }

  const { Box, Text } = $.ui.resolve(e)

  // The org type on a solid pill: red production, yellow sandbox, green scratch
  const pill = (type: OrgType) =&gt; (
    &lt;Text backgroundColor={ORG_TYPE_COLOR[type]} color="inverseText"&gt;{` ${ORG_TYPE_LABEL[type]} `}&lt;/Text&gt;
  )

  // sf › cli › alias › my-domain › [ type ]
  const line = (
    &lt;Box flexDirection="row"&gt;
      &lt;Text dimColor&gt;sf › cli › &lt;/Text&gt;
      &lt;Text bold&gt;{info.name}&lt;/Text&gt;
      {info.host ? &lt;Text dimColor&gt; › {domainName(info.host)}&lt;/Text&gt; : null}
      {/* › expires in N days, for scratch orgs */}
      &lt;Text dimColor&gt; › &lt;/Text&gt;
      {pill(info.type)}
    &lt;/Box&gt;
  )

  // The band is shared: keep what the mods after this one draw there
  const theirs = await next(e)
  return theirs ? (
    &lt;Box flexDirection="column"&gt;
      {line}
      {theirs}
    &lt;/Box&gt;
  ) : (
    line
  )
})
</pre>
<p><span style="font-weight: 400">A few details in this code are worth copying into your own mods:</span></p>
<p><b>The hook picks its render site with a matcher.</b> Claude Code fires <code>ui.render </code>for every part of the interface it draws. The <code>{ component: 'AbovePrompt' }</code> filter narrows the hook to the band above the prompt. When there&#8217;s no org to show, or the band is hidden, the hook returns <code>next(e)</code> and draws nothing.<br />
<b>The band is shared.</b> Every mod draws in the same band, so a mod that returns only its own tree hides everyone else&#8217;s. Putting the result of <code>await next(e</code>) under your line keeps theirs.<br />
<b>The colors are theme keys.</b> <code>success</code>, <code>warning</code>, <code>error</code>, and <code>inactive</code> follow the light or dark theme, so the band looks right in both.<br />
<b>It doesn’t guess a hosted server’s org. </b>A hosted server&#8217;s line has no org type until a call has shown its org, and <code>tool.call</code> runs the call with <code>await next(e)</code> before it looks.</p>
<h2><span style="font-weight: 400">Step 5: Try it</span></h2>
<p>Check the mod first. <code>claude plugin validate</code> lists every event the mod handles and every call it makes, without running it:</p>
<pre language="text">claude plugin validate ~/path/to/sf-headless-band
</pre>
<p><span style="font-weight: 400">Then open a Salesforce DX project, start Claude Code with the plugin folder, and turn the band on:</span></p>
<pre language="text">cd ~/projects/my-sfdx-project
claude --plugin-dir ~/path/to/sf-headless-band
</pre>
<p>Type <code>/sf-headless-band on</code>. The band appears above the prompt, with your default org, its My Domain, its type, and, for a scratch org, the days until it expires:</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207191" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261019082806/image1_e9737e-e1792423715401.png?w=1000" class="postimages" width="1000" height="272" alt="Terminal output showing Claude Code displaying the sf-headless-band mod interface with the timeline-lwc org details and a green &quot;scratch&quot; badge indicating 23 days until expiration." />
			  </span>
			</p>
<p><span style="font-weight: 400">Now try each case:</span></p>
<p><b>Switch orgs. </b>Run <code>sf config set target-org &lt;another-alias&gt;</code>. Within three seconds, the band shows the new org. A sandbox gets a yellow pill:</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207192" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261019083009/image3_f1298e-e1792423821893.png?w=1000" class="postimages" width="1000" height="272" alt="Terminal output showing Claude Code with the target org updated to uat-testing, highlighted by a yellow &quot;sandbox&quot; badge above the command prompt." />
			  </span>
			</p>
<p><b>Point at production.</b><span style="font-weight: 400"> Set the target org to a production org. Its pill is red:</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207193" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261019083047/image2_1fa8f9-e1792423860376.png?w=1000" class="postimages" width="1000" height="273" alt="Terminal output showing the sf-headless-band mod active in Claude Code, highlighting the data-ai target org marked with a red &quot;production&quot; badge." />
			  </span>
			</p>
<p><b>Ask Claude for data through a Hosted MCP Server. </b>After the first call, its line shows the org it reached.</p>
<p>The repo also has tests for the org lookup, the MCP servers, and the band. They run against a mock Claude Code, so you don&#8217;t need a real org. Run <code>claude plugin test</code> from the plugin folder. <a href="https://code.claude.com/docs/en/plugins/mods/test"><u>Test a Mod</u></a> explains how they work.</p>
<h2><span style="font-weight: 400">Step 6: Load it in every session</span></h2>
<p><code>--plugin-dir</code> loads the mod for one session. To load it in every session, add the folder to <code>CLAUDE_CODE_PLUGIN_DIRS</code> in the <code>env</code> block of <code>~/.claude/settings.json</code>. To share it with your team, add it to a plugin marketplace, and everyone can install it with <code>/plugin install</code>.</p>
<p><b>Tip:</b> You don&#8217;t have to write every change by hand. Start Claude Code with the plugin folder and ask for a change, such as a different color for Developer Edition orgs. Claude Code&#8217;s built-in <code>plugin-authoring</code> skill knows how mods work, and the mod reloads after each turn that changes it.</p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p>You built a mod that keeps your Salesforce context in view: which org you&#8217;re on, what kind it is, how long a scratch org has left, and which orgs your MCP servers reach. Along the way, you saw the core ideas behind every mod: hooks that respond to events, one that wraps a tool call, and a band drawn with <code>ui.render</code>.</p>
<p>The band is only a starting point. You could build a mod that asks before Claude runs <code>sf project deploy start</code> against production, shows a pane listing your scratch orgs and their expiry, or adds a <code>/</code> command that opens your default org in a browser.</p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Video: </span><a href="https://www.linkedin.com/posts/davenorris_salesforce-ugcPost-7516267544651976704--QWC/?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAABLpSwEBcjd8dGEdWL4UFK8PNWsOFk5r0pI"><span style="font-weight: 400">Salesforce Mods Overview</span></a><span style="font-weight: 400"> </span></li>
<li style="font-weight: 400"><span style="font-weight: 400">GitHub: </span><a href="https://github.com/deejay-hub/sf-headless-band"><span style="font-weight: 400">sf-headless-band</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation: </span><a href="https://code.claude.com/docs/en/plugins/mods/overview"><span style="font-weight: 400">Mods Overview</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation: </span><a href="https://code.claude.com/docs/en/plugins/mods/interface"><span style="font-weight: 400">Draw in the Interface</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation: </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 Setup Guide</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation: </span><a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_scratch_orgs.htm"><span style="font-weight: 400">Salesforce DX Developer Guide</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Dave Norris</b><span style="font-weight: 400"> is a Developer Advocate at Salesforce. He’s passionate about making technical subjects broadly accessible to a diverse audience. Dave has been with Salesforce for over a decade, has over 40 Salesforce and MuleSoft certifications, and became a Salesforce Certified Technical Architect in 2013.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/building-a-claude-code-mod-for-salesforce">Building a Claude Code Mod for Salesforce</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/10/building-a-claude-code-mod-for-salesforce/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207189</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261019085424/SingleHeadshot-9-e1792425278776.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261019085424/SingleHeadshot-9-e1792425278776.png?w=1000" medium="image" />
	</item>
		<item>
		<title>What’s New in Salesforce Multi-Framework: Angular Is Now GA</title>
		<link>https://developer.salesforce.com/blogs/2026/10/whats-new-in-salesforce-multi-framework-angular-is-now-ga</link>
		<comments>https://developer.salesforce.com/blogs/2026/10/whats-new-in-salesforce-multi-framework-angular-is-now-ga#respond</comments>
		<pubDate>Thu, 15 Oct 2026 15:00:43 +0000</pubDate>
		<dc:creator><![CDATA[Alice Oh]]></dc:creator>
				<category><![CDATA[Agentforce Vibes]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Salesforce Releases]]></category>
		<category><![CDATA[angular]]></category>
		<category><![CDATA[app development]]></category>
		<category><![CDATA[Managed Packages]]></category>
		<category><![CDATA[Microfrontends]]></category>
		<category><![CDATA[react]]></category>
		<category><![CDATA[salesforce developers]]></category>
		<category><![CDATA[Salesforce Multi-Framework]]></category>
		<category><![CDATA[Winter ’27 Release]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207183</guid>
		<description><![CDATA[<p>With the Winter &rsquo;27 release, Angular joins React as the second GA UI framework, Microfrontend is now GA, and many more features are added including managed packaging, localization, MFA, and Multi-Framework Insights.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/whats-new-in-salesforce-multi-framework-angular-is-now-ga">What’s New in Salesforce Multi-Framework: Angular 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">Salesforce Multi-Framework is a framework-agnostic runtime that lets developers build modern web apps with the frontend frameworks they already know and run them directly on the Salesforce Platform. Apps can securely access Salesforce data and platform capabilities while inheriting Salesforce authentication, security, and governance, all without a separate hosting stack or manual token management.</span></p>
<p><a href="https://developer.salesforce.com/blogs/2026/04/build-with-react-run-on-salesforce-introducing-salesforce-multi-framework"><span style="font-weight: 400">We introduced Salesforce Multi-Framework in open beta with React support</span></a><span style="font-weight: 400">, then made </span><a href="https://developer.salesforce.com/blogs/2026/07/build-with-react-on-salesforce-multi-framework-is-now-ga"><span style="font-weight: 400">React apps production-ready in our first generally available (GA) release</span></a><span style="font-weight: 400">. With the Winter ’27 release, we’re expanding the ways that developers, admins, and partners can build and deliver Multi-Framework experiences with support for new frameworks, new distribution channels, and new capabilities. </span></p>
<h2><span style="font-weight: 400">What’s new in Salesforce Multi-Framework</span></h2>
<p><span style="font-weight: 400">Here are the highlights for Winter ’27:</span></p>
<ul>
<li style="font-weight: 400"><b>Angular is GA:</b><span style="font-weight: 400"> Build, vibe code, preview, deploy, and run production Angular apps natively on Salesforce. Angular joins React as the second generally available UI framework for Multi-Framework.</span></li>
<li style="font-weight: 400"><b>Microfrontend is GA:</b><span style="font-weight: 400"> Embed React and Angular experiences in Lightning Experience, Experience Cloud, the Salesforce mobile app, and Mobile Publisher apps.</span></li>
<li style="font-weight: 400"><b>Managed packages:</b><span style="font-weight: 400"> Package, distribute, install, and upgrade non-site (internal, employee-facing) Multi-Framework apps as managed packages.</span></li>
<li style="font-weight: 400"><b>Localization and internationalization support:</b><span style="font-weight: 400"> Create experiences for users across languages and regions with locale-aware formatting and localized labels.</span></li>
<li style="font-weight: 400"><b>Multi-factor authentication (MFA) support:</b><span style="font-weight: 400"> Build more secure applications that work with Salesforce MFA requirements.</span></li>
<li style="font-weight: 400"><b>Multi-Framework Insights is Beta:</b><span style="font-weight: 400"> Review industry-standard performance and key usage insights for Multi-Framework apps.</span></li>
</ul>
<p><span style="font-weight: 400">Together, these Winter ’27 updates make Multi-Framework a more complete platform for building, embedding, distributing, and operating modern Salesforce experiences. Teams can choose React or Angular, surface apps across Salesforce with Microfrontend, distribute non-site apps through managed packages, meet enterprise requirements with localization and MFA, and monitor performance and usage with Multi-Framework Insights (Beta).</span></p>
<p><span style="font-weight: 400">Let’s look at each capability in more detail.</span></p>
<h2><span style="font-weight: 400">Build native Angular apps on Salesforce</span></h2>
<p><span style="font-weight: 400">Angular joins React as a generally available framework for Salesforce Multi-Framework. Developers can bring their Angular skills, libraries, component systems, and existing development workflows to Salesforce while building on trusted platform services.</span></p>
<p>Multi-Framework provides three Angular templates:</p>
<ul>
<li><code>angularinternalapp</code> for employee-facing apps opened from the Salesforce App Launcher</li>
<li><code>angularexternalapp</code> for customer- and partner-facing apps delivered through Experience Cloud</li>
<li><code>angularbasic</code> for a minimal starting point</li>
</ul>
<p><span style="font-weight: 400">The internal and external templates include the metadata and platform integration needed for their target audiences. </span></p>
<p><span style="font-weight: 400">Developers can get started from the Agentforce Vibes welcome page or generate a project from the command line. To scaffold an employee-facing Angular app with the Salesforce CLI, run:</span></p>
<pre language="text">sf template generate project --name account-pulse --template angularinternalapp
</pre>
<p><span style="font-weight: 400">The generated project uses familiar Angular tooling. Develop and test locally with the Angular CLI, build with your existing npm workflow, and deploy with the same Salesforce DX commands that you use for the rest of your project.</span></p>
<p>When deployed, the app becomes a <code>UIBundle</code> metadata type. Internal apps run on the dedicated <code>salesforce.app</code> domain, where browser-native origin boundaries isolate each app from the Lightning runtime and other apps. Users still get seamless Salesforce sign-on, while the platform continues to enforce object-level security, field-level security, sharing rules, and validation rules on supported data requests and saves.</p>
<p><span style="font-weight: 400">The framework-agnostic Data SDK provides Angular apps with the same GraphQL operations and platform capabilities as React apps. Choosing Angular doesn’t change the app’s Salesforce security model or require a separate authentication layer.</span></p>
<p><span style="font-weight: 400">New Angular samples in the Multi-Framework Recipes repository demonstrate common platform integration patterns.</span></p>
<h2><span style="font-weight: 400">Embed apps across Salesforce with Microfrontend</span></h2>
<p><span style="font-weight: 400">Microfrontend is now generally available. You can embed externally hosted apps built with any frameworks across:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Lightning Experience</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Experience Cloud</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Salesforce mobile app</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Mobile Publisher</span></li>
</ul>
<p>You don’t have to rebuild an existing app in Lightning Web Components (LWC) to surface it within a Salesforce experience. The <code>lightning-ui-embedding</code> base component hosts the app in a secure, sandboxed iframe and provides a two-way communication channel between the embedded app and its surrounding Salesforce page.</p>
<p><span style="font-weight: 400">This channel makes the embedded experience feel like part of the host page. An LWC can pass properties to the app, and the app can send events back to the LWC. Teams can combine a rich React or Angular workflow with native Salesforce components while maintaining a clear security boundary between them.</span></p>
<p><span style="font-weight: 400">For example, an LWC can pass a selected industry into an embedded Angular account dashboard. When a user selects an account in the Angular app, the app can send the record ID back to the LWC, which then renders the account with native Salesforce components.</span></p>
<h3><span style="font-weight: 400">Pass data from the host page to the embedded app</span></h3>
<p>Set the <code>props</code> attribute on <code>lightning-ui-embedding</code>. When a host value changes, Salesforce sends the updated properties to the app.</p>
<pre language="text">&lt;template&gt;
    &lt;lightning-radio-group
        label="Filter the embedded app"
        type="button"
        options={industryOptions}
        value={industry}
        onchange={handleIndustryChange}&gt;
    &lt;/lightning-radio-group&gt;

    &lt;lightning-ui-embedding
        lwc:ref="embedding"
        src={appUrl}
        props={embedProps}
        width="100%"
        height="560"&gt;
    &lt;/lightning-ui-embedding&gt;
&lt;/template&gt;
</pre>
<p><span style="font-weight: 400">Inside the React or Angular app, use the View SDK to read the initial state and subscribe to changes.</span></p>
<pre language="text">import { getViewSDK } from '@salesforce/platform-sdk';

const view = await getViewSDK();
const ui = view?.getUiState?.();

if (ui) {
    applyFilters(ui.state.props);
    ui.subscribe((next) =&gt; applyFilters(next.props));
}
</pre>
<h3><span style="font-weight: 400">Send events from the embedded app to the host page</span></h3>
<p>The same channel works in the other direction. Dispatch a <code>CustomEvent</code> through the View SDK, and Salesforce forwards it to the host component.</p>
<pre language="text">view?.dispatchEvent?.(
    new CustomEvent('accountpulse.accountselected', {
        detail: {
            recordId: account.id,
            name: account.name
        }
    })
);
</pre>
<p><span style="font-weight: 400">The host LWC can use the event details to update native components, navigate to a record, or coordinate with other elements on the page.</span></p>
<p><span style="font-weight: 400">With Microfrontend, teams can continue using LWC where deep platform composition is the priority, use React or Angular for self-contained application experiences, and connect them when a page needs both.</span></p>
<h2><span style="font-weight: 400">Distribute apps with managed packages</span></h2>
<p><span style="font-weight: 400">Partners can now package, distribute, install, and upgrade non-site Multi-Framework apps with managed packages.</span></p>
<p>A package can include the Multi-Framework app and the supporting Salesforce metadata that makes up the complete solution, such as its <code>UIBundle</code>, Custom Application metadata, permission sets, Apex, custom objects, and other dependencies.</p>
<p><span style="font-weight: 400">This support is important because a Multi-Framework app is rarely just frontend code. It often depends on a schema, permissions, server-side logic, and configuration. Partners can version and distribute the complete application as one installable, upgradeable unit instead of coordinating separate deployment paths for the UI and its Salesforce dependencies.</span></p>
<p><span style="font-weight: 400">Managed packages provide the namespace and lifecycle controls that ISVs need to distribute protected solutions. Customers can install and upgrade the application through the standard package lifecycle, and partners can make their Multi-Framework solutions available through AgentExchange.</span></p>
<p><span style="font-weight: 400">In Winter ’27, managed packaging support applies to non-site apps. Site-based Multi-Framework apps aren’t supported in managed packages.</span></p>
<h2><span style="font-weight: 400">Localize apps for users across languages and regions</span></h2>
<p><span style="font-weight: 400">Multi-Framework apps can now use Salesforce internationalization context instead of maintaining separate regional preferences in the frontend.</span></p>
<p>The <code>@salesforce/platform-sdk/i18n</code> module retrieves the running user’s context and creates locale-aware formatters for dates, times, numbers, percentages, and currencies. Here’s an example that formats revenue using the user’s Salesforce locale and currency:</p>
<pre language="text">import { createDataSDK } from '@salesforce/platform-sdk';
import {
    createI18nFormatters,
    fetchI18nContext
} from '@salesforce/platform-sdk/i18n';

const sdk = await createDataSDK();
const context = await fetchI18nContext(sdk);
const format = createI18nFormatters(context);

const annualRevenue = format.formatCurrency(5080000, {
    maximumFractionDigits: 0
});
</pre>
<p><span style="font-weight: 400">The Data SDK also includes a labels extension for retrieving custom labels by namespace and locale. This keeps translatable UI copy separate from application code and lets packaged apps resolve the correct namespaced labels at runtime.</span></p>
<p><span style="font-weight: 400">Localization is more than translating strings. A currency value, date, time, or decimal can be interpreted differently across locales even when the underlying data is the same. By using the context already maintained by Salesforce, an app can present that information consistently with the rest of the platform.</span></p>
<h2><span style="font-weight: 400">Support enterprise authentication with MFA</span></h2>
<p><span style="font-weight: 400">Multi-Framework apps now support Salesforce multi-factor authentication, helping organizations apply their enterprise authentication requirements consistently to modern frontend experiences.</span></p>
<p><span style="font-weight: 400">Developers don’t need to build a separate MFA flow into the application. Authentication remains managed by Salesforce, and the Multi-Framework app participates in the organization’s existing identity and access policies.</span></p>
<p>Combined with the <code>salesforce.app</code> security boundary and server-side enforcement of object-level security, field-level security, and sharing rules, MFA support makes Multi-Framework apps ready for a broader range of enterprise use cases.</p>
<h2><span style="font-weight: 400">Understand app performance and usage with Multi-Framework Insights (Beta)</span></h2>
<p><a href="https://help.salesforce.com/s/articleView?id=xcloud.mfw_insights.htm&amp;type=5"><span style="font-weight: 400">Multi-Framework Insights</span></a><span style="font-weight: 400"> gives teams industry-standard performance metrics and key usage insights for Multi-Framework apps.</span></p>
<p><span style="font-weight: 400">From Setup, click </span><b>Multi-Framework Insights (Beta) </b><span style="font-weight: 400">in Scale Center and generate an insights report. The dashboard surfaces performance and usage metrics and trends so teams can identify optimization opportunities for their most-viewed apps.</span></p>
<p><span style="font-weight: 400">Multi-Framework Insights closes an important operational gap: developers can go beyond successfully deploying an app and begin understanding how it performs for real users. Because this feature is Beta, review its current availability and limitations before relying on it for production monitoring workflows.</span></p>
<h2><span style="font-weight: 400">Choose how you want to build Multi-Framework apps</span></h2>
<p><span style="font-weight: 400">Multi-Framework supports developers, admins, and business users with three different ways to create modern Salesforce experiences.</span></p>
<p><span style="font-weight: 400">These options provide different entry points into the same broader platform: code-first development with Agentforce Vibes, prompt-first app creation with Builder Central, and description-based page generation with Quick Pages.</span></p>
<h3><span style="font-weight: 400">Agentforce Vibes: GA, now with Angular support</span></h3>
<p><span style="font-weight: 400">Agentforce Vibes is the Salesforce vibe coding environment for developers, available through a VS Code extension and web IDE. It helps teams plan, write, test, preview, and deploy apps and agents.</span></p>
<p><span style="font-weight: 400">Agentforce Vibes now supports Angular in addition to React. Developers can start an Angular Multi-Framework app directly from the welcome page, describe what they want to build, preview the result, and deploy it to Salesforce.</span></p>
<p><span style="font-weight: 400">Winter ’27 also improves agent-assisted development workflows. Expanded evaluations validate more generated React and Angular code, while new branding and Salesforce CMS skills help coding agents find brand guidance and search, update, and publish managed content.</span></p>
<p><span style="font-weight: 400">Because a Multi-Framework app remains a standard Salesforce DX project with an npm-based build, teams can also use tools such as Claude Code and other AI coding assistants as part of their workflow.</span></p>
<h3><span style="font-weight: 400">Builder Central: Beta</span></h3>
<p><span style="font-weight: 400">Builder Central is the Salesforce natural-language-based app builder. Start with an idea, then create and refine the application through conversation before deploying it in your org.</span></p>
<p><span style="font-weight: 400">Builder Central gives admins a guided way to build Multi-Framework experiences without beginning in a traditional code editor. It generates the application and its Salesforce configuration while letting you iterate with natural-language prompts.</span></p>
<h3><span style="font-weight: 400">Quick Pages: Beta</span></h3>
<p><span style="font-weight: 400">Quick Pages enables business users to describe the page they need in plain language and create an interactive page with multiple tabs, dynamic charts, and data tables, built on live Salesforce data. There is no application project to start and no changes to Salesforce metadata. </span></p>
<p><span style="font-weight: 400">Quick Pages runs inside Salesforce and builds Multi-Framework apps, and every page respects the field- and object-level security of the person viewing it, so people see only the data they already have access to. Admins decide who can create and view pages, and everything stays inside your org&#8217;s security boundary.</span></p>
<h2><span style="font-weight: 400">What’s next</span></h2>
<p><span style="font-weight: 400">We want to keep making the Salesforce Platform easier to build on and more flexible, so teams can choose the frameworks and tools that work best for them.</span></p>
<p><span style="font-weight: 400">Next, we’re working on Vue as the next supported UI framework, new agentic experiences enabled by Multi-Framework, and support for Government Cloud. We’ll share more as these capabilities become available.</span></p>
<p><span style="font-weight: 400">We’re excited to see what you build. Try the new capabilities, explore the recipes, and share your feedback with the </span><a href="https://trailhead.salesforce.com/trailblazer-community/groups/0F93A000000DJbJSAW?tab=discussion"><span style="font-weight: 400">Salesforce Developers community</span></a><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/multiframework/guide/"><span style="font-weight: 400">Salesforce Multi-Framework documentation</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_headless360_mfw.htm&amp;release=264&amp;type=5"><span style="font-weight: 400">Winter ’27 Release Note</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/trailheadapps/multiframework-recipes/tree/main/force-app/main/react-recipes"><span style="font-weight: 400">Multi-Framework React recipes</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/trailheadapps/multiframework-recipes/tree/main/force-app/main/angular-recipes"><span style="font-weight: 400">Multi-Framework Angular recipes</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/trailheadapps/multiframework-recipes#micro-frontend-recipes"><span style="font-weight: 400">Multi-Framework Microfrontend recipes</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/blogs/2026/10/developers-guide-to-the-winter-27-release"><span style="font-weight: 400">The Salesforce Developer’s Guide to the Winter ’27 Release</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the Authors</span></h2>
<p><b>Alice Oh</b><span style="font-weight: 400"> is a Director of Product at Salesforce building foundational products that enable flexible and extensible ways to create, vibe code, and surface apps across the Salesforce ecosystem. In her free time, she bikes around the Bay Area in search of the flakiest croissants. Follow and connect with her on </span><a href="https://www.linkedin.com/in/helloaliceoh/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p><b>Charles Watkins</b><span style="font-weight: 400"> is a self-taught software developer and Lead Developer Advocate at Salesforce. He spends his time blogging, crafting code samples, and finding new places to hike in the Pacific Northwest. You can follow him on </span><a href="https://www.linkedin.com/in/wcharlesw/"><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/10/whats-new-in-salesforce-multi-framework-angular-is-now-ga">What’s New in Salesforce Multi-Framework: Angular 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/10/whats-new-in-salesforce-multi-framework-angular-is-now-ga/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207183</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261014145046/DoubleHeadshot-8-1-e1792014657877.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261014145046/DoubleHeadshot-8-1-e1792014657877.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Stop Credential Stuffing with B2C Commerce eCDN</title>
		<link>https://developer.salesforce.com/blogs/2026/10/stop-credential-stuffing-with-b2c-commerce-ecdn</link>
		<comments>https://developer.salesforce.com/blogs/2026/10/stop-credential-stuffing-with-b2c-commerce-ecdn#respond</comments>
		<pubDate>Wed, 14 Oct 2026 15:00:41 +0000</pubDate>
		<dc:creator><![CDATA[Sarah Welker]]></dc:creator>
				<category><![CDATA[App Development]]></category>
		<category><![CDATA[Salesforce Releases]]></category>
		<category><![CDATA[Trust, Security, and Accessibility]]></category>
		<category><![CDATA[B2C Commerce]]></category>
		<category><![CDATA[Bot Mitigation]]></category>
		<category><![CDATA[Cloudflare]]></category>
		<category><![CDATA[Credential Stuffing]]></category>
		<category><![CDATA[eCDN]]></category>
		<category><![CDATA[SFRA]]></category>
		<category><![CDATA[SLAS]]></category>
		<category><![CDATA[WAF Rules]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207159</guid>
		<description><![CDATA[<p>Protect your B2C Commerce storefront from credential stuffing with new eCDN leaked-credential detection. Learn how to configure firewall and rate-limiting rules, detect suspicious login attempts, and prompt shoppers to reset exposed passwords.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/stop-credential-stuffing-with-b2c-commerce-ecdn">Stop Credential Stuffing with B2C Commerce eCDN</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">Credential stuffing is one of the most common attacks against a storefront login: attackers replay thousands of username/password pairs leaked from other breaches, betting that a shopper reused one.</span></p>
<p><span style="font-weight: 400">As of the September 2026 release of B2C Commerce (release 26.9), you don&#8217;t need a third-party bot-management entitlement to fight back. eCDN, the Cloudflare-based CDN built into B2C Commerce, sits in front of every storefront request. This release adds four new detection fields you can write firewall and rate-limiting rules against, plus a managed-transform header you can read at your origin to warn the shopper directly.</span></p>
<p><span style="font-weight: 400">This post covers: </span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The detection fields eCDN now exposes </span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The firewall-rule and rate-limiting-rule syntax to act on them</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">A working example in </span><a href="https://developer.salesforce.com/docs/commerce/sfra/guide/b2c-sfra-features-and-comps.html"><span style="font-weight: 400">SFRA</span></a><span style="font-weight: 400"> (B2C Commerce&#8217;s Storefront Reference Architecture) that turns the edge signal into a shopper-facing &#8220;reset your password&#8221; prompt </span></li>
<li style="font-weight: 400"><span style="font-weight: 400">How to monitor what the detection is catching</span></li>
</ul>
<h2><b>Prerequisites</b></h2>
<p><span style="font-weight: 400">Salesforce must enable the Leaked-Credential-Check managed transform on your zone or zones. This is a Cloudflare-side toggle, so no redeploy is required on your part. Custom rules and rate limit rules can be created at any time with the detection fields below. Contact Salesforce (through a support case) to enable the managed transform for your zone.</span></p>
<h2><b>The detection fields</b></h2>
<p><span style="font-weight: 400">Four fields are available in your eCDN firewall and rate-limiting rule expressions as of release 26.9:</span></p>
<ol>
<li><code>cf.waf.credential_check.password_leaked</code></li>
<li><code>cf.waf.credential_check.username_leaked</code></li>
<li><code>cf.waf.credential_check.username_and_password_leaked</code></li>
<li><code>cf.waf.credential_check.username_password_similar</code></li>
</ol>
<p><span style="font-weight: 400">They evaluate on the SFRA storefront login endpoints (</span><span style="font-weight: 400">Account-Login</span><span style="font-weight: 400">) and the SLAS login endpoint. SLAS submits credentials via HTTP Basic auth, which the detection engine decodes, so headless and PWA Kit implementations are covered too. If you run a custom login controller, contact Salesforce; we&#8217;ll work with you to make sure that endpoint is protected too.</span></p>
<h2><b>Challenge leaked credentials with a security rule</b></h2>
<p><span style="font-weight: 400">Managed Challenge is an action you can select when creating a Custom rule. It is an adaptive security check that Cloudflare presents when a request matches a rule. It tries to determine whether the requester is a legitimate human/browser or an automated/malicious client without always showing a traditional CAPTCHA. You can challenge login attempts that use a leaked password. From the </span><a href="https://developer.salesforce.com/docs/commerce/commerce-api/guide/cdn-zones-custom-rules.html#examples"><span style="font-weight: 400">custom-rules examples</span></a><span style="font-weight: 400">:</span></p>
<pre language="text"># Expression
(http.request.uri.path contains "/Account-Login" and cf.waf.credential_check.password_leaked)
# Action
managed_challenge
# Note: SLAS cannot solve a challenge, consider block or rate limiting.
</pre>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207162" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261013131057/eCDN-firewall-rule-editor-showing-a-Leaked-Credentials-custom-rule-expression-e1791922277355.png?w=1000" class="postimages" width="1000" height="501" alt="eCDN firewall rule editor showing a Leaked Credentials custom rule expression" />
			  </span>
			</p>
<h2><b>Throttle credential stuffing with a rate-limiting rule</b></h2>
<p><span style="font-weight: 400">To throttle credential stuffing without penalizing legitimate search crawlers, split the rule expression (what to count) from the counting expression (what increments the counter). From </span><a href="https://developer.salesforce.com/docs/commerce/commerce-api/guide/cdn-rate-limiting-rules.html#allow-verified-search-crawlers-and-detect-leaked-credentials"><span style="font-weight: 400">Allow verified search crawlers and detect leaked credentials</span></a><span style="font-weight: 400">:</span></p>
<pre language="text"># Rule expression — count login POSTs, excluding verified bots
(http.request.uri.path contains "/Account-Login" and http.request.method eq "POST" and not cf.client.bot)
# Counting expression — only increment when credentials are leaked
(cf.waf.credential_check.username_and_password_leaked)
</pre>
<p>The <code>not cf.client.bot</code> clause in the rule expression excludes requests identified as verified search crawlers, so legitimate bots aren&#8217;t throttled alongside credential-stuffing attackers.</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207163" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261013131401/eCDN-firewall-rule-editor-showing-a-leaked-credential-rate-limit-rule-expression-e1791922695864.png?w=1000" class="postimages" width="1000" height="283" alt="eCDN firewall rule editor showing a leaked-credential rate limit rule expression" />
			  </span>
			</p>
<h2><b>Warn the shopper in SFRA</b></h2>
<p>A plain block is the wrong tool here: a reused breached password isn&#8217;t proof of an attacker; it&#8217;s often the legitimate shopper who needs a nudge to rotate a password. When the Exposed-Credential-Check managed transform is on, eCDN adds a header to the login request before it reaches your storefront server. The shopper&#8217;s browser never sees it. It&#8217;s a request-side header, readable only server-side, on the same request that carries the login submission.</p>
<p><b>Header value → Meaning:</b></p>
<ul>
<li style="font-weight: 400"><b>1</b><span style="font-weight: 400"> → Previously leaked username and password</span></li>
<li style="font-weight: 400"><b>2</b><span style="font-weight: 400"> → Previously leaked username</span></li>
<li style="font-weight: 400"><b>3</b><span style="font-weight: 400"> → Similar combination of previously leaked username and password</span></li>
<li style="font-weight: 400"><b>4</b><span style="font-weight: 400"> → Previously leaked password</span></li>
</ul>
<p>In SFRA, <code>req.httpHeaders</code> is a case-insensitive map of the incoming request headers, available in every controller; no new middleware is needed. Here&#8217;s what&#8217;s new in Account.js&#8217;s <code>server.post('Login', ...)</code>, added so the warning is attached only when authentication succeeds:</p>
<pre language="javascript">var URLUtils = require('dw/web/URLUtils'); // add to the existing require block

   // Exposed-Credential-Check is added by Cloudflare to the request it forwards to
   // the origin; it is never sent by the shopper's browser.
   var exposedCredentialCheckMessages = {
       1: Resource.msg('warning.message.exposedcredentialcheck.usernamepassword', 'login', null),
       2: Resource.msg('warning.message.exposedcredentialcheck.username', 'login', null),
       3: Resource.msg('warning.message.exposedcredentialcheck.similar', 'login', null),
       4: Resource.msg('warning.message.exposedcredentialcheck.password', 'login', null)
   };
   var exposedCredentialCheckHeader = req.httpHeaders.get('exposed-credential-check');

   // ...inside the existing successful-login branch, before res.json(loginResponse):
   if (exposedCredentialCheckMessages[exposedCredentialCheckHeader]) {
       loginResponse.exposedCredentialWarning = {
           message: exposedCredentialCheckMessages[exposedCredentialCheckHeader],
           resetPasswordUrl: URLUtils.url('Account-EditPassword').toString()
       };
   }
</pre>
<p><b>Why only on a successful login?</b><span style="font-weight: 400"> Two reasons:</span></p>
<ol>
<li style="font-weight: 400"><b>You act only once you know it&#8217;s the real shopper.</b><span style="font-weight: 400"> A correct password is the proof point that it&#8217;s safe to say &#8220;these credentials are known-breached elsewhere — please rotate them.&#8221;</span></li>
<li style="font-weight: 400"><b>No oracle for attackers.</b><span style="font-weight: 400"> If the same flag were echoed on a failed login, a credential-stuffing attacker could learn which guessed username/password combinations are breach-flagged, independent of whether the guess was correct. Restricting to successful logins closes that side channel.</span></li>
</ol>
<p>If the header is absent or not 1–4, <code>exposedCredentialWarning</code> is simply omitted and the response is identical to today&#8217;s behavior.</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207169" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261013143709/Modal-shopper-notification-to-reset-password-e1791927441513.png?w=1000" class="postimages" width="1000" height="423" alt="Modal shopper notification to reset password." />
			  </span>
			</p>
<p><span style="font-weight: 400">For the resource-bundle messages, modal template, client-side handler, and build step, see the </span><a href="https://gist.github.com/rvenkatraman101/e322f71d91c63533e5b16c83da999ff4"><span style="font-weight: 400">full SFRA walkthrough gist</span></a><span style="font-weight: 400">.</span></p>
<h2><b>Monitor it</b></h2>
<p><span style="font-weight: 400">Before you tighten actions, check how much of your login traffic the detection flags. Use Security Analytics in Business Manager to filter on the credential-check detection and confirm your rule fires as expected.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207170" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261013144043/Security-Analytics-filtered-on-a-specific-rule-1-e1791927655235.png?w=1000" class="postimages" width="1000" height="516" alt="Security Analytics filtered on a specific rule." />
			  </span>
			</p>
<p><span style="font-weight: 400">Need to export the raw logs instead? </span><a href="https://developer.salesforce.com/docs/commerce/commerce-api/guide/cdn-zones-logpush.html"><span style="font-weight: 400">eCDN Logpush</span></a><span style="font-weight: 400"> can batch and ship your firewall logs to S3, Splunk, Datadog, and more.</span></p>
<h2><b>Demo video</b></h2>
<p><span style="font-weight: 400">Watch leaked-credential detection in action: </span><a href="https://youtu.be/vr3n6XLgEtU"><span style="font-weight: 400">https://youtu.be/vr3n6XLgEtU</span></a></p>
<h2><b>Extend the learning: full SFRA walkthrough</b></h2>
<p><span style="font-weight: 400">The controller change above is the core of the integration. For the rest of the origin-side code, including the resource-bundle messages, modal template, client-side handler, and build step, see the </span><a href="https://gist.github.com/rvenkatraman101/e322f71d91c63533e5b16c83da999ff4"><span style="font-weight: 400">full SFRA walkthrough gist</span></a><span style="font-weight: 400">.</span></p>
<h2><b>Conclusion</b></h2>
<p><span style="font-weight: 400">Release 26.9 raises the floor on credential-stuffing defense for every B2C Commerce customer, with no new entitlement required: detection fields you can act on at the edge, and a managed-transform header you can turn into a shopper-facing safeguard in a few lines of SFRA. Use this as your baseline, then see the </span><a href="https://developer.salesforce.com/docs/commerce/commerce-solutions/guide/bot-management.html"><span style="font-weight: 400">Bot Management Developer Guide</span></a><span style="font-weight: 400"> for how to layer in the rest of a defense-in-depth bot-mitigation program.</span></p>
<h2><b>Resources</b></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/commerce/commerce-solutions/guide/bot-management.html"><span style="font-weight: 400">Bot Management Developer Guide</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/commerce/commerce-solutions/guide/bot-mitigation-sk.html"><span style="font-weight: 400">Bot Mitigation Best Practices for Flash Sales</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/commerce/commerce-api/guide/slas-best-practices.html"><span style="font-weight: 400">SLAS Best Practices</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/commerce/commerce-api/guide/cdn-zones-custom-rules.html#examples"><span style="font-weight: 400">Create custom rules (with examples)</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/commerce/commerce-api/guide/cdn-rate-limiting-rules.html#allow-verified-search-crawlers-and-detect-leaked-credentials"><span style="font-weight: 400">Rate-limiting rules — allow verified crawlers &amp; detect leaked credentials</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/commerce/commerce-api/guide/cdn-zones-logpush.html"><span style="font-weight: 400">eCDN Logpush</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_b2c_ecdn_leaked_credentials.htm&amp;release=262&amp;type=5"><span style="font-weight: 400">Leaked Credentials release notes</span></a></li>
<li style="font-weight: 400"><a href="https://gist.github.com/rvenkatraman101/e322f71d91c63533e5b16c83da999ff4"><span style="font-weight: 400">Full SFRA walkthrough (sample code gist)</span></a></li>
</ul>
<h2><b>About the Author</b></h2>
<p><b>Raghu Venkatraman</b><span style="font-weight: 400">, Director, Product Management — Raghu Venkatraman is a Product Management Director at Salesforce, where he drives product and bot mitigation strategy and innovation for B2C Commerce. With extensive experience in enterprise software and a passion for building user-centric solutions, Raghu focuses on delivering products that empower organizations to work more efficiently and effectively. He brings deep expertise in product leadership and cross-functional collaboration to help customers maximize their Salesforce investments. Follow him on </span><a href="https://www.linkedin.com/in/raghu-venkatraman-b6bb02/"><span style="font-weight: 400">LinkedIn</span></a></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/stop-credential-stuffing-with-b2c-commerce-ecdn">Stop Credential Stuffing with B2C Commerce eCDN</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/10/stop-credential-stuffing-with-b2c-commerce-ecdn/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207159</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261013145359/Generic-C-7-e1791928453298.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261013145359/Generic-C-7-e1791928453298.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Salesforce Microfrontends: Embed Web Apps in Lightning</title>
		<link>https://developer.salesforce.com/blogs/2026/10/salesforce-microfrontends-embed-web-apps-in-lightning</link>
		<comments>https://developer.salesforce.com/blogs/2026/10/salesforce-microfrontends-embed-web-apps-in-lightning#respond</comments>
		<pubDate>Tue, 13 Oct 2026 15:00:48 +0000</pubDate>
		<dc:creator><![CDATA[SANGEETA PATNAIK]]></dc:creator>
				<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Lightning Web Components]]></category>
		<category><![CDATA[Front-End Architecture]]></category>
		<category><![CDATA[Multi-Framework]]></category>
		<category><![CDATA[react]]></category>
		<category><![CDATA[React in Salesforce]]></category>
		<category><![CDATA[Salesforce Microfrontends]]></category>
		<category><![CDATA[UI Embedding]]></category>
		<category><![CDATA[web development]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207149</guid>
		<description><![CDATA[<p>Learn how Salesforce Microfrontends let you securely embed modern web applications in Lightning while keeping their existing frameworks, tools, and development models.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/salesforce-microfrontends-embed-web-apps-in-lightning">Salesforce Microfrontends: Embed Web Apps in Lightning</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">For years,</span> <a href="https://developer.salesforce.com/docs/platform/lwc/guide"><span style="font-weight: 400">Lightning Web Components (LWC)</span></a><span style="font-weight: 400"> have been at the heart of building modern user experiences on Salesforce. Now, Salesforce developers and web development teams can expand that model with </span><b>Salesforce Microfrontends</b><span style="font-weight: 400">, bringing applications built with React, Angular, Vue, and other web technologies directly into </span><b>Salesforce Lightning experiences</b><span style="font-weight: 400">. With Salesforce Microfrontends, implemented through</span> <b>UI Embedding</b><span style="font-weight: 400">, you can securely embed a web application alongside LWC while exchanging context, data, and events between the two experiences. This gives teams a way to bring existing web investments into Salesforce without rebuilding every experience from scratch. In this post, we&#8217;ll explore how Salesforce Microfrontends work, build a simple host and guest example, and look at how Microfrontends fit into the broader Salesforce Multi-Framework story.</span></p>
<h2><b>What are Salesforce Microfrontends?</b></h2>
<p><span style="font-weight: 400">Salesforce Microfrontends let you bring independently developed web applications and widgets into Lightning while keeping a clear boundary between Salesforce and the embedded application.</span></p>
<p><span style="font-weight: 400">At a high level, the architecture has three parts:</span></p>
<p><b>Lightning host → UI Embedding → embedded web application</b></p>
<p>The <b>host</b> is a custom Lightning Web Component that renders the <code>lightning-ui-embedding</code> base component. It owns the Salesforce context, retrieves the platform data the embedded application needs, and determines what information to share with it.</p>
<p><span style="font-weight: 400">The </span><b>guest</b><span style="font-weight: 400"> is the web application or widget being embedded. It can receive context and UI state from its Lightning host and send custom events back through the UI Embedding channel.</span></p>
<p><span style="font-weight: 400">The guest runs inside a </span><b>sandboxed iframe</b><span style="font-weight: 400">, providing an isolation boundary between the two applications. UI Embedding provides the managed communication layer that connects them.</span></p>
<p><span style="font-weight: 400">This means, for example, that an Account page can use native LWC for record details, actions, and navigation while displaying an embedded React application for a specialized experience.</span></p>
<p><span style="font-weight: 400">To the user, these experiences come together within Lightning. To developers, they remain independently developed applications connected through a Salesforce-supported integration layer.</span></p>
<h3><b>More than embedding a web page</b></h3>
<p><span style="font-weight: 400">Unlike a plain iframe, a Microfrontend can exchange context, data, UI state, and events with its Lightning host through UI Embedding.</span></p>
<p><span style="font-weight: 400">Consider a simple </span><b>ScoreAccount</b><span style="font-weight: 400"> web application. The application displays an Account&#8217;s current rating and lets the user select </span><b>Hot</b><span style="font-weight: 400">, </span><b>Warm</b><span style="font-weight: 400">, or </span><b>Cold</b><span style="font-weight: 400">. When the Account page loads, the Lightning host retrieves the Salesforce record information and provides the relevant state to ScoreAccount. The embedded application renders the current rating.</span></p>
<p><span style="font-weight: 400">If the user selects a different rating, ScoreAccount sends an event back to the host. The Lightning Web Component handles that event and updates the Salesforce record.</span></p>
<p><span style="font-weight: 400">Conceptually, the interaction looks like this:</span></p>
<p><b>Salesforce → record context, data, and UI state → Microfrontend</b></p>
<p><b>Microfrontend → events and user interactions → Salesforce</b></p>
<p><span style="font-weight: 400">Neither application needs to know how the other is implemented. UI Embedding provides the contract between them.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207151" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261008135926/image1_4c899a-e1791493177467.png?w=1000" class="postimages" width="1000" height="667" alt="ScoreAccount Microfrontend embedded within a Salesforce Lightning Account page, with UI Embedding passing record context and UI state to the application and receiving rating events back." />
			  </span>
			</p>
<h2><b>Understand the security boundary</b></h2>
<p><span style="font-weight: 400">The isolation between Salesforce and the embedded application is an important part of the Microfrontends architecture.</span></p>
<p><span style="font-weight: 400">The embedded application runs inside a sandboxed iframe rather than directly inside the Lightning page. This creates a browser-enforced boundary between the two application environments.</span></p>
<p><span style="font-weight: 400">For an externally hosted application, its origin must also be configured appropriately in Salesforce so the browser is permitted to load it. The host then controls the information shared with the guest through the UI Embedding channel.</span></p>
<p><span style="font-weight: 400">The embedded application remains responsible for its own application architecture and any authentication or data access required outside the information explicitly provided by its Lightning host.</span></p>
<p><span style="font-weight: 400">This model allows the applications to communicate without collapsing them into the same runtime or tightly coupling their implementations.</span></p>
<p><span style="font-weight: 400">For the latest security configuration and supported options, refer to the</span> <a href="https://developer.salesforce.com/docs/platform/microfrontend/guide/get-started.html"><span style="font-weight: 400">UI Embedding setup documentation</span></a><span style="font-weight: 400">.</span></p>
<h2><b>Build the ScoreAccount Microfrontend</b></h2>
<p><span style="font-weight: 400">Let&#8217;s make the architecture concrete.</span></p>
<p><span style="font-weight: 400">The </span><b>ScoreAccount</b><span style="font-weight: 400"> application displays an Account&#8217;s current rating and lets the user change it to Hot, Warm, or Cold.</span></p>
<p>Bringing the application into Lightning involves four main pieces:</p>
<ol>
<li>Allow the application&#8217;s origin in your Salesforce security configuration.</li>
<li>Connect the guest application to the Salesforce UI Embedding channel.</li>
<li>Create a host LWC using <code>lightning-ui-embedding</code>.</li>
<li>Deploy the web application and Salesforce host through their respective deployment workflows.</li>
</ol>
<p><span style="font-weight: 400">The interesting part is how the guest and host communicate.</span></p>
<h3><b>Connect the guest application</b></h3>
<p><span style="font-weight: 400">The guest application connects to its Lightning host through the Salesforce Platform SDK.</span></p>
<p><span style="font-weight: 400">It can retrieve UI state supplied by the host, subscribe to future changes, and dispatch events when users interact with the application.</span></p>
<p><span style="font-weight: 400">Here&#8217;s the ScoreAccount example:</span></p>
<pre language="text">// Get SDK instance
import { getViewSDK } from "@salesforce/platform-sdk";

const RATINGS = ["Hot", "Warm", "Cold"];

export default function ScoreAccount() {
  const [connected, setConnected] = useState(false);
  const [props, setProps] = useState({});

  // Data in: read the props the host pushed, and stay subscribed.
  useEffect(() =&gt; {
    let unsubscribe;
    let cancelled = false;

    getViewSDK().then(
      (sdk) =&gt; {
        if (cancelled) return;
        const ui = sdk.getUiState();
        if (!ui) return; // no host state → stay disconnected
        setConnected(true);
        setProps(ui.state.props ?? {});
        unsubscribe = ui.subscribe((next) =&gt; setProps(next.props ?? {}));
      },
      () =&gt; {}, // SDK never resolved → stay disconnected
    );

    return () =&gt; {
      cancelled = true;
      unsubscribe?.();
    };
  }, []);

  // Data out: send the chosen rating back to the host.
  async function score(rating) {
    const sdk = await getViewSDK();
    sdk.dispatchEvent(
      new CustomEvent("score", { detail: { rating }, bubbles: true }),
    );
  }

  return (
    &lt;Card connected={connected} name={props.name} rating={props.rating}&gt;
      &lt;RatingPicker
        ratings={RATINGS}
        active={connected ? props.rating : null}
        disabled={!connected}
        onScore={score}
      /&gt;
    &lt;/Card&gt;
  );
}
</pre>
<p><span style="font-weight: 400">There are two important flows here.</span></p>
<p><b>Data comes in.</b><span style="font-weight: 400"> ScoreAccount reads the UI state provided by its Lightning host and subscribes to changes. If Salesforce provides an updated state, the embedded experience can react to it.</span></p>
<p><b>Events go out.</b><span style="font-weight: 400"> When the user selects a rating, ScoreAccount dispatches a </span><span style="font-weight: 400">score</span><span style="font-weight: 400"> event containing the selected value.</span></p>
<p><span style="font-weight: 400">The example uses React, but externally hosted Microfrontends aren&#8217;t tied to a specific frontend framework. You can use React, Angular, Vue, or other standards-based web technologies supported by the embedding model.</span></p>
<h2><b>Create the Lightning host</b></h2>
<p><span style="font-weight: 400">Now let&#8217;s look at the Salesforce side.</span></p>
<p>A host Lightning Web Component renders ScoreAccount using the <code>lightning-ui-embedding</code> base component.</p>
<p><span style="font-weight: 400">The markup is small:</span></p>
<pre language="text">&lt;!-- scoreAccountEmbedding.html --&gt;
&lt;template&gt;
  &lt;lightning-ui-embedding
    sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox"
    shell-title="Account Score Widget"
    src={computedSrc}
    props={account}
    onscore={handleScore}
  &gt;&lt;/lightning-ui-embedding&gt;
&lt;/template&gt;
</pre>
<p><span style="font-weight: 400">Each part has a clear responsibility.</span></p>
<p><code>src</code> identifies the guest application. <code>props</code> contains the information Salesforce sends to it. <code>sandbox</code> controls the standard browser iframe capabilities granted to the guest. And <code>onscore</code> lets the host respond to the custom event sent by ScoreAccount.</p>
<p><span style="font-weight: 400">The JavaScript connects this experience to Salesforce data:</span></p>
<pre language="text">// scoreAccountEmbedding.js
import { LightningElement, api, wire } from "lwc";
import { getRecord, getFieldValue, updateRecord } from "lightning/uiRecordApi";
import RATING from "@salesforce/schema/Account.Rating";

const FIELDS = [RATING];

export default class ScoreAccountEmbedding extends LightningElement {
  @api baseUrl = "http://localhost:5173"; // App origin
  @api recordId;

  @wire(getRecord, { recordId: "$recordId", fields: FIELDS })
  record;

  get computedSrc() {
    const url = new URL(this.baseUrl);
    url.pathname = "/embedding/score-account";
    return url.toString();
  }

  // Send updated Salesforce state to the guest.
  get account() {
    return {
      recordId: this.recordId,
      rating: getFieldValue(this.record?.data, RATING)
    };
  }

  // Handle the rating event from the guest.
  async handleScore(evt) {
    const { rating } = evt.detail ?? {};
    if (!this.recordId) return;

    await updateRecord({
      fields: {
        Id: this.recordId,
        Rating: rating
      }
    });
  }
}
</pre>
<p>Now the complete interaction becomes clear:</p>
<ol>
<li>The Account page loads and the host retrieves the current rating from Salesforce.</li>
<li>The host passes the Account context and rating into ScoreAccount.</li>
<li>ScoreAccount renders that state for the user.</li>
<li>The user selects <b>Hot</b>.</li>
<li>ScoreAccount dispatches the <code>score</code> event back to its Lightning host.</li>
<li>The host receives the event and updates the Salesforce record.</li>
<li>When the Salesforce data changes, the host provides the updated state back to the embedded application.</li>
</ol>
<p><b>The Lightning host understands Salesforce. The guest understands its application. UI Embedding connects them.</b></p>
<p><span style="font-weight: 400">For detailed state and event patterns, see</span> <a href="https://developer.salesforce.com/docs/platform/microfrontend/guide/exchange-data.html"><span style="font-weight: 400">Exchange Data Between the Page and an Embedded App</span></a><span style="font-weight: 400">.</span></p>
<h2><b>Bring existing web applications into Lightning</b></h2>
<p><span style="font-weight: 400">The ScoreAccount example is intentionally small, but the architecture applies to much larger applications.</span></p>
<p><span style="font-weight: 400">Many organizations already have web applications with their own frameworks, component libraries, engineering teams, deployment pipelines, and release schedules. Rebuilding those experiences simply to make them available in Salesforce may not always make sense.</span></p>
<p><span style="font-weight: 400">With Microfrontends, the application can remain externally hosted while participating in a Lightning experience. The application continues to own its frontend framework, application logic, hosting, and release lifecycle. Salesforce provides the embedding boundary and the communication model needed to connect it with Lightning.</span></p>
<p><span style="font-weight: 400">That could mean bringing an existing analytics experience into an Account workspace, embedding an internal search application, or surfacing a specialized workflow maintained by another engineering team.</span></p>
<p><b>Microfrontends allow existing web investments to participate in Salesforce without requiring the application and Lightning host to become the same application.</b></p>
<h2><b>Microfrontends and Multi-Framework</b></h2>
<p><span style="font-weight: 400">Microfrontends are also part of a broader evolution in how modern frontend applications can work with Salesforce.</span></p>
<p><b>Microfrontends with UI Embedding</b><span style="font-weight: 400"> focus on bringing web applications into Salesforce experiences.</span></p>
<p><span style="font-weight: 400">For externally hosted applications, your application can remain on its existing infrastructure and retain its development and deployment model.</span></p>
<p><b>Salesforce Multi-Framework</b><span style="font-weight: 400"> expands the development model further by enabling applications built with supported open-source frontend frameworks to be developed and deployed on Salesforce.</span></p>
<p><span style="font-weight: 400">These applications can be packaged as </span><b>UI bundles</b><span style="font-weight: 400">, a Salesforce metadata type that contains the application and the information Salesforce needs to build and run it.</span></p>
<p><span style="font-weight: 400">The two models are complementary:</span></p>
<table>
<tbody>
<tr>
<td></td>
<td><b>Externally hosted Microfrontend</b></td>
<td><b>Salesforce-hosted UI bundle</b></td>
</tr>
<tr>
<td><b>Application hosting</b></td>
<td><span style="font-weight: 400">Your infrastructure</span></td>
<td><span style="font-weight: 400">Salesforce</span></td>
</tr>
<tr>
<td><b>Development model</b></td>
<td><span style="font-weight: 400">Existing web development workflow</span></td>
<td><span style="font-weight: 400">Salesforce Multi-Framework workflow</span></td>
</tr>
<tr>
<td><b>Framework model</b></td>
<td><span style="font-weight: 400">Framework-agnostic web application</span></td>
<td><span style="font-weight: 400">Frameworks currently supported by UI bundles (React and Angular as of this writing)</span></td>
</tr>
<tr>
<td><b>Application lifecycle</b></td>
<td><span style="font-weight: 400">Managed independently</span></td>
<td><span style="font-weight: 400">Built and deployed through Salesforce development workflows</span></td>
</tr>
<tr>
<td><b>Lightning integration</b></td>
<td><span style="font-weight: 400">UI Embedding (GA since September 2026)</span></td>
<td><span style="font-weight: 400">UI Embedding (currently in Beta; GA targeted for mid-November 2026)</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">Framework support for Salesforce-hosted UI bundles may evolve across releases. Refer to the</span> <a href="https://developer.salesforce.com/docs/platform/microfrontend/guide/internal-ui-bundles.html?utm_source=chatgpt.com"><span style="font-weight: 400">current framework support documentation</span></a><span style="font-weight: 400"> for the latest supported frameworks.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207152" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261008140010/image2_cfd94e-e1791493225691.png?w=1000" class="postimages" width="1000" height="500" alt="Comparison of externally hosted Salesforce Microfrontends and Salesforce-hosted UI bundles, showing how both models can bring modern web applications into Salesforce experiences." />
			  </span>
			</p>
<h2><b>LWC and Microfrontends work together</b></h2>
<p><span style="font-weight: 400">Microfrontends don&#8217;t replace Lightning Web Components. </span><b>They expand the Salesforce frontend ecosystem around them.</b></p>
<p><span style="font-weight: 400">LWC remains the native component model for building Salesforce experiences and working deeply with Salesforce platform capabilities.</span></p>
<p><span style="font-weight: 400">Microfrontends add another architectural option when an experience already exists outside Salesforce or needs to retain its own frontend technology, application boundary, and development lifecycle.</span></p>
<p><span style="font-weight: 400">That means a single Lightning experience can combine native LWC for record information, Salesforce actions, and platform workflows with an embedded web application for a specialized experience.</span></p>
<p><span style="font-weight: 400">Different engineering teams can also maintain those experiences independently.</span></p>
<p><span style="font-weight: 400">The team responsible for the Microfrontend can own its framework, components, and release process. The Salesforce team can own the Lightning experience and control the context shared across the application boundary.</span></p>
<p><span style="font-weight: 400">The result isn&#8217;t </span><b>LWC versus Microfrontends</b><span style="font-weight: 400">. It&#8217;s an architecture where each can do what it does best.</span></p>
<h2><b>Where Microfrontends fit</b></h2>
<p><span style="font-weight: 400">Microfrontends are particularly useful when you&#8217;re bringing an established web experience into Lightning and want to preserve its existing technology and development lifecycle.</span></p>
<p><span style="font-weight: 400">For example, an organization may already have a specialized React analytics application maintained by a dedicated engineering team. Embedding that experience can preserve the team&#8217;s existing framework, component library, and release process while making the application available to Salesforce users.</span></p>
<p><span style="font-weight: 400">Native LWC remains a natural fit for experiences designed specifically around Salesforce platform capabilities and native Lightning development.</span></p>
<p><span style="font-weight: 400">The key architectural question isn&#8217;t simply which frontend framework you prefer. It&#8217;s </span><b>where the application boundary belongs</b><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">If an experience should remain an independently developed web application, Microfrontends provide a supported way to connect that boundary to Lightning.</span></p>
<p><span style="font-weight: 400">If you&#8217;re building a native Salesforce component that depends deeply on Salesforce platform behavior, LWC continues to provide the native development model. And if you&#8217;re building a supported modern framework application directly on Salesforce, Multi-Framework and UI bundles extend the set of development options further.</span></p>
<p><span style="font-weight: 400">Together, these models give teams more flexibility in how they design modern Salesforce applications.</span></p>
<h2><b>Try Salesforce Microfrontends</b></h2>
<p><span style="font-weight: 400">The easiest way to understand the model is to see the host and guest working together.</span></p>
<p><span style="font-weight: 400">The</span> <a href="https://developer.salesforce.com/docs/platform/microfrontend/guide/recipes.html"><span style="font-weight: 400">UI Embedding recipes</span></a><span style="font-weight: 400"> provide working patterns for embedding applications, exchanging state, sending events, managing sizing, and integrating an embedded application with Lightning.</span></p>
<p><span style="font-weight: 400">For implementation details, configuration, security requirements, and the latest supported capabilities, use the </span><b>Salesforce Developer documentation</b><span style="font-weight: 400"> as the source of truth.</span></p>
<p><span style="font-weight: 400">The core mental model is simple:</span></p>
<ul>
<li><span style="font-weight: 400">Embed your web application in Lightning using </span><b>lightning-ui-embedding</b><span style="font-weight: 400">.</span></li>
</ul>
<ul>
<li><span style="font-weight: 400">Pass the context and data it needs through the managed embedding channel.</span></li>
</ul>
<ul>
<li><span style="font-weight: 400">Handle events sent back by the embedded application.</span></li>
</ul>
<p><span style="font-weight: 400">Everything else builds on that foundation.</span></p>
<h2><b>Conclusion</b></h2>
<p><span style="font-weight: 400">Lightning Web Components have long provided Salesforce developers with a powerful native way to build user experiences.</span></p>
<p><b>Salesforce Microfrontends expand that story.</b></p>
<p><span style="font-weight: 400">With UI Embedding, teams can bring modern web applications into Lightning while allowing those applications to retain their frameworks, development models, and application boundaries.</span></p>
<p><span style="font-weight: 400">The Lightning host provides the Salesforce context. The external application provides its specialized application experience. Microfrontends securely connect the two.</span></p>
<p><span style="font-weight: 400">And with </span><a href="https://developer.salesforce.com/blogs/2026/07/build-with-react-on-salesforce-multi-framework-is-now-ga"><span style="font-weight: 400">Salesforce Multi-Framework</span></a><span style="font-weight: 400">, that story expands further by enabling supported modern framework applications to be built and hosted on Salesforce.</span></p>
<p><span style="font-weight: 400">For developers, this creates a broader frontend development model:</span></p>
<p><b>Build natively with LWC. Bring existing web applications into Lightning with Microfrontends. Build supported modern framework applications on Salesforce with Multi-Framework.</b></p>
<h2><b>Resources</b></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/microfrontend/guide/get-started.html"><span style="font-weight: 400">Get started with Salesforce Microfrontends and UI Embedding</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/microfrontend/guide/framework-support.html"><span style="font-weight: 400">Explore framework support</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/microfrontend/guide/exchange-data.html"><span style="font-weight: 400">Exchange data between Salesforce and an embedded application</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/microfrontend/guide/style-embedded-app.html"><span style="font-weight: 400">Style an embedded application</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/microfrontend/guide/recipes.html"><span style="font-weight: 400">Explore UI Embedding Recipes</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/microfrontend/guide/internal-ui-bundles.html"><span style="font-weight: 400">Embed an internally hosted UI Bundle</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/multiframework/overview"><span style="font-weight: 400">Learn more about Salesforce Multi-Framework</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/trailheadapps/multiframework-recipes"><span style="font-weight: 400">Explore Multi-Framework Recipes on GitHub</span></a></li>
</ul>
<h2><b>About the author</b></h2>
<p><b>Sangeeta Patnaik</b><span style="font-weight: 400"> is a Senior Product Manager at Salesforce, building products across platform security, developer productivity, and modern application experiences. She is passionate about AI, security, and making complex technology simpler to build, safer to use, and easier to connect.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/salesforce-microfrontends-embed-web-apps-in-lightning">Salesforce Microfrontends: Embed Web Apps in Lightning</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/10/salesforce-microfrontends-embed-web-apps-in-lightning/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207149</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261008132351/Generic-D-10-e1791491042101.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261008132351/Generic-D-10-e1791491042101.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Building Interactive UI with Headless Experience Layer</title>
		<link>https://developer.salesforce.com/blogs/2026/10/building-interactive-ui-with-hxl</link>
		<comments>https://developer.salesforce.com/blogs/2026/10/building-interactive-ui-with-hxl#respond</comments>
		<pubDate>Wed, 07 Oct 2026 17:55:57 +0000</pubDate>
		<dc:creator><![CDATA[Akshata Sawant]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Apex]]></category>
		<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Headless 360]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[developer]]></category>
		<category><![CDATA[Headless Experience Layer]]></category>
		<category><![CDATA[hxl]]></category>
		<category><![CDATA[Model Context Protocol (MCP)]]></category>
		<category><![CDATA[Sample Apps]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207132</guid>
		<description><![CDATA[<p>Learn how the Headless Experience Layer (HXL) lets you build one interactive widget that renders in Agentforce and MCP clients like Claude and ChatGPT.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/building-interactive-ui-with-hxl">Building Interactive UI with Headless Experience Layer</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 </span><a href="https://www.salesforce.com/artificial-intelligence/"><span style="font-weight: 400">AIforce</span></a><span style="font-weight: 400">, Salesforce comes to wherever your users already work: </span><a href="https://developer.salesforce.com/developer-centers/agentforce"><span style="font-weight: 400">Agentforce</span></a><span style="font-weight: 400">, Slackbot, Claude, ChatGPT, and whatever AI interface comes next. The Headless Toolkit is how developers build their own AIforce experiences, exposing Salesforce data, business logic, and actions to any agent through MCP, APIs, and the CLI.</span></p>
<p><span style="font-weight: 400">But data and actions are only half of the story. When an agent replies with a wall of text, users lose the rich, interactive experience they’re used to inside Salesforce. The </span><a href="https://www.salesforce.com/headless/agentic-experience-layer/"><span style="font-weight: 400">Headless Experience Layer (HXL)</span></a><span style="font-weight: 400"> closes that gap. HXL is the part of the Headless Toolkit that brings interactive experiences to any agent: you define a widget once in declarative JSON, and it renders natively in Agentforce and in any Model Context Protocol (MCP) client that supports </span><a href="https://modelcontextprotocol.io/extensions/apps/overview"><span style="font-weight: 400">MCP Apps</span></a><span style="font-weight: 400">. No more rebuilding the same experience for every channel.</span><span style="font-weight: 400"></span></p>
<p><span style="font-weight: 400">In this post, you&#8217;ll learn the core concepts, how HXL works, and where it fits in AIforce. We&#8217;ll then walk through a real widget from the Pronto sample app, following it from the Apex action to the MCP server.</span></p>
<h2><span style="font-weight: 400">Understanding the Headless Experience Layer</span></h2>
<p><span style="font-weight: 400">HXL is a centralized engine built on one principle: write once, render anywhere. Today, most agent actions return a plain-text sentence. With HXL, the same action returns a branded, interactive card that looks and behaves consistently, whether your user is in Agentforce, Slack, or Claude.</span></p>
<p><span style="font-weight: 400"></span><span style="font-weight: 400">Because you build the widget once and reuse it on every supported interface, you don&#8217;t maintain a separate UI for each one. This saves development time and keeps the experience consistent. A widget isn&#8217;t a Lightning Web Component (LWC) or a React app. It&#8217;s a declarative tree of components that each surface renders natively.</span></p>
<h3><span style="font-weight: 400">Core platform benefits</span></h3>
<ul>
<li style="font-weight: 400"><b>Your existing security model</b><span style="font-weight: 400">. A widget only displays what your action returns. When the action enforces sharing and field-level security, every surface shows each user only what they&#8217;re allowed to see.</span></li>
<li style="font-weight: 400"><b>One source of truth.</b><span style="font-weight: 400"> The same Apex action and the same widget definition serve every channel. There&#8217;s no second copy to keep in sync.</span></li>
<li style="font-weight: 400"><b>Less integration code.</b><span style="font-weight: 400"> Custom Lightning Types describe your data shape, so you don&#8217;t need custom API bridges between your org and each UI surface.</span></li>
</ul>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207136" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261007103254/A-user-prompt-flows-through-HXL-which-assembles-a-widget-and-renders-it-natively-on-supported-surfaces-such-as-Agentforce-Claude-and-ChatGPT-e1791394386486.png?w=1000" class="postimages" width="1000" height="563" alt="A user prompt flows through HXL, which assembles a widget and renders it natively on supported surfaces such as Agentforce, Claude, and ChatGPT." />
			  </span>
			</p>
<h2><span style="font-weight: 400">HXL Essentials: Components, Widgets, and Custom Lightning Types</span></h2>
<p><span style="font-weight: 400">The HXL architecture relies on three core concepts:</span></p>
<ul>
<li style="font-weight: 400"><b>Components</b><span style="font-weight: 400">: The foundational building blocks of HXL. Components are reusable UI primitives that can be composed to build different experiences. Each component is surface-aware and knows how to render across supported HXL surfaces.</span></li>
<li style="font-weight: 400"><b>Widgets</b><span style="font-weight: 400">: Composed UI experiences built by combining multiple components to address a specific use case. Widgets define the final UI structure and behavior delivered to the user.</span></li>
<li style="font-weight: 400"><b>Custom Lightning Types (CLTs):</b><span style="font-weight: 400"> The bridge between data and UI. A CLT describes the shape of your data. Its renderer tells the platform which widget draws that data and how the fields map onto the widget&#8217;s attributes.</span></li>
</ul>
<h2><span style="font-weight: 400">Where the widget&#8217;s data comes from</span><span style="font-weight: 400"><br />
</span></h2>
<p><span style="font-weight: 400">A widget doesn&#8217;t fetch data on its own. The data arrives through the Custom Lightning Type (CLT), which defines the structure your data must follow. Whatever source you use, its output has to match the CLT structure so its properties can bind to the widget attributes. In this post, the data source is an Apex invocable action, following the pattern used in the HXL docs and the Pronto sample app.</span></p>
<h2><span style="font-weight: 400">How HXL works</span></h2>
<p><span style="font-weight: 400">You write the Apex action once and the widget once. Only the wiring between them changes per channel.</span></p>
<table>
<thead>
<tr>
<th><b>Aspect</b></th>
<th><b>Agentforce</b></th>
<th><b>MCP clients (Claude, ChatGPT)</b></th>
</tr>
</thead>
<tbody>
<tr>
<td><b>CLTs you add</b></td>
<td><span style="font-weight: 400">One, typed to your Apex class</span></td>
<td>Two nested: the inner one mirrors your Apex output, and the outer one mirrors the MCP envelope (<code>actionName</code>, <code>isSuccess</code>, <code>outputValues</code>)</td>
</tr>
<tr>
<td><b>Which CLT has </b><b>renderer.json</b></td>
<td><span style="font-weight: 400">The single CLT</span></td>
<td><span style="font-weight: 400">Only the outer CLT</span></td>
</tr>
<tr>
<td><b>Renderer path</b></td>
<td><code>{!$attrs.averageRating}</code></td>
<td><code>{!$attrs.outputValues.reviews.averageRating}</code></td>
</tr>
<tr>
<td><b>How the surface finds the widget</b></td>
<td>Agent Script output with <code>complex_data_type_name</code> and <code>is_displayable: True</code></td>
<td>MCP server definition: a <code></code> UI resource that the tool&#8217;s <code></code> points at</td>
</tr>
</tbody>
</table>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207137" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261007103324/One-Apex-action-and-one-widget.-For-Agentforce-a-single-CLT-maps-the-action-output-to-the-widget.-For-MCP-two-nested-CLTs-and-an-MCP-server-definition-publish-the-same-widget-as-a-UI-resource-e1791394422405.jpg?w=1000" class="postimages" width="1000" height="558" alt="One Apex action and one widget. For Agentforce, a single CLT maps the action output to the widget. For MCP, two nested CLTs and an MCP server definition publish the same widget as a UI resource." />
			  </span>
			</p>
<h2><span style="font-weight: 400">What you can build in the beta</span></h2>
<p><span style="font-weight: 400">In the HXL beta, you can:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Build widgets as JSON in your Salesforce DX project, or vibe code them with an AI agent using the </span><a href="https://github.com/forcedotcom/sf-skills"><span style="font-weight: 400">Salesforce Skills library</span></a><span style="font-weight: 400">. </span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Learn the components and preview layouts in the </span><a href="https://www.headlessexperiencelayer.com"><span style="font-weight: 400">HXL Playground</span></a><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Render widgets in Agentforce and preview them in Agentforce Builder. See </span><a href="https://developer.salesforce.com/docs/platform/hxl/guide/agentforce-action-output.html"><span style="font-weight: 400">how to render a widget in Agentforce</span></a><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Render widgets in MCP clients that support the MCP Apps standard, such as Claude and ChatGPT.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Make widgets interactive with two client actions, sendMessage and openLink.</span></li>
</ul>
<h2><span style="font-weight: 400">Example: a Pronto customer reviews widget</span></h2>
<p>To keep things concrete, we&#8217;ll use Pronto, our fictional food-delivery sample app. Pronto ships an <code>Employee_Assistant</code> agent with a <b>Get Customer Reviews</b> action. Ask the agent, &#8220;Show me the reviews for The Green Fork,&#8221; and it will render a review card instead of a paragraph of text.</p>
<p><span style="font-weight: 400">The card shows a header with the average rating, followed by one entry per review with the customer&#8217;s name, the order date, the star rating, and the comment. It&#8217;s built from four pieces:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">An </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_InvocableMethod.htm"><b>Apex invocable action</b></a><span style="font-weight: 400"> that returns the review data.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">A </span><b>widget</b><span style="font-weight: 400"> that defines the card&#8217;s layout.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">A </span><b>Custom Lightning Type</b><span style="font-weight: 400"> that binds the action&#8217;s output to the widget.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">An </span><b>agent action output</b><span style="font-weight: 400"> (or an MCP tool) that tells the surface to render that type.</span></li>
</ol>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207138" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261007103406/The-Pronto-Employee-Assistant-showing-a-customer-reviews-card-with-an-average-rating-and-a-list-of-reviews-e1791394457271.png?w=1000" class="postimages" width="1000" height="515" alt="The Pronto Employee Assistant showing a customer reviews card with an average rating and a list of reviews." />
			  </span>
			</p>
<h2><span style="font-weight: 400">How the HXL files map in a Salesforce DX project</span></h2>
<p><span style="font-weight: 400">Here&#8217;s how the four pieces map to files in the Pronto repo. The rest of this post walks through them in this order.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207139" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261007103441/image2_5ea69e-e1791394493515.png?w=1000" class="postimages" width="1000" height="384" alt="File tree of the Pronto repo: GetCustomerReviewsAction.cls and ReviewService.cls, the customerReviews widget bundle, three Custom Lightning Types, the Employee_Assistant agent script, and the ProntoEmployeeServer MCP server definition." />
			  </span>
			</p>
<h2><span style="font-weight: 400">Start with the Apex action</span></h2>
<p><span style="font-weight: 400">Everything starts with the data. GetCustomerReviewsAction is a global Apex class with an @InvocableMethod. It returns a success flag, a message, and one structured reviews object. That object&#8217;s shape is what the widget and CLT bind to. Here&#8217;s the Apex class, trimmed to its contract:</span></p>
<pre language="apex">global with sharing class GetCustomerReviewsAction {
  @InvocableMethod(label='Get Customer Reviews'
    description='Retrieves customer reviews for a specific storefront')
  global static List&lt;Output&gt; getCustomerReviews(List&lt;Input&gt; inputs) { /* ... */ }

  global class Output {
    @InvocableVariable(label='Success') public Boolean isSuccess;
    @InvocableVariable(label='Message') public String message;
    @InvocableVariable(label='Reviews') public ReviewWrapper reviews;
  }

  global class ReviewWrapper {
    @InvocableVariable public Integer reviewCount;
    @InvocableVariable public Decimal averageRating;
    @InvocableVariable public List&lt;ReviewItemWrapper&gt; items;
  }

  global class ReviewItemWrapper {
    @InvocableVariable public String customerName;
    @InvocableVariable public Decimal rating;
    @InvocableVariable public Date orderDate;
    @InvocableVariable public String comments;
  }
}
</pre>
<p><span style="font-weight: 400">Note: <span>The Apex class computers averageRating and reviewCount itself, but you can now use Salesforce Formulas to compute values in the widget itself.</span></span></p>
<h2><span style="font-weight: 400">Define the widget: layout, contract, and registration</span></h2>
<p>A widget lives in a <code>UiWidgetBundle</code> under <code>uiWidgets/&lt;widgetName&gt;/</code>. The bundle holds up to three files, each with one job:</p>
<ul>
<li><code>customerReviews.uiwidget-meta.xml</code> registers the bundle and sets <code>widgetType</code> to <code>JSON</code>.</li>
<li><code>customerReviews.json</code> is the composition file. It&#8217;s the layout, built from <code>tile/*</code> blocks.</li>
<li><code>schema.json</code> is optional. It declares the type of each attribute, so tools can validate your bindings before you deploy.</li>
</ul>
<p>Every composition starts with a <code>lightning__agentforceWidget</code> root and a tile/widget tree. Values that change per record are<span> </span><code>{!$attrs.&lt;name&gt;}</code> placeholders. To repeat a component for each item in a list, you add a forEach meta property and name the loop variable with <code>forItem</code>. Here&#8217;s the core of Pronto&#8217;s reviews widget, trimmed for brevity:</p>
<pre language="json">{
  "type": "lightning__agentforceWidget",
  "contentBody": {
    "widgetBody": {
      "definition": "tile/widget",
      "children": [
        {
          "definition": "tile/column",
          "attributes": { "gap": "lg" },
          "children": [
            {
              "definition": "tile/text",
              "attributes": {
                "text": "Average: {!$attrs.averageRating}/5 ({!$attrs.reviewCount} reviews)",
                "variant": "body",
                "color": "muted"
              }
            },
            {
              "definition": "tile/column",
              "attributes": { "gap": "md" },
              "meta": { "forEach": "{!$attrs.items}", "forItem": "$review" },
              "children": [
                {
                  "definition": "tile/text",
                  "attributes": { "text": "{!$review.customerName}", "variant": "h4", "weight": "semibold" }
                },
                {
                  "definition": "tile/text",
                  "attributes": { "text": "{!$review.rating}/5", "variant": "body", "weight": "semibold" }
                },
                {
                  "definition": "tile/text",
                  "attributes": { "text": "{!$review.comments}", "variant": "body", "color": "muted" }
                }
              ]
            }
          ]
        }
      ]
    }
  }
}

</pre>
<p>Inside the loop, <code>$review</code> is the current item. You reach each field with <code>{!$review.&lt;field&gt;}</code>. There&#8217;s no JavaScript in a widget, so the layout only places and styles values.</p>
<p>The schema is the widget&#8217;s contract. It names each attribute and gives it a <code>lightning:type</code>.<br />
Notice that items is a list whose items point at an Apex class you just saw:</p>
<pre language="json">{
  "title": "Customer Reviews Widget",
  "type": "object",
  "properties": {
    "attributes": {
      "lightning:type": "lightning__objectType",
      "properties": {
        "reviewCount":   { "lightning:type": "lightning__numberType" },
        "averageRating": { "lightning:type": "lightning__numberType" },
        "items": {
          "lightning:type": "lightning__listType",
          "items": {
            "lightning:type": "@apexClassType/c__GetCustomerReviewsAction$ReviewItemWrapper"
          }
        }
      }
    }
  }
}
</pre>
<h2><span style="font-weight: 400">Preview widgets in the HXL Playground</span></h2>
<p><span style="font-weight: 400">The </span><a href="https://www.headlessexperiencelayer.com/playground/"><span style="font-weight: 400">HXL Playground</span></a><span style="font-weight: 400"> is a browser-based learning tool for HXL. It isn&#8217;t part of the build or deploy flow, so you don&#8217;t need it to ship a widget. It&#8217;s simply the fastest way to learn the components and try out a layout.</span></p>
<p><span style="font-weight: 400">The Playground has three areas. The </span><b>Components</b><span style="font-weight: 400"> area documents each component and its attributes. The </span><b>Widgets</b><span style="font-weight: 400"> area lets you compose components and preview the result on several surfaces. </span><b>Tutorials</b><span style="font-weight: 400"> walk you from a single text block to a full widget.</span></p>
<p>To try Pronto&#8217;s reviews card, create a widget and paste the composition from <code>customerReviews.json</code> into the Structure tab. Then add sample values for <code>averageRating</code>, <code>reviewCount</code>, and items in the Data tab. The preview updates as you type, so you can fix the layout before you copy it back into your project. Previews can differ from the live experience, so always test on each target surface.</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207140" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261007103515/he-HXL-Playground-with-the-customer-reviews-widget-JSON-in-the-Structure-tab-and-a-live-Agentforce-preview-of-the-card-showing-an-average-of-3.75-out-of-5-from-8-reviews-e1791394531782.png?w=1000" class="postimages" width="1000" height="516" alt="The HXL Playground with the customer reviews widget JSON in the Structure tab and a live Agentforce preview of the card, showing an average of 3.75 out of 5 from 8 reviews." />
			  </span>
			</p>
<h2><span style="font-weight: 400">Bind data to UI with a Custom Lightning Type</span></h2>
<p>A <b>Custom Lightning Type (CLT)</b> connects your action&#8217;s output to a widget. A widget is a shell until data flows in. For Agentforce, the CLT is typed directly to the Apex class that the action returns. Pronto&#8217;s <code>customerReviewsOutput</code> type points at the action&#8217;s <code>ReviewWrapper</code> inner class:</p>
<pre language="json">{
  "title": "Customer Reviews Output",
  "description": "Customer reviews and rating summary returned by GetCustomerReviewsAction, rendered as a widget",
  "lightning:type": "@apexClassType/c__GetCustomerReviewsAction$ReviewWrapper"
}
</pre>
<p>The CLT&#8217;s <code>renderer.json</code> points at the widget and binds each widget attribute to an output field:</p>
<pre language="json">{
  "renderer": {
    "componentOverrides": {
      "$": {
        "definition": "@widget/c/customerReviews",
        "attributes": {
          "reviewCount":   "{!$attrs.reviewCount}",
          "averageRating": "{!$attrs.averageRating}",
          "items":         "{!$attrs.items}"
        }
      }
    }
  }
}
</pre>
<p>Read each binding from right to left. The output field on the right feeds the widget attribute on the left. Two details matter here. The <code>"$"</code> key means &#8220;the whole type,&#8221; and the <code>@widget/</code> prefix tells the platform to resolve a <code>UiWidgetBundle</code>.</p>
<h2><span style="font-weight: 400">Render the widget in Agentforce</span></h2>
<p>The agent needs to know that this output should render. In Pronto&#8217;s Agent Script, the action&#8217;s <code>reviews</code> output names the CLT and marks itself displayable:</p>
<pre language="yaml">reviews: object
    label: "Reviews"
    complex_data_type_name: "c__customerReviewsOutput"
    filter_from_agent: False
    is_displayable: True
</pre>
<p>With <code>is_displayable: True</code>, the agent can show the output in the conversation. Agentforce then resolves the CLT, follows its renderer to the widget, and draws the card. Keep <code>filter_from_agent: False</code> so the output also stays in the agent&#8217;s context.</p>
<p>The model still decides when to show the card. In our testing, it usually renders on the first question in a fresh conversation. Follow-up questions in the same conversation sometimes come back as text. To make rendering more consistent, name the action explicitly in your subagent&#8217;s instructions. Pronto&#8217;s <code>Employee_Assistant</code> does exactly that, referencing the action by name: <code>Call {!@actions.getCustomerReviews} to retrieve customer reviews for a storefront.</code></p>
<h2><span style="font-weight: 400">Render the same widget through MCP</span></h2>
<p>To render the same widget through MCP, you add two nested Custom Lightning Types, and only the outer one carries the renderer. This is where &#8220;render anywhere&#8221; pays off. Pronto exposes Get Customer Reviews as a tool on its <code>ProntoEmployeeServer</code> MCP server. HXL widgets follow the MCP Apps standard, so any MCP client that supports MCP Apps, such as Claude and ChatGPT, can render the same card.</p>
<p>MCP needs a different CLT shape, though. An MCP tool call wraps the action output in an envelope with <span>actionName</span><span>, </span><span>isSuccess</span><span>, and outputValues. So you add two nested types. The inner type, </span><code>customerReviewsResponse</code>, mirrors the Apex Output class. The outer type, <code>customerReviews</code>, mirrors the envelope. Only the outer type has a renderer, and its paths read two levels deep:</p>
<pre language="json">{
  "renderer": {
    "componentOverrides": {
      "$": {
        "definition": "@widget/c/customerReviews",
        "attributes": {
          "reviewCount":   "{!$attrs.outputValues.reviews.reviewCount}",
          "averageRating": "{!$attrs.outputValues.reviews.averageRating}",
          "items":         "{!$attrs.outputValues.reviews.items}"
        }
      }
    }
  }
}
</pre>
<p><span style="font-weight: 400">The widget and the attribute names on the left are identical. Only the paths on the right change. In the MCP server definition, an entry publishes the outer type as a UI resource. The tool then points at that resource by name:</span></p>
<pre language="xml">&lt;tools&gt;
    &lt;apiDefinition&gt;
        &lt;apiIdentifier&gt;aa:apex-GetCustomerReviewsAction&lt;/apiIdentifier&gt;
        &lt;apiSource&gt;API_CATALOG&lt;/apiSource&gt;
        &lt;operation&gt;GetCustomerReviewsAction&lt;/operation&gt;
    &lt;/apiDefinition&gt;
    &lt;uiResource&gt;customerReviews&lt;/uiResource&gt;
    &lt;readOnly&gt;true&lt;/readOnly&gt;
    &lt;toolName&gt;getCustomerReviews&lt;/toolName&gt;
    &lt;toolTitle&gt;Get Customer Reviews&lt;/toolTitle&gt;
&lt;/tools&gt;
&lt;resources&gt;
    &lt;resourceName&gt;customerReviews&lt;/resourceName&gt;
    &lt;resourceUri&gt;ui://widget/lightningType/c__customerReviews&lt;/resourceUri&gt;
    &lt;resourceTitle&gt;Customer Reviews&lt;/resourceTitle&gt;
&lt;/resources&gt;
</pre>
<p>Every client that calls <code>getCustomerReviews</code> follows the resource back to the same <code>@widget/c/customerReviews</code> definition. After you deploy, activate the server in Setup under API Catalog &gt; MCP Servers. Then connect your MCP client through an External Client App. The <a href="https://developer.salesforce.com/docs/platform/hxl/guide/hxl-mcp-channels.html"><u>HXL MCP channels guide</u></a> covers the client setup in detail.</p>
<h2><span style="font-weight: 400">How to get started</span></h2>
<p><span style="font-weight: 400">First, enable the beta. In Setup, search for </span><b>Headless Experience Layer Settings</b><span style="font-weight: 400"> under </span><b>Feature Settings</b><span style="font-weight: 400">. Turn on </span><b>Headless Experience Layer (Beta)</b><span style="font-weight: 400"> and accept the beta terms. Your project should use API version 67.0 or later.</span></p>
<p>Next, explore a working example. Clone the Pronto sample app, install it in your org, and ask the <code>Employee_Assistant</code> for a storefront&#8217;s reviews. Then open the <code>customerReviews</code> widget and its two CLT shapes to see how the pieces fit.</p>
<p><span style="font-weight: 400">When you&#8217;re ready to build your own widget, let an AI agent do the heavy lifting. 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"> is optimized for Agentforce Vibes and works with other AI coding tools. Its HXL skills can plan the Apex display fields, the widget, and the Lightning Type from a single prompt. They can also update your Agent Script and generate the nested MCP types.</span></p>
<p><span style="font-weight: 400">To try the whole flow hands-on, take the </span><a href="https://developer.salesforce.com/workshops/aiforce-workshop/give-agents-ui-with-the-headless-experience-layer-hxl/overview"><span style="font-weight: 400">Give Agents UI with the Headless Experience Layer</span></a><span style="font-weight: 400"> workshop. It builds a Pronto storefront profile card and renders it in both Agentforce and an MCP client.</span></p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">HXL makes one hard thing easy: shipping consistent, interactive UI to every agent surface your users touch. You build a widget once and map your data with a Custom Lightning Type. The same card then renders inside Agentforce and across MCP clients, backed by the security your action already enforces. That means less UI code, no drift between channels, and faster delivery.</span></p>
<p><span style="font-weight: 400">The fastest way to see it is to install Pronto and watch its review card render. Have questions or something to show? Bring them to the </span><a href="https://trailhead.salesforce.com/trailblazer-community"><span style="font-weight: 400">Trailblazer Community</span></a><span style="font-weight: 400">, or share what you build with us on LinkedIn.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hxl/guide/get-started.html"><span style="font-weight: 400">Headless Experience Layer: Get Started</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hxl/guide/hxl-rich-ui.html"><span style="font-weight: 400">Build Rich UI with HXL</span></a></li>
<li style="font-weight: 400"><a href="https://www.headlessexperiencelayer.com/playground/"><span style="font-weight: 400">HXL Playground</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/tools/salesforcecli"><span style="font-weight: 400">Salesforce CLI</span></a></li>
<li style="font-weight: 400"><a href="https://trailhead.salesforce.com/trailblazer-community"><span style="font-weight: 400">Trailblazer Community</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hxl/guide/hxl-mcp-channels.html"><span style="font-weight: 400">Use HXL Widgets on MCP | Build Rich UI | Headless Experience Layer Developer Guide (Beta)</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hxl/guide/hxl-vibes.html"><span style="font-weight: 400">Use Agentforce Vibes to Build Widgets | Headless Experience Layer Developer Guide (Beta)</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/forcedotcom/sf-skills"><span style="font-weight: 400">GitHub &#8211; forcedotcom/sf-skills: Salesforce&#8217;s curated collection of agent skills for building applications. Optimized for Agentforce Vibes, compatible with all AI tools.</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/workshops/aiforce-workshop/give-agents-ui-with-the-headless-experience-layer-hxl/overview">Give Agents UI with the Headless Experience Layer (HXL)</a></li>
</ul>
<h2><b>About the author</b></h2>
<p><b>Akshata Sawant</b><span style="font-weight: 400"> is a Lead 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><b>Monika Singh</b><span style="font-weight: 400"> is a Senior Product Manager at Salesforce, focused on Headless Experience Layer (HXL). She has a background in AI/ML product management and is passionate about building intelligent, personalized digital experiences. For more details, visit her </span><a href="http://www.linkedin.com/in/monika-singh-ms"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400"> profile.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/building-interactive-ui-with-hxl">Building Interactive UI with Headless Experience Layer</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/10/building-interactive-ui-with-hxl/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207132</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261007104934/DoubleHeadshot-8-e1791395412468.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261007104934/DoubleHeadshot-8-e1791395412468.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Advanced Code Extension PySpark Examples for Batch Data Transforms</title>
		<link>https://developer.salesforce.com/blogs/2026/10/advanced-code-extension-pyspark-examples-for-batch-data-transforms</link>
		<comments>https://developer.salesforce.com/blogs/2026/10/advanced-code-extension-pyspark-examples-for-batch-data-transforms#respond</comments>
		<pubDate>Tue, 06 Oct 2026 15:00:45 +0000</pubDate>
		<dc:creator><![CDATA[Mark DeLaVergne]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Data 360]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Batch Data Transform]]></category>
		<category><![CDATA[Code Extension]]></category>
		<category><![CDATA[Data Engineering]]></category>
		<category><![CDATA[Data Lake Objects]]></category>
		<category><![CDATA[Machine Learning]]></category>
		<category><![CDATA[PySpark]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[Salesforce CLI]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207119</guid>
		<description><![CDATA[<p>Code Extension scripts for batch data transform allow you to use custom Python logic to transform your data lake objects (DLOs) and data model objects (DMOs) in an isolated compute environment in Data 360. In this blog, we&rsquo;ll cover advanced batch transform script examples. Batch data transforms Data 360&#8217;s native batch data transform feature allows [&#8230;]</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/advanced-code-extension-pyspark-examples-for-batch-data-transforms">Advanced Code Extension PySpark Examples for Batch Data Transforms</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_code_extension.htm&amp;language=en_US&amp;type=5"><b>Code Extension scripts for batch data transform</b></a><span style="font-weight: 400"> allow you to use custom Python logic to transform your data lake objects (DLOs) and data model objects (DMOs) in an isolated compute environment in </span><a href="https://www.salesforce.com/data/"><span style="font-weight: 400">Data 360</span></a><span style="font-weight: 400">. In this blog, we’ll cover advanced batch transform script examples.</span></p>
<h2><span style="font-weight: 400">Batch data transforms</span></h2>
<p><span style="font-weight: 400">Data 360&#8217;s native </span><b>batch data transform</b><span style="font-weight: 400"> feature allows for data preparation over DLOs and DMOs, including through operations like joins, filters, and aggregates. You can use the Transform Builder UI to create batch transforms on the platform. The </span><a href="https://trailhead.salesforce.com/content/learn/modules/batch-data-transforms-in-data-cloud-quick-look/get-started-with-batch-data-transforms-in-data-360"><span style="font-weight: 400">Batch Data Transforms in Data 360 Trailhead module</span></a><span style="font-weight: 400"> is a great starting point.</span></p>
<h2><span style="font-weight: 400">Code Extension </span></h2>
<p><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_code_extension.htm&amp;language=en_US&amp;type=5"><b>Code Extension</b> </a><span style="font-weight: 400">is a Data 360 feature that runs custom PySpark logic against DLOs and DMOs inside an isolated Spark compute environment. It powers multiple pipelines, including search-index chunking and batch transform logic, the use case that this post covers.</span></p>
<p><span style="font-weight: 400">If you are not familiar with the basics of Code Extension, you can check out the following resources before going deeper into this blog:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Blog: </span><a href="https://www.salesforce.com/blog/extend-data-360-with-the-power-of-code-introducing-code-extension/"><span style="font-weight: 400">Extend Data 360 with the Power of Code.</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">YouTube: </span><a href="https://www.youtube.com/playlist?list=PLACV3VmGs318"><span style="font-weight: 400">Extend Data 360 with Code Extension.</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Trailhead: </span><a href="https://trailhead.salesforce.com/content/learn/modules/code-extension-in-data-360"><span style="font-weight: 400">Code Extension in Data 360 Module.</span></a></li>
</ul>
<h2><span style="font-weight: 400">Under the hood: the development workflow for Code Extension batch data transform scripts</span></h2>
<p>With Code Extensions, you author a PySpark script that drives the same batch transform pipeline as the one used by native batch transforms. A batch transform Code Extension script runs under strict isolation, using the <a href="https://github.com/forcedotcom/datacloud-customcode-python-sdk/"><u>datacustomcode SDK</u></a>&#8216;s <code>Client()</code> for reading and writing to DLOs or DMOs.</p>
<p><span style="font-weight: 400">Here&#8217;s a high-level overview of what happens on your (the developer’s) machine and what happens in Data 360:</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207121" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261005155455/image3_c99417-e1791240907757.png?w=1000" class="postimages" width="1000" height="836" alt="Architecture diagram showing two zones - developer machine and Data 360 - and the commands, APIs, Data 360 objects, and compute used within both." />
			  </span>
			</p>
<p>On the developer machine, a Python virtual environment is used for local execution involving the SDK, your custom PySpark script, and its dependencies. To run the extension locally, you use the following <a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/set-up-sdk.html"><u>Salesforce Command Line Interface (SF CLI) for Code Extension</u></a> command: <code>sf data-code-extension script run</code>. The command executes a Spark job on your computer, which reads DLOs or DMOs using the Connect API (by default, the first 1000 rows), and writes only to console output. This is where you’ll iterate until the logic works as intended.</p>
<p>The next step is to deploy to your org. To do so, use the SF CLI command: <code>sf data-code-extension script deploy</code>. The command zips up the project using Docker and uploads it via the Connect API into a Data 360 Transform and Code Extension pair.</p>
<p><span style="font-weight: 400">When running the transform (now or on a schedule), Data 360 manages the Spark job, which executes your PySpark script to read from and write to DLOs or DMOs.</span></p>
<table>
<thead>
<tr>
<th></th>
<th><b>Local run (</b><b><code>script run</code></b><b>)</b></th>
<th><b>Deployed run (</b><b><code>script deploy</code></b><b> + transform)</b></th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="font-weight: 400">Runs on</span></td>
<td><span style="font-weight: 400">Your machine, in a Python virtual environment</span></td>
<td><span style="font-weight: 400">Data 360 compute</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Reads default limit</span></td>
<td><span style="font-weight: 400">By default, limit to 1000. See </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/author-custom-script.html"><span style="font-weight: 400">docs</span></a><span style="font-weight: 400"> for how to override.</span></td>
<td><span style="font-weight: 400">No default limit</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Writes to</span></td>
<td><span style="font-weight: 400">Console output only</span></td>
<td><span style="font-weight: 400">Target DLO or DMO</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Spark managed by</span></td>
<td><span style="font-weight: 400">The SF CLI on your computer</span></td>
<td><span style="font-weight: 400">Data 360</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">We recently published </span><a href="https://developer.salesforce.com/blogs/2026/09/build-deploy-run-and-troubleshoot-complex-transformations-with-python-and-pyspark-while-keeping-execution-governed-by-data-360"><span style="font-weight: 400">Run Complex Data Transformations in Data 360 with Code Extension</span></a><span style="font-weight: 400">, which gives you more information about the basics. Below, we build on that introduction and walk through three more examples: rolling up an account hierarchy, exploding a JSON event stream, and scoring leads with a bundled scikit-learn model.</span></p>
<h2><b>Example 1: Iterative rollup of an account hierarchy</b></h2>
<p>The use case: Roll up each account&#8217;s <i>total-tree Annual Recurring Revenue (ARR),</i> including its own plus every descendant&#8217;s, at any depth, into <code>Account_Rollup__dll</code>.</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207122" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261005155542/image5_82146d.png?w=986" class="postimages" width="986" height="815" alt="Diagram of a sample Account__dll tree (A1→A2→A4/A5, A1→A3, A6→A7→A8), where each account has an ARR value that needs to be rolled up" />
			  </span>
			</p>
<p>The solution: With the <code>Account__dll</code> records having various descendant depths, we can use a loop and keep joining the frontier one hop deeper until nothing new comes back.</p>
<pre language="python">from pyspark.sql.functions import col, sum as _sum, coalesce, lit

from datacustomcode.client import Client
from datacustomcode.io.writer.base import WriteMode


def main():
    client = Client()

    accounts = (
        client.read_dlo("Account__dll")
        .select("id__c", "parent_id__c", "arr__c")
        .persist()
    )

    descendants = accounts.select(
        col("id__c").alias("root_id"),
        col("id__c").alias("descendant_id"),
    ).persist()

    frontier = descendants
    while True:
        next_hop = (
            frontier.alias("f")
            .join(
                accounts.alias("a"),
                col("a.parent_id__c") == col("f.descendant_id"),
                "inner",
            )
            .select(
                col("f.root_id").alias("root_id"),
                col("a.id__c").alias("descendant_id"),
            )
        )
        if next_hop.isEmpty():
            break
        descendants = descendants.union(next_hop).persist()
        frontier = next_hop

    totals = (
        descendants.alias("d")
        .join(
            accounts.alias("a"),
            col("d.descendant_id") == col("a.id__c"),
            "left",
        )
        .select(col("d.root_id"), col("a.arr__c"))
        .groupBy("root_id")
        .agg(coalesce(_sum("arr__c"), lit(0)).alias("tree_arr__c"))
        .withColumnRenamed("root_id", "id__c")
    )

    client.write_to_dlo("Account_Rollup__dll", totals, WriteMode.OVERWRITE)


if __name__ == "__main__":
    main()
</pre>
<p>The convergence check, stop, accumulate loop is plain Python driving <a href="https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.html"><u>DataFrames</u></a>. NOTE: This convergence check assumes an acyclic hierarchy. If <code>parent_id__c</code><code> </code>can form a cycle, add a max-depth counter to break the loop so a bad record can&#8217;t hang the Spark job indefinitely.</p>
<p>The results written to <code>Account_Rollup__dll</code> are as expected for this sample dataset:</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207123" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261005155719/image2_b98081-e1791241052522.png?w=1000" class="postimages" width="1000" height="465" alt="Query results showing Account_Rollup__dll records with correct, rolled up values for the tree_arr__c column." />
			  </span>
			</p>
<p><span style="font-weight: 400">You can find this entire example, including the sample data used, in the SDK’s </span><a href="https://github.com/forcedotcom/datacloud-customcode-python-sdk/tree/main/docs/examples/script/account_rollup"><span style="font-weight: 400">docs/examples/script/account_rollup</span></a><span style="font-weight: 400"> directory.</span></p>
<h2><b>Example 2: Flatten a batched event stream from a JSON-array column</b></h2>
<p><span style="font-weight: 400">The use case: Analytics SDKs commonly deliver events </span><b>batched by session. </b><span style="font-weight: 400">One row per session is stored in a raw landing DLO, with each session&#8217;s events packed into a single JSON-array column. Downstream analysis use cases, such as funnel analysis, cohort segmentation, or identity-graph inputs, need one row per event.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207124" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261005155815/image1_4a18f7-e1791241109457.png?w=1000" class="postimages" width="1000" height="554" alt="Diagram showing one raw session row with a JSON-array events column expanding into multiple rows, one per event." />
			  </span>
			</p>
<p>The solution: The parse-and-explode logic is worth pulling out of the entrypoint into a <b>reusable helper</b>. With Code Extensions, this can be done by putting files or modules in the <code>payload/py-files/</code> directory, which needs to have this exact name.</p>
<pre language="python"># payload/py-files/events.py
from pyspark.sql.functions import col, explode_outer, from_json, to_timestamp
from pyspark.sql.types import (
    ArrayType, StringType, StructField, StructType,
)


EVENT_SCHEMA = StructType([
    StructField("event_id", StringType(), True),
    StructField("event_type", StringType(), True),
    StructField("ts", StringType(), True),
    StructField("path", StringType(), True),
    StructField("value", StringType(), True),
])
EVENT_ARRAY_SCHEMA = ArrayType(EVENT_SCHEMA, True)

# One row per event, carrying its parent session + user.
def parse_events(df, session_col, user_col, events_col):
    field = next(f for f in df.schema.fields if f.name == events_col)
    events = (
        col(events_col)
        if isinstance(field.dataType, ArrayType)
        else from_json(col(events_col).cast("string"), EVENT_ARRAY_SCHEMA)
    )
    return df.select(
        col(session_col),
        col(user_col),
        explode_outer(events).alias("evt"),
    ).select(
        col("evt.event_id").alias("event_id__c"),
        col(session_col),
        col(user_col),
        col("evt.event_type").alias("event_type__c"),
        to_timestamp(col("evt.ts")).alias("event_ts__c"),
        col("evt.path").alias("path__c"),
        col("evt.value").alias("value__c"),
    )
</pre>
<p><span style="font-weight: 400">The entrypoint is now thinner: read, explode, filter, write.</span></p>
<pre language="python"># payload/entrypoint.py
from pyspark.sql.functions import col

from datacustomcode.client import Client
from datacustomcode.io.writer.base import WriteMode
from events import parse_events


def main():
    client = Client()

    sessions = client.read_dlo("User_Sessions__dll").select(
        "session_id__c", "user_id__c", "events__c"
    )
    exploded = parse_events(sessions, "session_id__c", "user_id__c", "events__c")

    output = exploded.filter(col("event_id__c").isNotNull())

    client.write_to_dlo("User_Events__dll", output, WriteMode.OVERWRITE)


if __name__ == "__main__":
    main()
</pre>
<p>The results written to <code>User_Events__dll</code> are as expected for the sample dataset:</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207125" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261005160239/image4_14ae4a-e1791241373183.png?w=1000" class="postimages" width="1000" height="508" alt="Query results showing User_Events__dll records with the expected, parsed-and-exploded values in columns like event_type__c and path__c, corresponding to the correct values in user_id__c and session_id__c." />
			  </span>
			</p>
<p><span style="font-weight: 400">You can find this entire example, including the sample data used, in the SDK’s </span><a href="https://github.com/forcedotcom/datacloud-customcode-python-sdk/tree/main/docs/examples/script/json_explode"><span style="font-weight: 400">docs/examples/script/json_explode</span></a><span style="font-weight: 400"> directory.</span></p>
<h2><b>Example 3: Score leads with a bundled scikit-learn model</b></h2>
<p><span style="font-weight: 400">The use case: The data scientist on your team trained a small scikit-learn classifier over categorical lead features (for example, industry, employee band, region, and source) and handed you the serialized artifact. You want a score on every lead as part of the nightly pipeline.</span></p>
<p>The solution: Similar to the <code>py-files</code> directory from the last example, you can ship other file types in the <code>files</code> directory. You can also ship external dependencies in Code Extensions by using the <code>requirements.txt</code> file, which in this example brings in <code>scikit-learn</code>, <code>joblib</code>, and <code>pandas</code>. Bundle the model as <code>payload/files/lead_scorer.zip</code> (a zip around <code>lead_scorer.joblib</code> — the joblib carries the sklearn pipeline plus feature schema and value domain). The <a href="https://github.com/forcedotcom/datacloud-customcode-python-sdk/blob/main/docs/examples/script/lead_scoring/train_model.py"><u>training script</u></a> is a plain scikit-learn <code>Pipeline</code> fit, run locally before deploying the Code Extension for any model adjustments.</p>
<p>The PySpark entrypoint can <code>joblib.load</code> the model, enumerate every combination of its categorical inputs, score that small grid with<code> predict_proba</code>, and left-join the grid against the leads DLO.</p>
<pre language="python">import itertools
import tempfile
import zipfile
from pathlib import Path

import joblib
import pandas as pd
from pyspark.sql import Row
from pyspark.sql.functions import coalesce, col, lit

from datacustomcode.client import Client
from datacustomcode.io.writer.base import WriteMode


UNKNOWN = "__unknown__"
MODEL_ARCHIVE = "lead_scorer.zip"
MODEL_MEMBER = "lead_scorer.joblib"

OUTPUT_COLUMNS = [
    "id__c", "first_name__c", "last_name__c", "industry__c",
    "employee_band__c", "region__c", "source__c", "score__c",
]


def load_bundled_model(client):
    archive_path = client.find_file_path(MODEL_ARCHIVE)
    extract_dir = Path(tempfile.mkdtemp(prefix="lead_scorer_"))
    with zipfile.ZipFile(archive_path) as zf:
        zf.extract(MODEL_MEMBER, path=extract_dir)
    return joblib.load(extract_dir / MODEL_MEMBER)


def build_score_grid(spark, bundle):
    pipeline = bundle["pipeline"]
    feature_cols = bundle["feature_cols"]
    domain = bundle["feature_domain"]

    combos = list(itertools.product(*(domain[c] for c in feature_cols)))
    grid_pd = pd.DataFrame(combos, columns=feature_cols)
    grid_pd["score__c"] = pipeline.predict_proba(grid_pd)[:, 1]

    rows = [
        Row(**{c: r[c] for c in feature_cols}, score__c=float(r["score__c"]))
        for r in grid_pd.to_dict(orient="records")
    ]
    return spark.createDataFrame(rows), feature_cols


def main():
    client = Client()

    leads = client.read_dlo("Lead__dll")
    spark = leads.sparkSession

    bundle = load_bundled_model(client)
    grid, feature_cols = build_score_grid(spark, bundle)

    normalized = leads
    for c in feature_cols:
        normalized = normalized.withColumn(c, coalesce(col(c), lit(UNKNOWN)))

    scored = (
        normalized.alias("l")
        .join(grid.alias("g"), feature_cols, "left")
        .withColumn("score__c", coalesce(col("score__c"), lit(0.0)))
    )

    client.write_to_dlo(
        "Lead_Scored__dll", scored.select(*OUTPUT_COLUMNS), WriteMode.OVERWRITE
    )


if __name__ == "__main__":
    main()
</pre>
<p>The grid is small, just a few thousand rows, so Spark&#8217;s planner auto-broadcasts it and every lead gets its score from a plain equi-join. This approach works well when features are naturally categorical or already banded (industry, region, tier, or segment). Same pattern for churn, propensity, and product-affinity: any model whose inputs are categorical or bucketable. <code>coalesce</code> to <code>__unknown__</code> keeps the join total as new industries or sources appear.</p>
<p>The results written to <code>Lead_Scored__dll</code> are as expected for the sample dataset:</p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207126" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261005160328/image6_fe4e34-e1791241423652.png?w=1000" class="postimages" width="1000" height="522" alt="Query results showing Lead_Scored__dll records with score__c values calculated on each lead record." />
			  </span>
			</p>
<p>Because <code>Lead_Scored__dll</code> is a first-class Data 360 object, once mapped into a DMO, an Agentforce agent like <a href="https://developer.salesforce.com/docs/data/agentforce-coworker/guide/agentforce-coworker-connect-to-existing-data-sources.html"><u>Coworker</u></a> can query it directly: <i>&#8220;Who are my top 10 hottest leads this week?&#8221;</i></p>
<p><span style="font-weight: 400">You can find this entire example, including the sample data used, in the SDK’s </span><a href="https://github.com/forcedotcom/datacloud-customcode-python-sdk/tree/main/docs/examples/script/lead_scoring"><span style="font-weight: 400">docs/examples/script/lead_scoring</span></a><span style="font-weight: 400"> directory.</span></p>
<h2><b>Limits and other considerations</b></h2>
<p><span style="font-weight: 400">Code Extension jobs run under managed Spark with fixed memory, timeout, and dependency-size limits documented in the </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/considerations-when-writing-custom-code.html"><span style="font-weight: 400">Considerations When Writing Code Extensions</span></a><span style="font-weight: 400"> guide. Because local runs sample only the first 1000 rows (by default) via the Connect API, data-scale problems, such as partition skew on a deep account tree or an out-of-memory issue on a large explode, only surface on the deployed run, so test at full scale after deploy. The </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/considerations-when-writing-custom-code.html#limitations"><span style="font-weight: 400">documented limitations</span></a><span style="font-weight: 400"> will evolve over time based on the roadmap, which you can influence by reaching out to your account executive and discussing feature requests.</span></p>
<h2><b>Conclusion</b></h2>
<p><span style="font-weight: 400">Code Extension is the right tool for the job when Python is the right language: imperative control flow, an existing library you want to reuse, a serialized model, a helper module worth unit-testing on its own. Code Extension for batch data transform is GA. What use cases does this unblock for you and your team? </span></p>
<h2><b>More Resources</b></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Python SDK: </span><a href="https://github.com/forcedotcom/datacloud-customcode-python-sdk"><span style="font-weight: 400">forcedotcom/datacloud-customcode-python-sdk</span></a><span style="font-weight: 400"> · </span><a href="https://pypi.org/project/salesforce-data-customcode/"><span style="font-weight: 400">PyPI</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Salesforce CLI plugin: </span><a href="https://github.com/salesforcecli/plugin-data-code-extension"><span style="font-weight: 400">salesforcecli/plugin-data-code-extension</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Developer guide: </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/use-custom-code.html"><span style="font-weight: 400">Data 360 Code Extension</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Help: </span><a href="https://help.salesforce.com/apex/HTViewHelpDoc?id=data.c360_a_batch_transform_overview.htm"><span style="font-weight: 400">Batch Data Transforms Overview</span></a></li>
</ul>
<h2><b>About the author</b></h2>
<p><b>Mark DeLaVergne</b><span style="font-weight: 400"> is a Principal Software Engineer at Salesforce working on Code Extension in Data 360.  With 19 years in software engineering and architecture, he currently leads Code Extension and partners on wider Data 360 design.  You can find him on </span><a href="https://www.linkedin.com/in/markdelavergne"><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/10/advanced-code-extension-pyspark-examples-for-batch-data-transforms">Advanced Code Extension PySpark Examples for Batch Data Transforms</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/10/advanced-code-extension-pyspark-examples-for-batch-data-transforms/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207119</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261005160554/Generic-B-7-e1791241572316.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261005160554/Generic-B-7-e1791241572316.png?w=1000" medium="image" />
	</item>
		<item>
		<title>The Salesforce Developer’s Guide to the Winter ’27 Release</title>
		<link>https://developer.salesforce.com/blogs/2026/10/developers-guide-to-the-winter-27-release</link>
		<comments>https://developer.salesforce.com/blogs/2026/10/developers-guide-to-the-winter-27-release#respond</comments>
		<pubDate>Mon, 05 Oct 2026 17:00:50 +0000</pubDate>
		<dc:creator><![CDATA[Mohith Shrivastava]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Announcements]]></category>
		<category><![CDATA[Apex]]></category>
		<category><![CDATA[App Development]]></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[2GP Packaging]]></category>
		<category><![CDATA[Agent Script]]></category>
		<category><![CDATA[AIforce]]></category>
		<category><![CDATA[angular]]></category>
		<category><![CDATA[Apex Cursors]]></category>
		<category><![CDATA[Apex Integration Tests]]></category>
		<category><![CDATA[ApexGuru]]></category>
		<category><![CDATA[API v68.0]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[Complex Template Expressions]]></category>
		<category><![CDATA[Data SDK]]></category>
		<category><![CDATA[EventLogFile]]></category>
		<category><![CDATA[GraphQL]]></category>
		<category><![CDATA[Headless Experience Layer]]></category>
		<category><![CDATA[Headless Toolkit]]></category>
		<category><![CDATA[hxl]]></category>
		<category><![CDATA[lwc:external]]></category>
		<category><![CDATA[MCP tools]]></category>
		<category><![CDATA[Microfrontends]]></category>
		<category><![CDATA[Model context protocol]]></category>
		<category><![CDATA[Multi-Agent Orchestration]]></category>
		<category><![CDATA[Python Code Extensions]]></category>
		<category><![CDATA[RAG Retrievers]]></category>
		<category><![CDATA[Salesforce CLI]]></category>
		<category><![CDATA[Salesforce Multi-Framework]]></category>
		<category><![CDATA[Scale Center]]></category>
		<category><![CDATA[Web Console]]></category>
		<category><![CDATA[Winter ’27 Release]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207099</guid>
		<description><![CDATA[<p>Winter &rsquo;27 developer highlights: the Salesforce Development plugin for Claude Code, Headless Experience Layer widgets, Angular and microfrontends in Multi-Framework, agents as MCP tools, LWC template expressions, Agent Script updates, and Web Console, with code examples.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/developers-guide-to-the-winter-27-release">The Salesforce Developer’s Guide to the Winter ’27 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-winter-27-release-countdown"><span style="font-weight: 400">Winter ’27 release</span></a><span style="font-weight: 400"> has deployed across most instances, with the final rollout set for October 9, 2026. Please review the </span><a href="https://status.salesforce.com/products/all/maintenances"><span style="font-weight: 400">maintenance schedule</span></a><span style="font-weight: 400"> to verify the exact deployment window for your organization.</span></p>
<p><span style="font-weight: 400">This post covers the key updates for developers. We start with AIforce and the Headless Toolkit, including Salesforce Multi-Framework, MCP, Salesforce CLI, and API updates. Then we cover Lightning Web Components (LWC), Apex, Agentforce, Data 360, and platform developer tools.</span></p>
<h2><b>AIforce and Headless Toolkit</b></h2>
<p><a href="https://www.salesforce.com/artificial-intelligence/?d=cta-body-promo-8"><span style="font-weight: 400">AIforce</span></a><span style="font-weight: 400"> brings the full power of Salesforce to any interface. You build the capability once, and it shows up directly inside your users&#8217; agents, apps, and workflows.</span></p>
<p><span style="font-weight: 400">Under the hood, this runs on a headless architecture. Every major Salesforce capability is available as an API, a Model Context Protocol (MCP) tool, or a CLI command. Any authenticated caller can use it, whether that&#8217;s an app, a human, or an AI agent. The Headless Toolkit is the biggest developer theme of the Winter ’27 release. It brings AI Skills, the Headless Experience Layer (HXL), Multi-Framework apps, MCP tools, and CLI and API enhancements. Let’s dive into the main developer features.</span></p>
<h3><b>Salesforce Development plugin for Claude Code</b></h3>
<p><span style="font-weight: 400">The </span><a href="https://github.com/forcedotcom/sf-skills/tree/main/plugins/builder/salesforce-development"><span style="font-weight: 400">Salesforce Development plugin</span></a><span style="font-weight: 400"> teaches Claude Code how to build Salesforce apps and agents. It detects your Salesforce DX project and gives Claude Code Salesforce-specific skills and org context through pre-configured Salesforce-hosted MCP servers. Install the plugin from the Claude Plugin Marketplace, or run this command in Claude Code.</span></p>
<pre language="sh">/plugin install salesforce-development@claude-plugins-official
</pre>
<p><span style="font-weight: 400">Using Codex, Cursor, or another coding agent? The plugin’s skills come from 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">, which works with any agent that supports AI Skills. Install it with one command.</span></p>
<pre language="sh">npx skills add forcedotcom/sf-skills
</pre>
<h4><b>Explore what’s inside the plugin</b></h4>
<p><span style="font-weight: 400">The core plugin ships skills that cover the everyday Salesforce development lifecycle. They generate Apex, custom objects and fields, permission sets, Flows, reports, and Lightning pages. They also write and run Apex tests, analyze debug logs, run SOQL queries, configure sharing, scan code with Code Analyzer, and validate and deploy metadata.</span></p>
<p><span style="font-weight: 400">The plugin also connects Claude Code to MCP servers for Salesforce API and metadata guidance. A bundled Apex and SOQL language server gives the agent the same code intelligence your IDE uses.</span></p>
<h4><b>Go beyond the core plugin with specialized plugins</b></h4>
<p>New in Winter ’27, the plugin knows when a task needs more than its core skills. Describe your task, and it recommends a specialized plugin from the Salesforce catalog. The catalog includes specialized plugins such as:</p>
<ul>
<li><code>dx-org-lifecycle:</code><code> </code>Manage Dev Hubs, scratch orgs, sandbox refreshes, trial orgs, and managed package post-install setup.</li>
<li><code>dx-devops:</code> Run DevOps Center pipelines and work items, and configure their automated tests.</li>
<li><code>agentforce-adlc:</code> Author, test, and deploy Agentforce agents.</li>
<li><code>experience-lwc</code><span> and </span><code>experience-react:</code> Build Lightning web components and React apps.</li>
<li><code>integration:</code> Set up Named Credentials, callouts, OAuth apps, and Change Data Capture.</li>
</ul>
<p>To search the catalog yourself, add the <code>plugins</code> keyword and a description of your task.</p>
<pre language="sh">/salesforce-development:discover plugins &lt;description of your task&gt;
</pre>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207102" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261002142117/image4_2d2259-e1790976091354.png?w=1000" class="postimages" width="1000" height="626" alt="Claude Code terminal where the Salesforce Development plugin recommends a specialized plugin and asks for approval before installing it." />
			  </span>
			</p>
<p>Prefer fewer suggestions? Run <code>/salesforce-development:plugin-recommendations</code> to set how readily the plugin recommends plugins, from off to high.</p>
<h3><b>Salesforce Multi-Framework</b></h3>
<p><a href="https://developer.salesforce.com/docs/platform/multiframework/guide"><span style="font-weight: 400">Salesforce Multi-Framework</span></a><span style="font-weight: 400"> lets you build web apps with frontend frameworks you already know, like Angular or React, and run them on the Salesforce Platform. Your app gets Salesforce data, security, and governance without a separate hosting stack. Winter ’27 adds Angular alongsideReact, a richer Data SDK, a way to embed your app in Lightning pages using microfrontends, and 2GP packaging. </span></p>
<p><span style="font-weight: 400">Additionally, we’ve updated the </span><a href="https://github.com/trailheadapps/multiframework-recipes"><span style="font-weight: 400">Multi-Framework Recipes sample repository</span></a><span style="font-weight: 400"> with new Angular and microfrontend code samples to help you get started.</span></p>
<h4><b>Build Multi-Framework apps with Angular</b></h4>
<p><span>Angular joins React as a supported framework. Bring your existing Angular skills, libraries, and apps to the platform. Scaffold an app from the </span><code>angularinternalapp</code><span> template for employee-facing apps, or the </span><code>angularexternalapp</code><span> template for customer and partner apps or </span><code>angularbasic</code><span> </span><span>for a barebones template</span><span>. </span><code>Angularinternalapp </code>and<code> angularexternalapp </code><span>templates come pre-wired with platform integration.</span></p>
<pre language="sh">sf template generate project --name account-pulse --template angularinternalapp
</pre>
<p><span>Your app is a </span><code>UIBundle</code><span> metadata type. You build it with the standard Angular CLI and deploy it with the same Salesforce DX commands you use for the rest of your project. </span></p>
<h4><b>Reuse data logic with Data SDK extensions</b></h4>
<p><span>The </span><a href="https://developer.salesforce.com/docs/platform/multiframework/guide/data-sdk-intro.html"><u>Data SDK</u></a><span> works with any framework and enforces object-level security, field-level security, and sharing rules on every request. In Winter ’27, you can package your own data logic as an </span><a href="https://developer.salesforce.com/docs/platform/multiframework/guide/data-sdk-extension.html"><u>extension</u></a><span>. Write the extension once with </span><code>defineDataExtension</code><span>, and call it through a typed </span><code>sdk.ext.&lt;name&gt;</code><span> namespace in every bundle that needs it.</span></p>
<p><span>Here’s an extension that returns the top accounts by annual revenue, using the GraphQL API in User Interface API (UI API).</span></p>
<pre language="typescript">import { defineDataExtension, gql } from '@salesforce/platform-sdk';
interface TopAccountsQuery {
    uiapi: {
        query: {
            Account: {
                edges: {
                    node: {
                        Id: string;
                        Name: { value: string | null } | null;
                        AnnualRevenue: { value: number | null } | null;
                    };
                }[];
            };
        };
    };
}

const TOP_ACCOUNTS = gql`
    query TopAccounts($first: Int) {
        uiapi {
            query {
                Account(first: $first, orderBy: { AnnualRevenue: { order: DESC, nulls: LAST } }) {
                    edges {
                        node {
                            Id
                            Name @optional { value }
                            AnnualRevenue @optional { value }
                        }
                    }
                }
            }
        }
    }
`;
export const pulseExtension = defineDataExtension({
    name: 'pulse',
    setup: (sdk) =&gt; ({
        async topAccounts(first = 5) {
            if (!sdk.graphql) {
                throw new Error('GraphQL is not available on this surface');
            }
            const result = await sdk.graphql.query&lt;TopAccountsQuery, { first: number }&gt;({
                query: TOP_ACCOUNTS,
                variables: { first }
            });
            return (result.data?.uiapi.query.Account.edges ?? []).map(({ node }) =&gt; ({
                id: node.Id,
                name: node.Name?.value,
                annualRevenue: node.AnnualRevenue?.value
            }));
        }
    })
});
</pre>
<p><span style="font-weight: 400">Attach the extension when you create the SDK, then call it like any other SDK method.</span></p>
<pre language="typescript">import { createDataSDK } from '@salesforce/platform-sdk';
import { pulseExtension } from './pulse-extension';

const sdk = await createDataSDK({ extensions: [pulseExtension] });
const accounts = await sdk.ext.pulse.topAccounts(5);
</pre>
<p><span style="font-weight: 400">The Data SDK also picks up these additions:</span></p>
<ul>
<li><b><span>Labels extension:</span></b><span> Retrieve custom labels by namespace and locale.</span></li>
<li><b><span>Minimum API version:</span></b><span> Set a minimum Salesforce API version on an extension so that it attaches only where its data exists.</span></li>
<li><b><span>Per-request headers:</span></b><span> Pass a </span><code><span>headers</span></code><span> option on an individual GraphQL query or mutation. Headers are part of the cache key, so requests with different headers don’t share cached responses.</span></li>
</ul>
<h4><b>Localize your app and talk to the host page</b></h4>
<p><span>Use the new </span><code>@salesforce/platform-sdk/i18n</code><span> module to match dates, numbers, and currency to each user’s Salesforce locale. Call </span><b><code>getCurrentApp()</code></b><span> to read the running app’s namespace, name, and qualified name. </span></p>
<pre language="typescript">import { createDataSDK, getCurrentApp, getViewSDK } from '@salesforce/platform-sdk';
import { createI18nFormatters, fetchI18nContext } from '@salesforce/platform-sdk/i18n';

const sdk = await createDataSDK();

// Format currency with the running user's locale and currency
const ctx = await fetchI18nContext(sdk);
const fmt = createI18nFormatters(ctx);
const total = fmt.formatCurrency(5080000, { maximumFractionDigits: 0 });

// Read the running app's identity, for example c__w27AccountPulse
const app = await getCurrentApp();
console.log(app.identity?.qualifiedName);

</pre>
<h4><b>Embed your app in Lightning pages as a microfrontend (Beta)</b></h4>
<p><span>You don’t have to rebuild your app in LWC to show it on a Lightning page. Write a thin Lightning web component that renders the </span><code>lightning-ui-embedding</code><span> base component and points it at your deployed app’s URL. Salesforce hosts your app in a secure, sandboxed iframe and gives it a two-way channel to the surrounding page.</span></p>
<p><span style="font-weight: 400">That channel is what makes a microfrontend feel native. The host component can pass data down to your app, and your app can send events back up. In our example below, an industry filter in the LWC filters the Angular app’s account list, and clicking an account in the Angular app shows that record in native Salesforce components.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207103" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261002142214/image10-e1790976145756.png?w=1000" class="postimages" width="1000" height="785" alt="A Salesforce App Page where an Angular app lists top accounts, an LWC filter sits above it, and a native record panel below it shows the account selected in the Angular app." />
			  </span>
			</p>
<h4><b>Pass data from the host LWC to your app</b></h4>
<p><span>Set the </span><code>props</code><span> attribute on </span><code>lightning-ui-embedding</code><span>. When a value changes, Salesforce pushes the new props to your app.</span></p>
<pre language="html">&lt;template&gt;
    &lt;lightning-radio-group
        label=&quot;Filter the embedded app&quot;
        type=&quot;button&quot;
        options={industryOptions}
        value={industry}
        onchange={handleIndustryChange}&gt;
    &lt;/lightning-radio-group&gt;
    &lt;lightning-ui-embedding
        lwc:ref=&quot;embedding&quot;
        src={appUrl}
        props={embedProps}
        width=&quot;100%&quot;
        height=&quot;560&quot;&gt;
    &lt;/lightning-ui-embedding&gt;
&lt;/template&gt;
</pre>
<p><span>In your app, read the props with </span><code>getUiState()</code><span> from the view SDK, and subscribe to get every change.</span></p>
<pre language="typescript">const view = await getViewSDK();
const ui = view?.getUiState?.&lt;PulseUiState&gt;();
if (ui) {
    this.industry.set(ui.state.props.industry ?? '');
    ui.subscribe((next) =&gt; this.industry.set(next.props.industry ?? ''));
}
</pre>
<h4><b>Send events from your app to the host LWC</b></h4>
<p><span>Dispatch a </span><code>CustomEvent</code><span> through the </span><span>view</span><span> SDK. Salesforce forwards it to the host page.</span></p>
<pre language="typescript">select(account: PulseAccount): void {
    this.view?.dispatchEvent?.(
        new CustomEvent('pulse.accountselected', {
            detail: { recordId: account.id, name: account.name }
        })
    );
}
</pre>
<p><span>The event arrives on the </span><code>lightning-ui-embedding</code><span> element with the same type and detail. Embedding event names contain a dot, so you can’t bind them in the template. Add the listener once in </span><code>renderedCallback</code><span> instead.</span></p>
<pre language="javascript">import { LightningElement, api } from 'lwc';
export default class MicrofrontendHost extends LightningElement {
    @api appUrl; // deployed app URL on the salesforce.app domain
    industry = '';
    selected;
    listening = false;

    get embedProps() {
        return { industry: this.industry };
    }
    renderedCallback() {
        const embedding = this.refs.embedding;
        if (embedding &amp;&amp; !this.listening) {
            this.listening = true;
            embedding.addEventListener('pulse.accountselected', (event) =&gt; {
                this.selected = event.detail; // { recordId, name }
            });
        }
    }
    handleIndustryChange(event) {
        this.industry = event.detail.value;
    }
}
</pre>
<p><span>The host then renders </span><code>selected.recordId</code><span> </span><span>with </span><code>lightning-record-view-form</code><span>, so the details panel is plain Salesforce UI. Here’s the same page after a user picks Retail in the LWC filter and selects an account in the Angular app.</span></p>
<p><span style="font-weight: 400">Embedding supports internal, employee-facing apps built with React or Angular and served from the salesforce.app domain. You can add the host component to Lightning pages and Experience Cloud site pages.</span></p>
<h4><b>Package, manage, and monitor your apps</b></h4>
<ul>
<li style="font-weight: 400"><b>Package with 2GP:</b><span style="font-weight: 400"> Distribute Multi-Framework apps with second-generation packaging (2GP). Use unlocked packages for apps you want to keep editable, or managed packages to distribute locked, namespaced internal apps through AppExchange.</span></li>
<li style="font-weight: 400"><b>Manage apps in App Manager:</b><span style="font-weight: 400"> From Setup, open App Manager and select </span><b>New App | New Multi-Framework App</b><span style="font-weight: 400">. Choose Builder Central to build from a natural language description, or Agentforce Vibes to work directly with code.</span></li>
<li style="font-weight: 400"><b>Analyze performance in Scale Center (Beta):</b><span style="font-weight: 400"> From Setup, open </span><b>Multi-Framework Insights (Beta)</b><span style="font-weight: 400"> and click </span><b>Generate Insight Report</b><span style="font-weight: 400">. The dashboard shows key metrics for up to ten apps with the most page views over the last seven days.</span></li>
</ul>
<h3><b>Headless Experience Layer (Beta)</b></h3>
<p><span style="font-weight: 400">The </span><a href="https://developer.salesforce.com/docs/platform/hxl/guide/get-started.html"><span style="font-weight: 400">Headless Experience Layer (HXL)</span></a><span style="font-weight: 400"> lets you define a rich UI once, as a widget, and render it natively in Agentforce, Claude, and Slackbot. To show how it works, I built a snow report widget for an Agentforce agent. Four pieces of metadata connect the agent’s Apex action to the UI, and each piece refers to the next by name.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207104" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261002142317/image9-e1790976209534.png?w=1000" class="postimages" width="1000" height="783" alt="Diagram showing how an Apex action, an Agent Script action output, a custom Lightning type, and an HXL widget bundle reference each other by name, and the surfaces where the widget renders." />
			  </span>
			</p>
<p><span>First, the agent’s action calls the Apex invocable method and types its output with a custom Lightning type, </span><code>c__w27SnowReport</code><span>.</span></p>
<pre language="yaml">Get_Snow_Report:
   description: "Returns today's snow report for a resort"
   inputs:
      resort: string
         is_required: True
   outputs:
      report: object
         complex_data_type_name: "c__w27SnowReport"
         is_displayable: True
   target: "apex://W27_SnowReportService"
</pre>
<p><span>The Lightning type is a </span><code>LightningTypeBundle</code><span> with two files. Its </span><code>schema.json</code><span> binds the type to the </span><code>Report</code><span> Apex class that the action returns.</span></p>
<pre language="json">{
  "title": "Snow Report",
  "lightning:type": "@apexClassType/c__W27_SnowReportService$Report"
}
</pre>
<p><span>Its </span><code>renderer.json</code><span> points to the </span><span>w27SnowReport</span><span> </span><span>widget and maps each field of the Apex class to a widget attribute.</span></p>
<pre language="json">{
  "renderer": {
    "componentOverrides": {
      "$": {
        "definition": "@widget/c/w27SnowReport",
        "attributes": {
          "resort": "{!$attrs.resort}",
          "conditions": "{!$attrs.conditions}",
          "newSnowCm": "{!$attrs.newSnowCm}",
          "baseDepthCm": "{!$attrs.baseDepthCm}",
          "liftsOpen": "{!$attrs.liftsOpen}",
          "liftsTotal": "{!$attrs.liftsTotal}"
        }
      }
    }
  }
}
</pre>
<p><span>The </span><span>w27SnowReport</span><span> </span><span>widget itself is a </span><code>UiWidgetBundle</code><span>. Its composition file lays out HXL components and binds them to those attributes. Here’s an excerpt: the header, the lifts progress bar, and a button that sends a follow-up message to the agent.</span></p>
<pre language="json">{ "definition": "tile/text", "attributes": { "text": "{!$attrs.resort}", "variant": "h2" } },
{ "definition": "tile/badge", "attributes": { "label": "{!$attrs.conditions}", "variant": "success" } },
{ "definition": "tile/progress",
  "attributes": { "label": "Lifts open", "value": "{!$attrs.liftsOpen}", "max": "{!$attrs.liftsTotal}", "size": "md" } },
{ "definition": "tile/button",
  "attributes": {
    "label": "Show lift hours",
    "actions": { "click": [
      { "definition": "action/sendMessage", "attributes": { "content": "What are the lift hours today?" } }
    ] }
  } }
</pre>
<p><span style="font-weight: 400">Here’s the result in the Agentforce Builder preview. The action returns structured data, and the agent shows it as an interactive card instead of plain text.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207105" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261002142424/image1_ce14e8-e1790976276375.png?w=1000" class="postimages" width="1000" height="682" alt="Agentforce Builder preview where a snow report request returns an HXL widget card for Glacier Peak with a Powder day badge, new snow and base depth values, a lifts open progress bar, and a Show lift hours button, next to a trace that shows the Get Snow Report action." />
			  </span>
			</p>
<p><span>To try it, turn on </span><b><span>Headless Experience Layer Settings</span></b><span> in Setup, and deploy the widget and the Lightning type with </span><code>sf project deploy start</code><span>. To use the same widget in Claude or Slackbot, add the action as a tool on a </span><a href="https://developer.salesforce.com/docs/platform/hxl/guide/hxl-mcp-channels.html"><u>custom MCP server</u></a><span>. You can also explore components in the </span><a href="https://www.headlessexperiencelayer.com/playground/"><u>HXL Playground</u></a><span>.</span></p>
<h3><b>Salesforce MCP updates</b></h3>
<p><span style="font-weight: 400">Model Context Protocol (MCP) now works in both directions on Salesforce, through </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide"><span style="font-weight: 400">Salesforce Hosted MCP Servers</span></a><span style="font-weight: 400"> and Agentforce. Your Agentforce agents can call MCP tools, and external AI clients can call your agents as MCP tools. Here’s what’s new.</span></p>
<h4><b>Expose your Agentforce agents as MCP tools</b></h4>
<p><span style="font-weight: 400">Make an Agentforce agent available to any MCP client, such as Claude, ChatGPT, or Cursor, by adding it as a tool on a </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/agentforce.html"><span style="font-weight: 400">custom MCP server</span></a><span style="font-weight: 400">. The external client sends a request, and your agent handles it with its own instructions, actions, and knowledge sources. Every call runs as the authenticated user, so sharing rules, field-level security, and Einstein Trust Layer policies still apply.</span></p>
<p><span style="font-weight: 400">To expose an agent:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">In Setup, go to </span><b>Integration &gt; Salesforce MCP Servers</b><span style="font-weight: 400"> and open your custom server.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Add a tool, and select </span><b>Agentforce Agent</b><span style="font-weight: 400"> as the backing type.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Choose the agent, then give the tool a name and a description.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Publish the server.</span></li>
</ol>
<p><span style="font-weight: 400">The agent’s required inputs become the tool’s parameter schema. Only agents built with the new Agent Script Builder can be exposed, so upgrade legacy agents first. </span></p>
<p><b>Tip:</b><span style="font-weight: 400"> External models choose tools based only on the name and description. Make each tool task-specific. For example, one sales agent can back a “Schedule Customer Meeting” tool and a “Forecast Deal” tool instead of a single generic “Sales Agent” tool. Update the description whenever the agent’s instructions change.</span></p>
<h4><b>Add MCP server tools to your Agentforce agents</b></h4>
<p><span style="font-weight: 400">Going the other way, you can now add </span><a href="https://agentexchange.salesforce.com/"><span style="font-weight: 400">AgentExchange</span></a><span style="font-weight: 400"> MCP server tools to an agent directly in Agentforce Builder. The Actions modal lists MCP server tools next to standard agent actions. Use the </span><b>MCP Server Tools</b><span style="font-weight: 400"> filter to find them quickly.</span></p>
<p><span style="font-weight: 400">Two related updates make the right server easier to find and manage:</span></p>
<ul>
<li style="font-weight: 400"><b>Agentforce Registry:</b><span style="font-weight: 400"> Each MCP server on AgentExchange has its own card that shows its tools and the solutions it belongs to. From Setup, open </span><b>Agentforce Registry</b><span style="font-weight: 400">, select </span><b>Registered MCP Servers</b><span style="font-weight: 400">, and click </span><b>New+</b><span style="font-weight: 400"> to open AgentExchange and browse server cards.</span></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=platform.api_catalog.htm&amp;language=en_US"><b>API Catalog</b></a><b>:</b><span style="font-weight: 400"> Activate Salesforce hosted MCP servers for Agentforce and supported external MCP clients, and register external MCP servers, including MuleSoft.</span></li>
</ul>
<h4><b>Automate Marketing Cloud Engagement with 40 new MCP tools</b></h4>
<p><span style="font-weight: 400">The </span><a href="https://developer.salesforce.com/blogs/2026/06/the-mcp-server-for-marketing-cloud-engagement-is-now-ga"><span style="font-weight: 400">Marketing Cloud Engagement MCP server</span></a><span style="font-weight: 400"> adds 40 tools, so your AI agent can handle more day-to-day operations in natural language. New tools create and run Automation Studio activities, manage campaigns and Content Builder folders, upsert bulk data, query data extensions, erase contacts, and retrieve email tracking events.</span></p>
<h3><b>Salesforce CLI updates</b></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</span></a><span style="font-weight: 400"> ships every week, so a lot has landed since Summer ’26. Here are five updates worth acting on now.</span></p>
<h4><b>Move off the temporary secrets workaround</b></h4>
<p><span>Commands such as </span><code>org display</code><span> and </span><code>org list --json</code><span> no longer print access tokens, SFDX auth URLs, or passwords. The </span><code>SF_TEMP_SHOW_SECRETS=true</code><span> workaround that restores the old output goes away on </span><b><span>October 28, 2026</span></b><span>. When a script needs a secret, ask for it explicitly.</span></p>
<pre language="sh">sf org auth show-access-token --target-org my-org
sf org auth show-sfdx-auth-url --target-org my-org
sf org auth show-user-password --target-org my-org
</pre>
<p><span>Check your CI jobs now. A script that reads </span><code>accessToken</code><span> from </span><code>org display --json</code><span> gets </span><code>[REDACTED]</code><span> instead of a token, and the failure shows up later as a 401 error.</span></p>
<h4><b>Scaffold async Apex with built-in best practices</b></h4>
<p><span>New </span><code>Batchable</code><span> and </span><code>Queueable</code><span> values for the </span><code>--template</code><span> flag generate async Apex that follows platform best practices.</span></p>
<pre language="sh">sf template generate apex class --name ProcessAccounts --template Batchable
sf template generate apex class --name SendEmail --template Queueable
</pre>
<p><span>The </span><code>Batchable</code><span> template uses a typed scope, an inline SOQL query, and </span><code>Database.RaisesPlatformEvents</code><span>. The </span><code>Queueable</code><span> template attaches a </span><code>Finalizer</code><span>, so your post-job logic runs even after an unhandled exception.</span></p>
<pre language="apex">public with sharing class SendEmail implements Queueable, Finalizer {

    public void execute(QueueableContext context) {
        System.attachFinalizer(this);
        // TODO: implement job logic here
    }

    public void execute(FinalizerContext context) {
        if (context.getResult() == ParentJobResult.UNHANDLED_EXCEPTION) {
           // handle failure
        }
    }
}
</pre>
<h4><b>Control Apex debug logs from the command line</b></h4>
<p><span>Set the debug log level when you run anonymous Apex. Use </span><code>--debug-level</code><span> for a predefined level, or </span><code>--category-level</code><span> for per-category control.</span></p>
<pre language="sh">sf apex run --file test.apex --debug-level DETAIL
sf apex run --file test.apex --category-level Apex_code=FINEST --category-level Db=FINE
</pre>
<p><span>New </span><code>apex trace</code><span> commands to create, list, and delete trace flags are coming next in the release candidate.</span></p>
<h4><b>Set up scratch orgs for realistic testing</b></h4>
<p><span style="font-weight: 400">Assign a role to a new scratch org user with </span><span style="font-weight: 400">roleDeveloperName</span><span style="font-weight: 400"> in the user definition file.</span></p>
<pre language="json">{
  "Username": "w27.presenter@winter27demos.example",
  "LastName": "Presenter",
  "Email": "w27.presenter@winter27demos.example",
  "profileName": "Standard User",
  "roleDeveloperName": "W27_Demo_Presenter"
}
</pre>
<p><span style="font-weight: 400">To let Apex tests send email to unverified domains, enable </span><span style="font-weight: 400">enableSubstituteFromAddress</span><span style="font-weight: 400"> in your scratch org definition file.</span></p>
<pre language="json">{
  "settings": {
    "emailAuthorizationSettings": {
      "enableSubstituteFromAddress": true
    }
  }
}
</pre>
<h4><b>Skip the source-tracking scan on large projects</b></h4>
<p><span>When your org is already in sync with your project, such as an org pre-seeded with your metadata, set </span><code>SF_SOURCE_TRACKING_ASSUME_SYNCED</code><span> to skip the local scan before deploys and retrieves. On very large projects, this can save some time.</span></p>
<pre language="sh">export SF_SOURCE_TRACKING_ASSUME_SYNCED=true
sf project deploy start --target-org my-org
</pre>
<p><span style="font-weight: 400">Tip: Use it with extra care. If the org isn’t really in sync, it drifts further out of sync.</span></p>
<p><span>Also new: the </span><code>api request rest</code><span> and </span><code>api request graphql</code><span> commands are generally available, and Salesforce CLI now requires Node.js 22 or later. See the </span><a href="https://github.com/forcedotcom/cli/blob/main/releasenotes/README.md"><u>Salesforce CLI release notes</u></a><span> for every command and flag.</span></p>
<h3><b>Salesforce Platform API updates</b></h3>
<p><span style="font-weight: 400">Winter ’27 ships API version 68.0. Two changes need action from integration owners now, and two make everyday API work easier.</span></p>
<h4><b>Assign Use Any API Auth before December 1, 2026</b></h4>
<p><span>Beginning December 1, 2026, every user who authenticates with </span><a href="https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_calls_login.htm"><u>SOAP API <code>login()</code></u></a><span> needs the </span><b><span>Use Any API Auth</span></b><span> user permission. This applies to all orgs. Users without it get an error. Test the change in advance with the release update in Setup.</span></p>
<p><span>This is a step toward the bigger change. SOAP </span><code>login()</code><span> in API versions 31.0 through 64.0 </span><a href="https://help.salesforce.com/s/articleView?id=005132110&amp;type=1&amp;language=en_US"><u>retires in Summer ’27</u></a><span>. Move those integrations to OAuth with </span><a href="https://help.salesforce.com/s/articleView?id=xcloud.external_client_apps.htm&amp;type=5&amp;language=en_US"><u>external client apps</u></a><span>.</span></p>
<h4><b>Plan for the retirement of API versions 31.0 through 40.0</b></h4>
<p><span>SOAP API, REST API, and Bulk API versions 31.0 through 40.0 are being </span><a href="https://help.salesforce.com/s/articleView?id=000380623&amp;type=1&amp;language=en_US"><u>retired</u></a><span>. For REST, this covers every URI under </span><code>/services/data/vXX.X/</code><span>.</span></p>
<ul>
<li style="font-weight: 400"><b>Summer ’27 (deprecation):</b><span style="font-weight: 400"> These versions stop getting security updates and bug fixes.</span></li>
<li style="font-weight: 400"><b>Summer ’28 (retirement):</b><span style="font-weight: 400"> Calls to these versions fail.</span></li>
</ul>
<p><span style="font-weight: 400">Search your integrations, scripts, and middleware for hard-coded versions in this range, and move them to a current version.</span></p>
<h4><b>Use latest instead of a numbered REST API version</b></h4>
<p><span>Replace the version segment in a REST API URI with </span><code>latest</code><span>. Salesforce routes the request to the most recent version that your org supports. To see which version </span><code>latest</code><span> resolves to, call the </span><a href="https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_versions.htm"><u>List Available REST API Versions</u></a><span> resource.</span></p>
<pre language="sh">sf api request rest "/services/data/latest/sobjects/Account" --target-org my-org
</pre>
<p><span>In our Winter ’27 org, </span><code>latest</code><span> resolved to v68.0. Use it for scripts and internal tools. Keep a pinned version for production integrations that depend on a specific contract.</span></p>
<h4><b>Monitor composite API requests with EventLogFile</b></h4>
<p><span>Query the </span><code>EventLogFile</code><span> object for the new </span><a href="https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_eventlogfile_composite_api.htm"><u><code>CompositeApi</code></u></a><span> and </span><code>CompositeApiSubrequest</code><span> event types. You get details for each composite and composite graph request, and for each subrequest. That helps when a composite call succeeds overall but one subrequest fails.</span></p>
<pre language="sql">SELECT EventType, LogDate, LogFileLength
FROM EventLogFile
WHERE EventType IN ('CompositeApi', 'CompositeApiSubrequest')
ORDER BY LogDate DESC
</pre>
<h2><b>LWC updates</b></h2>
<p><span style="font-weight: 400">Winter ’27 brings two Lightning Web Components (LWC) features to general availability and fills a few gaps for state managers and console apps. Upgrade your components to </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/get-started-api-versioning.html"><span style="font-weight: 400">LWC API version 68.0</span></a><span style="font-weight: 400"> to pick up the latest behavior. According to the release notes, version 68.0 has no version-specific changes, so it’s an easy upgrade.</span></p>
<h3><b>Write logic in templates with complex template expressions (GA)</b></h3>
<p><span style="font-weight: 400">You no longer need a getter just to join strings, count items, or pick a CSS class. Write a subset of JavaScript directly in the template with </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-components-html-expressions.html"><span style="font-weight: 400">template expressions</span></a><span style="font-weight: 400">. Template expressions require API version 66.0 or later.</span></p>
<pre language="html">&lt;template&gt;
    &lt;p&gt;{`Hello, ${firstName} ${lastName}!`}&lt;/p&gt;
    &lt;p&gt;{isLoggedIn ? 'Welcome back!' : 'Please log in'}&lt;/p&gt;
    &lt;p&gt;Theme: {user?.profile?.settings?.theme ?? 'default'}&lt;/p&gt;
    &lt;p&gt;In-stock: {items.filter(item =&gt; item.qty &gt; 0).length} of {items.length}&lt;/p&gt;
    &lt;ul&gt;
        &lt;template for:each={items} for:item=&quot;item&quot;&gt;
            &lt;li key={item.name} class=&quot;{item.qty &gt; 0 ? 'slds-text-color_success' : 'slds-text-color_error'}&quot;&gt;
                {item.name} ({item.qty})
            &lt;/li&gt;
        &lt;/template&gt;
    &lt;/ul&gt;
&lt;/template&gt;
</pre>
<p><span style="font-weight: 400">Keep these rules in mind:</span></p>
<ul>
<li><b><span>Quote attribute expressions:</span></b><span> The compiler requires double quotes around an expression in an attribute value, as in </span><code><span>class="{...}"</span></code><span>.</span></li>
<li><b><span>Keep expressions simple:</span></b><span> You can’t use </span><code><span>this</span></code><span>, </span><code><span>new</span></code><span>, or function declarations in an expression.</span></li>
<li><b><span>Iterate over expressions:</span></b><span> </span><code><span>for:each</span></code><span> accepts a quoted expression that returns an array, such as </span><code><span>for:each="{items.filter(item =&gt; item.qty &gt; 0)}"</span></code><span>. Each item still needs a unique </span><code><span>key</span></code><span>.</span></li>
</ul>
<h3><b>Use third-party web components with lwc:external (GA)</b></h3>
<p><span>Use an existing </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-use-third-party-components.html"><u>third-party web component</u></a><span> library in LWC without rewriting it or wrapping it in an iframe. Load the library, then add </span><code>lwc:external</code><span> to the custom element so it renders as a native web component.</span></p>
<p><span>Our example uses </span><a href="https://gist.github.com/msrivastav13/691a13d17394d5b3f42be7e4d3bf5dbf"><u><code><span>w27-rating-stars</span></code></u></a><span>, a plain, framework-free custom element packaged as an IIFE and uploaded as a static resource named </span><code><span>w27RatingStars</span></code><span>. It knows nothing about LWC. It reads </span><code><span>value</span></code><span> and </span><code><span>max</span></code><span> attributes and fires a standard </span><code><span>rate</span></code><span> event. In your LWC, load the static resource and render the element with </span><code><span>lwc:external</span></code><span>.</span></p>
<pre language="html">&lt;template&gt;
    &lt;template lwc:if={libraryLoaded}&gt;
        &lt;w27-rating-stars lwc:external value={rating} max=&quot;5&quot; lwc:spread={starProps} onrate={handleRate}&gt;
        &lt;/w27-rating-stars&gt;
    &lt;/template&gt;
&lt;/template&gt;
</pre>
<pre language="javascript">import { LightningElement } from 'lwc';
import { loadScript } from 'lightning/platformResourceLoader';
import RATING_STARS from '@salesforce/resourceUrl/w27RatingStars';

export default class ExternalComponent extends LightningElement {
    rating = 4;
    starProps = { 'data-qa': 'product-rating' };
    libraryLoaded = false;

    async connectedCallback() {
        await loadScript(this, RATING_STARS);
        this.libraryLoaded = true;
    }

    handleRate(event) {
        this.rating = event.detail.value;
    }
}
</pre>
<p><b><span>Note:</span></b><span> Third-party web components require Lightning Web Security. Load libraries as IIFE or UMD bundles, because </span><code>loadScript</code><span> doesn’t support ES modules. On an </span><code>lwc:external</code><span> element, LWC passes values as attributes by default and sets properties only when the custom element defines them.</span></p>
<p><span>Here’s the </span><a href="https://gist.github.com/msrivastav13/691a13d17394d5b3f42be7e4d3bf5dbf"><u><code>w27-rating-stars</code></u></a><span> component rendered inside a Lightning web component.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207106" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261002142503/image8.png?w=964" class="postimages" width="964" height="358" alt="A product rating widget built from the third-party w27-rating-stars web component, rendered inside a Lightning web component and showing four of five stars." />
			  </span>
			</p>
<h3><b>Refresh state manager data with refresh()</b></h3>
<p><span>Some state managers serve data without a single record ID, such as related list records. You can now call </span><code>refresh()</code><span> on these managers to pull fresh data after an update. You don’t need to reload the page or create a new manager. Ensure the current status is </span><code>loaded</code><span> before calling it. The status remains </span><code>loaded</code><span> during execution, and the returned promise resolves once </span><code>data</code><span> populates with the latest results.</span></p>
<pre language="javascript">import { LightningElement, api } from 'lwc';
import relatedListRecords from 'lightning/stateManagerRelatedListRecords';

export default class OpportunityList extends LightningElement {
    @api recordId;
    related = relatedListRecords({
        parentRecordId: undefined,
        relatedListId: 'Opportunities',
        fields: ['Opportunity.Name']
    });

    connectedCallback() {
        this.related.value.setParentRecordId(this.recordId); // load the Account's Opportunities
    }

    async handleRefresh() {
        const sm = this.related.value;
        if (sm.status === 'loaded') {
            await sm.refresh(); // rejects if the status isn't loaded
        }
    }
}
</pre>
<p><b><span>Note:</span></b><span> </span><code>status</code><span>, </span><code>data</code><span>,</span><span> and </span><code>refresh()</code><span> live on </span><code>related.value</code><span>, not on </span><code>related</code><span>.</span></p>
<h2><b>Apex updates</b></h2>
<p><span style="font-weight: 400">Winter ’27 gives Apex more room to work and better ways to test and inspect your code. Here are the highlights, with examples.</span></p>
<h3><b>Handle larger payloads with higher heap limits</b></h3>
<p><span style="font-weight: 400">The </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm"><span style="font-weight: 400">Apex heap limit</span></a><span style="font-weight: 400"> increases from 6 MB to 10 MB for synchronous transactions and from 12 MB to 25 MB for asynchronous transactions. The new limits are turned on automatically. Deploying from a Winter ’27 sandbox to a Summer ’26 production org? In the sandbox, open </span><b>Apex Settings</b><span style="font-weight: 400"> in Setup and select </span><b>Enforce the Summer ’26 Apex heap limit</b><span style="font-weight: 400"> until production upgrades.</span></p>
<h3><b>Test real callouts with Apex integration tests (Developer Preview)</b></h3>
<p><span>Unit tests can’t make real callouts, so a mock can hide a broken named credential or payload. </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_integration_testing.htm"><u>Integration tests</u></a><span> let you call the real service from a scratch org. They aren’t available in production orgs or sandboxes, and they don’t count toward code coverage. Turn on the </span><code>ApexIntegrationTests</code><span> feature in your scratch org definition file, then annotate the class and its test methods with </span><code>@IntegrationTest</code><span>.</span></p>
<pre language="apex">@IntegrationTest
public with sharing class W27_ExternalServiceIntegrationTest {
    @BeforeClass
    static void setup() {
        insert as user new Account(Name = 'W27 PaymentTestAccount');
    }
    @IntegrationTest
    static void testExternalServiceCallout() {
        Account a = [SELECT Id FROM Account WHERE Name = 'W27 PaymentTestAccount' WITH USER_MODE LIMIT 1];
        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:W27_Echo/get?status=verified&amp;account=' + a.Id);
        req.setMethod('GET');
        HttpResponse res = new Http().send(req);
        Assert.areEqual(200, res.getStatusCode());
        Assert.isTrue(res.getBody().contains('verified'));
    }
    @TearDown
    static void tearDown() {
        delete [SELECT Id FROM Account WHERE Name = 'W27 PaymentTestAccount'];
    }
}
</pre>
<p><span>Integration tests don’t roll back data, so clean up in </span><code>@TearDown</code><span>. </span><span>It runs after each test method, so with several methods, delete only per-method data there. Only one integration test class can run per org at a time, and runs are asynchronous only. Run them with </span><code>sf apex run test</code><span>, without </span><code>--synchronous</code><span>. If you register </span><code>Test.setMock()</code><span>, the mock still wins.</span></p>
<h3><b>Compare two fields in SOQL with FORMULA() (Beta)</b></h3>
<p><span>Compare fields directly in a </span><code>WHERE</code><span> clause with </span><a href="https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_formula.htm"><u><code>FORMULA()</code></u></a><span> instead of creating a formula field just for a query. </span><code>FORMULA()</code><span> requires API version 68.0 and isn’t available in production orgs.</span></p>
<pre language="apex">List&lt;Opportunity&gt; openBalances = Database.query(
    'SELECT Name, Amount_To_Pay__c, Amount_Paid__c FROM Opportunity ' +
    'WHERE FORMULA(\'Amount_To_Pay__c - Amount_Paid__c\') &gt; 0 ' +
    'WITH USER_MODE ORDER BY Name'
);
</pre>
<h3><b>Prevent field name collisions in managed package queries</b></h3>
<p><span>If a subscriber creates a field with the same API name as a field in your managed package, dynamic SOQL in your package can resolve the wrong field. Build a </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_Database_QueryOptions.htm"><u><code>Database.QueryOptions</code></u></a><span> object with </span><code>withExplicitNamespace(true)</code><span>, and bind it with a </span><a href="https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_set_options.htm"><u><code>SET OPTIONS</code></u></a><span> clause. For details, see </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_manpkgs_dynamic_soql.htm"><u>Prevent Field Name Collisions in Managed SOQL Queries</u></a><span>.</span></p>
<pre language="apex">Database.QueryOptions options = Database.QueryOptions.builder()
    .withExplicitNamespace(true)
    .build();
List&lt;SObject&gt; rows = Database.queryWithBinds(
    'SELECT Id, Name FROM Opportunity WHERE Name LIKE :prefix LIMIT 5 SET OPTIONS :options',
    new Map&lt;String, Object&gt;{ 'prefix' =&gt; 'W27%', 'options' =&gt; options },
    AccessLevel.USER_MODE
);
</pre>
<p><b><span>Note:</span></b><span> Put </span><code>SET OPTIONS</code><span> after the other clauses, including </span><code>WHERE</code><span>, </span><code>ORDER BY</code><span>, </span><code>LIMIT</code><span>, </span><span>and</span><span> </span><code>OFFSET</code><span>. Only </span><code>FOR UPDATE</code><span> can follow it.</span></p>
<h3><b>Look up Apex types with the Apex Symbol API (Beta)</b></h3>
<p><span>The </span><a href="https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/intro_rest_resources_apex_symbols.htm"><u>Apex Symbol API</u></a><span> is a Tooling API resource that returns the same type information the Apex compiler uses, for built-in, custom, packaged, and dynamic types. Use it to power code completion or to ground a coding agent before it writes Apex. Set </span><code>category</code><span> to </span><code>builtin</code><span>, </span><code>database</code><span>, </span><span>or </span><code>dynamic</code><span>, and narrow the results with </span><code>namespace</code><span> and </span><code>name</code><span>. The API handles one request per org at a time.</span></p>
<pre language="sh">sf api request rest "/services/data/v68.0/tooling/symbols?category=builtin&amp;namespace=Database&amp;name=QueryOptionsBuilder" --target-org my-org
</pre>
<h3><b>Page through Data 360 objects with Apex cursors (Beta)</b></h3>
<p><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_cursors.htm"><span style="font-weight: 400">Apex cursors</span></a><span style="font-weight: 400"> now work with Data 360 Data Model Objects (DMOs). Fetch any slice of a large result set, forward or backward, without Batch Apex. In one of my orgs with Data 360, the same pattern pages through more than 1.4 million rows successfully.</span></p>
<pre language="apex">Database.Cursor cursor = Database.getCursor(
    'SELECT device_id__c, event_ts__c, power_draw_w__c FROM Radiant_Reading__dlm ORDER BY event_ts__c DESC'
);
Integer total = cursor.getNumRecords();
List&lt;SObject&gt; lastPage = cursor.fetch(Math.max(total - 50, 0), 50); // jump straight to the end
</pre>
<p><span>DMO queries can consume Data Services credits from your Data 360 subscription. Track usage with </span><code>Limits.getDataCloudCursors()</code><span> and </span><code>Limits.getDataCloudCursorRows()</code><span>. Cursors expire 24 hours after you create them.</span></p>
<h3><b>More Apex updates</b></h3>
<ul>
<li><a href="https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/intro_rest_resources_testing_discovery.htm"><b><u><span>Test Discovery API</span></u></b></a><b><span>:</span></b><span> Filter results with the </span><code><span>testLevel</span></code><span> parameter, such as </span><code><span>RunLocalTests</span></code><span>. In API version 68.0, </span><code><span>testLevel</span></code><span> replaces </span><code><span>showAllMethods</span></code><span>.</span></li>
<li><b><span>Recompile only invalid Apex:</span></b><span> In Setup, use </span><b><span>Compile only invalid classes</span></b><span> or </span><b><span>Compile only invalid triggers</span></b><span> instead of recompiling the whole org. To get the errors programmatically, call the Tooling API </span><a href="https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/intro_rest_resources_recompile_invalid_apex.htm"><u><span>apexCompileResults</span></u></a><span> resource.</span></li>
<li><b><span>Old API version warnings:</span></b><span> The compiler now warns about classes and triggers saved at API versions 9.0 through 19.0, which will be retired. Move them to </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_version_settings_api.htm"><u><span>API version 20.0 or later</span></u></a><span>.</span></li>
<li><b><span>Elastic limits for Batch Apex (Beta):</span></b><span> </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_limits_elastic_limits.htm"><u><span>Elastic limits</span></u></a><span> now cover Batch jobs, in addition to Queueable jobs and future methods. Jobs keep running above the rolling 24-hour async limit, and you can test this in non-production orgs.</span></li>
</ul>
<h2><b>Agentforce monthly updates</b></h2>
<p><span style="font-weight: 400">Agentforce ships updates every month, and the August and September updates arrive with Winter ’27. This round gives </span><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 lot of new language features. It also makes multi-agent solutions generally available and simplifies how you move agents between orgs.</span></p>
<h3><b>Write more expressive agents with new Agent Script language features</b></h3>
<p><span style="font-weight: 400">Agent Script now handles more logic without an extra action or a prompt workaround.</span></p>
<ul>
<li><b><span>New </span></b><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/ascript-ref-expressions.html"><b><u><span>expression functions</span></u></b></a><b><span>:</span></b><span> </span><code><span>lower()</span></code><span>, </span><code><span>upper()</span></code><span>, </span><code><span>to_json()</span></code><span>, </span><code><span>from_json()</span></code><span>, </span><span>and</span><span> </span><code><span>json_path(obj, path, default)</span></code><span>.</span><br />
<b></b></li>
<li><b><code><span>else if</span></code><span> chains:</span></b><span> Branch on more than two conditions. Nested </span><code><span>if</span></code><span> statements and </span><code><span>elif</span></code><span> still aren’t supported.</span></li>
<li><b><span>New </span></b><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/ascript-ref-variables-system.html"><b><u><span>system variables</span></u></b></a><b><span>:</span></b><span> </span><code><span>@system_variables.current_connection</span></code><span> and </span><code><span>@system_variables.current_modality</span></code><span> tell your agent where and how it’s talking to the user.</span></li>
<li><b><span>Deterministic escalation:</span></b><span> A top-level </span><code><span>escalate</span></code><span> statement hands the conversation to a human when a condition is met. The example below uses each of these features.</span></li>
</ul>
<pre language="yaml">before_reasoning:
   set @variables.tier = upper(@variables.tier_input)
   set @variables.first_sku = json_path(from_json(@variables.order_json), "$.items[0].sku", "none")
   if @variables.score &gt;= 90:
      set @variables.band = "high"
   else if @variables.score &gt;= 70:
      set @variables.band = "medium"
   else:
      set @variables.band = "low"
reasoning:
   instructions: -&gt;
      | Recent tags: {!@variables.recent_tags[0:2]}. You're on {!@system_variables.current_modality}.
after_reasoning:
   if @variables.needs_human:
      escalate
</pre>
<h3><b>Format action output per connection</b></h3>
<p><span>Tell your agent exactly how to format an action’s output on each connection. Add a </span><code>when @connection.&lt;name&gt;</code><span> block to the action, and pick a response format with </span><code>render</code><span>. The new built-in </span><code>json</code><span> format sends the action’s structured output directly to the client. Add </span><code>show_and_return: True</code><span> to send the response and end the turn.</span></p>
<pre language="yaml">connection messaging:
   label: "Messaging"
subagent menu:
   reasoning:
      actions:
         get_menu: @actions.Get_Menu_For_Restaurant
            with restaurant=...
            when @connection.messaging
               render: @response_formats.json
                  show_and_return: True
</pre>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207107" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261002142621/image6-e1790976394297.png?w=1000" class="postimages" width="1000" height="625" alt="Agentforce Builder Script view showing a messaging connection and a render rule that formats an action output as JSON." />
			  </span>
			</p>
<p><span style="font-weight: 400">The compiler checks that each format belongs to its connection. After we published the agent, the rule appeared as a messaging render rule that returns JSON and ends the turn. To shape replies with natural language instead, add </span><a href="https://help.salesforce.com/s/articleView?id=ai.agent_connection_instructions.htm&amp;language=en_US"><span style="font-weight: 400">connection instructions</span></a><span style="font-weight: 400"> to the Messaging, Service Email, or Marketing Email connection.</span></p>
<h3><b>Accept file uploads in agent conversations</b></h3>
<p><span style="font-weight: 400">Service and Employee agents can now accept screenshots, receipts, and PDFs on Enhanced Chat v1, Enhanced Chat v2, and Lightning Experience. Your agent can read the file, attach it to a record, or use it as context. This feature is rolling out starting the week of September 21, 2026.</span></p>
<p><span>Turn it on in the agent’s </span><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/ascript-blocks.html#file-upload-sub-block"><u><code>config</code><span> block</span></u></a><span>. The mode can be </span><code>auto</code><span> (the default), </span><code>managed</code><span>, </span><code>disabled</code><span>, or </span><code>error</code><span>. In </span><code>managed</code><span> mode, uploaded files land in </span><code>@system_variables.uploaded_files</code><span>, so your script decides what to do with them.</span></p>
<pre language="yaml">config:
   developer_name: "W27_Channels_Lab"
   file_upload:
      mode: "managed"
   runtime:
      streaming: True
</pre>
<p><span>The new </span><code>runtime</code><span> block shown here also lets you set runtime behavior, such as streaming and citations, per agent.</span></p>
<h3><b>Delegate to specialist agents with Multi-Agent Orchestration (GA)</b></h3>
<p><a href="https://help.salesforce.com/s/articleView?id=ai.agent_multi_orch.htm&amp;type=5&amp;language=en_US"><span style="font-weight: 400">Multi-Agent Orchestration</span></a><span style="font-weight: 400"> is generally available. Connect an orchestrator agent to specialized agents in your org, and let each agent do what it does best. Declare the specialist as a </span><span style="font-weight: 400">connected_subagent</span><span style="font-weight: 400">, then hand off to it like any other subagent.</span></p>
<pre language="yaml">start_agent concierge:
   reasoning:
      instructions: -&gt;
         | Answer resort questions yourself. Hand off menu questions to the menu specialist.
      actions:
         ask_menu_specialist: @utils.transition to @connected_subagent.Menu_Specialist
            description: "Hand off restaurant menu and food questions"

connected_subagent Menu_Specialist:
   target: "agent://W27_Menu_Specialist"
   description: "Answers questions about restaurant menus"
   delegate_escalation: True
</pre>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207108" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261002142718/image5-e1790976450145.png?w=1000" class="postimages" width="1000" height="625" alt="Agentforce Builder canvas showing a concierge agent connected to a Menu Specialist connected subagent." />
			  </span>
			</p>
<p><span style="font-weight: 400">In our test example, a menu question moved from the concierge to the specialist, which called its Apex action and answered. A question about ski lift hours stayed with the concierge.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207109" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261002142829/image2_ad9a17-e1790976521781.png?w=1000" class="postimages" width="1000" height="625" alt="Agentforce Builder preview where the trace shows the concierge transitioning to the Menu Specialist connected subagent, which answers with the restaurant menu." />
			  </span>
			</p>
<h3><b>Deploy agents between orgs with simplified metadata types</b></h3>
<p><span>Starting in API version 68.0, an agent is described by two </span><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/agent-dx-metadata.html"><u>metadata types</u></a><span>: </span><code>AiAgentDefinition</code><span> and </span><code>AiAgentDefinitionVersion</code><span>. You no longer list every planner, plugin, Apex class, flow, and prompt template by hand. Retrieve a version with its dependencies in one command.</span></p>
<pre language="sh">sf project retrieve start --metadata "AiAgentDefinitionVersion:W27_Channels_Lab#1" --root-type-with-dependencies AiAgentDefinitionVersion
</pre>
<p><span style="font-weight: 400">In our org, this returned the agent definition, the version, its Agent Script authoring bundle, and the Apex class behind its action. Both orgs must be on API version 68.0. Until production upgrades, keep using the previous metadata types to deploy from a Winter ’27 sandbox.</span></p>
<p><b>Also new:</b><span style="font-weight: 400"> Compare and merge agent versions in Agentforce Builder, and the Agentforce platform is now turned on by default. Gemini 2.5 Pro, Flash, and Flash-Lite requests are rerouted to newer Gemini models on October 20, 2026, so test your prompts now.</span></p>
<p><span style="font-weight: 400">To see the latest Agent Script grammar, linter, and compiler, explore the open-source </span><a href="https://github.com/salesforce/agentscript"><span style="font-weight: 400">Agent Script repository</span></a><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400">Data 360 monthly updates</span></h2>
<p><span style="font-weight: 400">Data 360 ships updates as often as monthly, and changes included in Winter ’27 are generally listed under October 2026 in the </span><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_change_log.htm&amp;release=264&amp;type=5"><span style="font-weight: 400">release notes</span></a><span style="font-weight: 400">. Here are two key updates for developers.</span></p>
<h3><b>Measure RAG retriever quality with advanced retriever metrics</b></h3>
<p><span style="font-weight: 400">Retrievers find the content that grounds retrieval-augmented generation (RAG). To measure one, test it in the Retriever Playground in Advanced Mode with a question and a reference answer. Data 360 now returns a </span><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_ai_retriever_test_metrics.htm&amp;language=en_US"><span style="font-weight: 400">context precision score</span></a><span style="font-weight: 400">. Context precision measures how well the retriever ranks relevant chunks above irrelevant ones. A higher score means the relevant chunks are at the top. The metrics panel also shows the retrieval response time and the retrieved chunks.</span></p>
<p><span>To track retriever quality from code, query the AI Retriever Quality Metric DMO (</span><code>ssot__AiRetrieverQualityMetric__dlm</code><span>). Each row holds the context precision, answer relevancy, and faithfulness scores for a retriever request, along with the user utterance and the IDs of the retrieved context. This query averages the scores for each retriever over the last seven days.</span></p>
<pre language="apex">List&lt;AggregateResult&gt; scores = [
    SELECT ssot__RetrieverApiName__c retriever,
           AVG(ssot__ContextPrecisionScoreNumber__c) contextPrecision,
           AVG(ssot__AnswerRelevancyScoreNumber__c) answerRelevancy,
           AVG(ssot__FaithfulnessRelevancyScoreNumber__c) faithfulness,
           COUNT(Id) samples
    FROM ssot__AiRetrieverQualityMetric__dlm
    WHERE ssot__MetricCreatedTimestamp__c = LAST_N_DAYS:7
    GROUP BY ssot__RetrieverApiName__c
];
</pre>
<p><span style="font-weight: 400">Compare these averages before and after you change chunking, the search index, or the model, so you know which change moved the score. Testing retrievers consumes credits. For details, see </span><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_ai_retriever_test_billing.htm&amp;language=en_US"><span style="font-weight: 400">Billing Considerations for Testing Retrievers</span></a><span style="font-weight: 400">.</span></p>
<h3><b>Transform zero-copy data with Python code extensions</b></h3>
<p><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/author-custom-script.html"><span style="font-weight: 400">Code extension</span></a><span style="font-weight: 400"> runs your custom Python batch transforms in Data 360. Winter ’27 extends them to </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/read-zero-copy-sources.html"><span style="font-weight: 400">zero-copy data</span></a><span style="font-weight: 400">: a data lake object (DLO) backed by a zero-copy connector, or a data model object (DMO) mapped from that DLO. The connector reads the data in place on the external platform, so you skip the ingestion step and the cost of storing a copy. Your script needs no special configuration. It runs the same way on federated data as on ingested data.</span></p>
<p><span style="font-weight: 400">Write the script with the </span><a href="https://github.com/salesforcecli/plugin-data-code-extension"><span style="font-weight: 400">Code Extension plugin for Salesforce CLI (Beta)</span></a><span style="font-weight: 400">. The SDK hands you a </span><a href="https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.html"><span style="font-weight: 400">PySpark DataFrame</span></a><span style="font-weight: 400">. This script reads a DMO, averages signal strength for each device, and writes the result to another DMO.</span></p>
<pre language="python">from pyspark.sql.functions import avg, count, round

from datacustomcode.client import Client
from datacustomcode.io.writer.base import WriteMode


def main():
    client = Client()

    readings = client.read_dmo("Radiant_Reading__dlm")

    signal_by_device = readings.groupBy("device_id__c").agg(
        round(avg("signal_dbm__c"), 1).alias("avg_signal_dbm__c"),
        count("*").alias("reading_count__c"),
    )

    client.write_to_dmo(
        "Radiant_Device_Signal__dlm", signal_by_device, write_mode=WriteMode.OVERWRITE
    )


if __name__ == "__main__":
    main()
</pre>
<p><span>Create the package, run </span><code>scan</code><span> to fill in the read and write permissions in </span><code>config.json</code><span>, and then run the script locally against your org.</span></p>
<pre language="sh">sf plugins install @salesforce/plugin-data-code-extension
sf data-code-extension script init --package-dir ./radiant-script
sf data-code-extension script scan --entrypoint ./radiant-script/payload/entrypoint.py
sf data-code-extension script run --entrypoint ./radiant-script/payload/entrypoint.py --target-org my-org
</pre>
<p><span>A local run reads a sample of rows through the Query API and prints the output </span><a href="https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html"><u>DataFrame</u></a><span> instead of writing it, so you can test against real data safely. When the output looks right, zip and deploy the package with </span><code>sf data-code-extension script deploy</code><span>, and then run it from a batch data transform.</span></p>
<h2><b>Platform development tool updates</b></h2>
<p><span style="font-weight: 400">Winter ’27 brings a new in-browser IDE, Apex performance scans from your coding agent, and region-aware routing for Agentforce Vibes.Here’s what’s new.</span></p>
<h3><b>Write and run code in the browser with Web Console (GA)</b></h3>
<p><a href="https://developer.salesforce.com/blogs/2026/09/announcing-general-availability-web-console-your-modern-in-browser-ide"><span style="font-weight: 400">Web Console</span></a><span style="font-weight: 400"> is a browser-based IDE built on VS Code for Web and built into Salesforce, so there’s nothing to install. Open it from the Setup menu by selecting </span><b>Web Console</b><span style="font-weight: 400">, or click </span><b>Edit</b><span style="font-weight: 400"> on an Apex class or trigger in Setup.</span></p>
<p><span style="font-weight: 400">In Web Console, you can:</span></p>
<ul>
<li style="font-weight: 400"><b>Edit code:</b><span style="font-weight: 400"> Edit and save Apex, and view and edit Lightning web components with Org Browser.</span></li>
<li style="font-weight: 400"><b>Run Apex and SOQL:</b><span style="font-weight: 400"> Run anonymous Apex scripts, and build queries with SOQL Builder and see their query plans.</span></li>
<li style="font-weight: 400"><b>Test and debug:</b><span style="font-weight: 400"> Run Apex tests from the Testing panel, create a trace flag, and get debug logs.</span></li>
<li style="font-weight: 400"><b>Work with metadata:</b><span style="font-weight: 400"> Browse, retrieve, and deploy your org’s metadata.</span></li>
</ul>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207110" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261002142928/image11.png?w=1000" class="postimages" width="1000" height="553" alt="Web Console in the browser with a Lightning web component open in the editor and the Org Browser panel showing org metadata." />
			  </span>
			</p>
<p><span style="font-weight: 400">Apex is read-only in production orgs, so make your edits in a sandbox or other non-production org. Running queries requires the </span><b>Use Any API Client</b><span style="font-weight: 400"> user permission. See </span><a href="https://developer.salesforce.com/docs/platform/webconsole/guide/supported-orgs.html"><span style="font-weight: 400">supported editions</span></a><span style="font-weight: 400"> for availability.</span></p>
<p><span style="font-weight: 400">In Winter ’27, Developer Console doesn’t appear in the Setup menu by default. To bring it back, from Setup, enter </span><span style="font-weight: 400">Development</span><span style="font-weight: 400"> in the Quick Find box, select </span><b>Web Console</b><span style="font-weight: 400">, and set </span><b>Enable Developer Console</b><span style="font-weight: 400"> to Active. If you switch back, share your reasons in the </span><a href="https://github.com/forcedotcom/web-console-feedback/issues"><span style="font-weight: 400">Web Console feedback repository</span></a><span style="font-weight: 400">.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207111" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261002143039/image3.png?w=1000" class="postimages" width="1000" height="445" alt="The Web Console page in Setup with the Enable Developer Console setting switched to Active." />
			  </span>
			</p>
<h3><b>Scan Apex for performance antipatterns with ApexGuru</b></h3>
<p><span>Ask your coding agent to check Apex for performance issues with the </span><code>scan_apex_class_for_antipatterns</code><span> tool from </span><a href="https://developer.salesforce.com/blogs/2026/04/performance-first-apex-development-with-apexguru-in-salesforce-dx-mcp-server"><u>ApexGuru in the Salesforce DX MCP Server</u></a><span>. The tool works in Agentforce Vibes, Claude Code, Cursor, and other MCP clients. Turn it on with the </span><code>scale-products</code><span> toolset.</span></p>
<pre language="json">{
  "servers": {
    "Salesforce DX": {
      "command": "npx",
      "args": ["-y", "@salesforce/mcp", "--orgs", "DEFAULT_TARGET_ORG", "--toolsets", "scale-products"]
    }
  }
}
</pre>
<p><span style="font-weight: 400">ApexGuru also finds exact and near-duplicate Apex code, ranked by how many characters you can remove. From Setup, open </span><b>Scale Center</b><span style="font-weight: 400">, go to </span><b>Scale Insights</b><span style="font-weight: 400">, and click </span><b>ApexGuru Insights</b><span style="font-weight: 400"> to review the </span><b>Code Duplicates</b><span style="font-weight: 400"> recommendations. ApexGuru runs in production and Full Copy sandbox orgs, and it’s included at no extra cost for Unlimited Edition, Signature Success Plan, and Scale Test customers.</span></p>
<h3><b>Keep Agentforce Vibes requests in your org’s region</b></h3>
<p><span>Route Agentforce Vibes AI requests to the </span><a href="https://developer.salesforce.com/docs/platform/agentforcevibes/guide/afv-geo-aware-routing.html"><u>model endpoint closest to your Salesforce org</u></a><span>, instead of to the United States by default. When geo-aware routing is on, the model picker shows only the models available in your org’s region. To turn it on, from Setup, enter </span><code>Agentforce Vibes Extension</code><span> in the Quick Find box, and turn on </span><b><span>Enable Geo-Aware Routing for this Org</span></b><span>. Data 360 must be turned on first.</span></p>
<p><span style="font-weight: 400">Scratch orgs don’t inherit this setting, so add it to your scratch org definition file.</span></p>
<pre language="json">{
  "settings": {
    "agentforceForDevelopersSettings": {
      "agentforceForDevelopersGeoAwareOptIn": true
    }
  }
}
</pre>
<p><span style="font-weight: 400">By default, a request falls back to the United States when no model is available in your region. To return an error instead, in Einstein Setup, turn on </span><b>Enable In-Region Model Request Only</b><span style="font-weight: 400">. This setting doesn’t yet apply to Anthropic and Gemini models.</span></p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">Winter ’27 is the release that makes Salesforce truly headless. Every major capability, including 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. With the Headless Experience Layer, the UI you build once rendered in Lightning Experience, ChatGPT, Claude, and Slackbot.</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 Winter ’27 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=264&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 Salesforce 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/10/developers-guide-to-the-winter-27-release">The Salesforce Developer’s Guide to the Winter ’27 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/10/developers-guide-to-the-winter-27-release/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207099</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261005111025/Dev-Blog-1000x563-2.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261005111025/Dev-Blog-1000x563-2.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Agentforce Voice for Agent Script: Voice-Enabling Best Practices</title>
		<link>https://developer.salesforce.com/blogs/2026/10/agentforce-voice-for-agent-script-voice-enabling-best-practices</link>
		<comments>https://developer.salesforce.com/blogs/2026/10/agentforce-voice-for-agent-script-voice-enabling-best-practices#respond</comments>
		<pubDate>Thu, 01 Oct 2026 16:55:17 +0000</pubDate>
		<dc:creator><![CDATA[Alex Martinez]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Agent Script]]></category>
		<category><![CDATA[AI Agents]]></category>
		<category><![CDATA[Conversational AI]]></category>
		<category><![CDATA[Developer Guide]]></category>
		<category><![CDATA[Salesforce Voice]]></category>
		<category><![CDATA[Voice Agents]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207090</guid>
		<description><![CDATA[<p>Learn how to voice-enable an Agent Script agent with Agentforce Voice, from configuring voice settings to writing instructions that make spoken conversations clear and natural.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/agentforce-voice-for-agent-script-voice-enabling-best-practices">Agentforce Voice for Agent Script: Voice-Enabling Best Practices</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">Adding voice to an agent built in </span><a href="https://developer.salesforce.com/docs/einstein/genai/guide/agent-script.html"><span style="font-weight: 400">Agent Script</span></a><span style="font-weight: 400"> (the language for defining Agentforce agents in code) is not a rewrite. It is a two-step process: configuring the agent for voice, and shaping how it speaks. For the first step, you enable </span><a href="https://www.salesforce.com/ca/agentforce/voice/"><span style="font-weight: 400">Agentforce Voice</span></a><span style="font-weight: 400"> in your org and add a few voice settings to your script; for the second, you tune the agent&#8217;s instructions for the ear. Your text agent is most of the way there.</span></p>
<p><span style="font-weight: 400">A lot of people using Salesforce would rather talk than type. Agentforce Voice lets an Agentforce agent hold a spoken conversation, and it is available today. If you have already built a text agent, you can reach those people without starting over.</span></p>
<p><span style="font-weight: 400">Voice does add a few new challenges. Once an agent takes spoken input, things can break. When a user says &#8220;async&#8221;, the agent can receive it as &#8220;a sink.&#8221; A reply that looks fine on screen can sound robotic out loud. These problems are easy to miss until a real conversation goes wrong.</span></p>
<p><span style="font-weight: 400">This post covers key best practices for voice-enabling Agent Script agents, using one small Agent Script as an example. It walks through what it takes overall, what your org needs first, how to add the voice settings to your script, and how to write instructions for the ear.</span></p>
<h2><span style="font-weight: 400">What does it take to voice-enable an Agent Script agent?</span></h2>
<p><span style="font-weight: 400">It takes two steps, and not much else. The first step, </span><i><span style="font-weight: 400">configuring the agent for voice</span></i><span style="font-weight: 400">, is org setup plus a few settings: you turn on voice in your org, then add blocks that connect the agent to a voice channel and pick its voice. The second step, </span><i><span style="font-weight: 400">adjusting how it speaks</span></i><span style="font-weight: 400">, is instructions: a reply read out loud follows different rules than one read on a screen. Everything else stays the same. You keep the same personality, the same goal, and most of the same instructions.</span></p>
<p><span style="font-weight: 400">Our example is a Rubber Duck Debugger agent. It is a debugging buddy that helps developers find bugs by asking one question at a time, instead of just giving the answer. It comes in two versions: a plain text version and a voice version tuned for speaking. This table shows what changes between them:</span></p>
<table>
<thead>
<tr>
<th><b>Concern</b></th>
<th><b>Text version</b></th>
<th><b>Voice version</b></th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="font-weight: 400">Personality and goal</span></td>
<td><span style="font-weight: 400">Rubber duck helper</span></td>
<td><span style="font-weight: 400">Same, no change</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Reply length</span></td>
<td><span style="font-weight: 400">A few sentences </span></td>
<td><span style="font-weight: 400">Short and easy to hear</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Formatting</span></td>
<td><span style="font-weight: 400">Prose </span></td>
<td><span style="font-weight: 400">No code or markdown read aloud</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Symbols and IDs</span></td>
<td>Can include <code>index++</code><span>, for example</span></td>
<td><span style="font-weight: 400">Say them as words: &#8220;index plus plus&#8221;, for example</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Fixing bad transcripts</span></td>
<td><span style="font-weight: 400">Not needed</span></td>
<td><span style="font-weight: 400">Fixes misheard terms</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Voice settings</span></td>
<td><span style="font-weight: 400">None</span></td>
<td><code>language</code>, <code>connection telephony</code>, <code>modality voice</code></td>
</tr>
<tr>
<td><span style="font-weight: 400">Org needed</span></td>
<td><span style="font-weight: 400">Any org</span></td>
<td><span style="font-weight: 400">An org with Agentforce Voice turned on</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">The screenshot below shows the instructions side by side: the text version on the left, and the spoken-style version on the right. Notice how the spoken version adds transcript repair and spells symbols out in words. </span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207091" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261001091600/image1-e1790871376797.png?w=1000" class="postimages" width="1000" height="525" alt="The text and spoken-style bundles open side-by-side in Visual Studio Code, comparing the debugging subagent&apos;s reasoning instructions, with the spoken version adding speech-to-text repair and spelling out symbols." />
			  </span>
			</p>
<h2><span style="font-weight: 400">What does my org need before I can use voice?</span></h2>
<p><span style="font-weight: 400">Before you write any voice code, there is one thing to check in your org that will save you from following this entire post and then hitting a wall at deploy. The voice version only works in a Salesforce org where </span><b>Agentforce Voice is turned on</b><span style="font-weight: 400">. That is org setup, and a script cannot do it for you.</span></p>
<p><span style="font-weight: 400">An admin configures Agentforce Voice in </span><b>Setup</b><span style="font-weight: 400">. See the </span><a href="https://www.salesforce.com/ca/agentforce/voice/"><span style="font-weight: 400">Agentforce Voice</span></a><span style="font-weight: 400"> product page for what it can do. Agentforce Voice runs on Salesforce Voice with Telephony Providers (formerly Service Cloud Voice), so the voice connection can be a browser voice preview, or full phone support through Amazon Connect or a partner provider.</span></p>
<p><span style="font-weight: 400">Want to check quickly whether your org is already set up? Open any agent in Agentforce Builder. In the Explorer on the left, click the plus button (</span><b>+</b><span style="font-weight: 400">) next to </span><b>Connections</b><span style="font-weight: 400">, choose </span><b>Add Connections</b><span style="font-weight: 400">, and search for </span><b>Telephony</b><span style="font-weight: 400">. If it does not show up, your org is not configured for voice yet.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207092" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261001091657/image2-e1790871430895.png?w=1000" class="postimages" width="1000" height="497" alt="The Add Connections dialog in Agentforce Builder with the Telephony card selected, described as transforming your contact center with voice-enabled agents, next to a Slack connection card." />
			  </span>
			</p>
<p><b>Heads up:</b><span style="font-weight: 400"> As of this writing, a free </span><b>Developer Edition</b><span style="font-weight: 400"> org does not include Agentforce Voice. It includes the licenses, but it does not show the full Agentforce Voice setup, so you cannot turn on a voice connection there.</span></p>
<h2><span style="font-weight: 400">How do I add voice settings to an Agent Script agent?</span></h2>
<p><span style="font-weight: 400">With voice turned on in your org, the next step is the code. Three blocks turn a text agent into a voice agent, and they also let you pick the voice model and persona right in the script. Keeping them in the script means the voice setup travels with the recipe, instead of living in a bunch of clicks in the UI. </span></p>
<p><b>Block 1: Set the language.</b></p>
<pre language="yaml">language:
   default_locale: "en_US"
</pre>
<p>The top-level <code>language</code> block sets the language the agent replies in. Voice mode only works with certain languages, so pick one you know is supported. It is also required once you add voice: without it, the agent uses your org&#8217;s default language, and if that language is not supported, Agentforce Builder warns you about it. To use more than one language, see the <a href="https://github.com/trailheadapps/agent-script-recipes/tree/main/force-app/main/01_languageEssentials/languageSettings"><u>LanguageSettings recipe</u></a>.</p>
<p><b>Block 2: Connect to a voice channel.</b></p>
<pre language="yaml">connection telephony:
   adaptive_response_allowed: True
</pre>
<p>The <code>connection telephony</code> block is what actually makes it a voice agent. Without it, you just have a text agent with short instructions. This block is the one that needs voice turned on in your org, since it points to the telephony connection you checked for earlier.</p>
<p><b>Block 3: Pick the voice model and persona.</b></p>
<pre language="yaml">modality voice:
   language:
      default_locale: "en_US"
   outbound:
      persona_id: "74752e92d40e"
      model:
         id: "eleven_flash_v2"
   inbound:
      filler_words_detection: True
</pre>
<p>The <code>modality voice</code> block picks the voice model and the persona that speaks your agent&#8217;s replies. It has three parts:</p>
<ul>
<li><code>outbound</code> is how the agent speaks. <code>persona_id</code> is the voice, and <code>model.id</code> is the voice model. Here we use <code>eleven_flash_v2</code>, a lower-latency English model. Each language has a default model, so you can leave <code>model</code> out and get the default, but setting it lets you pick a faster model, with finer control over speed and stability (Flash), or a Japanese voice (Kotoba). At the time this post was written, Agentforce Voice supports ElevenLabs v3 Conversational (<code>eleven_v3_conversational</code>), ElevenLabs Flash v2.5 (<code>eleven_flash_v2_5</code>), ElevenLabs Flash v2 (<code>eleven_flash_v2</code>, English only), and Kotoba (<code>kotoba</code>, Japanese).</li>
<li><code>inbound</code> is how the agent listens. <code>filler_words_detection</code> helps it handle &#8220;um&#8221; and &#8220;uh&#8221; in what it hears.</li>
<li><code>language</code> sets the locale for the voice, and this one matters more than it looks. You do not need to memorize a <code>persona_id</code>. Each model has a voice catalog you copy one from.</li>
</ul>
<p>You may have noticed <code>language</code> appears twice: once at the top level and once inside <code>modality voice</code>. That is not a mistake. You need both. The top-level block sets the language the agent replies in; the nested one sets the language for the voice. The nested block is also what makes the <code>persona_id</code> work: a persona belongs to a specific model and language, so without a language inside <code>modality voice</code>, the persona has nothing to resolve against and the voice quietly falls back to the default.</p>
<p>To find a <code>persona_id</code> in a model&#8217;s catalog, or to read more about voice models, see <a href="https://developer.salesforce.com/docs/ai/agentforce/guide/ascript-voice-catalog.html"><u>Voice Catalog for Agentforce Voice</u></a>.</p>
<p>Set the voice model in the <b>Script view</b>. The Canvas view does not expose a voice-model selector.</p>
<p>A legacy format for <code>modality voice</code> uses a flat <code>voice_id</code> with <code>outbound_speed</code>, <code>outbound_stability</code>, and <code>outbound_similarity</code>. It still works, but it cannot pick a voice model, so to choose one, use the <code>model</code> structure above.</p>
<h2><span style="font-weight: 400">How do I write agent instructions for voice?</span></h2>
<p>A listener cannot reread a sentence. That one fact shapes every voice instruction you write. The spoken <code>debugging</code> subagent keeps the same job as the text version, but its <code>instructions</code> field is written in three parts.</p>
<p><b>Part 1: Keep the same rubber-duck role.</b></p>
<pre language="yaml">| You are the developer's rubber duck, talking with them out loud.
  Help them discover the bug on their own instead of handing over the fix.
</pre>
<p><span style="font-weight: 400">This is the same job as the text version. The agent guides developers to the bug instead of handing over the fix. Voice does not change the personality or the goal, so this part barely changes.</span></p>
<p><b>Part 2: Repair the speech-to-text transcript.</b></p>
<pre language="yaml">| Their words reach you as speech-to-text, so technical vocabulary is
  often garbled - "agent for script" means "Agent Script", "returns on
  the even" means "returns undefined", "a sink" means "async". Read every
  message charitably in a software-debugging context and quietly repair
  obvious mishears.
</pre>
<p><span style="font-weight: 400">The agent never receives raw audio. Instead it receives a speech-to-text (STT) transcript, and that transcript is not always right. Technical words break first; &#8220;async&#8221; can arrive as &#8220;a sink.&#8221; This is the part most teams skip. It tells the agent to expect the noise and quietly fix obvious mishears from the context. Think of it as a safety net: most transcripts are fine, but when a word gets garbled, the agent recovers instead of getting confused.</span></p>
<p><b>Part 3: Speak for the ear.</b></p>
<pre language="yaml">| Keep replies short and easy to follow by ear: speak in full sentences,
  never read code or markdown aloud, and say symbols in words (for example
  "index plus plus" for "i++"). Guide with simple questions like "What did
  you expect to happen?" or "What did you change last?", and stay warm and
  playful.
</pre>
<p>Out loud, formatting is just noise. The agent should never read code or markdown aloud, and it should say symbols in words, so <code>index++</code> becomes &#8220;index plus plus.&#8221; Full sentences help too, since a listener cannot piece together a fragment. These small changes are the difference between a reply that sounds natural and one that sounds like a screen reader.</p>
<p><span style="font-weight: 400">Notice what did </span><i><span style="font-weight: 400">not</span></i><span style="font-weight: 400"> change across all three parts. The personality and the goal are the same as the text version. You are only changing how the agent talks and how it reads what it hears, not rebuilding it.</span></p>
<h2><span style="font-weight: 400">Try it yourself</span></h2>
<p><span style="font-weight: 400">The best way to learn this is to deploy the recipe yourself. Clone the </span><a href="https://github.com/trailheadapps/agent-script-recipes"><span style="font-weight: 400">Agent Script Recipes repo</span></a><span style="font-weight: 400"> and then deploy the voice agent:</span></p>
<pre language="bash">sf project deploy start --source-dir force-app/main/04_architecturalPatterns/voiceAgent
</pre>
<p><span style="font-weight: 400">Then open Agentforce Studio, start a voice chat, and describe a bug out loud. Notice how the voice version keeps each reply short and recovers when a word gets misheard.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207093" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261001091758/image4-e1790871492458.png?w=1000" class="postimages" width="1000" height="563" alt="A Live Test in Agentforce Builder where the Rubber Duck Debugger greets the developer, hears that a function returns undefined, and replies with a short Socratic question instead of the fix, with the reasoning trace shown on the right." />
			  </span>
			</p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p>Adding voice to an Agent Script agent is not a rewrite. First, <b>make sure your org has Agentforce Voice turned on</b>, or the voice version will not deploy. Then <b>add the </b><b><code>language</code></b><b>, </b><b><code>connection telephony</code></b><b>, and </b><b><code>modality voice</code></b><b> blocks</b> so the voice setup lives in your script, and pick the voice model and persona right there in the code. Finally, <b>write the instructions for a listener</b>: short replies, no code read aloud, symbols said as words, and one line that fixes misheard terms. That is what keeps a spoken conversation on track. Put the text and voice versions side by side and the pattern is clear: it is the same agent, just tuned for the ear. Have questions? Ask on the <a href="https://trailhead.salesforce.com/trailblazercommunity"><u>Trailblazer Community</u></a> or reach out to <a href="https://twitter.com/salesforcedevs"><u>@SalesforceDevs</u></a>.</p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><a href="https://github.com/trailheadapps/agent-script-recipes"><span style="font-weight: 400">Agent Script Recipes (GitHub)</span></a></li>
<li style="font-weight: 400"><a href="https://www.salesforce.com/ca/agentforce/voice/"><span style="font-weight: 400">Agentforce Voice</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/einstein/genai/guide/agent-script.html"><span style="font-weight: 400">Agent Script (Agentforce Developer Guide)</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/einstein/genai/guide/get-started.html"><span style="font-weight: 400">Agentforce Developer Guide</span></a></li>
<li style="font-weight: 400"><a href="https://www.youtube.com/@SalesforceDevs"><span style="font-weight: 400">Salesforce Developers YouTube</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Alex Martinez</b><span style="font-weight: 400"> was part of the MuleSoft Community before joining Salesforce as a Developer Advocate. Today they help developers build with Agentforce and MuleSoft. You can find more of their content on </span><a href="https://prostdev.com"><span style="font-weight: 400">ProstDev</span></a><span style="font-weight: 400">, a site that shares MuleSoft tutorials and walkthroughs. Follow Alex on </span><a href="https://www.linkedin.com/in/alexandra-n-martinez"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400"> or in the </span><a href="https://www.salesforce.com/trailblazer/devalexmartinez"><span style="font-weight: 400">Trailblazer Community</span></a><span style="font-weight: 400">.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/agentforce-voice-for-agent-script-voice-enabling-best-practices">Agentforce Voice for Agent Script: Voice-Enabling Best Practices</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/10/agentforce-voice-for-agent-script-voice-enabling-best-practices/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207090</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261001092313/SingleHeadshot-8-e1790871831604.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261001092313/SingleHeadshot-8-e1790871831604.png?w=1000" medium="image" />
	</item>
	</channel>
</rss>
