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


<channel>
	<title>Salesforce Developers Blog</title>
	<atom:link href="https://developer.salesforce.com/blogs/feed" rel="self" type="application/rss+xml" />
	<link>https://developer.salesforce.com/blogs</link>
	<description>Elevating developer skills and connecting with the Salesforce Developers community</description>
	<lastBuildDate>Thu, 12 Nov 2026 23:11:47 +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>How HXL Widgets Render in Agentforce and MCP Clients</title>
		<link>https://developer.salesforce.com/blogs/2026/11/how-hxl-widgets-render-in-agentforce-and-mcp-clients</link>
		<comments>https://developer.salesforce.com/blogs/2026/11/how-hxl-widgets-render-in-agentforce-and-mcp-clients#respond</comments>
		<pubDate>Thu, 12 Nov 2026 16:00:43 +0000</pubDate>
		<dc:creator><![CDATA[Alba Rivas]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Headless 360]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Claude and ChatGPT]]></category>
		<category><![CDATA[Generative AI]]></category>
		<category><![CDATA[hxl]]></category>
		<category><![CDATA[LLM Tool Calling]]></category>
		<category><![CDATA[Model Context Protocol (MCP)]]></category>
		<category><![CDATA[Pronto]]></category>
		<category><![CDATA[salesforce]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207367</guid>
		<description><![CDATA[<p>Learn how Headless Experience Layer (HXL) widgets render in Agentforce and MCP clients like Claude and ChatGPT. Explore both rendering workflows and discover how to troubleshoot when widgets don't appear.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/how-hxl-widgets-render-in-agentforce-and-mcp-clients">How HXL Widgets Render in Agentforce and MCP Clients</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://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"> beta lets you define a widget once and show it as an interactive card in Agentforce, Slackbot, Claude, and ChatGPT. When a user asks a question, the widget reaches the screen in different ways, depending on which client the user is chatting in. </span></p>
<p><span style="font-weight: 400">Runtime behavior follows one of two routes:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Route 1 runs inside Agentforce. </span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Route 2 runs through the Model Context Protocol (MCP), the open standard that MCP clients such as Claude and ChatGPT use to call external tools. </span></li>
</ul>
<p><span style="font-weight: 400">Knowing which component does what in each route helps you design better actions, and tells you where to look when a card doesn&#8217;t show up. In this blog post, you’ll:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Walk through Route 1, from the user’s question to the card in Agentforce.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Walk through Route 2, first in a simplified view and then in full.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Learn how to troubleshoot each route when a card doesn’t show up.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Compare both routes side by side.</span></li>
</ul>
<p><span style="font-weight: 400">Note: An earlier blog post, </span><a href="https://developer.salesforce.com/blogs/2026/10/building-interactive-ui-with-hxl"><span style="font-weight: 400">Building Interactive UI with Headless Experience Layer</span></a><span style="font-weight: 400">, introduces the topic and shows the code and metadata you need to create widgets. Make sure to read that post first.</span></p>
<h2><span style="font-weight: 400">Example widget: Get customer reviews in Pronto</span></h2>
<p>Both routes use the same example from <a href="https://github.com/trailheadapps/pronto"><u>Pronto</u></a>, a sample app for a fictional food delivery platform. A Pronto employee asks, &#8220;Show me the reviews for The Green Fork.&#8221; The agent calls the <a href="https://github.com/trailheadapps/pronto/blob/main/src-base/main/default/aiAuthoringBundles/Employee_Assistant/Employee_Assistant.agent#L252"><u><code>getCustomerReviews</code><span> action</span></u></a><span>, which runs the </span><a href="https://github.com/trailheadapps/pronto/blob/main/src-base/main/default/classes/actions/reviews/GetCustomerReviewsAction.cls"><u><code>GetCustomerReviewsAction</code><span> Apex class</span></u></a><span> and returns a </span><a href="https://github.com/trailheadapps/pronto/tree/main/src-base/main/default/uiWidgets/customerReviews"><u><code>customerReviews</code><span> widget</span></u></a><span>: a card with the average rating and the list of reviews. Pronto wires that same widget into both routes, so you can deploy the app and test it yourself.</span></p>
<h2><span style="font-weight: 400">Route 1: Render HXL widgets as Agentforce action output</span></h2>
<p><span style="font-weight: 400">In Route 1, the large language model (LLM) points at a stored action result, and the Agentforce Client builds the widget in the browser from JSON. Route 1 is described in the </span><a href="https://developer.salesforce.com/docs/platform/hxl/guide/agentforce-action-output.html"><span style="font-weight: 400">Agentforce action output guide</span></a><span style="font-weight: 400">. </span></p>
<p><span style="font-weight: 400">Here is the full sequence for it (HXL-specific messages are in orange):</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-full wp-image-207382" >
			    <img fetchpriority="high" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261111131518/image1_35fad9-e1794431729960.png" class="postimages" width="1000" height="563" alt="Sequence diagram of Route 1 with four lifelines (User, Agentforce Conversation Client, Agentforce Runtime, large language model) showing eight numbered messages, with HXL-specific messages in orange." />
			  </span>
			</p>
<p><span style="font-weight: 400">In the diagram, there are four actors: </span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The User that sends the prompt.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The Agentforce Conversation Client, which is the chat panel that runs in Lightning Experience, within the browser.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The Agentforce Runtime, which is the entity that runs the reasoning loop on Salesforce.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The LLM that the runtime calls at each step.</span></li>
</ul>

<h3 class="wp-block-heading">Steps 1 to 4: The agent calls the action</h3>



<ol class="wp-block-list">
<li>The user message travels from the client (browser) to the Agentforce runtime.</li>
<li>The runtime starts the reasoning loop.</li>
<li>The runtime sends the LLM the prompt plus the list of available actions.</li>
<li>The LLM decides the next step and calls <code>getCustomerReviews</code> with the storefront ID. In Pronto, the agent first turns the storefront name into a record ID with the standard Identify Record By Name action. The diagram skips that call to keep the flow short.</li>
</ol>



<p class="wp-block-paragraph">So far, this is a regular Agentforce action call. Nothing HXL-specific has happened yet.</p>

<h3 class="wp-block-heading">Steps 5 and 6: The LLM asks to show the result</h3>



<p class="wp-block-paragraph">This is where HXL comes in.</p>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">5.  The runtime runs the action and stores its output under a short ID. When it returns the result to the LLM, it adds two extra fields:</p>

<pre language="json">{
  "reviews": { "reviewCount": 8, "averageRating": 4.125, "items": ["..."] },
  "__tool_idx__": "4bd08ecf",
  "__supports_result_display__": true
}</pre>

<ul class="wp-block-list">
<li><code>__supports_result_display__</code> tells the LLM that this result can be shown as a card. The runtime only sets it when the surface can render widgets and the action has an output marked <code>is_displayable: True</code> in your Agent Script.</li>
<li><code>__tool_idx__</code> is the ID of the stored result.</li>
</ul>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">6.  If the LLM wants the card, it calls a built-in action named <code>__show_tool_results__</code>, passing in the <code>__tool_idx__</code> value:</p>

<pre language="json">{
  "caption": "Here are the reviews for The Green Fork. You can view individual ratings, comments, and customer names. Let me know if you'd like to reply to any review or need more details.",
  "tool_ids": ["4bd08ecf"]
}</pre>

<p>
			  <span class="postimagessection_specify alignnone size-full wp-image-207383" >
			    <img decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261111131602/image2_fe0664-e1794431775169.png" class="postimages" width="1000" height="616" alt="Agentforce Builder preview trace showing the LLM response calling __show_tool_results__ with a caption and one tool ID, next to the rendered customer review card" />
			  </span>
			</p>
<p>Rendering depends on the model choosing to call <code>__show_tool_results__</code>. The model can skip it, for example when a short text answer seems enough. To make the card more likely, ask for it in the agent&#8217;s instructions. Note that this call carries no data and no widget, only a caption and an ID. The LLM points at a result and the runtime does the rest.</p>

<h3 class="wp-block-heading">Steps 7 and 8: The runtime resolves the results and the client renders the widget</h3>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">7.  The runtime looks up the result by its ID and crafts a response. That response holds three things: the LLM response text (caption), the data returned by the Apex class, and the configured Custom Lightning Type (CLT) with its widget. In Pronto, that CLT is <code>customerReviewsOutput</code>, the type of the reviews output in the agent&#8217;s Agent Script.</p>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">8.  The Agentforce Conversation Client then does the HXL work in the browser. It reads the It reads the CLT&#8217;s widget mapping to find the widget. Then it hydrates each <code>{!$attrs...}</code> placeholder with the matching value from the data. Finally, it draws the widget&#8217;s component tree as Lightning web components. No HTML travels over the wire in this route, only JSON.</p>
<h3><strong>Troubleshooting Route 1 in Agentforce</strong></h3>
<p><span style="font-weight: 400">If you get a wall of text instead of a card, check these steps in order. You can see the action calls in the trace in Agentforce Builder or Agentforce DX while you test, and in production through </span><a href="https://help.salesforce.com/s/articleView?id=ai.generative_ai_session_trace.htm&amp;type=5"><span style="font-weight: 400">Agentforce Session Tracing</span></a><span style="font-weight: 400">:<br /></span></p>
<ol>
<li>Is the output marked <code>is_displayable: True</code>? Without that, the runtime doesn&#8217;t add <code>__supports_result_display__</code> (step 5).</li>
<li>Does the output use the CLT as its <code>complex_data_type_name</code>? Without it, the response has no widget (step 7).</li>
<li>Did the LLM call <code>__show_tool_results__</code> at all? Look for it in the trace (step 6). If it&#8217;s missing, ask for the card in the agent&#8217;s instructions.</li>
</ol>
<h2><span style="font-weight: 400">Route 2: Expose HXL widgets to MCP clients with a custom MCP server</span></h2>
<p><span style="font-weight: 400">In Route 2, the MCP server sends a widget already filled with data, and the MCP host draws it in a sandboxed iframe. Route 2 is described in the </span><a href="https://developer.salesforce.com/docs/platform/hxl/guide/hxl-mcp-channels.html"><span style="font-weight: 400">HXL MCP channels guide</span></a><span style="font-weight: 400">. This route follows the standard </span><a href="https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx"><span style="font-weight: 400">MCP Apps protocol</span></a><span style="font-weight: 400">. The protocol defines how an MCP tool points to a UI resource, how the MCP host loads it, and how the host passes tool results to it.</span></p>
<p><span style="font-weight: 400">Let&#8217;s start with a simplified view. In this diagram, everything that happens inside the MCP client is folded into one lifeline, labeled “MCP Client”:</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-full wp-image-207384" >
			    <img decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261111131643/image4-e1794431814798.png" class="postimages" width="1000" height="563" alt="Simplified sequence diagram of Route 2 with three lifelines (User, MCP Client, MCP Server), split into an On connect phase and a Request phase." />
			  </span>
			</p>
<p><span style="font-weight: 400">In the diagram, there are three actors: </span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The User that sends the prompt.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The MCP Client, such as ChatGPT, Claude, or Slackbot. The next diagram expands the client into its iframe, host, and the LLM.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The MCP Server in Salesforce.</span></li>
</ul>

<h3 class="wp-block-heading">On connect: The client discovers the renderer</h3>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">1.  When Claude or ChatGPT connects to your MCP server, it calls <code>tools/list</code>.</p>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">2.  Each tool that has a widget comes back with a renderer address, a <code>ui://</code> Uniform Resource Identifier (URI) that MCP Apps defines in <code>_meta.ui.resourceUri</code>:</p>
<pre language="json">{
  "name": "getCustomerReviews",
  "_meta": {
    "ui": { "resourceUri": "ui://widget/lightningType/c__customerReviews" }
  }
}</pre>



<p class="wp-block-paragraph">In Pronto, that address comes from the <a href="https://example.com" target="_blank" rel="noreferrer noopener">Pronto Employee Server MCP server definition</a>. There, the <code>getCustomerReviews</code> tool points to the <code>customerReviews</code> UI resource (the CLT that maps to the widget).</p>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">3.  The client then calls <code>resources/read</code> on that address.</p>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">4.  It gets back the HXL renderer, an HTML page that reads an HXL widget and draws it on screen. The MCP client loads that page in a sandboxed iframe, isolated from the rest of the app. The client can fetch the renderer when it connects or the first time it needs it. Each host decides, and it can cache the renderer for later calls. In Route 1, the renderer is already part of the Agentforce Client. The actual widget, already filled with data, arrives later with each tool result.</p>



<h3 class="wp-block-heading">Request: The server returns data and a hydrated widget</h3>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">5.  The user asks for the reviews.</p>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">6.  The LLM picks <code>getCustomerReviews</code> and the client sends <code>tools/call</code>. As in Route 1, the LLM first needs the storefront ID, which it can get from another Pronto tool such as <code>searchStorefronts</code>.</p>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">7.  The server runs the action. Then HXL resolves the CLT to its widget and hydrates the widget with the data. The <code>tools/call</code> result carries three parts:</p>
<pre language="json">{
  "content": [{ "type": "text", "text": "the same JSON, as a string" }],
  "structuredContent": { "...": "the raw action output, serialized as JSON" },
  "_meta": {
    "salesforce/uiMetadata": { "...": "the hydrated widget" }
  }
}</pre>



<ul class="wp-block-list">
<li><code>structuredContent</code> is the raw tool response, wrapped in an envelope with <code>actionName</code>, <code>isSuccess</code>, and <code>outputValues</code>, which holds the action&#8217;s output. Because the action returns a list, the envelope sits inside an array.</li>
<li><code>content</code> carries the same JSON as text. That&#8217;s the part the LLM reads.</li>
<li><code>_meta</code> holds the widget, and it&#8217;s only for rendering.</li>
</ul>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">8.  The client hands the result to the renderer, which draws the card, and the agent summarizes the result in its answer to the user. Per the protocol, the agent doesn&#8217;t read <code>_meta</code>. It reads <code>content</code>, which carries the same JSON as <code>structuredContent</code>. Note: Slackbot shows HXL widgets as HTML styled to look like Block Kit.</p>



<h3 class="wp-block-heading">The full picture: Host, LLM, and iframe</h3>



<p class="wp-block-paragraph">The simplified view hides who does what inside the client. This one splits the MCP Client into its three parts:</p>



<ul class="wp-block-list">
<li>The Widget Iframe is the sandboxed frame where the renderer runs.</li>
<li>The MCP Host is the app (for example, Claude or ChatGPT) that talks to the server.</li>
<li>The LLM is the model.</li>
</ul>
<p>
			  <span class="postimagessection_specify alignnone size-full wp-image-207385" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261111131712/image3_12f60b-e1794431846154.png" class="postimages" width="1000" height="563" alt="Detailed sequence diagram of Route 2 with five lifelines (User, Widget Iframe, MCP Host, LLM, MCP Server) showing thirteen numbered messages, with HXL-specific messages in orange." />
			  </span>
			</p>



<p class="wp-block-paragraph">Four steps are worth a closer look (9 to 12 in the diagram):</p>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">9.  The <code>tools/call</code> result reaches the host.</p>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">10. The host pushes the tool result into the iframe. The renderer reads the widget from it and draws the card.</p>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">11. The host sends the tool result to the LLM, without the widget.</p>



<p class="wp-block-paragraph" style="padding-left: 2em;text-indent: -2em">12. The LLM writes the final answer from the data, and the host shows the answer next to the card.</p>
<h3><strong>Troubleshooting Route 2 in MCP clients</strong></h3>
<p>If you get text instead of a card, check each step of the sequence in order. You can use the <a href="https://github.com/modelcontextprotocol/inspector"><u>MCP Inspector</u></a> to see the raw responses the server returns:</p>
<ol>
<li>Does the client support MCP Apps? Claude, ChatGPT, and Slackbot do. Claude Code in the terminal doesn&#8217;t render widgets, so you&#8217;ll see text only.</li>
<li>Does the <code>tools/list</code> result include <code>_meta.ui.resourceUri</code> for your tool (step 2)? If not, check that the tool points to the UI resource in your MCP server definition.</li>
<li>Does the UI resource load? Call <code>resources/read</code> on that URI in the MCP Inspector, and make sure it returns the HXL renderer (step 4).</li>
<li>Does the <code>tools/call</code> result include <code>salesforce/uiMetadata</code> (step 7 in the simplified diagram)? If not, check <code>isError</code> first. When the action fails, the server returns only an error message, with no data and no widget.</li>
</ol>
<h2><span style="font-weight: 400">Route 1 vs. Route 2: What&#8217;s different</span></h2>
<p><span style="font-weight: 400">Both routes start from the same Apex action and the same widget. They differ in who decides to show the card, where the widget gets filled with data, and who draws it.</span></p>
<table>
<tbody>
<tr>
<td></td>
<td><br />Route 1: Agentforce</td>
<td><br />Route 2: MCP (Claude, ChatGPT, Slackbot)</td>
</tr>
<tr>
<td><br />Who decides to show the card</td>
<td><br />The LLM, by calling <code>__show_tool_results__</code></td>
<td><br />The host, whenever the tool has a renderer address. The LLM can&#8217;t drop the card.</td>
</tr>
<tr>
<td><br />What the LLM sees</td>
<td><br />The result plus an ID, never the widget</td>
<td><br />The result data, never <code>_meta</code></td>
</tr>
<tr>
<td><br />What travels to the client</td>
<td><br />Response text, data, and the CLT with its widget</td>
<td><br />Raw tool response plus the hydrated widget in <code>_meta</code></td>
</tr>
<tr>
<td><br />Where data meets the widget</td>
<td><br />In the browser, in the Agentforce Client</td>
<td><br />On the server, in HXL</td>
</tr>
<tr>
<td><br />Who draws the card</td>
<td><br />The Agentforce Client, as Lightning web components</td>
<td><br />The HXL renderer, inside a sandboxed iframe, as HTML</td>
</tr>
<tr>
<td><br />When the HXL renderer loads</td>
<td><br />Already part of the Agentforce Conversation Client</td>
<td><br />Through <code>resources/read</code>, on connect or on first use</td>
</tr>
</tbody>
</table>
<p></p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">You now know what happens between a user&#8217;s question and the card on the screen in both routes. In Agentforce, the LLM points at a stored result and the client builds the card in the browser. In MCP clients, the Salesforce MCP server sends a hydrated widget and the host draws it in an iframe. </span></p>
<p><span style="font-weight: 400">To see it in action, deploy </span><a href="https://github.com/trailheadapps/pronto"><span style="font-weight: 400">Pronto</span></a><span style="font-weight: 400"> to an org and try both routes. To go deeper, watch the recorded codeLive, </span><a href="https://www.youtube.com/watch?v=LQHv9hMhqa4"><span style="font-weight: 400">Build Rich Agentic UI with Headless Experience Layer</span></a><span style="font-weight: 400">, and </span><a href="https://www.youtube.com/watch?v=mnvGnN3ny-o"><span style="font-weight: 400">Ask Me Anything (AMA)</span></a><span style="font-weight: 400"> sessions.</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/building-interactive-ui-with-hxl"><span style="font-weight: 400">Building Interactive UI with Headless Experience Layer</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/trailheadapps/pronto"><span style="font-weight: 400">Pronto sample app</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hxl/guide/get-started.html"><span style="font-weight: 400">HXL Get Started</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hxl/guide/agentforce-action-output.html"><span style="font-weight: 400">Render HXL Widgets as Agentforce Action Output</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">Expose HXL Widgets through MCP</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://modelcontextprotocol.io/extensions/apps"><span style="font-weight: 400">MCP Apps overview</span></a><span style="font-weight: 400"> and </span><a href="https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx"><span style="font-weight: 400">MCP Apps specification</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><span style="font-weight: 400"><strong>Alba Rivas</strong> works as a Principal Developer Advocate at Salesforce. You can follow her on </span><a href="https://github.com/albarivas"><span style="font-weight: 400">GitHub</span></a><span style="font-weight: 400"> or </span><a href="https://www.linkedin.com/in/alba-rivas/"><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/11/how-hxl-widgets-render-in-agentforce-and-mcp-clients">How HXL Widgets Render in Agentforce and MCP Clients</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/11/how-hxl-widgets-render-in-agentforce-and-mcp-clients/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207367</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261111111801/SingleHeadshot-14-e1794424697863.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261111111801/SingleHeadshot-14-e1794424697863.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Agentforce Metadata Types in Winter &#8217;27: Dive into AiAgentDefinition</title>
		<link>https://developer.salesforce.com/blogs/2026/11/agentforce-metadata-types-in-winter-27-dive-into-aiagentdefinition</link>
		<comments>https://developer.salesforce.com/blogs/2026/11/agentforce-metadata-types-in-winter-27-dive-into-aiagentdefinition#respond</comments>
		<pubDate>Wed, 11 Nov 2026 16:00:42 +0000</pubDate>
		<dc:creator><![CDATA[Sarah Welker]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Salesforce Releases]]></category>
		<category><![CDATA[Agent Script]]></category>
		<category><![CDATA[AiAgentDefinition]]></category>
		<category><![CDATA[Application Lifecycle Management]]></category>
		<category><![CDATA[CI/CD]]></category>
		<category><![CDATA[devops]]></category>
		<category><![CDATA[Metadata API]]></category>
		<category><![CDATA[Salesforce CLI]]></category>
		<category><![CDATA[Winter '27]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207363</guid>
		<description><![CDATA[<p>Explore two new Agentforce metadata types in Winter &rsquo;27 that simplify managing, retrieving, and deploying agents across Salesforce orgs. Learn how these enhancements streamline agent versioning and development workflows.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/agentforce-metadata-types-in-winter-27-dive-into-aiagentdefinition">Agentforce Metadata Types in Winter &#8217;27: Dive into AiAgentDefinition</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p>If you move Agentforce agents between orgs with Salesforce CLI or a continuous integration and continuous delivery (CI/CD) pipeline, the <a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_agentforce_metadata.htm&amp;release=264&amp;type=5"><u>Winter &#8217;27 release</u></a> makes your work simpler. API version 68.0 introduces two new metadata types for agents: <code>AiAgentDefinition</code> and <code>AiAgentDefinitionVersion</code>. Each agent now has one definition and one folder per version, with readable names.</p>
<p>An earlier post, <a href="https://developer.salesforce.com/blogs/2026/05/new-agentforce-metadata-and-development-lifecycle"><u>The New Agentforce Metadata and Development Lifecycle</u></a>, showed how Agent Script agents map to the <code>Bot</code>, <code>GenAiPlannerBundle</code>, and <code>AiAuthoringBundle</code> metadata types. This post picks up from there. It covers why we simplified agent metadata and compares the old and new structures side by side, using a real agent. It also covers what to know before you retrieve and deploy with the new types.</p>
<h2>Agent metadata before API version 68.0 (Winter &#8217;27)</h2>
<p><span style="font-weight: 400">To see what changed, let&#8217;s start with a real agent. TestAgent is an Agentforce Service Agent built with Agent Script. It has one version, 10 subagents, and 26 actions. Here&#8217;s what you get when you retrieve it into a Salesforce DX project at API version 67.0.</span></p>
<pre language="text">├── aiAuthoringBundles
│   └── TestAgent
│       ├── TestAgent.agent
│       └── TestAgent.bundle-meta.xml
├── bots
│   └── TestAgent
│       ├── TestAgent.bot-meta.xml
│       └── v1.botVersion-meta.xml
└── genAiPlannerBundles
    └── TestAgent_v1
        ├── TestAgent_v1.genAiPlannerBundle
        ├── agentGraph
        │   └── TestAgent_v1_graph.json
        ├── agentScript
        │   └── TestAgent_v1_definition.agent
        └── localActions
            ├── AccountManagement_1793afaabbea5e6
            ├── CaseManagement_1797b79603146ff
            ├── DeliveryIssues_1799f005010de87
            │   ├── AnswerQuestionsWithKnowledge_17288a938fc0c53
            │   │   ├── input
            │   │   │   └── schema.json
            │   │   └── output
            │   │       └── schema.json
            │   └── ... 5 more actions
            ├── GeneralFAQ_17958732d5d84f7
            ├── OrderInquiries_179524d7fde40be
            ├── ReservationManagement_17932b500c2b88d
            └── ServiceCustomerVerification_1796152b9540575
                ├── SendEmailVerificationCode_1724f12735f68c6
                └── VerifyCustomer_1726b9bee0044be
</pre>
<p>One agent with one version is spread across three metadata types (<code>Bot</code>, <code>GenAiPlannerBundle</code>, and <code>AiAuthoringBundle</code>). The agent&#8217;s settings live in <code>TestAgent.bot-meta.xml</code> (the bot), and the version&#8217;s settings and welcome message live in <code>v1.botVersion-meta.xml</code> (the version). The subagents and actions live in <code>TestAgent_v1</code> (the planner bundle), and the Agent Script source sits in <code>TestAgent.agent</code> (the authoring bundle).</p>
<p><span style="font-weight: 400">Here are the four biggest pain points we heard about from you regarding this structure:</span></p>
<p><b>One agent, many names.</b> The same agent shows up as <code>TestAgent.bot-meta.xml</code>, <code>v1.botVersion-meta.xml</code>, <code>TestAgent_v1</code>, and <code>TestAgent.agent</code>. Deployment and retrieval operations can fail unless your tooling understands how these names correspond and strictly adheres to a complex naming structure. If you write your manifest by hand, every new version also adds a <code>GenAiPlannerBundle</code> member and an <code>AiAuthoringBundle</code> member to it.</p>
<p><b>Generated suffixes everywhere.</b> Every subagent and action in a version gets a unique name with a generated suffix, such as <code>ServiceCustomerVerification_1796152b9540575</code>. In our one-version sample, the planner bundle contains 37 suffixed names, used 111 times, plus 33 suffixed folders. The suffixes mean nothing to you. Each version also gets its own suffixes, so comparing two versions shows changes that you never made.</p>
<p><b>Messages hidden in dialogs.</b><span style="font-weight: 400"> To find your agent&#8217;s welcome message, you have to dig through a dialog, its steps, and its messages. Each one has its own generated identifier.</span></p>
<p><b>Folder names that move.</b> At API version 67.0 and earlier, the newest authoring bundle has no version number, while older ones do. For example, once TestAgent has four versions, its newest bundle is <code>TestAgent</code>, and the older ones are <code>TestAgent_1</code>, <code>_2</code>, and <code>_3</code>. When you create a version, a different folder becomes the unnumbered one. So the same bundle can have a different name from one retrieve to the next.</p>
<h2>What&#8217;s new in API version 68.0</h2>
<p><span style="font-weight: 400">API version 68.0 gives each agent a simpler shape. One metadata type holds the agent, and one metadata type holds everything that belongs to a version.</span></p>
<table>
<tbody>
<tr>
<td><b>Metadata type</b></td>
<td><b>What it represents</b></td>
<td><b>Member name</b></td>
</tr>
<tr>
<td><code>AiAgentDefinition</code></td>
<td>The agent: its type, description, context variables, and other agent-level settings</td>
<td><code>TestAgent</code></td>
</tr>
<tr>
<td><code>AiAgentDefinitionVersion</code></td>
<td>One version of the agent: its settings, conversation variables, and messages, plus an inline <code>&lt;planner&gt;</code> element with its subagents and actions</td>
<td><code>TestAgent#1</code></td>
</tr>
<tr>
<td><code>AiAuthoringBundle</code></td>
<td>The Agent Script source that you edit (unchanged)</td>
<td><code>TestAgent_1</code></td>
</tr>
</tbody>
</table>
<p>In a Salesforce DX project, the files for these types live in the <code>aiAgents</code>, <code>aiAgentDefinitionVersions</code>, and <code>aiAuthoringBundles</code> folders.</p>
<p>At API version 68.0, Agentforce agents use the new types, and the <code>Bot</code> and <code>BotVersion</code> types are for classic Einstein Bots. If you retrieve or deploy an Agentforce agent as <code>Bot</code> or <code>BotVersion</code> at this API version, you get an error that tells you to use <code>AiAgentDefinition</code> and <code>AiAgentDefinitionVersion</code> instead. To keep using the old types, stay on API version 67.0.</p>
<h3>Before and after: the folder structure</h3>
<p><span style="font-weight: 400">Here&#8217;s the same agent retrieved at API version 68.0.</span></p>
<pre language="text">├── aiAgents
│   └── TestAgent.aiAgentDefinition-meta.xml
├── aiAgentDefinitionVersions
│   └── TestAgent#1
│       ├── TestAgent#1.aiAgentDefinitionVersion-meta.xml
│       ├── agentGraph
│       │   └── TestAgent_v1_graph.json
│       ├── agentScript
│       │   └── TestAgent_v1_definition.agent
│       └── localActions
│           ├── AccountManagement
│           ├── CaseManagement
│           ├── DeliveryIssues
│           │   ├── AnswerQuestionsWithKnowledge
│           │   │   ├── input
│           │   │   │   └── schema.json
│           │   │   └── output
│           │   │       └── schema.json
│           │   └── ... 5 more actions
│           ├── GeneralFAQ
│           ├── OrderInquiries
│           ├── ReservationManagement
│           └── ServiceCustomerVerification
│               ├── SendEmailVerificationCode
│               └── VerifyCustomer
└── aiAuthoringBundles
    └── TestAgent_1
        ├── TestAgent_1.agent
        └── TestAgent_1.bundle-meta.xml
</pre>
<p>If you know the old types, here&#8217;s how they map:</p>
<ul>
<li>The agent-level part of <code>Bot</code> becomes <code>AiAgentDefinition</code>.</li>
<li>Each bot version and its <code>GenAiPlannerBundle</code> merge into one <code>AiAgentDefinitionVersion</code>.</li>
<li>The planner is no longer a separate component. Instead, it&#8217;s a <code>&lt;planner&gt;</code> element inside the version file.</li>
<li><code>AiAuthoringBundle</code> <span>remains largely unchanged, except that every </span><code>AiAuthoringBundle</code><span> file name also explicitly names its version as a suffix</span><span>.</span></li>
</ul>
<p>A key enhancement in API version 68.0 is support for selecting specific version members using the <code>#</code> delimiter. In your <code>package.xml</code> manifest or with the <code>--metadata</code> flag in Salesforce CLI, you can target an individual agent version, retrieve every version of a specific agent, or query across all versions for all agents, as shown in the table below.</p>
<table>
<tbody>
<tr>
<td><b>Member</b></td>
<td><b>What it selects</b></td>
</tr>
<tr>
<td><code>TestAgent#1</code></td>
<td>Version 1 of TestAgent</td>
</tr>
<tr>
<td><code>TestAgent#*</code></td>
<td>All versions of TestAgent</td>
</tr>
<tr>
<td><code>*</code></td>
<td>All versions of all agents</td>
</tr>
</tbody>
</table>
<p>A few things stand out.</p>
<ul>
<li>Everything that belongs to version 1, except its Agent Script source, lives in one folder, <code>TestAgent#1</code>.</li>
<li>Subagent and action folders use the names that you gave them, like <code>DeliveryIssues/AnswerQuestionsWithKnowledge</code>.</li>
<li>The authoring bundle folder always carries its version number. <code>TestAgent_1</code> keeps its name when you add version 2.</li>
</ul>
<p>The content itself didn&#8217;t change. When the two retrieves were compared, all 52 input and output schemas, the agent graph, and the <code>.agent</code> source file were identical. Only the names and the packaging changed.</p>
<h3>Before and after: subagents and actions</h3>
<p><span style="font-weight: 400">The biggest difference is inside the files. Here&#8217;s the Service Customer Verification subagent and one of its actions in the API version 67.0 planner bundle. Some elements have been trimmed to save space.</span></p>
<pre language="xml">&lt;localTopics&gt;
    &lt;fullName&gt;ServiceCustomerVerification_1796152b9540575&lt;/fullName&gt;
    &lt;canEscalate&gt;false&lt;/canEscalate&gt;
    &lt;description&gt;Verifies the customer's identity before granting access to sensitive data. ...&lt;/description&gt;
    &lt;developerName&gt;ServiceCustomerVerification_1796152b9540575&lt;/developerName&gt;
    &lt;!-- instructions omitted --&gt;
    &lt;language&gt;en_US&lt;/language&gt;
    &lt;localActionLinks&gt;
        &lt;functionName&gt;SendEmailVerificationCode_1724f12735f68c6&lt;/functionName&gt;
    &lt;/localActionLinks&gt;
    &lt;localActionLinks&gt;
        &lt;functionName&gt;VerifyCustomer_1726b9bee0044be&lt;/functionName&gt;
    &lt;/localActionLinks&gt;
    &lt;localActions&gt;
        &lt;fullName&gt;VerifyCustomer_1726b9bee0044be&lt;/fullName&gt;
        &lt;description&gt;Verifies whether the verification code entered by the user matches the code sent to the user's email address.&lt;/description&gt;
        &lt;developerName&gt;VerifyCustomer_1726b9bee0044be&lt;/developerName&gt;
        &lt;invocationTarget&gt;SvcCopilotTmpl__VerifyCode&lt;/invocationTarget&gt;
        &lt;invocationTargetType&gt;flow&lt;/invocationTargetType&gt;
        &lt;isConfirmationRequired&gt;false&lt;/isConfirmationRequired&gt;
        &lt;isIncludeInProgressIndicator&gt;true&lt;/isIncludeInProgressIndicator&gt;
        &lt;localDeveloperName&gt;VerifyCustomer&lt;/localDeveloperName&gt;
        &lt;masterLabel&gt;Verify Customer&lt;/masterLabel&gt;
        &lt;source&gt;SvcCopilotTmpl__VerifyCustomer&lt;/source&gt;
    &lt;/localActions&gt;
    &lt;!-- SendEmailVerificationCode action omitted --&gt;
    &lt;localDeveloperName&gt;ServiceCustomerVerification&lt;/localDeveloperName&gt;
    &lt;masterLabel&gt;Service Customer Verification&lt;/masterLabel&gt;
    &lt;pluginType&gt;Topic&lt;/pluginType&gt;
&lt;/localTopics&gt;
</pre>
<p>And here&#8217;s the same subagent in the API version 68.0 version file, inside the <code>&lt;planner&gt;</code> element.</p>
<pre language="xml">&lt;localTopics&gt;
    &lt;canEscalate&gt;false&lt;/canEscalate&gt;
    &lt;description&gt;Verifies the customer's identity before granting access to sensitive data. ...&lt;/description&gt;
    &lt;!-- instructions omitted --&gt;
    &lt;language&gt;en_US&lt;/language&gt;
    &lt;localActionLinks&gt;
        &lt;functionName&gt;SendEmailVerificationCode&lt;/functionName&gt;
    &lt;/localActionLinks&gt;
    &lt;localActionLinks&gt;
        &lt;functionName&gt;VerifyCustomer&lt;/functionName&gt;
    &lt;/localActionLinks&gt;
    &lt;!-- SendEmailVerificationCode action omitted --&gt;
    &lt;localActions&gt;
        &lt;description&gt;Verifies whether the verification code entered by the user matches the code sent to the user's email address.&lt;/description&gt;
        &lt;invocationTarget&gt;SvcCopilotTmpl__VerifyCode&lt;/invocationTarget&gt;
        &lt;invocationTargetType&gt;flow&lt;/invocationTargetType&gt;
        &lt;isConfirmationRequired&gt;false&lt;/isConfirmationRequired&gt;
        &lt;isIncludeInProgressIndicator&gt;true&lt;/isIncludeInProgressIndicator&gt;
        &lt;localDeveloperName&gt;VerifyCustomer&lt;/localDeveloperName&gt;
        &lt;masterLabel&gt;Verify Customer&lt;/masterLabel&gt;
        &lt;source&gt;SvcCopilotTmpl__VerifyCustomer&lt;/source&gt;
    &lt;/localActions&gt;
    &lt;localDeveloperName&gt;ServiceCustomerVerification&lt;/localDeveloperName&gt;
    &lt;masterLabel&gt;Service Customer Verification&lt;/masterLabel&gt;
    &lt;pluginType&gt;Topic&lt;/pluginType&gt;
&lt;/localTopics&gt;
</pre>
<p>The suffixed <code>fullName</code> and <code>developerName</code> elements are gone. Now <code>localDeveloperName</code> is the name, and links between subagents and actions use it too. In this sample, all 37 suffixed subagent and action names disappeared. Because subagent and action names stay the same from one version to the next, diffs between versions get much smaller and easier to review.</p>
<h3>Before and after: welcome and error messages</h3>
<p>In API version 67.0, your welcome message sits inside a <code>Welcome</code> dialog in the bot version file.</p>
<pre language="xml">&lt;botDialogs&gt;
    &lt;botSteps&gt;
        &lt;botMessages&gt;
            &lt;message&gt;Hi, I'm an AI service assistant. How can I help you?&lt;/message&gt;
            &lt;messageIdentifier&gt;7c57d39d-5d76-40d9-a8e8-181b1a20b657&lt;/messageIdentifier&gt;
        &lt;/botMessages&gt;
        &lt;stepIdentifier&gt;42386b78-770c-4911-8ad9-60f664fdeab0&lt;/stepIdentifier&gt;
        &lt;type&gt;Message&lt;/type&gt;
    &lt;/botSteps&gt;
    &lt;botSteps&gt;
        &lt;stepIdentifier&gt;fdf3a8b3-f4b0-4329-a7f5-349f387b84db&lt;/stepIdentifier&gt;
        &lt;type&gt;Wait&lt;/type&gt;
    &lt;/botSteps&gt;
    &lt;developerName&gt;Welcome&lt;/developerName&gt;
    &lt;isPlaceholderDialog&gt;false&lt;/isPlaceholderDialog&gt;
    &lt;label&gt;Welcome&lt;/label&gt;
    &lt;showInFooterMenu&gt;false&lt;/showInFooterMenu&gt;
&lt;/botDialogs&gt;
&lt;!-- ... --&gt;
&lt;entryDialog&gt;Welcome&lt;/entryDialog&gt;
</pre>
<p><span style="font-weight: 400">In API version 68.0, it&#8217;s one line.</span></p>
<pre language="xml">&lt;welcomeMessage&gt;Hi, I'm an AI service assistant. How can I help you?&lt;/welcomeMessage&gt;
</pre>
<p>The version file has three simple message elements: <code>welcomeMessage</code>, <code>errorMessage</code>, and <code>escalationMessage</code>. They hold the first message of the <code>Welcome</code>, <code>Error_Handling</code>, and <code>Transfer_To_Agent</code> dialogs. When you deploy, Salesforce builds the dialogs from these messages for you.</p>
<h3>Before and after: package.xml</h3>
<p><span style="font-weight: 400">Now suppose that TestAgent has four versions. It also uses a global subagent and two global actions, which call an Apex class and a prompt template. Here&#8217;s a hand-written API version 67.0 manifest for it.</span></p>
<pre language="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;Package xmlns="http://soap.sforce.com/2006/04/metadata"&gt;
    &lt;types&gt;
        &lt;members&gt;TestAgent&lt;/members&gt;
        &lt;name&gt;Bot&lt;/name&gt;
    &lt;/types&gt;
    &lt;types&gt;
        &lt;members&gt;TestAgent_v1&lt;/members&gt;
        &lt;members&gt;TestAgent_v2&lt;/members&gt;
        &lt;members&gt;TestAgent_v3&lt;/members&gt;
        &lt;members&gt;TestAgent_v4&lt;/members&gt;
        &lt;name&gt;GenAiPlannerBundle&lt;/name&gt;
    &lt;/types&gt;
    &lt;types&gt;
        &lt;members&gt;TestAgent_1&lt;/members&gt;
        &lt;members&gt;TestAgent_2&lt;/members&gt;
        &lt;members&gt;TestAgent_3&lt;/members&gt;
        &lt;members&gt;TestAgent&lt;/members&gt;
        &lt;name&gt;AiAuthoringBundle&lt;/name&gt;
    &lt;/types&gt;
    &lt;types&gt;
        &lt;members&gt;Custom_Subagent&lt;/members&gt;
        &lt;name&gt;GenAiPlugin&lt;/name&gt;
    &lt;/types&gt;
    &lt;types&gt;
        &lt;members&gt;SayHello&lt;/members&gt;
        &lt;name&gt;ApexClass&lt;/name&gt;
    &lt;/types&gt;
    &lt;types&gt;
        &lt;members&gt;Custom_PromptTemplate&lt;/members&gt;
        &lt;name&gt;GenAiPromptTemplate&lt;/name&gt;
    &lt;/types&gt;
    &lt;types&gt;
        &lt;members&gt;Say_Hello&lt;/members&gt;
        &lt;members&gt;Custom_PromptTemplate&lt;/members&gt;
        &lt;name&gt;GenAiFunction&lt;/name&gt;
    &lt;/types&gt;
    &lt;version&gt;67.0&lt;/version&gt;
&lt;/Package&gt;
</pre>
<p><span style="font-weight: 400">That&#8217;s 7 types and 14 members. At API version 68.0, you name the agent and its versions.</span></p>
<pre language="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;Package xmlns="http://soap.sforce.com/2006/04/metadata"&gt;
    &lt;types&gt;
        &lt;members&gt;TestAgent&lt;/members&gt;
        &lt;name&gt;AiAgentDefinition&lt;/name&gt;
    &lt;/types&gt;
    &lt;types&gt;
        &lt;members&gt;TestAgent#*&lt;/members&gt;
        &lt;name&gt;AiAgentDefinitionVersion&lt;/name&gt;
    &lt;/types&gt;
    &lt;version&gt;68.0&lt;/version&gt;
&lt;/Package&gt;
</pre>
<p>The <code>#*</code> wildcard picks up every version, so you don&#8217;t need to edit the manifest when you add one.</p>
<h3>How to retrieve Agentforce metadata dependencies with the Salesforce CLI</h3>
<p>What about the Apex class, the prompt template, and the global subagent and actions? Each agent version knows what it uses, so you can ask the Metadata API to bring those along. With Salesforce CLI, add the <code>--root-type-with-dependencies</code> flag and set it to <code>AiAgentDefinitionVersion</code>. You can use it with a manifest or with the <code>--metadata</code> flag. Using the <code>#</code> delimiter syntax and the new flag requires Salesforce CLI version 2.149.9 or later, so be sure to execute <code>sf update</code> prior to running these commands.</p>
<pre language="bash"># With the manifest above
sf project retrieve start --manifest manifest/package.xml --root-type-with-dependencies AiAgentDefinitionVersion --target-org my-sandbox

# Without a manifest, for version 1 of TestAgent
sf project retrieve start --metadata "AiAgentDefinitionVersion:TestAgent#1" --root-type-with-dependencies AiAgentDefinitionVersion --target-org my-sandbox
</pre>
<p>In both cases, the retrieve then adds what each version depends on. That includes its parent <code>AiAgentDefinition</code> and its <code>AiAuthoringBundle</code>, which holds the Agent Script source that you edit. It also includes the flows, Apex classes, and prompt templates that its actions call, and any global subagents and actions that it uses. The compiled Agent Script file in the version&#8217;s <code>agentScript</code> folder isn&#8217;t a dependency. It&#8217;s part of the version, so you get it with or without the flag.</p>
<p>Without the flag, you get only what you name. For example, <code>--metadata AiAgentDefinitionVersion</code> on its own retrieves every version of every agent. It doesn&#8217;t bring each version&#8217;s parent <code>AiAgentDefinition</code>, its <code>AiAuthoringBundle</code>, or anything else that it uses, so name the ones that you need too. If you name versions with <code>--metadata</code>, put the value in double quotes so that your shell doesn&#8217;t treat <code>#</code> or <code>*</code> as special characters. Without a manifest, Salesforce CLI uses the <code>sourceApiVersion</code> in your <code>sfdx-project.json</code> file, so set it to 68.0.</p>
<p>Retrieving with dependencies isn&#8217;t new. From API version 64.0 (Summer &#8217;25) through 67.0, you could do it only for a whole <code>Bot</code>, so you always got every version of the agent. Now you can do it for a single version, so you can move exactly the version that you want, plus what it needs.</p>
<p>A few limits apply. You can make up to 25 retrieve requests with dependencies per day, and each request can cover up to 100 components. The retrieve also goes only one level deep. It adds what each version uses directly, but not what those dependencies use in turn. For example, if an action calls an Apex class that uses another Apex class, you get only the first class. Add other metadata that your agent needs, such as custom objects or Data 360 dependencies, to your manifest or <code>--metadata</code> list.</p>
<h2>Deployment validations in API version 68.0</h2>
<p><span style="font-weight: 400">The new types come with a few deployment validation rules. Most of them protect you from half-deployed agents. Deployment of agents on API 68.0 will fail if:</span></p>
<p><b>You mix the old and new formats.</b> Pick either API 67.0 or API 68.0 format. Don&#8217;t try to deploy <code>Bot</code> or <code>BotVersion</code> and the new types for the same agent in the same deployment.</p>
<p><b>You deploy the definition without at least one version.</b> A version can&#8217;t deploy unless its <code>AiAgentDefinition</code> already exists in the target org or is in the same deployment. A new definition also needs at least one version. The first time you deploy an agent to an org, include the definition, not just a version.</p>
<p><b>Your Agent Script source is not included.</b> For agents built with Agent Script, you must deploy the version’s <code>AiAuthoringBundle</code> along with the <code>AiAgentDefinitionVersion</code>. Note the <code>agentScript</code> folder inside the version is a compiled copy, not the source that you edit. Storing the compiled copy in your source control is not recommended.</p>
<p><b>You don’t set the agent user correctly.</b> Your retrieved metadata includes the agent username from the source org. For a draft agent, use <a href="https://developer.salesforce.com/docs/ai/agentforce/guide/string-replace-example.html"><u>string replacement</u></a> to swap in a username from the target org. Committed agents can&#8217;t be edited, so to give one a new user, create a new version and add the user there. Either way, the agent needs a user before it can run in the new org.</p>
<h2>Migration tips for switching to API 68.0</h2>
<ul>
<li><b>Retrieve instead of converting by hand.</b> Run <code>sf update</code> to get the latest Salesforce CLI, set your manifest version and the <code>sourceApiVersion</code> in your <code>sfdx-project.json</code> file to 68.0, and retrieve. Salesforce writes the new structure for you.</li>
<li><b>Make the switch in its own commit.</b> Every agent folder and file name changes, so keep the switch separate from other work. Real changes won&#8217;t get lost in the rename during code review.</li>
<li><b>Update scripts that read agent XML.</b> If your pipeline parses <code>GenAiPlannerBundle</code> files or relies on suffixed names, point it at the <code>&lt;planner&gt;</code> element in the version file and use <code>localDeveloperName</code>.</li>
</ul>
<h2>Conclusion</h2>
<p><span style="font-weight: 400">API version 68.0 gives every Agentforce agent a simpler, more predictable metadata shape. You get one definition per agent, one folder per version, names without generated suffixes, and messages that you can read at a glance. Your manifests get shorter, your diffs get smaller, and you can retrieve a single version with everything it depends on. Try it in a Winter &#8217;27 org: retrieve one of your agents at API version 68.0 and compare it with what&#8217;s in your source control today.</span></p>
<h2>Resources</h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Release notes: </span><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_agentforce_metadata.htm&amp;release=264&amp;type=5"><span style="font-weight: 400">Move Agents Between Orgs More Easily with Simplified Agent Metadata Types</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Agentforce Developer Guide: </span><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/agent-dx-deploy-metadata.html"><span style="font-weight: 400">Use Metadata to Move an Agent to a New Org</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Agentforce Developer Guide: </span><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/agent-dx-api-v67-earlier.html"><span style="font-weight: 400">Define Agent Metadata (v67 and Earlier)</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Agentforce Developer Guide: </span><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/string-replace-example.html"><span style="font-weight: 400">Example &#8211; Configure String Replacement for Agent Username</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Salesforce CLI Command Reference: </span><a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_project_commands_unified.htm#cli_reference_project_retrieve_start_unified"><span style="font-weight: 400">project retrieve start</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/metadata-coverage"><span style="font-weight: 400">Metadata Coverage Report</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Blog: </span><a href="https://developer.salesforce.com/blogs/2026/05/new-agentforce-metadata-and-development-lifecycle"><span style="font-weight: 400">The New Agentforce Metadata and Development Lifecycle</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Blog: </span><a href="https://developer.salesforce.com/blogs/2026/02/agent-script-decoded-intro-to-agent-script-language-fundamentals"><span style="font-weight: 400">Intro to Agent Script Language Fundamentals</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Trailhead: </span><a href="https://trailhead.salesforce.com/content/learn/projects/create-an-agent-using-pro-code-tools/create-an-agent-from-your-dx-project"><span style="font-weight: 400">Create and Activate Your Agent with Agent Script Techniques</span></a></li>
</ul>
<h2>About the author</h2>
<p><b>Shreyas Ramesh</b><span style="font-weight: 400"> serves as a Lead Software Engineer with Salesforce AI in San Francisco. He is also a Fellow of the Institute of Analytics (FIoA) alongside BCS, The Chartered Institute for IT. Within the Agentforce Core Platform group, Shreyas directs the architecture for Agentforce Metadata, Agentforce Versioning, and additional design-time functionality aimed at providing enterprise-grade application lifecycle management (ALM) for Agentforce. Connect with Shreyas via </span><a href="https://www.linkedin.com/in/rshreyas2/"><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/11/agentforce-metadata-types-in-winter-27-dive-into-aiagentdefinition">Agentforce Metadata Types in Winter &#8217;27: Dive into AiAgentDefinition</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/11/agentforce-metadata-types-in-winter-27-dive-into-aiagentdefinition/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207363</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261110110101/Generic-B-8-e1794337302733.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261110110101/Generic-B-8-e1794337302733.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Accelerate Agent-Driven Development with the Headless Toolkit</title>
		<link>https://developer.salesforce.com/blogs/2026/11/accelerate-agent-driven-development-with-the-headless-toolkit</link>
		<comments>https://developer.salesforce.com/blogs/2026/11/accelerate-agent-driven-development-with-the-headless-toolkit#respond</comments>
		<pubDate>Tue, 10 Nov 2026 16:00:07 +0000</pubDate>
		<dc:creator><![CDATA[Mohith Shrivastava]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Headless 360]]></category>
		<category><![CDATA[New Developments]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[CI/CD]]></category>
		<category><![CDATA[Code Quality]]></category>
		<category><![CDATA[Coding agents]]></category>
		<category><![CDATA[devops]]></category>
		<category><![CDATA[Headless Toolkit]]></category>
		<category><![CDATA[MCP]]></category>
		<category><![CDATA[Model context protocol]]></category>
		<category><![CDATA[Salesforce Development]]></category>
		<category><![CDATA[Software Development Lifecycle]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207306</guid>
		<description><![CDATA[<p>Learn how to accelerate agent-driven Salesforce development with the Headless Toolkit. Explore a seven-stage lifecycle using Salesforce skills, MCP servers, and developer tools to build, test, review, and deploy apps and agents.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/accelerate-agent-driven-development-with-the-headless-toolkit">Accelerate Agent-Driven Development with the Headless Toolkit</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 way developers build on Salesforce is changing. Coding agents like Claude Code, Codex, Cursor, and Agentforce Vibes can now write Apex, Lightning web components (LWC), flows, and metadata. But on their own, these coding agents often drift from platform best practices, miss what already exists in your org, and spend extra tokens hunting for context. They also lack important tools to check and ship their work.</span></p>
<p><span style="font-weight: 400">The </span><a href="https://developer.salesforce.com/docs/platform/headless-get-started/guide/headless-get-started.html"><span style="font-weight: 400">Headless Toolkit</span></a><span style="font-weight: 400"> closes those gaps for building Salesforce apps and agents. 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"> (sf-skills) gives coding agents Salesforce expertise on demand. </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/hosted-mcp-servers-overview.html"><span style="font-weight: 400">Hosted Model Context Protocol (MCP) Servers</span></a><span style="font-weight: 400"> give them just the org context they need. The </span><a href="https://github.com/salesforcecli"><span style="font-weight: 400">Salesforce CLI</span></a><span style="font-weight: 400"> lets them test, validate, and deploy their changes.</span></p>
<p><span style="font-weight: 400">This post offers one way to put the Headless Toolkit to work: an agent-driven development lifecycle with seven stages. For each stage, you&#8217;ll see what changes and which skills and tools to use. You&#8217;ll also see what to add each time the agent gets something wrong. This approach isn&#8217;t the only way, so experiment with the skills and tools covered in this post and adapt the parts that work for your team.</span></p>
<p><span style="font-weight: 400">The post starts with how the lifecycle works, </span><span style="font-weight: 400">defining what a harness is and how it fits into the workflow</span><span style="font-weight: 400">. It then explains how to set up the skills and walks through each stage: ground, specify, build, verify, review, release, and observe.</span></p>
<h2>From a linear process to an agent-driven loop</h2>
<p><span style="font-weight: 400">On a traditional Salesforce project, work moves in a line. A requirement becomes a user story in Jira or another tracker, a developer builds it, QA tests it, and someone deploys it. The context for each change is spread across the story, Slack threads, design docs, the org itself, and the memory of people who&#8217;ve worked on it. A developer knows where to look. An agent can use only what it can reach.</span></p>
<p><span style="font-weight: 400">With coding agents, the line becomes a loop. The agent does more of the building, testing, and deploying, and your role shifts toward directing it and judging what it produces. Directing can be as simple as one prompt that states the goal and how the agent should check its own result. Your attention moves to a few decision points: approving the spec, reviewing the proposed design and architecture, judging review findings, and approving production releases.</span></p>
<p><span style="font-weight: 400">Two ideas make the loop work. First, each stage leaves an artifact the next stage can read, such as a spec, a test, or a review report, so a new session doesn&#8217;t start from zero. Second, every mistake the agent makes leaves something behind, whether the mistake is caught in verification, review, or production. That might be a new test, a new rule, or a new spec for the next change.</span></p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207310" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261105105354/image1_7c5ea2-e1793904846277.png" class="postimages" width="1000" height="704" alt="A diagram showing seven lifecycle stages around your project harness, with Observe feeding back into Ground." /><br />
			  </span></p>
<p><span style="font-weight: 400">Use this table as your map of the lifecycle. Each section below covers its row in more detail.</span></p>
<table>
<tbody>
<tr>
<td><b>Stage</b></td>
<td><b>Before coding agents</b></td>
<td><b>In an agent-driven lifecycle</b></td>
<td><b>Artifact in the repo</b></td>
</tr>
<tr>
<td><b>1. Ground</b></td>
<td><span style="font-weight: 400">Context is spread across stories, docs, and people</span></td>
<td><span style="font-weight: 400">Business terms are written down, and the agent can look up org metadata</span></td>
<td><span>Feature map, </span><code>ontology.yaml</code><span>, </span><code>AGENTS.md</code></td>
</tr>
<tr>
<td><b>2. Specify</b></td>
<td><span style="font-weight: 400">The plan stays with the developer and architect and spread in architecture docs</span></td>
<td><span style="font-weight: 400">A short spec captures changes and architecture decisions; you review it and keep it up to date for agents and humans</span></td>
<td><span style="font-weight: 400">Living spec</span></td>
</tr>
<tr>
<td><b>3. Build</b></td>
<td><span style="font-weight: 400">One developer builds each piece in order</span></td>
<td><span style="font-weight: 400">One agent builds step-by-step, or subagents build in parallel for large features</span></td>
<td><span style="font-weight: 400">Metadata and code, plus a dependency graph for parallel builds</span></td>
</tr>
<tr>
<td><b>4. Verify</b></td>
<td><span style="font-weight: 400">QA tests near the end</span></td>
<td><span style="font-weight: 400">The agent runs independent checks and fixes failures as it goes</span></td>
<td><span style="font-weight: 400">Test results, scan reports</span></td>
</tr>
<tr>
<td><b>5. Review</b></td>
<td><span style="font-weight: 400">Humans read every line</span></td>
<td><span style="font-weight: 400">A separate reviewer agent (an adversarial agent) goes first; humans judge its findings</span></td>
<td><span style="font-weight: 400">Review report with file:line evidence</span></td>
</tr>
<tr>
<td><b>6. Release</b></td>
<td><span style="font-weight: 400">Teams deploy with change sets and manual runbooks, or automate via CI/CD</span></td>
<td><span style="font-weight: 400">The agent validates and promotes; a human owns the production gate</span></td>
<td><span style="font-weight: 400">Validated deploy</span></td>
</tr>
<tr>
<td><b>7. Observe</b></td>
<td><span style="font-weight: 400">The team waits for users to file tickets</span></td>
<td><span style="font-weight: 400">The agent reads logs and traces; findings from users or the agent start the next loop</span></td>
<td><span style="font-weight: 400">Findings, new specs</span></td>
</tr>
</tbody>
</table>
<h2>Two harnesses: the agent&#8217;s and yours</h2>
<p><span style="font-weight: 400">A harness is the layer around a model that gives it tools and manages the context it sees. Every coding agent ships with one.</span><a href="https://code.claude.com/docs/en/how-claude-code-works"> <span style="font-weight: 400">Claude Code</span></a><span style="font-weight: 400">, for example, gives the model built-in tools to read and edit files, search code, run commands, and orchestrate multiple agents. It also compacts long conversations and asks permission before actions you haven&#8217;t allowed. You don&#8217;t build this layer, but you can configure it, for example with permission modes and settings.</span></p>
<p><span style="font-weight: 400">Your project adds a second layer on top, and that&#8217;s the layer this post is about:</span></p>
<p><span>Your project adds a second layer on top, and that&#8217;s the layer this post is about:</span></p>
<ul>
<li><b><span>Skills</span></b><span> that teach the agent Salesforce tasks, such as the ones in sf-skills.</span></li>
<li><b><span>Instructions</span></b><span> in </span><code><span>AGENTS.md</span></code><span> or </span><code><span>CLAUDE.md</span></code><span>, plus your ontology (business-specific vocabulary) and feature map.</span></li>
<li><b><span>Tools</span></b><span> such as the Salesforce CLI, Salesforce Hosted MCP Servers, and the Apex language server.</span></li>
<li><b><span>Checks</span></b><span> such as tests, Code Analyzer rules, hooks, and quality gates.</span></li>
</ul>
<p><span>The Headless Toolkit supplies most of these pieces. You add the instructions and checks that are specific to your org.</span></p>
<p><span>The agent&#8217;s built-in harness is the same for everyone who uses that agent. Your project harness is what makes it effective on your org, and it&#8217;s never finished. Here&#8217;s a small example instructions file, </span><code><span>AGENTS.md</span></code><span>, to start from.</span></p>
<pre language="text"># Agent instructions
- Check ontology.yaml before mapping a business term to a Salesforce object.
- Features live in force-app/&lt;feature&gt;/. Update docs/feature-map.md in the same change.

## Hard rules
- All SOQL in Apex runs WITH USER_MODE.
- Never hardcode record IDs. Use Custom Metadata, Custom Labels, or Custom Settings.
</pre>
<p><span>Keep both layers lean. Models get less accurate as the context window fills, an effect researchers call</span><a href="https://www.trychroma.com/research/context-rot"> <u>context rot</u></a><span>. In my experience, deep technical work goes best under roughly 300K tokens for most models. Skills help because the agent loads a skill&#8217;s full instructions only when it uses that skill. In Claude Code, set </span><code>disable-model-invocation: true</code><span> on skills you only run by hand, and run </span><b><code>/context</code></b><span> to check usage.</span></p>
<p><span style="font-weight: 400">Newer models also now need less prompting than most of us still give them. For Claude Opus 5 and Fable 5, </span><a href="https://claude.dev/blog/the-new-rules-of-context-engineering-for-claude-5-generation-models/"><span style="font-weight: 400">over 80% of the Claude Code system prompt</span></a><span style="font-weight: 400"> was removed with no measurable drop in its coding evals.</span></p>
<h2>Set up the Salesforce Skills Library</h2>
<p><span style="font-weight: 400">A skill is a folder of instructions, scripts, and reference files that teaches an agent a specific task. The Salesforce Skills Library holds more than 250 skills and follows the open</span><a href="https://agentskills.io/"> <span style="font-weight: 400">Agent Skills specification</span></a><span style="font-weight: 400">, so it works with any agent that supports skills. </span><span style="font-weight: 400">If you use Agentforce Vibes, the skills come pre-installed with automatic updates</span><span style="font-weight: 400">. In Claude Code, install the plugins for the stages you use, and add standalone skills with npx, which also works for Codex, Cursor, and OpenCode.</span></p>
<pre language="bash">/plugin install salesforce-development@claude-plugins-official

/plugin marketplace add forcedotcom/sf-skills
/plugin install salesforce-code-quality@salesforce
/plugin install experience-lwc@salesforce
/plugin install dx-devops@salesforce
/plugin install agentforce-adlc@salesforce
/plugin install platform-observability@salesforce

npx skills add forcedotcom/sf-skills --skill dx-org-analyze external-diagram-mermaid-generate platform-docs-get dx-apexguru-scan
</pre>
<p><span>Here&#8217;s what each plugin covers:</span></p>
<ul>
<li><code><span>salesforce-development:</span></code><span> metadata, Apex, Flow, permissions, tests, and deploys.</span></li>
<li><code><span>salesforce-code-quality:</span></code><span> Code Analyzer. (You can also include the </span><code><span>architecture-review</span></code><span> subagent to review architecture.)</span></li>
<li><code><span>experience-lwc:</span></code><span> LWC generation, React or Angular apps, accessibility tests, and security validation.</span></li>
<li><code><span>dx-devops:</span></code><span> DevOps Center work items, pipelines, test suites, and quality gates.</span></li>
<li><code><span>agentforce-adlc</span></code><span> and </span><code><span>platform-observability:</span></code><span> Agentforce testing and tracing.</span></li>
</ul>
<p><span style="font-weight: 400">Some skills call Salesforce Hosted MCP Servers in your org, and those servers are off by default. Your coding agent may not function properly without turning on the MCP servers. To do so, go to Setup, enter </span><b>MCP Servers</b><span style="font-weight: 400"> in Quick Find, open </span><b>MCP Servers</b><span style="font-weight: 400"> under API Catalog, and turn on the MCP servers your skills reference. Activation can take up to two minutes. </span></p>
<h2>Stage 1: Ground the agent in your business and org</h2>
<p><span style="font-weight: 400">An agent starts every session without knowing which objects matter or what your business means by a &#8220;customer.&#8221; Grounding gives it that knowledge.</span></p>
<p><span>Start with structure. The default Salesforce DX project groups files by type, such as all Apex in one folder. Try grouping them by business capability instead, with folders like </span><code>customer_care</code><span> and </span><code>field_service</code><span>. If you can&#8217;t restructure, add a feature map: a short file that lists the Apex, LWC, flows, permissions, and tests behind each feature.</span></p>
<p><span style="font-weight: 400">Next, write down what the agent can&#8217;t guess. Coding agents know Salesforce, but they don&#8217;t reliably know </span><span style="font-weight: 400">how your business terms map to Salesforce</span><span style="font-weight: 400">. Is a &#8220;customer&#8221; an Account or a Contact? Keep a small </span><span style="font-weight: 400">ontology.yaml</span><span style="font-weight: 400"> for those terms, and tell the agent to check it.</span></p>
<pre language="yaml">terms:
  customer:
    meaning: context_dependent
    resolve:
      homeowner_context: "Account is the household; Contact is the resident."
      enterprise_sales_context: "Account (RecordType=Business) is the B2B customer."
    never_assume: "Customer == Contact"
  device:
    sf: Asset
    note: "An installed unit. Its catalog definition is Product2."
</pre>
<h3><span style="font-weight: 400">Look up what already exists in your org</span></h3>
<p><span>Objects and flows created in Setup aren&#8217;t in your repo until someone retrieves them. The</span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/references/reference/metadata-grounding.html"> <u>Metadata Grounding MCP Server</u></a><span> (Beta) lets the agent find them first. The agent calls </span><code>describe_metadata</code><span> to list types, </span><code>search_metadata</code><span> to search in plain language, and </span><code>query_metadata</code><span> to fetch full records. Use it to avoid duplicates and to draft your feature map and ontology from the real org.</span></p>
<p><span>The server is read-only and returns only what the signed-in user can access. Its results come from the Salesforce Catalog index, so very recent changes can lag. It isn&#8217;t available in scratch orgs or in Developer Edition orgs with namespaces, and it requires Data 360, API access, and Salesforce Catalog. Turn it on in Setup with the other MCP servers.</span></p>
<p><b><span>Skills to use:</span></b><span> </span><code><span>platform-metadata-grounding-get </span></code><span>finds metadata that already exists in a Salesforce org by meaning, when you do not know its exact API name</span><code><span>.</span></code><span> </span><code><span>dx-org-analyze</span></code><span> inventories an org&#8217;s metadata, permissions, and packages. </span><code><span>external-diagram-mermaid-generate</span></code><span> turns your data model into a text diagram you can keep in the repo.</span></p>
<p><b><span>Improve the harness:</span></b><span> When the agent misreads a business term, add it to the ontology.</span></p>
<h2><span style="font-weight: 400">Stage 2: Specify what to build</span></h2>
<p><span style="font-weight: 400">A plan made in an agent&#8217;s plan mode usually lives in one session. A spec is a file in the repo that humans approve and agents reread, so the decisions survive between sessions.</span></p>
<p><span style="font-weight: 400">Specs come in three common levels. In </span><i><span style="font-weight: 400">spec-first</span></i><span style="font-weight: 400">, you write the spec, build from it, and move on. In </span><i><span style="font-weight: 400">spec-anchored</span></i><span style="font-weight: 400">, you keep the spec and update it as the feature changes. In </span><i><span style="font-weight: 400">spec-as-source</span></i><span style="font-weight: 400">, humans edit only the spec, and code is generated from it.</span></p>
<p><span style="font-weight: 400">For Salesforce, I prefer spec-anchored. In my experience, much of the risk hides in decisions the code doesn&#8217;t show, such as sharing assumptions and which automation owns a field.</span></p>
<p><span style="font-weight: 400">Not sure what to build yet? Ask the agent to interview you until it can resolve all the decisions required to write a spec. You can also use open-source skills like </span><a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/grill-me/SKILL.md"><span style="font-weight: 400">/grill-me</span></a><span style="font-weight: 400"> for this. Once the spec is finalized, start a new session and build from that spec.</span></p>
<h3><span style="font-weight: 400">When a spec is worth it</span></h3>
<p><span style="font-weight: 400">Not everyone thinks specs are worth the effort, and the critiques are fair. Spec-driven workflows can be overkill for small changes, they can produce Markdown that&#8217;s tedious to review, and a spec doesn&#8217;t stop an agent from skipping instructions. Three habits address those problems:</span></p>
<ul>
<li style="font-weight: 400"><b>Right-size it.</b><span style="font-weight: 400"> For bug fixes and one-file changes, skip the spec. One prompt that states the goal and how to verify it is usually enough. Write a spec when a feature spans several metadata types or several sessions.</span></li>
<li style="font-weight: 400"><b>Record decisions, not a description of the code.</b><span style="font-weight: 400"> Capture what the code can&#8217;t show, such as sharing, which automation owns a field, and data volume. Keep it to a page or less.</span></li>
<li style="font-weight: 400"><b>Check it, don&#8217;t trust it.</b><span style="font-weight: 400"> A spec is advice to the agent, like any instruction. Turn each requirement into a test, and have a reviewer compare the spec with the code, </span><span style="font-weight: 400">so that any requirement the code doesn’t implement fails a test and gets flagged in review.</span></li>
</ul>
<p><b><span>Skills to use:</span></b><span> </span><code>platform-docs-get</code><span> pulls official Salesforce documentation so the spec cites real platform behavior. Here is an unofficial example </span><a href="https://github.com/msrivastav13/salesforce-write-spec-skill"><u>spec-writing skill</u></a><span> that you can take inspiration from.</span></p>
<p><b><span>Improve the harness:</span></b><span> When rework traces back to a decision the spec didn&#8217;t cover, add that topic to your spec template. Common additions are sharing, automation ownership, governor-limit expectations, and data volume.</span></p>
<h2><span style="font-weight: 400">Stage 3: Build step by step or in parallel</span></h2>
<p><span style="font-weight: 400">You have two ways to build with an agent, and neither is always better. Pick the one that fits the feature and the way you like to work.</span></p>
<ul>
<li style="font-weight: 400"><b>One agent, step by step.</b><span style="font-weight: 400"> The agent builds each piece in order while you watch, steer, and review as it goes. This can be easier to follow and keeps you in flow, with no agents to coordinate. It suits bug fixes, small features, and work where each step depends on the last.</span></li>
<li style="font-weight: 400"><b>Subagents in parallel.</b><span style="font-weight: 400"> The agent splits the work across subagents, which are separate agents that each start with a fresh context window. Claude Code often does this on its own, so you rarely need to ask. This can finish large features with independent parts sooner. The trade-off is that you need a clear plan up front, you review more output at once, and every subagent adds cost and waiting time.</span></li>
</ul>
<h3><span style="font-weight: 400">Give each subagent only the context it needs</span></h3>
<p><span style="font-weight: 400">Subagents don&#8217;t make the model smarter by default. Their value is a separate, focused context window. A subagent doesn&#8217;t carry the main session&#8217;s history, so it starts with only what you give it. An accessibility subagent needs the accessibility testing skill, not your Apex, flow, and deployment skills. Each subagent also spends tokens of its own and takes time to gather context. Save them for substantial, standalone work, such as a long audit or a migration. For a small change, one agent is usually faster and cheaper.</span></p>
<p>For example, in Claude Code, you define a<a href="https://code.claude.com/docs/en/sub-agents"> <u>subagent</u></a> as a Markdown file in <code>.claude/agents/</code>. The <code>skills</code> field loads the listed skills when the subagent starts, and the <code>tools</code> field limits what it can do.</p>
<pre language="markdown">---
name: lwc-accessibility-tester
description: Writes and runs Jest accessibility tests for Lightning web components. Use after a component is built.
tools: Read, Write, Edit, Bash
skills:
  - experience-lwc-accessibility-jest-run
---
Test only the components named in the task. Report each failure with its file and line.
</pre>
<h3><span style="font-weight: 400">When parallel pays off</span></h3>
<p><span style="font-weight: 400">Parallel helps only when a feature has independent work, and it pays off once a single session can finish a task without you. Get one session to a passing result first. For a large feature, ask the agent to map the metadata dependency graph from the spec, and review it. Then let subagents build the independent parts at the same time. </span></p>
<p>Keep that work apart with git worktrees. Subagents that edit the same project folder can overwrite each other&#8217;s files. A worktree gives each subagent its own folder and branch in the same repository. In Claude Code, add <code>isolation: worktree</code> to a subagent&#8217;s definition, or start a parallel session with <code>claude --worktree</code>.</p>
<p><b><span>Skills to use:</span></b><span> Either way, give the agent the skill for each piece, such as </span><code>platform-custom-object-generate</code><span>,</span><code> platform-custom-field-generate</code><span>,</span><code> platform-apex-generate</code><span>,</span><code> experience-lwc-generate</code><span>,</span><code> </code>or<code> automation-flow-generate</code><span>.</span><code> platform-metadata-api-context-get</code><span> supplies the exact schema for each metadata type, so the files deploy cleanly.</span></p>
<p><b>Improve the harness:</b><span style="font-weight: 400"> Give the agent only what each piece needs: the schema it depends on, your coding standards, and your test expectations. When it breaks a standard, fix the standards file or skill, not just the code.</span></p>
<h2><span style="font-weight: 400">Stage 4: Verify with independent checks</span></h2>
<p><span style="font-weight: 400">The agent runs checks as it builds, reads the results, and fixes what&#8217;s broken. The checks must be independent tools, not the agent&#8217;s opinion of its own work. </span><span style="font-weight: 400">The agent can run them as skills, or hooks can run them automatically.</span></p>
<ul>
<li><code><span>platform-apex-test-generate</span></code><span> and </span><code><span>platform-apex-test-run</span></code><span> write bulk-safe Apex tests, run them, and drive a test-fix loop.</span></li>
<li><code><span>dx-code-analyzer-run</span></code><span> runs Salesforce Code Analyzer, including PMD and ESLint, and can scan just the files in your git diff.</span></li>
<li><code><span>dx-apexguru-scan</span></code><span> finds performance problems, such as SOQL or DML inside loops, and reports line numbers for each.</span></li>
<li><code><span>design-systems-slds-validate</span></code><span> and </span><code><span>experience-lwc-accessibility-jest-run</span></code><span> check LWC styling and accessibility. </span><code><span>agentforce-test</span></code><span> runs test suites for Agentforce agents.</span></li>
<li><span>For UI tests, the</span><a href="https://marketplace.visualstudio.com/items?itemName=salesforce.salesforce-ui-script-recorder"><span> </span><u><span>Salesforce UI Script Recorder</span></u></a><span> turns your clicks in an org into a Playwright script.</span></li>
</ul>
<p><span>Give the agent a check it can read. If it can run something that returns pass or fail, or a number to beat, it can keep going without you watching. One example is &#8220;all Apex tests pass and coverage stays above 90%.&#8221; For long background work, </span><code><span>/goal</span></code><span> sets a condition like that, and a separate model checks it after every turn, judging only what the agent has shown.</span></p>
<p><span style="font-weight: 400">Don&#8217;t take &#8220;done&#8221; on trust. When the agent reports back, ask it to show you the command and its output, or a screenshot.</span></p>
<h3><span style="font-weight: 400">Run checks automatically with hooks</span></h3>
<p><span style="font-weight: 400">A skill runs only when the agent decides to use it, but a hook runs every time. A hook is a command your coding agent runs at a set point, such as after each file edit or before it finishes a task. Claude Code and Codex both support hooks. When a check fails, the agent sees the error and fixes it before it moves on.</span></p>
<p>The Salesforce plugins you installed earlier already include hooks. <code>experience-lwc</code> checks each LWC file the agent writes against Salesforce Lightning Design System (SLDS) 2 and Code Analyzer rules. <code>agentforce-adlc</code> checks Agent Script files after every edit. <code>salesforce-development</code> runs Apex language server diagnostics before each deployment.</p>
<p><b>Improve the harness:</b><span style="font-weight: 400"> Every bug that gets past verification is a missing check. Add the test, rule, or evaluation that would have caught it. </span><span style="font-weight: 400">If the check should run every time, make it a hook.</span></p>
<h2><span style="font-weight: 400">Stage 5: Review with a separate agent</span></h2>
<p><span style="font-weight: 400">Agents can produce code faster than people can read it, so review can become the bottleneck. The builder shouldn&#8217;t grade its own work. A separate reviewer agent (an adversarial agent) goes first, and humans focus on its findings.</span></p>
<p><span style="font-weight: 400">Four settings make a reviewer sharper: a </span><b>fresh context</b><span style="font-weight: 400"> without the builder&#8217;s history, </span><b>read-only</b><span style="font-weight: 400"> access, a </span><b>different model</b><span style="font-weight: 400"> when you can, and a </span><b>skill</b><span style="font-weight: 400"> to review against.</span></p>
<p><b><span>Skills to use:</span></b><span> The read-only </span><code>platform-architecture-analyze</code><span> checks sharing, field-level security (FLS), bulkification, and SOQL selectivity, and it cites the file and line for every finding. It grades against the original Trusted, Easy, and Adaptable pillars of</span><a href="https://architect.salesforce.com/docs/architect/well-architected/guide/framework.html"> <u>Salesforce Well-Architected</u></a><span>, which now has five pillars, so treat its grades as a baseline. </span><code>experience-lwc-security-validate</code><span> checks LWC bundles for Lightning Web Security (LWS) issues.</span></p>
<p><span>A reviewer can catch what tests and scans miss. For example, an Apex service queries Asset fields </span><code>WITH USER_MODE</code><span>, but the permission set never grants access to them. The tests pass because they run as an admin, and the scan is clean. Yet a support rep with only that permission set hits an error. A reviewer catches this by comparing the permission set’s field access with the query’s fields.</span></p>
<p><b>Improve the harness:</b><span style="font-weight: 400"> Turn each review finding into a check that runs on its own. For the permission gap above, add an Apex test that runs as a minimum-access user with only the permission set. It fails until the permission set is fixed.</span></p>
<h2><span style="font-weight: 400">Stage 6: Release behind deterministic gates</span></h2>
<p><span style="font-weight: 400">The agent drives the path from validation through promotion, deterministic checks guard each step, and a human owns the production gate.</span></p>
<p><span>In scratch orgs, sandboxes, and lower pipeline stages, the agent can validate changes and fix failures on its own. Production is different. The Salesforce Development plugin&#8217;s deploy gate checks the target org before every </span><code>sf project deploy</code><span>. It allows scratch, sandbox, and trial orgs. For a production org, it blocks the command and directs the agent to validate first, then quick deploy after a person approves.</span></p>
<p><b><span>Skills to use:</span></b><span> </span><code>platform-deploy-validate</code><span> runs a dry run for sandboxes and a validation for production, which returns a quick-deploy job ID. </span><code>platform-quick-deploy</code><span> ships that job without rerunning tests. </span></p>
<p><span>For DevOps Center, </span><code>dx-devops-promote</code><span> moves work items between stages, and </span><code>dx-devops-test-pipeline-configure</code><span> adds quality gates, such as a coverage threshold, to a stage.</span></p>
<p><b><span>Improve the harness:</span></b><span> When a release fails late, move the check earlier. </span><code>dx-devops-test-failures-analyze</code><span> explains a DevOps Center pipeline failure in plain language, so you can add a test suite or quality gate to a lower stage.</span></p>
<h2><span style="font-weight: 400">Stage 7: Observe and feed the loop</span></h2>
<p><span style="font-weight: 400">Instead of waiting for users to report problems, the agent can read debug logs, platform traces, and agent sessions to find issues and explain them.</span></p>
<p><b><span>Skills to use:</span></b><span> </span><code>platform-apex-logs-debug</code><span> analyzes debug logs and governor-limit failures. </span><code>platform-tracing-configure</code><span> and </span><code>platform-tracing-agentforce-configure</code><span> turn on tracing. </span></p>
<p><b>Improve the harness:</b><span style="font-weight: 400"> Ask two questions about every production finding. What should we build next? That becomes a new spec. Why did this get through? That becomes a new constraint, as the next section shows.</span></p>
<h2><span style="font-weight: 400">Make every repeated mistake a constraint</span></h2>
<p><span style="font-weight: 400">Coding agents copy patterns that already exist in your repo, including the bad ones. OpenAI saw this while</span><a href="https://openai.com/index/harness-engineering/"> <span style="font-weight: 400">building a large app with Codex</span></a><span style="font-weight: 400">, and</span><a href="https://labs.snyk.io/resources/copilot-amplifies-insecure-codebases-by-replicating-vulnerabilities/"> <span style="font-weight: 400">Snyk found</span></a><span style="font-weight: 400"> that AI assistants can replicate existing security vulnerabilities. So when the agent repeats a mistake, leave behind a constraint that catches it next time.</span></p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207312" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261105105439/image2_6c902b-e1793904891465.png" class="postimages" width="1000" height="533" alt="A ladder of six harness constraints running from advisory (instructions, skills, clean examples) to deterministic (tests, Code Analyzer rules, hooks and gates)." /><br />
			  </span></p>
<p><span style="font-weight: 400">Instructions and skills are advisory: </span><span style="font-weight: 400">they reduce mistakes, but the agent can still ignore them</span><span style="font-weight: 400">.</span><span style="font-weight: 400"> Tests, Code Analyzer rules, hooks, and quality gates are deterministic: they catch mistakes no matter which agent or human wrote the code. Pick the strongest one that fits.</span></p>
<p><span>For code patterns, use </span><code>dx-code-analyzer-custom-rule-create</code><span>. Describe the pattern in plain language, such as &#8220;ban hardcoded record IDs.&#8221; The skill picks the engine and writes the rule with a script, so the YAML is escaped correctly. Then it tests the rule on one file that should fail and one that should pass. Here&#8217;s the rule it adds to </span><code>code-analyzer.yml</code><span>.</span></p>
<pre language="yaml">engines:
  regex:
    custom_rules:
      NoHardcodedSalesforceIds:
        regex: '/[''"](?&lt;target&gt;(?=[a-zA-Z0-9]{0,14}\d)[a-zA-Z0-9]{15}(?:[A-Z0-5]{3})?)\\?[''"]/g'
        description: "Detects hardcoded 15 or 18 character Salesforce record IDs"
        violation_message: "Replace hardcoded ID with Custom Label, Custom Metadata, or Custom Setting"
        severity: 2
        tags:
          - "Custom"
          - "Security"
        file_extensions:
          - ".cls"
          - ".trigger"
</pre>
<p><span>Then use </span><code>dx-code-analyzer-configure</code><span> to run Code Analyzer in your CI/CD pipeline, such as GitHub Actions. Add the rule to your agent instructions, and clean up old code that breaks it, so the agent has nothing wrong to copy.</span></p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">When agents write more of the code, your value moves to grounding them, deciding what to build, and judging the result. An agent-driven lifecycle gives that work a shape: ground, specify, build, verify, review, release, and observe. The Headless Toolkit, together with tools you already know, such as Code Analyzer, </span><a href="https://help.salesforce.com/s/articleView?id=xcloud.apexguru_workflow.htm&amp;type=5"><span style="font-weight: 400">ApexGuru</span></a><span style="font-weight: 400">, and DevOps Center, covers every stage.</span></p>
<p><span style="font-weight: 400">You don&#8217;t need to adopt it all at once. Install the skills, pick the stages that your team finds most efficient, and start there. Every time the agent gets something wrong, improve your harness.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><a href="https://github.com/forcedotcom/sf-skills"><span style="font-weight: 400">Salesforce Skills Library (sf-skills) on GitHub</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/forcedotcom/sf-skills/tree/main/plugins/builder/salesforce-development"><span style="font-weight: 400">Salesforce Development plugin</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/forcedotcom/sf-skills/tree/main/plugins/builder/salesforce-code-quality"><span style="font-weight: 400">Salesforce Code Quality plugin</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/activate-mcp-servers.html"><span style="font-weight: 400">Activate MCP Servers</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/references/reference/metadata-grounding.html"><span style="font-weight: 400">Metadata Grounding MCP Server (Beta)</span></a></li>
</ul>
<p><b>Mohith Shrivastava</b><span style="font-weight: 400"> is a Principal Developer Advocate at Salesforce. Mohith focuses on AI-assisted development and shares practical patterns for building Salesforce apps and agents with coding agents. Follow Mohith on</span><a href="https://www.linkedin.com/in/mohith-shrivastava/"> <span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400"> and</span><a href="https://github.com/msrivastav13"> <span style="font-weight: 400">GitHub</span></a><span style="font-weight: 400">.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/accelerate-agent-driven-development-with-the-headless-toolkit">Accelerate Agent-Driven Development with the Headless Toolkit</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/11/accelerate-agent-driven-development-with-the-headless-toolkit/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207306</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261105105231/SingleHeadshot-13-e1793904767965.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261105105231/SingleHeadshot-13-e1793904767965.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Quick Pages (Beta) を Developer Edition で試す : 有効化からページの作成まで</title>
		<link>https://developer.salesforce.com/blogs/2026/11/quick-pages-beta-developer-edition-jp</link>
		<comments>https://developer.salesforce.com/blogs/2026/11/quick-pages-beta-developer-edition-jp#respond</comments>
		<pubDate>Mon, 09 Nov 2026 01:33:40 +0000</pubDate>
		<dc:creator><![CDATA[Hiroyuki Inaba]]></dc:creator>
				<category><![CDATA[App Development]]></category>
		<category><![CDATA[customize]]></category>
		<category><![CDATA[quick pages]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207339</guid>
		<description><![CDATA[<p>&#12505;&#12540;&#12479;&#20844;&#38283;&#12373;&#12428;&#12383; Quick Pages &#12434; Developer Edition &#12391;&#35430;&#12377;&#25163;&#38918;&#12391;&#12377;&#12290;&#27231;&#33021;&#12398;&#26377;&#21177;&#21270;&#12392;&#27177;&#38480;&#12475;&#12483;&#12488;&#12398;&#21106;&#12426;&#24403;&#12390;&#12363;&#12425;&#12289;&#33258;&#28982;&#35328;&#35486;&#12391;&#12398;&#12506;&#12540;&#12472;&#20316;&#25104;&#12289;&#20250;&#35441;&#12391;&#12398;&#20462;&#27491;&#12289;&#20445;&#23384;&#12539;&#20849;&#26377;&#12414;&#12391;&#12434;&#30011;&#38754;&#20184;&#12365;&#12391;&#32057;&#20171;&#12375;&#12414;&#12377;&#12290;&#12480;&#12511;&#12540;&#12487;&#12540;&#12479;&#12434;&#20316;&#12427; Anonymous Apex &#12398;&#12473;&#12463;&#12522;&#12503;&#12488;&#12418;&#20844;&#38283;&#12375;&#12390;&#12356;&#12414;&#12377;&#12290;</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/quick-pages-beta-developer-edition-jp">Quick Pages (Beta) を Developer Edition で試す : 有効化からページの作成まで</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p>みなさん、こんにちは！<br />
<strong>Quick Pages</strong> がベータ版として公開されました。「こんなページがほしい」と自然言語で伝えるだけで、エージェントが組織のデータを使ったページを作ってくれる機能です。しかも <strong>Developer Edition でも使えます</strong>。<br />
この記事では、払い出したばかりの Developer Edition で、Quick Pages の機能の有効化から、ページを作る・会話で直す・保存して共有するまでを順に紹介します。最新の Developer Edition にはサンプルデータが入っていなかったので、ダミーデータを作る Anonymous Apex のスクリプトもあわせて公開します。<br />
<!-- TODO: YouTube の URL を入れる --></p>
<ul>
<li><a href="https://www.salesforce.com/blog/introducing-quick-pages-builder-tool/">Introducing Quick Pages（Salesforce ブログ）</a></li>
<li><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_quick_pages_beta.htm&amp;release=264&amp;type=5">Bring Custom UIs to Life with Quick Pages (Beta)（Winter &#8217;27 リリースノート）</a></li>
</ul>
<blockquote><p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4cc.png" alt="📌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>この記事は 2026 年 11 月時点（Winter &#8217;27）の情報で作成しています。一般提供までに、機能や画面、提供条件が変わることがあります。</strong></p></blockquote>
<blockquote><p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>注意</strong></p>
<ul>
<li>Quick Pages は<strong>ベータ版のサービス</strong>です。ご利用はお客様ご自身の判断で行ってください。ベータサービスのご利用には、Beta Services Terms と、Product Terms Directory の Non-GA Agentforce / Non-GA Gen AI の条件が適用されます。</li>
<li>ベータ期間中、Quick Pages の AI 利用は無料ですが、<strong>一般提供後は従量課金</strong>の対象になります。</li>
<li>本番組織ではなく、<strong>Developer Edition</strong> などで試すことをおすすめします。本記事の手順は Developer Edition で動作を確認しています。</li>
<li>購入をご検討の際は、現在一般提供されている機能をもとにご判断ください。</li>
</ul>
<p>参考:</p>
<ul>
<li><a href="https://help.salesforce.com/s/articleView?id=platform.quick_pages_considerations.htm&amp;type=5">Considerations and Limitations for Quick Pages (Beta)</a></li>
<li><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_quick_pages_beta.htm&amp;release=264&amp;type=5">Bring Custom UIs to Life with Quick Pages (Beta)（Winter &#8217;27 リリースノート）</a></li>
</ul>
</blockquote>
<p>動画も公開しました。</p>
<div class="video-container"><iframe loading="lazy" title="Quick Pages (Beta) を Developer Edition で試す" width="500" height="281" src="https://www.youtube.com/embed/piz1G3OlmjQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></div>
<hr />
<h2 id="quick-pages-とは">Quick Pages とは</h2>
<p>一言で表現すると、<strong>「話しかけるだけで、組織のデータを使ったページを作れる機能」</strong> です。</p>
<ul>
<li>作りたいページを説明すると、エージェントが React ベースの 1 ページのアプリを作ります。ライブプレビューを見ながら、チャットで直していけます。</li>
<li>組織のメタデータ（カスタムオブジェクトや項目も含む）に沿って作られ、データはいつも最新です。グラフ、表、タブなどを入れられます。</li>
<li>作ったページは、個人、公開グループ、組織全体に共有できます。見る人には、その人がアクセスできるデータだけが表示されます。</li>
<li><strong>管理者権限がなくても、自分で作れます。</strong> ページを作っても組織のメタデータは変わらず、管理者は組織内のすべてのページを確認できます。</li>
</ul>
<h3 id="使える組織の条件">使える組織の条件</h3>
<ul>
<li>Lightning Experience</li>
<li>Enterprise / Performance / Unlimited / <strong>Developer</strong> Edition</li>
<li>Foundations が有効で、<strong>Hyperforce</strong> 上にある組織</li>
</ul>
<p>参考:</p>
<ul>
<li><a href="https://help.salesforce.com/s/articleView?id=platform.quick_pages_setup.htm&amp;type=5">Set Up Quick Pages (Beta)</a></li>
<li><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_quick_pages_beta.htm&amp;release=264&amp;type=5">Bring Custom UIs to Life with Quick Pages (Beta)（Winter &#8217;27 リリースノート）</a></li>
</ul>
<hr />
<h2 id="全体の流れ">全体の流れ</h2>
<ol type="1">
<li>Quick Pages をオンにする</li>
<li>権限セットを割り当てる</li>
<li>ダミーデータを入れる（任意）</li>
<li>ページを作る</li>
<li>会話で直す</li>
<li>保存して共有する</li>
</ol>
<p>今回は Developer Edition &amp; システム管理者での操作で解説します。</p>
<hr />
<h2 id="事前準備">事前準備</h2>
<p>公式の <a href="https://help.salesforce.com/s/articleView?id=platform.quick_pages_setup.htm&amp;type=5">Set Up Quick Pages (Beta)</a> では、機能をオンにする前に次の 4 つを確認するよう案内されています。</p>
<ul>
<li>組織が Hyperforce 上にあること（設定 &gt; 組織情報 で、インスタンスが「英字 3 文字＋数字」になっていれば Hyperforce）</li>
<li>設定 &gt; Salesforce Multi-Framework Apps で、ドメインが有効になっていること（「Enable Domain」ボタンがあれば押す）</li>
<li>設定 &gt; 私のドメイン &gt; ルーティングとポリシー で、「Use Salesforce Edge Network」が有効になっていること</li>
<li>設定 &gt; Einstein 設定 で、Einstein がオンになっていること</li>
</ul>
<p><strong>最新の Developer Edition では、これらは最初から満たされています。</strong> そのため、この記事ではすぐに機能の有効化から始めます。既存の組織で試す場合は、上の 4 つを先に確認してください。</p>
<hr />
<h2 id="ステップ-1quick-pages-をオンにする">ステップ 1：Quick Pages をオンにする</h2>
<p>まずは組織で Quick Pages の機能を有効化します。</p>
<ul>
<li>設定 &gt; クイック検索に「Quick」と入力（英語で入力します）</li>
<li>「Quick Pages (Beta)」を選択</li>
<li>「Quick Pages」のトグルをオンにする</li>
</ul>
<p>「Quick Pages is turned on in your org.」と表示されれば成功です。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207342" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162402/01-quick-pages-on.png?w=1710" class="postimages" width="1710" height="986" alt="" /><br />
			  </span></p>
<hr />
<h2 id="ステップ-2権限セットを割り当てる">ステップ 2：権限セットを割り当てる</h2>
<p>次に、ページを作る人に権限セットを割り当てます。Quick Pages 用の権限セットは 2 つあります。</p>
<ul>
<li><strong>クイックページを作成</strong>（Create and View Quick Pages (Beta)）: ページを作る人</li>
<li><strong>クイックページを表示</strong>（View Quick Pages (Beta)）: ページを見るだけの人</li>
</ul>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207343" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162406/02-permission-sets.png?w=1710" class="postimages" width="1710" height="986" alt="" /><br />
			  </span></p>
<p>今回は自分でページを作るので、「クイックページを作成」を自分に割り当てます。</p>
<ul>
<li>設定 &gt; クイック検索に「権限セット」と入力し、「権限セット」を選択</li>
<li>「クイックページを作成」を開く</li>
<li>「割り当ての管理」&gt;「割り当てを追加」で自分のユーザーを選び、割り当てる</li>
</ul>
<p>「1 の割り当てに成功しました。」と表示されれば成功です。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207344" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162408/03-permset-assigned.png?w=1710" class="postimages" width="1710" height="986" alt="" /><br />
			  </span></p>
<p>以上で初期設定は完了です。</p>
<hr />
<h2 id="ステップ-3ダミーデータを入れる任意">ステップ 3：ダミーデータを入れる（任意）</h2>
<p>最新の Developer Edition には、取引先や商談のサンプルデータが入っていませんでした。データが無いとページを作っても中身が空になってしまうので、Anonymous Apex でダミーデータを作ります。お手持ちの組織にデータがある場合は、このステップは飛ばしてください。<br />
作るデータは次のとおりです。</p>
<ul>
<li>取引先「アストロ製作所」1 件（すでにあれば、それを使います）</li>
<li>商談 10 件（フェーズ・金額・完了予定日をばらして作成）</li>
</ul>
<p>ここでは、ブラウザ上で使える Web コンソールから実行します。</p>
<ul>
<li>画面右上の歯車アイコン（設定メニュー）&gt;「Web コンソール」を開く</li>
<li>コマンドパレットで <code>SFDX: Create Anonymous Apex Script</code> を実行し、スクリプトのファイルを作る</li>
<li>作られたファイルに、下のスクリプトを貼り付ける</li>
<li>エディターの 1 行目の上に出る「Execute」を押す</li>
</ul>
<p>「Anonymous Apex executed successfully」と表示されれば成功です。<br />
スクリプトは次のとおりです。<br />
<code>scripts/apex/sampledata.apex</code>:</p>
<pre class="apex"><code>// ============================================================
// ダッシュボード用ダミーデータ作成 (Anonymous Apex) 改訂版
// 取引先: アストロ製作所 (基本情報を拡充)
// 商談 10件
// 実行: Web Console &gt; SFDX: Create Anonymous Apex Script で
//       このファイルの内容を貼り付け &gt; SFDX: Execute Anonymous Apex
// ============================================================
Savepoint sp = Database.setSavepoint();
try {
    // ---------- 取引先 ----------
    Account acc;
    List&lt;Account&gt; existing = [SELECT Id FROM Account WHERE Name = 'アストロ製作所' LIMIT 1];
    if (existing.isEmpty()) {
        acc = new Account(
            Name = 'アストロ製作所',
            Site = '東京本社',
            Type = 'Customer - Direct',
            Industry = 'Manufacturing',
            Rating = 'Hot',
            AccountSource = 'Web',
            Phone = '03-0000-0001',
            Fax = '03-0000-0002',
            Website = 'https://www.example.com/astro',
            NumberOfEmployees = 320,
            AnnualRevenue = 4500000000.00,
            BillingStreet = '大森北1-2-3 アストロビル',
            BillingCity = '大田区',
            BillingPostalCode = '143-0000',
            ShippingStreet = '京浜島4-5-6 第一工場',
            ShippingCity = '大田区',
            ShippingPostalCode = '143-0003',
            Description = '精密部品・産業機械の製造メーカー。国内3拠点の工場を持ち、'
                        + '生産管理・品質管理の高度化を推進中。ダッシュボード検証用ダミーデータ。'
        );
        insert acc;
    } else {
        acc = existing[0];
    }

    // ---------- 商談 (10件: ステージ・金額・クローズ日を分散) ----------
    Date today = Date.today();
    List&lt;Opportunity&gt; opps = new List&lt;Opportunity&gt;{
        new Opportunity(AccountId = acc.Id, Name = 'アストロ製作所 - 生産管理システム導入',
            StageName = 'Prospecting', Amount = 3000000, CloseDate = today.addDays(90),
            LeadSource = 'Web', NextStep = '要件ヒアリング日程調整',
            Description = '既存の表計算ベース管理からの移行案件。'),
        new Opportunity(AccountId = acc.Id, Name = 'アストロ製作所 - 工場見える化ダッシュボード',
            StageName = 'Prospecting', Amount = 1800000, CloseDate = today.addDays(120),
            LeadSource = 'Web', NextStep = '初回提案資料の送付',
            Description = '稼働状況をリアルタイムに把握したいとの要望。'),
        new Opportunity(AccountId = acc.Id, Name = 'アストロ製作所 - 受発注システム刷新',
            StageName = 'Qualification', Amount = 4200000, CloseDate = today.addDays(75),
            LeadSource = 'Partner Referral', NextStep = '予算・決裁フローの確認',
            Description = 'パートナー経由の紹介案件。'),
        new Opportunity(AccountId = acc.Id, Name = 'アストロ製作所 - IoTセンサー追加',
            StageName = 'Needs Analysis', Amount = 5500000, CloseDate = today.addDays(60),
            LeadSource = 'Partner Referral', NextStep = '現地調査の実施',
            Description = '第二工場へのセンサー増設。'),
        new Opportunity(AccountId = acc.Id, Name = 'アストロ製作所 - 設備保全クラウド',
            StageName = 'Proposal/Price Quote', Amount = 2700000, CloseDate = today.addDays(45),
            LeadSource = 'Phone Inquiry', NextStep = '見積書の提出',
            Description = '予防保全の仕組み化を検討中。'),
        new Opportunity(AccountId = acc.Id, Name = 'アストロ製作所 - 基幹システム連携開発',
            StageName = 'Proposal/Price Quote', Amount = 6000000, CloseDate = today.addDays(35),
            LeadSource = 'Web', NextStep = '技術要件のすり合わせ',
            Description = '既存ERPとのAPI連携。'),
        new Opportunity(AccountId = acc.Id, Name = 'アストロ製作所 - 保守契約更新',
            StageName = 'Negotiation/Review', Amount = 1200000, CloseDate = today.addDays(20),
            LeadSource = 'Phone Inquiry', NextStep = '契約書レビュー',
            Description = '年間保守契約の更新。値引き交渉中。'),
        new Opportunity(AccountId = acc.Id, Name = 'アストロ製作所 - 品質管理ツール',
            StageName = 'Closed Won', Amount = 2400000, CloseDate = today.addDays(-15),
            LeadSource = 'Web', NextStep = '導入後フォロー',
            Description = '受注済み。導入完了済み。'),
        new Opportunity(AccountId = acc.Id, Name = 'アストロ製作所 - 従業員向け研修プログラム',
            StageName = 'Closed Won', Amount = 3600000, CloseDate = today.addDays(-70),
            LeadSource = 'Partner Referral', NextStep = '効果測定のレビュー',
            Description = '全拠点で実施した研修パッケージ。'),
        new Opportunity(AccountId = acc.Id, Name = 'アストロ製作所 - 在庫最適化PoC',
            StageName = 'Closed Lost', Amount = 800000, CloseDate = today.addDays(-30),
            LeadSource = 'Other', NextStep = '失注理由の振り返り',
            Description = '競合製品を採用されたため失注。')
    };
    insert opps;

    System.debug('&#x2705; 作成完了: Account=' + acc.Id + ', Opportunity=' + opps.size());
} catch (Exception e) {
    Database.rollback(sp);
    System.debug(LoggingLevel.ERROR, '&#x274c; エラー: ' + e.getMessage() + ' / ' + e.getStackTraceString());
}

// ============================================================
// 【削除用スクリプト】(必要なときだけ別途実行)
// List&lt;Account&gt; a = [SELECT Id FROM Account WHERE Name = 'アストロ製作所'];
// delete [SELECT Id FROM Opportunity WHERE AccountId IN :a];
// delete a;
// ============================================================</code></pre>
<blockquote><p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4dd.png" alt="📝" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 試し終わってデータを消したいときは、末尾の【削除用スクリプト】のコメントを外した部分だけを、別のファイルで実行してください。</p></blockquote>
<p>取引先「アストロ製作所」を開いて、商談が入っていれば準備完了です。</p>
<hr />
<h2 id="ステップ-4ページを作る">ステップ 4：ページを作る</h2>
<p>それではページを作ってみましょう。</p>
<ul>
<li>アプリケーションランチャーで「ページ」と検索し、「Quick Pages (Beta)」を開く</li>
<li>「New Quick Page」ボタンを押す</li>
</ul>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207345" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162410/06-new-quick-page.png?w=1710" class="postimages" width="1710" height="986" alt="" /><br />
			  </span></p>
<p>右側のチャット欄に、作りたいページを日本語で説明して送ります。今回は次のように入力しました。</p>
<pre class="text"><code>取引先"アストロ製作所"の商談ダッシュボードを作成して。商談数や金額合計をタイルで表示、またフェーズごとの数量を棒グラフで見せて</code></pre>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207346" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162412/07-prompt.png?w=1710" class="postimages" width="1710" height="986" alt="" /><br />
			  </span></p>
<blockquote><p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>現時点でのハマりどころ：日本語入力の Enter で送信されてしまう</strong><br />
現在のベータ版では、日本語入力のかな漢字変換を確定する Enter で、<strong>入力途中の指示が送信されてしまう</strong>ことがあります。別の場所（テキストエディターなど）で書いた指示を貼り付けて送るのがおすすめです。(改修対応中)</p></blockquote>
<p>送信すると、エージェントがページを組み立て始めます。今回は 20 秒ほどで、次のようなページができあがりました。上には商談数と金額合計のタイル、その下にはフェーズ別件数の棒グラフと、商談の一覧が並んでいます。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207347" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162415/08-generated-top.png?w=1710" class="postimages" width="1710" height="986" alt="" /><br />
			  </span></p>
<blockquote><p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> プロンプトは短く具体的に書くのがコツです。現時点では、長いプロンプトは 2 分でタイムアウトします。</p></blockquote>
<hr />
<h2 id="ステップ-5会話で直す">ステップ 5：会話で直す</h2>
<p>次は、できたページを会話で直していきます。<br />
チャット欄を見ると、エージェントが「NEXT」として、次に直せそうなことを候補で出してくれています。今回は「商談を完了予定日の昇順で並べ替える」を押してみます。<br />
商談の一覧が完了予定日の順に並び替わりました。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207348" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162417/10-sorted.png?w=1710" class="postimages" width="1710" height="986" alt="" /><br />
			  </span></p>
<p>続けて、次の候補「商談をフェーズで絞り込んで表示する」を押します。ページの上にフェーズのドロップダウンができました。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207349" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162419/11-filter.png?w=1710" class="postimages" width="1710" height="986" alt="" /><br />
			  </span></p>
<p>ドロップダウンでフェーズを選ぶと、その場で商談が絞り込まれます。チャットで頼んだ機能が、そのまま動くページになっています。<br />
もちろん候補を押すだけでなく、チャット欄に自分で指示を書いて直すこともできます。</p>
<hr />
<h2 id="ステップ-6保存して共有する">ステップ 6：保存して共有する</h2>
<p>できあがったら、保存して共有します。</p>
<ul>
<li>画面右上の「Save」ボタンを押す</li>
<li>「Name your page」にページ名を入力し、「Save」ボタンを押す</li>
</ul>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207350" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162422/12-name-page.png?w=1710" class="postimages" width="1710" height="986" alt="" /><br />
			  </span></p>
<blockquote><p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 現時点の仕様: <strong>保存したあとはページ名を変えられません。</strong> ここで決めておきましょう。</p></blockquote>
<p>保存すると、画面右上に「Edit」「Share」「Delete」ボタンが出ます。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207351" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162428/13-saved.png?w=1710" class="postimages" width="1710" height="986" alt="" /><br />
			  </span></p>
<p>直すときは「Edit」です。保存した変更は、共有先にもそのまま反映されます。保存しないで終えた変更は下書きに残り、共有先に見えるのは最後に保存した版です。<br />
共有するときは「Share」ボタンを押します。</p>
<ul>
<li>組織全体に共有する場合: 「All org」にチェックを入れる</li>
<li>特定の人に共有する場合: 「Add people or groups」で個人や公開グループを検索して追加する</li>
<li>「Save」ボタンを押す</li>
</ul>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207352" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162430/14-share.png?w=1710" class="postimages" width="1710" height="986" alt="" /><br />
			  </span></p>
<p>ダイアログにも書かれているとおり、<strong>見る人には、その人がアクセスできるデータだけが表示されます。</strong> 同じページでも、見る人の権限によって表示される内容が変わります。<br />
以上で、ページの作成から共有までは完了です。</p>
<hr />
<h2 id="ベータ中の制約">ベータ中の制約</h2>
<p><a href="https://help.salesforce.com/s/articleView?id=platform.quick_pages_considerations.htm&amp;type=5">Considerations and Limitations for Quick Pages (Beta)</a> から、試すときに気にしておきたいものを抜き出しました（2026 年 11 月時点）。</p>
<ul>
<li>ページは<strong>読み取り専用</strong>です。ページからレコードの作成・更新・削除はできません。データを変えたい場合は元のレコードを直して、ページを再読み込みします。</li>
<li>プロンプトは短く具体的に。長いプロンプトは 2 分でタイムアウトします。</li>
<li>保存したあとはページ名を変えられません。</li>
<li>管理者は他の人のページを見たり共有したりできますが、編集・削除はできません。</li>
<li>Connect REST API や Apex クラスからデータを取ること、Data 360 や外部 API とつなぐこと、Salesforce CMS やファイルの素材を使うことはできません。</li>
<li>データエクスポートで組織間を移すことや、パッケージに入れることはできません。</li>
</ul>
<p>これらは正式リリースの際には変わっている可能性があります。最新の情報は公式ドキュメントをご確認ください。</p>
<hr />
<h2 id="おわりに">おわりに</h2>
<p>この記事では、Developer Edition で Quick Pages (Beta) を有効化し、ダミーデータを入れて、ページを作る・会話で直す・保存して共有するまでの流れを紹介しました。振り返ると、やることは次のとおりです。</p>
<ul>
<li>機能をオンにして、権限セット「クイックページを作成」を割り当てる</li>
<li>データが無ければ、Anonymous Apex でダミーデータを作る</li>
<li>作りたいページを説明して、会話で直して、保存して共有する</li>
</ul>
<p>これまで、ちょっとした集計や一覧のページがほしいときは、レポートやダッシュボードを組んだり、Lightning Web Components で画面を作ったりする必要がありました。Quick Pages を使えば、<strong>話しかけるだけで、組織の実データを使ったページを、管理者権限なしで作れます。</strong> 見る人の権限もそのまま尊重されるので、気軽にチームへ共有できるのもうれしいところです。<br />
ぜひ Developer Edition で Quick Pages を試してみてください。</p>
<h2 id="参考資料">参考資料</h2>
<ul>
<li><a href="https://www.salesforce.com/blog/introducing-quick-pages-builder-tool/">Introducing Quick Pages（Salesforce ブログ）</a></li>
<li><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_quick_pages_beta.htm&amp;release=264&amp;type=5">Bring Custom UIs to Life with Quick Pages (Beta)（Winter &#8217;27 リリースノート）</a></li>
<li><a href="https://help.salesforce.com/s/articleView?id=platform.quick_pages_setup.htm&amp;type=5">Set Up Quick Pages (Beta)</a></li>
<li><a href="https://help.salesforce.com/s/articleView?id=platform.quick_pages_create.htm&amp;type=5">Create and Share Quick Pages (Beta)</a></li>
<li><a href="https://help.salesforce.com/s/articleView?id=platform.quick_pages_considerations.htm&amp;type=5">Considerations and Limitations for Quick Pages (Beta)</a></li>
</ul>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/quick-pages-beta-developer-edition-jp">Quick Pages (Beta) を Developer Edition で試す : 有効化からページの作成まで</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/11/quick-pages-beta-developer-edition-jp/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207339</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162256/quick-pages-beta-developer-edition_1200x600.png?w=1200" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261107162256/quick-pages-beta-developer-edition_1200x600.png?w=1200" medium="image" />
	</item>
		<item>
		<title>Amazon Bedrock から MCP サーバー経由で Salesforce を触る</title>
		<link>https://developer.salesforce.com/blogs/2026/11/jp-access-salesforce-from-amazon-bedrock-via-mcp-server</link>
		<comments>https://developer.salesforce.com/blogs/2026/11/jp-access-salesforce-from-amazon-bedrock-via-mcp-server#respond</comments>
		<pubDate>Fri, 06 Nov 2026 23:26:57 +0000</pubDate>
		<dc:creator><![CDATA[odasho]]></dc:creator>
				<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Headless 360]]></category>
		<category><![CDATA[amazon web services]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Hosted MCP Servers]]></category>
		<category><![CDATA[Model Context Protocol (MCP)]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207317</guid>
		<description><![CDATA[<p>&#33258;&#20998;&#12391;&#20316;&#12387;&#12383; AI &#12456;&#12540;&#12472;&#12455;&#12531;&#12488;&#12363;&#12425;&#12418;&#12289;Salesforce Hosted MCP Server &#32076;&#30001;&#12391; Salesforce &#12398;&#12487;&#12540;&#12479;&#12434;&#35501;&#12415;&#26360;&#12365;&#12391;&#12365;&#12414;&#12377;&#12290;Amazon Bedrock &#12392; Strands Agents &#12398;&#12456;&#12540;&#12472;&#12455;&#12531;&#12488;&#12363;&#12425;&#12289;&#12465;&#12540;&#12473;&#12398;&#20778;&#20808;&#24230;&#12434;&#21028;&#26029;&#12375;&#12390;&#26360;&#12365;&#25147;&#12377;&#12414;&#12391;&#12434;&#12289;&#12467;&#12540;&#12489;&#20184;&#12365;&#12391;&#35299;&#35500;&#12375;&#12414;&#12377;&#12290;</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/jp-access-salesforce-from-amazon-bedrock-via-mcp-server">Amazon Bedrock から MCP サーバー経由で Salesforce を触る</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p>みなさん、こんにちは！外部の AI エージェントから、Salesforce のデータを読んだり更新したりしたいと思ったことはありませんか？</p>
<p>Salesforce Hosted MCP Server を使うと、ChatGPT や Claude などの AI アプリだけでなく、自分で作った AI エージェントからも、MCP (Model Context Protocol) で Salesforce のデータを扱えます。</p>
<p>この記事では、AWS のオープンソースの AI エージェント SDK、Strands Agents でエージェントを作り、Salesforce のケースを読んで、Amazon Bedrock のモデルが判断した優先度を書き戻すまでを解説します。</p>
<blockquote><p>※この記事の内容を試すには、Salesforce 組織と AWS アカウントが必要です。Salesforce の環境は、無料で取得できる <a href="https://developer.salesforce.com/signup">Developer Edition</a> で試せます。Amazon Bedrock の利用には料金がかかります (この記事のエージェントは、1 回の実行で入力が約 1 万トークン、出力が約 3,500 トークンでした)。Hosted MCP Server も組織によっては請求の対象になるので、本番組織では、設定の「MCP サーバー」の画面に出る Flex Credits の案内を確認してください。</p></blockquote>
<blockquote><p>※2026 年 11 月時点の情報で作成しています。</p></blockquote>
<h2>この記事のポイント</h2>
<ul>
<li>Strands Agents の MCPClient にサーバー URL とアクセストークンを渡すと、Salesforce の標準ツールをそのまま使える</li>
<li>読み取りと書き込みをサーバーの単位で分け、渡すツールも絞れる。動かせるのは、認可したユーザーの権限の範囲だけ</li>
<li>書き込みの結果は、エージェント自身に読み直して確かめさせる</li>
</ul>
<hr />
<h2>1. 今回作るもの</h2>
<p>Python のエージェントを実行すると、Amazon Bedrock のモデルが Salesforce の未対応のケースを読み、件名と説明から優先度を判断して、Priority 項目を書き換えます。ケースの読み書きには Hosted MCP Server のツールを使うので、Salesforce の API を呼ぶコードは書きません。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207321" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261106164438/01-architecture.png?w=1920" class="postimages" width="1920" height="1000" alt="" /><br />
			  </span></p>
<p>処理の流れは次のとおりです。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207322" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261106164442/02-sequence.png?w=1395" class="postimages" width="1395" height="1000" alt="" /><br />
			  </span></p>
<h3>権限の絞り方</h3>
<p>エージェントに触らせる範囲は、外側から順に 4 段で絞れます。</p>
<ol>
<li>ユーザーの権限: 認可したユーザーに許された範囲 (オブジェクトの権限、項目レベルセキュリティ、共有ルール) しか動かせません</li>
<li>サーバー: 書き込みには、削除のツールを持たない sobject-mutations を使います</li>
<li>ツール: Strands の tool_filters で、渡すツールを soqlQuery と updateSobjectRecord に絞ります</li>
<li>人の確認: この記事では省いていますが、Strands の Interrupts で、書き込みの前に人が確認する仕組みも入れられます</li>
</ol>
<hr />
<h2>2. 事前準備</h2>
<p>次のものを用意します。所要時間は 1 時間ほどです。</p>
<ul>
<li>Salesforce 組織 (この記事では Developer Edition) と、システム管理者のユーザー</li>
<li><a href="https://developer.salesforce.com/tools/salesforcecli">Salesforce CLI</a> (sf)。古い場合は sf update で更新します (詳しくは <a href="https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-update-cli.html">Update Salesforce CLI</a>)</li>
<li>AWS アカウント (Amazon Bedrock を使える権限)</li>
<li>Python のパッケージ管理ツールの <a href="https://docs.astral.sh/uv/">uv</a> (Python 3.12 は uv が入れます)。古い場合は uv self update で更新します (詳しくは <a href="https://docs.astral.sh/uv/getting-started/installation/#upgrading-uv">Upgrading uv</a>)</li>
</ul>
<blockquote><p>※設定のクイック検索に「MCP」と入れて「MCP サーバー」が出ない組織では、Hosted MCP Server を使えません。新しく <a href="https://developer.salesforce.com/signup">Developer Edition</a> を取得してください。</p></blockquote>
<p>この記事は、Python 3.12.14、strands-agents 1.64.0、mcp 2.3.0、Salesforce CLI 2.157.8 で動作を確認しています。</p>
<p>最初に、作業用のプロジェクトを作ります。このあと作るファイルは、すべてこのフォルダに置きます。</p>
<pre><code>uv init --python 3.12 bedrock-case-agent
cd bedrock-case-agent
uv add strands-agents python-dotenv
</code></pre>
<p>作り終えたときのファイル構成は次のとおりです (uv が作るファイルは省いています)。</p>
<pre><code>bedrock-case-agent/
├── .env              # 4 で作成。接続情報なので、リポジトリに含めない
├── agent.py          # 6 で作成
├── sf_auth.py        # 5 で作成
├── reset_cases.apex  # 7 で作成 (試し直すとき用)
└── data/
    └── cases.json    # 3 で作成
</code></pre>
<hr />
<h2>3. Salesforce 側の設定</h2>
<h3>ステップ 1：MCP サーバーを有効化する</h3>
<p>Hosted MCP Server は、初期状態ではすべて無効になっています。</p>
<ol>
<li>設定のクイック検索で「MCP サーバー」と入力し、API カタログの「MCP サーバー」を開く</li>
<li>一覧から「sobject-reads」を開き、「有効化」をクリックする</li>
<li>同じように「sobject-mutations」も有効化する</li>
</ol>
<p>サーバーの状態が「稼働中」になれば完了です。有効化してから使えるようになるまで、最大 2 分ほどかかります。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207323" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261106164445/03-mcp-servers.png?w=1608" class="postimages" width="1608" height="1000" alt="" /><br />
			  </span></p>
<p>続けて、sobject-reads と sobject-mutations の詳細画面を開き、「サーバー URL」をそれぞれコピーしておきます。4 のステップ 2 で .env に書きます。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207324" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261106164449/03b-mcp-server-url.png?w=2000" class="postimages" width="2000" height="791" alt="" /><br />
			  </span></p>
<p>Developer Edition では、次の形の URL が表示されます。</p>
<pre><code>https://api.salesforce.com/platform/mcp/v1/d/&lt;My Domain 名&gt;/develop/platform/sobject-reads
https://api.salesforce.com/platform/mcp/v1/d/&lt;My Domain 名&gt;/develop/platform/sobject-mutations
</code></pre>
<blockquote><p>※URL には組織の My Domain 名が入ります。本番組織や Sandbox では形が少し変わるので、画面の URL をそのまま使ってください (形式の一覧は <a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/server-urls.html">Find Your Server URL</a>)。</p></blockquote>
<h3>ステップ 2：外部クライアントアプリケーションを作成する</h3>
<p>エージェントが OAuth で接続するための外部クライアントアプリケーションを作成します。画面の流れは「<a href="https://developer.salesforce.com/jpblogs/2026/04/salesforce-hosted-mcp-server-jp">Salesforce Hosted MCP Server 使い始め</a>」で詳しく解説されているので、ここでは設定する値をまとめます。</p>
<ol>
<li>設定のクイック検索で「外部クライアントアプリケーションマネージャー」を開き、「新規外部クライアントアプリケーション」をクリックする</li>
<li>下の表の値を設定して、「作成」をクリックする</li>
<li>作成したら「設定」タブの「OAuth 設定」で「コンシューマー鍵と秘密」をクリックし、メールの確認コードを入力して、コンシューマー鍵をコピーする</li>
</ol>
<table>
<thead>
<tr>
<th><strong>項目</strong></th>
<th><strong>値</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>外部クライアントアプリケーション名</td>
<td>任意 (例: Hosted MCP Clients)</td>
</tr>
<tr>
<td>取引先責任者メール</td>
<td>自分のメールアドレス</td>
</tr>
<tr>
<td>OAuth を有効化</td>
<td>オン</td>
</tr>
<tr>
<td>コールバック URL</td>
<td>http://localhost:8765/callback</td>
</tr>
<tr>
<td>OAuth 範囲</td>
<td>「Salesforce でホストされている MCP サーバーにアクセス (mcp_api)」と「いつでも要求を実行 (refresh_token, offline_access)」の 2 つ</td>
</tr>
<tr>
<td>フローの有効化</td>
<td>すべてオフ</td>
</tr>
<tr>
<td>セキュリティ</td>
<td>「指名ユーザーの JSON Web トークン (JWT) ベースのアクセストークンを発行」をオン、「Web サーバーフローの秘密が必要」と「更新トークンフローの秘密が必要」をオフ</td>
</tr>
</tbody>
</table>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207325" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261106164453/04-eca-oauth.png?w=2000" class="postimages" width="2000" height="667" alt="" /><br />
			  </span></p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207326" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261106164456/05-eca-security.png?w=1054" class="postimages" width="1054" height="1000" alt="" /><br />
			  </span></p>
<blockquote><p>※外部クライアントアプリケーションは、作成してから使えるようになるまで、最大 30 分かかることがあります。</p></blockquote>
<blockquote><p>※コールバック URL は複数登録できるので、ほかの MCP クライアント (Claude や Postman など) のものも足せば、同じアプリを使い回せます。本番の組織では、クライアントごとにアプリを分けると管理しやすくなります。</p></blockquote>
<h3>ステップ 3：試すためのケースを投入する</h3>
<p>エージェントが触るケースを絞るため、ケースの「会社名」 (SuppliedCompany) に目印の BEDROCK-DEMO を入れて投入し、エージェントの SOQL でもこの値で絞ります。</p>
<p>緊急度にばらつきのある 6 件を、優先度はすべて Medium で用意します。data/cases.json を次の内容で作ります。</p>
<pre><code>{
  "records": [
    {
      "attributes": { "type": "Case", "referenceId": "Case01" },
      "Subject": "本番環境で全ユーザーがログインできない",
      "Description": "本日 9:30 頃から全ユーザーが管理ポータルにログインできず、受注処理が止まっています。至急対応をお願いします。",
      "Status": "New", "Priority": "Medium", "SuppliedCompany": "BEDROCK-DEMO"
    },
    {
      "attributes": { "type": "Case", "referenceId": "Case02" },
      "Subject": "夜間バッチ後に取引先の住所データが消えている",
      "Description": "昨夜の連携バッチの後、取引先の約 300 件で住所が空になりました。請求書の発送に影響します。",
      "Status": "New", "Priority": "Medium", "SuppliedCompany": "BEDROCK-DEMO"
    },
    {
      "attributes": { "type": "Case", "referenceId": "Case03" },
      "Subject": "心当たりのない海外 IP からのログイン成功通知が届いた",
      "Description": "今朝、海外の IP アドレスからのログイン成功通知が届きました。チームの誰も海外にはいません。",
      "Status": "New", "Priority": "Medium", "SuppliedCompany": "BEDROCK-DEMO"
    },
    {
      "attributes": { "type": "Case", "referenceId": "Case04" },
      "Subject": "営業ダッシュボードの表示が遅い",
      "Description": "ダッシュボードの表示に 30 秒以上かかるようになりました。使えてはいますが、朝会で待たされています。",
      "Status": "New", "Priority": "Medium", "SuppliedCompany": "BEDROCK-DEMO"
    },
    {
      "attributes": { "type": "Case", "referenceId": "Case05" },
      "Subject": "二要素認証の設定手順書はありますか",
      "Description": "新入社員向けに、二要素認証の設定手順の資料があれば共有してください。急ぎではありません。",
      "Status": "New", "Priority": "Medium", "SuppliedCompany": "BEDROCK-DEMO"
    },
    {
      "attributes": { "type": "Case", "referenceId": "Case06" },
      "Subject": "見積書テンプレートに会社ロゴを入れたい",
      "Description": "見積書のテンプレートに、自社ロゴを入れられるようにしてほしいという要望です。",
      "Status": "New", "Priority": "Medium", "SuppliedCompany": "BEDROCK-DEMO"
    }
  ]
}
</code></pre>
<p>Salesforce CLI で組織にログインし、ケースを投入します。1 行目でブラウザが開くので、使う組織でログインします。&#8211;alias my-de で付けた別名は、このあとのコマンドの &#8211;target-org で使います。</p>
<blockquote><p>※既定のブラウザでは、使う組織にだけログインしておきます。ほかの組織にもログインしていると、別の組織を認可してしまうことがあります (5 の sf_auth.py も同じです)。</p></blockquote>
<pre><code>sf org login web --alias my-de
sf data import tree --files data/cases.json --target-org my-de
</code></pre>
<p>次のコマンドで、6 件の優先度がすべて Medium になっていれば準備完了です。</p>
<pre><code>sf data query --query "SELECT CaseNumber, Subject, Priority FROM Case WHERE SuppliedCompany = 'BEDROCK-DEMO' ORDER BY CaseNumber" --target-org my-de
</code></pre>
<p>画面では、ケースの「私のケース」のリストビューで確かめられます。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207327" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261106164459/06-listview-before.png?w=2000" class="postimages" width="2000" height="474" alt="" /><br />
			  </span></p>
<hr />
<h2>4. AWS 側の設定</h2>
<h3>ステップ 1：Bedrock API キーを発行する</h3>
<p>今回は、Amazon Bedrock の API キーで認証します。</p>
<ol>
<li>AWS マネジメントコンソールのリージョンを「アジアパシフィック (東京)」にする</li>
<li>Amazon Bedrock のコンソールで、左のメニューの「API キー」を開く</li>
<li>「短期 API キー」タブの「短期 API キーを生成」をクリックし、表示されたキーをコピーする (キーは一度しか表示されません)</li>
</ol>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207328" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261106164502/07-bedrock-api-key.png?w=2000" class="postimages" width="2000" height="870" alt="" /><br />
			  </span></p>
<blockquote><p>※短期 API キーは、発行したリージョンでしか使えません。Amazon Nova のモデルは、利用開始の手続きなしで使えます。</p></blockquote>
<p>モデルは Amazon Nova 2 Lite を、推論プロファイル jp.amazon.nova-2-lite-v1:0 (東京と大阪のリージョンで処理する ID) で呼び出します。</p>
<h3>ステップ 2：.env に接続情報を書く</h3>
<p>プロジェクトの直下に .env を作り、次の値を入れます。.env はリポジトリに含めないでください。</p>
<pre><code># Salesforce
SF_CONSUMER_KEY=&lt;コンシューマー鍵&gt;
SF_LOGIN_URL=https://login.salesforce.com
SF_MCP_READS_URL=&lt;sobject-reads のサーバー URL&gt;
SF_MCP_MUTATIONS_URL=&lt;sobject-mutations のサーバー URL&gt;

# Amazon Bedrock
AWS_BEARER_TOKEN_BEDROCK=&lt;Bedrock API キー&gt;
AWS_REGION=ap-northeast-1
BEDROCK_MODEL_ID=jp.amazon.nova-2-lite-v1:0
</code></pre>
<p>SF_MCP_READS_URL と SF_MCP_MUTATIONS_URL には、3 のステップ 1 でコピーしたサーバー URL を書きます。</p>
<p>AWS_BEARER_TOKEN_BEDROCK は、AWS の SDK が Bedrock の API キーとして読み取る環境変数なので、コードでキーを扱う必要はありません。</p>
<hr />
<h2>5. Salesforce の認可を通す</h2>
<p>Hosted MCP Server には、OAuth 2.0 の認可コードフロー (PKCE 付き) で取得したアクセストークンで接続します。ブラウザで認可してトークンを保存する、小さなスクリプト sf_auth.py を用意します。</p>
<pre><code>"""Salesforce Hosted MCP Server 用のアクセストークンを、認可コードフロー + PKCE で取得する。

  uv run sf_auth.py login   ブラウザで認可し、トークンを保存する
  uv run sf_auth.py token   アクセストークンを出力する (MCP Inspector での疎通確認用)
"""
import base64
import hashlib
import http.server
import json
import os
import secrets
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
import webbrowser
from pathlib import Path

from dotenv import load_dotenv

load_dotenv()

LOGIN_URL = os.environ.get("SF_LOGIN_URL", "https://login.salesforce.com").rstrip("/")
CLIENT_ID = os.environ.get("SF_CONSUMER_KEY", "")
REDIRECT_URI = "http://localhost:8765/callback"  # 外部クライアントアプリケーションのコールバック URL と同じにする
SCOPES = "mcp_api refresh_token"
TOKEN_FILE = Path("~/.config/bedrock-case-agent/sf_token.json").expanduser()


def _b64url(data: bytes) -&gt; str:
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode()


def _post_token(form: dict) -&gt; dict:
    req = urllib.request.Request(
        f"{LOGIN_URL}/services/oauth2/token",
        data=urllib.parse.urlencode(form).encode(),
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )
    try:
        with urllib.request.urlopen(req, timeout=30) as res:
            return json.loads(res.read())
    except urllib.error.HTTPError as e:
        sys.exit(f"トークンの取得に失敗しました (HTTP {e.code}): {e.read().decode()}")


def _save(tokens: dict) -&gt; None:
    tokens["saved_at"] = int(time.time())
    TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True)
    TOKEN_FILE.write_text(json.dumps(tokens))
    TOKEN_FILE.chmod(0o600)  # 自分だけが読めるようにする


def login() -&gt; None:
    if not CLIENT_ID:
        sys.exit(".env に SF_CONSUMER_KEY (コンシューマー鍵) を設定してください。")

    # PKCE: code_verifier から code_challenge を作り、認可画面に渡す
    verifier = _b64url(secrets.token_bytes(64))
    challenge = _b64url(hashlib.sha256(verifier.encode()).digest())
    state = secrets.token_urlsafe(16)
    params = {
        "response_type": "code",
        "client_id": CLIENT_ID,
        "redirect_uri": REDIRECT_URI,
        "scope": SCOPES,
        "code_challenge": challenge,
        "code_challenge_method": "S256",
        "state": state,
    }

    result: dict = {}

    class CallbackHandler(http.server.BaseHTTPRequestHandler):
        def do_GET(self):
            url = urllib.parse.urlparse(self.path)
            if url.path == "/callback":
                result.update(urllib.parse.parse_qsl(url.query))
            self.send_response(200)
            self.send_header("Content-Type", "text/plain; charset=utf-8")
            self.end_headers()
            self.wfile.write("認可が完了しました。ターミナルに戻ってください。".encode())

        def log_message(self, *args):
            pass

    server = http.server.HTTPServer(("localhost", 8765), CallbackHandler)
    webbrowser.open(f"{LOGIN_URL}/services/oauth2/authorize?" + urllib.parse.urlencode(params))
    while "code" not in result and "error" not in result:
        server.handle_request()  # コールバックが届くまで待つ
    server.server_close()

    if "error" in result or result.get("state") != state:
        sys.exit(f"認可に失敗しました: {result.get('error_description', result)}")

    # PKCE: トークンを取るときに code_verifier を送る
    tokens = _post_token({
        "grant_type": "authorization_code",
        "code": result["code"],
        "client_id": CLIENT_ID,
        "redirect_uri": REDIRECT_URI,
        "code_verifier": verifier,
    })
    _save(tokens)
    print(f"トークンを保存しました: {TOKEN_FILE}")


def get_access_token(max_age_sec: int = 3600) -&gt; str:
    if not TOKEN_FILE.exists():
        sys.exit("トークンがありません。先に `uv run sf_auth.py login` を実行してください。")
    tokens = json.loads(TOKEN_FILE.read_text())
    if time.time() - tokens["saved_at"] &gt; max_age_sec:  # 保存から時間が経っていたら取り直す
        new = _post_token({
            "grant_type": "refresh_token",
            "refresh_token": tokens["refresh_token"],
            "client_id": CLIENT_ID,
        })
        tokens = {**tokens, **new}
        _save(tokens)
    return tokens["access_token"]


if __name__ == "__main__":
    command = sys.argv[1] if len(sys.argv) &gt; 1 else ""
    if command == "login":
        login()
    elif command == "token":
        print(get_access_token())
    else:
        sys.exit("使い方: uv run sf_auth.py login | token")
</code></pre>
<p>ポイントは 2 つです。</p>
<ul>
<li>PKCE: 認可画面を開くときに code_challenge を付け、トークンを取るときに code_verifier を送ります。認可コードの横取りを防ぐ仕組みで、コンシューマーの秘密は要りません</li>
<li>トークンの保存: プロジェクトの外 (~/.config/) に自分だけが読める権限で保存し、60 分を過ぎたらリフレッシュトークンで取り直します</li>
</ul>
<pre><code>uv run sf_auth.py login
</code></pre>
<p>許可の画面が開いたら、要求しているアプリが自分で作った外部クライアントアプリケーションであることを確かめて、「許可」をクリックします。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207329" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261106164504/08-oauth-consent.png?w=549" class="postimages" width="549" height="1000" alt="" /><br />
			  </span></p>
<p>ターミナルに「トークンを保存しました」と表示されれば完了です。</p>
<blockquote><p>※MCP Inspector などで接続を確かめたいときは、uv run sf_auth.py token で出力したアクセストークンを使えます。</p></blockquote>
<hr />
<h2>6. エージェントを書く</h2>
<p>agent.py は次のとおりです。</p>
<pre><code>import os

from dotenv import load_dotenv
from strands import Agent
from strands.models import BedrockModel
from strands.tools.mcp import MCPClient

from sf_auth import get_access_token

load_dotenv()  # .env の値 (Bedrock の API キーを含む) を環境変数に読み込む

headers = {"Authorization": f"Bearer {get_access_token()}"}

# 読み取り用: sobject-reads の soqlQuery だけを渡す
reads = MCPClient(
    url=os.environ["SF_MCP_READS_URL"],  # sobject-reads のサーバー URL
    headers=headers,
    tool_filters={"allowed": ["soqlQuery"]},
)

# 書き込み用: sobject-mutations の updateSobjectRecord だけを渡す
mutations = MCPClient(
    url=os.environ["SF_MCP_MUTATIONS_URL"],  # sobject-mutations のサーバー URL
    headers=headers,
    tool_filters={"allowed": ["updateSobjectRecord"]},
)

model = BedrockModel(
    model_id=os.environ["BEDROCK_MODEL_ID"],  # 例: jp.amazon.nova-2-lite-v1:0
    region_name=os.environ["AWS_REGION"],  # 例: ap-northeast-1
    temperature=0,
    # Nova 2 の推論 (考えてから答える機能) をオンにする。オフのままだと、更新のツールを呼ばないことがあった
    additional_request_fields={"reasoningConfig": {"type": "enabled", "maxReasoningEffort": "low"}},
)

SYSTEM_PROMPT = """あなたは Salesforce のサポートケース (Case) をトリアージする日本語の AI エージェントです。

# 手順
1. 次の SOQL を soqlQuery で実行して、未対応のケースを取得する (そのまま使うこと)。
   SELECT Id, CaseNumber, Subject, Description, Priority FROM Case
   WHERE Status = 'New' AND SuppliedCompany = 'BEDROCK-DEMO'
   ORDER BY CaseNumber LIMIT 10
2. 各ケースの件名と説明から、優先度を High、Medium、Low のどれかに判断する。
   - High: 業務停止、データ消失、セキュリティ、法令、本番障害など緊急性が高いもの
   - Medium: 不具合や設定の問題だが、業務は継続できるもの
   - Low: 操作方法の質問、軽微な要望、情報提供の依頼など
3. 判断した優先度が、1 で取得した Priority と異なるケースは、すべて updateSobjectRecord で更新する。
   High への変更も Low への変更も、同じように 1 件ずつ更新する。
   sobject-name は "Case"、id は 1 の結果の Id をそのまま使い、body は {"Priority": "Low"} の形にする。
   Priority 以外の項目は変更しない。
4. 更新が終わったら、1 と同じ SOQL をもう一度 soqlQuery で実行し、Priority が判断どおりになったかを確かめる。
   判断どおりになっていないケースがあれば、もう一度 updateSobjectRecord で更新してから、もう一度確かめる。
5. 最後に、4 で確かめた結果を Markdown の表でまとめる (列: CaseNumber、件名、更新前の Priority、判定、理由、更新後の Priority)。
"""

agent = Agent(model=model, system_prompt=SYSTEM_PROMPT, tools=[reads, mutations])
agent("未対応のケースの優先度を判断して、Priority を更新してください。")
</code></pre>
<p>ポイントは 3 つです。</p>
<ul>
<li>MCPClient を 2 つ作る: sobject-reads と sobject-mutations に分け、tool_filters で渡すツールを 1 つずつに絞ります。接続の開始と終了は Strands Agents が管理します</li>
<li>推論をオンにする: additional_request_fields で Nova 2 の推論 (考えてから答える機能) をオンにしています。オフのままだと、更新のツールを呼ばないことがありました</li>
<li>指示文: 使う SOQL と判断基準を固定し、更新のあとに読み直して確かめる手順まで書いています。結果の表の「更新後の Priority」は、読み直した値です</li>
</ul>
<hr />
<h2>7. 動かしてみる</h2>
<pre><code>uv run agent.py
</code></pre>
<p>エージェントが soqlQuery でケースを取得し、優先度が変わる 5 件について updateSobjectRecord を呼び出したあと、もう一度 soqlQuery で読み直します。</p>
<p>ログの [REDACTED]. は、推論の中身が表示されない部分です。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207330" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261106164507/09-run-log.png?w=1400" class="postimages" width="1400" height="690" alt="" /><br />
			  </span></p>
<p>最後に、判断の結果が表にまとめて出力されます。</p>
<table>
<thead>
<tr>
<th><strong>CaseNumber</strong></th>
<th><strong>件名</strong></th>
<th><strong>更新前の Priority</strong></th>
<th><strong>判定</strong></th>
<th><strong>理由</strong></th>
<th><strong>更新後の Priority</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>00001026</td>
<td>本番環境で全ユーザーがログインできない</td>
<td>Medium</td>
<td>High</td>
<td>業務停止、緊急性が高い</td>
<td>High</td>
</tr>
<tr>
<td>00001027</td>
<td>夜間バッチ後に取引先の住所データが消えている</td>
<td>Medium</td>
<td>High</td>
<td>データ消失、請求書に影響</td>
<td>High</td>
</tr>
<tr>
<td>00001028</td>
<td>心当たりのない海外 IP からのログイン成功通知が届いた</td>
<td>Medium</td>
<td>High</td>
<td>セキュリティリスク</td>
<td>High</td>
</tr>
<tr>
<td>00001029</td>
<td>営業ダッシュボードの表示が遅い</td>
<td>Medium</td>
<td>Medium</td>
<td>不具合だが業務は継続可能</td>
<td>Medium</td>
</tr>
<tr>
<td>00001030</td>
<td>二要素認証の設定手順書はありますか</td>
<td>Medium</td>
<td>Low</td>
<td>操作方法の質問</td>
<td>Low</td>
</tr>
<tr>
<td>00001031</td>
<td>見積書テンプレートに会社ロゴを入れたい</td>
<td>Medium</td>
<td>Low</td>
<td>軽微な要望</td>
<td>Low</td>
</tr>
</tbody>
</table>
<p>3 の sf data query をもう一度実行するか、リストビューを更新すると、Priority が書き換わっていることを確認できます。</p>
<p>			  <span class="postimagessection_specify alignnone size-medium wp-image-207331" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261106164509/10-listview-after.png?w=2000" class="postimages" width="2000" height="474" alt="" /><br />
			  </span></p>
<p>もう一度実行しても、優先度はすでに判断どおりなので、更新は行われません。最初から試し直すときは、次の reset_cases.apex を作って実行し、優先度を Medium に戻します。</p>
<pre><code>// BEDROCK-DEMO の目印が付いたケースを、優先度 Medium、状況 New に戻す
List&lt;Case&gt; cases = [SELECT Id FROM Case WHERE SuppliedCompany = 'BEDROCK-DEMO'];
for (Case c : cases) {
    c.Priority = 'Medium';
    c.Status = 'New';
}
update cases;
</code></pre>
<pre><code>sf apex run --file reset_cases.apex --target-org my-de
</code></pre>
<p>戻して実行するのを 3 回繰り返したところ、3 回とも同じ結果になりました。</p>
<hr />
<h2>8. 活用法</h2>
<p>対象のオブジェクトと指示文を変えれば、同じ形で、期限を過ぎた商談のフェーズの一括更新や、フォローアップの ToDo の作成などにも使えます。読み取りだけで足りる使い方なら、sobject-reads のクライアントだけを渡します。ほかのサービスの MCP サーバーのツールと組み合わせることもできます。</p>
<hr />
<h2>9. よくあるエラーと対処法</h2>
<h3>invalid_client_id</h3>
<p><strong>原因</strong>: コンシューマー鍵が違うか、外部クライアントアプリケーションを作成した直後で、まだ使えない</p>
<p><strong>対処</strong>: 鍵を確かめてください。作成した直後なら、最大 30 分待ってからやり直します (待ち続けている sf_auth.py は Ctrl + C で止めます)</p>
<h3>redirect_uri_mismatch</h3>
<p><strong>原因</strong>: 外部クライアントアプリケーションのコールバック URL と、sf_auth.py の REDIRECT_URI が違う</p>
<p><strong>対処</strong>: どちらも http://localhost:8765/callback にしてください</p>
<h3>MCP サーバーに接続できない (404)</h3>
<p><strong>原因</strong>: MCP サーバーを有効化していない (Server &#8216;sobject-reads&#8217; exists but is not active. と返ります)</p>
<p><strong>対処</strong>: 3 のステップ 1 でサーバーを有効化し、最大 2 分ほど待ってください</p>
<h3>ValidationException (モデル ID の指定)</h3>
<p><strong>原因</strong>: モデル ID に、推論プロファイルではない ID (amazon.nova-2-lite-v1:0 など) を指定している</p>
<p><strong>対処</strong>: jp.amazon.nova-2-lite-v1:0 のように、推論プロファイルの ID を指定してください</p>
<h3>Bedrock の認証が通らない</h3>
<p><strong>原因</strong>: 短期 API キーを発行したリージョンが AWS_REGION と違うか、キーの期限 (最長 12 時間) が切れている</p>
<p><strong>対処</strong>: AWS_REGION と同じリージョンで、キーを発行し直してください</p>
<h3>Your account is currently being verified</h3>
<p><strong>原因</strong>: AWS アカウントを作った直後で、確認が終わっていない</p>
<p><strong>対処</strong>: 確認は通常 2 時間以内に終わるので、待ってから実行してください</p>
<h3>更新していないのに「更新しました」と表示される</h3>
<p><strong>原因</strong>: モデルが、更新のツールを呼ばないまま結果の表を書くことがあります。この記事のエージェントでも、推論をオフにしていたときに起きました</p>
<p><strong>対処</strong>: 推論をオンにし、更新のあとに読み直して確かめる手順を指示文に入れたところ、3 回続けて想定どおりに動きました。それでも表はモデルの報告なので、書き込みを伴うエージェントでは、sf data query などで実際の値も確かめてください</p>
<hr />
<h2>まとめ</h2>
<p>Strands Agents と Salesforce Hosted MCP Server を組み合わせると、Amazon Bedrock の AI エージェントから、ユーザーの権限の範囲で Salesforce のデータを読み書きできます。ほかのフレームワークのエージェントでも、OAuth で取得したアクセストークンを付けて MCP サーバーに接続する考え方は同じです。書き込みを伴う場合は、読み直して確かめる手順を指示文に入れておくと安心です。</p>
<p>まずは無料で取得できる <a href="https://developer.salesforce.com/signup">Developer Edition</a> で試してみてください。</p>
<hr />
<h2>参考リソース</h2>
<ul>
<li>Salesforce Developers (英語): <a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/hosted-mcp-servers-overview.html">Hosted MCP Servers: Get Started</a> / <a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/server-urls.html">Find Your Server URL</a></li>
<li>Salesforce Developers (英語): <a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/sobject-reads.html">SObject Reads</a> / <a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/sobject-mutations.html">SObject Mutations</a></li>
<li>Salesforce Developers (英語): <a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/create-external-client-app.html">Create an External Client App</a> / <a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/log-into-org.html">Log In to Your Org</a></li>
<li>Strands Agents (英語): <a href="https://strandsagents.com/docs/user-guide/sdk/tools/mcp-tools/">Connect your agent to MCP tools</a> / <a href="https://strandsagents.com/docs/user-guide/sdk/tools/mcp-transports/">MCP Transports</a> / <a href="https://strandsagents.com/docs/user-guide/sdk/model-providers/amazon-bedrock/">Amazon Bedrock</a> / <a href="https://strandsagents.com/docs/user-guide/sdk/interrupts/">Interrupts</a></li>
<li>Amazon Bedrock: <a href="https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/api-keys.html">API キー</a> / <a href="https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/model-card-amazon-nova-2-lite.html">Nova 2 Lite</a></li>
<li>Amazon Nova: <a href="https://docs.aws.amazon.com/ja_jp/nova/latest/nova2-userguide/using-tools.html">ツールの使用 (関数呼び出し)</a> / <a href="https://docs.aws.amazon.com/ja_jp/nova/latest/nova2-userguide/troubleshooting.html">トラブルシューティング</a></li>
</ul>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/jp-access-salesforce-from-amazon-bedrock-via-mcp-server">Amazon Bedrock から MCP サーバー経由で 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/11/jp-access-salesforce-from-amazon-bedrock-via-mcp-server/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207317</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261106171448/2026-11-07_10-11-59.jpg?w=1118" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261106171448/2026-11-07_10-11-59.jpg?w=1118" medium="image" />
	</item>
		<item>
		<title>Metadata Grounding: Let Your Coding Agent Search Your Org by Intent</title>
		<link>https://developer.salesforce.com/blogs/2026/11/metadata-grounding-let-your-coding-agent-search-your-org-by-intent</link>
		<comments>https://developer.salesforce.com/blogs/2026/11/metadata-grounding-let-your-coding-agent-search-your-org-by-intent#respond</comments>
		<pubDate>Thu, 05 Nov 2026 16:00:55 +0000</pubDate>
		<dc:creator><![CDATA[Gonzalo Gambino]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Announcements]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Agent-Driven Development]]></category>
		<category><![CDATA[AI Coding Agents]]></category>
		<category><![CDATA[developer tooling]]></category>
		<category><![CDATA[Headless Toolkit]]></category>
		<category><![CDATA[Metadata Grounding]]></category>
		<category><![CDATA[Salesforce MCP]]></category>
		<category><![CDATA[Salesforce Metadata]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207282</guid>
		<description><![CDATA[<p>Introducing Metadata Grounding MCP (Beta). Learn how coding agents can search Salesforce org metadata using natural language, discover existing components, and reuse what's already built.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/metadata-grounding-let-your-coding-agent-search-your-org-by-intent">Metadata Grounding: Let Your Coding Agent Search Your Org by Intent</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><span style="font-weight: 400">You ask a coding agent to fix a bug on your menu page. It searches your project, finds nothing that matches, and starts writing a new component. Your org already has one. Most projects hold only part of an org, and this one lives in the part you haven&#8217;t retrieved.</span></p>
<p><span style="font-weight: 400">Coding agents can already explore your org with the Salesforce CLI, but that means listing metadata one type at a time, across hundreds of types and thousands of components. The </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/metadata-grounding.html"><span style="font-weight: 400">Metadata Grounding MCP Server (Beta)</span></a><span style="font-weight: 400"> searches a pre-indexed catalog of your org&#8217;s metadata instead, so the agent can find what already exists before it builds something new.</span></p>
<p><span style="font-weight: 400">In this post, you&#8217;ll learn how Metadata Grounding works, how to connect it, and how to use it in two examples from a food delivery org called </span><a href="https://github.com/trailheadapps/pronto"><span style="font-weight: 400">Pronto</span></a><span style="font-weight: 400">. </span></p>
<h2><span style="font-weight: 400">Why coding agents need your org&#8217;s metadata</span></h2>
<p><span style="font-weight: 400">The </span><a href="https://developer.salesforce.com/docs/platform/headless-get-started/guide/headless-get-started.html"><span style="font-weight: 400">Headless Toolkit</span></a><span style="font-weight: 400"> is how Salesforce brings your org to any agent, app, and experience. Three pieces matter when you build with a coding agent:</span></p>
<ul>
<li style="font-weight: 400"><b>Skills teach the agent how to build on Salesforce.</b><span style="font-weight: 400"> They carry the patterns, the APIs, and the guardrails.</span></li>
<li style="font-weight: 400"><b>The Headless 360 MCP Server lets the agent act.</b><span style="font-weight: 400"> It can configure, provision, and change things in your org.</span></li>
<li style="font-weight: 400"><b>Metadata Grounding lets the agent see what already exists.</b><span style="font-weight: 400"> It answers the question the other two can&#8217;t: what is currently in this org?</span></li>
</ul>
<p><span style="font-weight: 400">Without that third piece, an agent can write correct Apex against the wrong object. With it, the agent checks the org first.</span></p>
<p><span style="font-weight: 400">If you’ve read our announcement of the Headless 360 MCP Server Beta, you&#8217;ve seen how the Discover tool finds the right Salesforce action to run. Metadata Grounding answers a different question. It finds the things that already exist in your org, such as an object, a field, or a component, so the agent knows what to point that action at.</span></p>
<p><span style="font-weight: 400">Here&#8217;s how the options an agent has today compare:</span></p>
<table>
<thead>
<tr>
<th><b>Where the agent looks</b></th>
<th><b>Finds metadata that isn&#8217;t in your project?</b></th>
<th><b>Search by intent?</b></th>
<th><b>Cost to the agent</b></th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="font-weight: 400">Your local DX project</span></td>
<td><span style="font-weight: 400">No, only what you retrieved</span></td>
<td><span style="font-weight: 400">No, text match only</span></td>
<td><span style="font-weight: 400">Low, but blind to the rest of the org</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Metadata API or SOQL</span></td>
<td><span style="font-weight: 400">Yes</span></td>
<td><span style="font-weight: 400">No, you need the type and name first</span></td>
<td><span style="font-weight: 400">Low per call, but the agent must already know what to ask for</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Retrieve everything, then search</span></td>
<td><span style="font-weight: 400">Yes</span></td>
<td><span style="font-weight: 400">No</span></td>
<td><span style="font-weight: 400">High. Thousands of files burn context, tokens, and time</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Metadata Grounding (Beta)</span></td>
<td><span style="font-weight: 400">Yes</span></td>
<td><span style="font-weight: 400">Yes, across 70+ types</span></td>
<td><span style="font-weight: 400">Low. The agent pulls only what it asks for</span></td>
</tr>
</tbody>
</table>
<h2><span style="font-weight: 400">How Metadata Grounding works</span></h2>
<p><span style="font-weight: 400">Metadata Grounding exposes three read-only tools:</span></p>
<table>
<tbody>
<tr>
<td><code>describe_metadata</code></td>
<td><span style="font-weight: 400">Lists the metadata types in your org and the schema for each, so the agent knows what it can search</span></td>
</tr>
<tr>
<td><code>search_metadata</code></td>
<td><span style="font-weight: 400">Finds metadata by what it does, using natural language, with optional filters for type and subtype</span></td>
</tr>
<tr>
<td><code>query_metadata</code></td>
<td><span style="font-weight: 400">Returns the full record for a result, including its type-specific properties</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">As of this writing, it covers more than 70 metadata types, including objects, fields, Lightning web components, Apex classes, flows, and permission sets. Behind it is Salesforce Catalog, where your org&#8217;s metadata is stored and indexed. </span></p>
<p><span style="font-weight: 400">The pattern the tools are built for is describe, search, then query. Let the agent learn what exists, narrow to the right type, and read the record before it writes any code.</span></p>
<p><b>Note:</b><span style="font-weight: 400"> </span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Metadata Grounding runs as the authenticated user and returns only the metadata that the user is permitted to access. It&#8217;s read-only, so it can find and explain, but never change your org.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Metadata Grounding currently covers core Salesforce metadata. If you need to ground your agents on Data 360 metadata, use the Data 360 MCP Server.</span></li>
</ul>
<h3><span style="font-weight: 400">What&#8217;s behind the tools</span></h3>
<p><span style="font-weight: 400">Metadata Grounding reads from </span><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_salesforce_catalog.htm&amp;type=5"><span style="font-weight: 400">Salesforce Catalog</span></a><span style="font-weight: 400">, which Data 360 powers. Four pieces matter:<br />
</span></p>
<ul>
<li><b>Extraction.</b> Metadata extractors read your standard and custom metadata from the org. When you turn Salesforce Catalog on, Data 360 runs an initial full sync. How long that takes depends on the size of your org.</li>
<li><b>Storage.</b> Data 360 stores the metadata in a metadata data lake object and maps it into a Catalog data model.</li>
<li><b>Search index.</b> A hybrid index combines a vector index, which matches on meaning, with a keyword index, which matches on exact terms. That&#8217;s why a plain-English query can find <code>MenuBrowserController</code> without the agent knowing its name.</li>
<li><b>Access.</b> The Metadata Grounding MCP Server queries that index and returns only what the authenticated user may see.</li>
</ul>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207297" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261104085438/image1_5965d7-e1793811288189.png" class="postimages" width="1000" height="448" alt="Diagram showing how Metadata Grounding works. Salesforce Core metadata is extracted into Data 360, labeled Salesforce Catalog, where it is stored and indexed. Metadata Grounding connects that index to coding agents and other MCP clients." /><br />
			  </span></p>
<p><span style="font-weight: 400">Salesforce Catalog stays current in two ways. For supported metadata types, it picks up changes without waiting for the scheduled refresh. A full refresh also runs every 24 hours to cover all types, including those that incremental sync doesn&#8217;t support yet.</span><span style="font-weight: 400"><br />
</span></p>
<h2><span style="font-weight: 400">Metadata Grounding MCP Server in action</span></h2>
<p><span style="font-weight: 400">Most work in an org starts with a question about what&#8217;s already there. Where&#8217;s the code behind this bug? Do we already have this logic?</span></p>
<p><span style="font-weight: 400">The two use cases below answer those questions in that order. Each one ran in </span><a href="https://github.com/trailheadapps/pronto"><span style="font-weight: 400">Pronto</span></a><span style="font-weight: 400">, our sample food delivery org, so the queries and results are real. Both ran in Claude Code with Metadata Grounding connected and a project rule in place that tells the agent to check the org first. You&#8217;ll find the rule at the end of this section. Your results depend on your org&#8217;s metadata and how well it&#8217;s described.</span></p>
<h3><span style="font-weight: 400">Find the metadata component behind a bug</span></h3>
<p><span style="font-weight: 400">Here&#8217;s the ticket I used in an org installed with </span><a href="https://github.com/trailheadapps/pronto"><span style="font-weight: 400">Pronto</span></a><span style="font-weight: 400"> sample app:</span></p>
<p><em>&#8220;On the menu page, clicking a category doesn&#8217;t do anything. I click Breakfast and it still shows me the whole menu. It works when the page first loads, then breaks the moment you touch it. The All Categories button does nothing at all.&#8221;</em></p>
<p><span style="font-weight: 400">The developer isn&#8217;t familiar with this feature, and the org has hundreds of components. There&#8217;s no field name to search for, only a symptom. The project folder is empty, so there&#8217;s nothing local to grep. I gave the ticket to Claude Code with Metadata Grounding connected and added: &#8220;Find whatever is behind that. I don&#8217;t know what it&#8217;s called. Search by type: look through Apex classes first, then Lightning web components.&#8221;</span></p>
<p><span style="font-weight: 400">The agent starts with <code>describe_metadata</code> to see what&#8217;s available. Then it searches Apex classes by plain English, not by name:</span></p>
<pre language="text">{
  "query": "code that loads a restaurant menu and its categories and items",
  "metadataType": "ApexClass",
  "limit": 8
}
</pre>
<p>Among the results is <code>MenuBrowserController</code>, whose name and purpose match the symptom. A second search, limited to Lightning web components, finds the component that goes with it:</p>
<pre language="text">{
  "query": "menu",
  "metadataType": "LightningComponentBundle",
  "limit": 8
}
</pre>
<p>In my org that returns <code>c__menuBrowser</code> as the first match. A <code>query_metadata</code> call on it shows the targets it&#8217;s exposed on, including record pages, app pages, and Experience Cloud pages.</p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207298" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261104085758/image2_259092-e1793811494860.png" class="postimages" width="1000" height="277" alt="Screenshot of Claude Code using Metadata Grounding to search the Pronto org for the menu bug. Searches filtered by Apex class and Lightning web component return MenuBrowserController and c__menuBrowser." /><br />
			  </span></p>
<p><span style="font-weight: 400">Metadata Grounding tells the agent what exists and where it lives. It doesn&#8217;t return source code. To read the code, the agent switches tools and retrieves only the items it needs into the empty project, such as the controller and the component. The org stays on the server, and only what the fix touches comes down.</span></p>
<p>Two things to notice. The match came from a business symptom, not a name the developer already knew. And the type filter did real work. The same query without <code>metadataType</code> returns standard fields from across the platform, which is noise.</p>
<p><b>Note:</b><span style="font-weight: 400"> The Lightning web component record currently has no description, so the ranking relies on the component name and its properties. The richer the descriptions in your org, the better the search gets. More on that in the roadmap “What’s next” section below.</span></p>
<h3><span style="font-weight: 400">Extend what exists instead of rebuilding it</span></h3>
<p><span style="font-weight: 400">A developer asks the agent to add a customer review summary to a storefront page for the </span><a href="https://github.com/trailheadapps/pronto"><span style="font-weight: 400">Pronto</span></a><span style="font-weight: 400"> app. The project rule tells the agent to check the org first, so before writing anything it searches Apex classes:</span></p>
<pre language="text">{
  "query": "summarize customer reviews for a restaurant",
  "metadataType": "ApexClass",
  "limit": 8
}
</pre>
<p>The first result is <code>AgentSummarizeReviewsActions</code>, which already exists in the org. A <code>query_metadata</code> call on it returns the full class, including an invocable method labeled &#8220;Summarize Reviews&#8221; that retrieves recent reviews for a storefront and returns summary statistics. The agent can see the inputs it requires: an account ID and a storefront ID, as well as optional limits for how many days back to look and how many reviews to return.</p>
<p><span style="font-weight: 400">So the agent builds on the existing class instead of writing a new query. The developer keeps working code, and the team doesn&#8217;t end up with two classes that do the same job.</span></p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207299" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261104085957/image3_7d5b6e-e1793811612422.png" class="postimages" width="1000" height="358" alt="Screenshot of Claude Code searching Apex classes for &quot;summarize customer reviews for a restaurant.&quot; The top result is the existing AgentSummarizeReviewsActions class." /><br />
			  </span></p>
<p><span style="font-weight: 400">You can make this behavior the default. Add an instruction like this to your project rules or agent instructions:</span></p>
<p><span style="font-weight: 400">Before you write code that references an object, field, component, or Apex class, search my org with Metadata Grounding. Query the record you plan to use and confirm it exists. If something similar already exists, extend it instead of creating a new one.</span></p>
<p><span style="font-weight: 400">You can also install skills instead of writing the rule yourself. The </span><a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/platform-metadata-grounding-get"><span style="font-weight: 400">platform-metadata-grounding-get</span></a><span style="font-weight: 400"> skill teaches your agent how to use Metadata Grounding. The </span><a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/platform-metadata-discovery-get"><span style="font-weight: 400">platform-metadata-discovery-get</span></a><span style="font-weight: 400"> skill helps it choose between Metadata Grounding, SOQL, and the Salesforce CLI. Both are in the </span><a href="https://github.com/forcedotcom/sf-skills"><span style="font-weight: 400">public sf-skills repo</span></a><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400">Choose where to connect</span></h2>
<p><span style="font-weight: 400">Metadata Grounding is available on two servers, both of which are in Beta.</span></p>
<p><b>The Metadata Grounding MCP Server (Beta)</b><span style="font-weight: 400"> is the best way to get started today. It&#8217;s read-only, and it works with any MCP-compatible client such as Claude Code, Cursor, or ChatGPT. If you want discovery and reuse and nothing else, start here.</span></p>
<p><span style="font-weight: 400">The Metadata Grounding MCP Server will be available out of the box in Agentforce Vibes and included in the </span><a href="https://developer.salesforce.com/docs/platform/salesforce-skills-plugins/overview"><span style="font-weight: 400">Salesforce Development Plugin for Claude Code</span></a><span style="font-weight: 400">.</span></p>
<p><b>The Headless 360 MCP Server (Beta)</b><span style="font-weight: 400"> is where Metadata Grounding is headed. It&#8217;s the one-stop MCP for the Headless Toolkit, exposing the same Metadata Grounding capabilities, along with the tools an agent uses to act on your org. One connection and one place to manage access, with grounding next to action.</span></p>
<p><span style="font-weight: 400">The shape differs a little. The standalone server gives your agent three tools directly. Inside the Headless 360 MCP Server, the agent reaches the same capability through the four tools it already uses: </span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Discover finds the grounding workflow, </span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Describe shows its steps, and </span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Dispatch / Dispatch Read Only runs the search and query.</span></li>
</ul>
<p><span style="font-weight: 400">If you build prompts or tests around the three tool names exposed in the standalone MCP server, they won&#8217;t carry over unchanged.</span></p>
<p><b>Note:</b><span style="font-weight: 400"> We&#8217;re working to bring the Headless 360 MCP Server to full parity with the standalone server. We&#8217;ll publish guidance on any change before it affects you, and we&#8217;ll update this post. The Headless 360 MCP Server will be renamed to the Salesforce MCP Server when it reaches general availability.</span></p>
<h3><span style="font-weight: 400">Before you begin</span></h3>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Your org needs an edition that Data 360 supports, with Data 360 provisioned and API Enabled. Salesforce Catalog is opt-in through Data Cloud Setup Feature Manager.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Data 360 isn&#8217;t supported in scratch orgs. Salesforce Catalog isn&#8217;t available in Developer Edition orgs that use namespaces.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">An admin sets up the connection and activates the server. All Platform Hosted MCP Servers require admin activation.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The server returns only the metadata that the authorized user is permitted to access.</span></li>
</ul>
<p><b>Note:</b><span style="font-weight: 400"> The </span><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_headless360_mcp_servers_metadata_grounding.htm&amp;release=264&amp;type=5"><span style="font-weight: 400">Metadata Grounding MCP Server</span></a><span style="font-weight: 400"> is a beta service that is subject to the Beta Services Terms. Use of this beta service is at the Customer&#8217;s sole discretion.</span></p>
<p><b>Tip:</b><span style="font-weight: 400"> Try Metadata Grounding in a sandbox or developer org first, then move to production once your team is comfortable with how the agent uses it.</span></p>
<h3><span style="font-weight: 400">Connect the Metadata Grounding MCP Server</span></h3>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Follow the </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/setup-overview.html"><span style="font-weight: 400">Platform Hosted MCP Server setup guide</span></a><span style="font-weight: 400"> to create an External Client App and log in to your org.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">In Setup, find the Salesforce Hosted MCP Servers section, locate Metadata Grounding Beta, and click Activate.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Add the </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/metadata-grounding.html"><span style="font-weight: 400">server to your MCP client and authorize</span></a><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Ask a first question to confirm it works, such as &#8220;Which objects track reservations?&#8221;</span></li>
</ol>
<h3><span style="font-weight: 400">What to know before you rely on it</span></h3>
<ul>
<li style="font-weight: 400"><b>Search finds candidates; query confirms them.</b><span style="font-weight: 400"> Have your agent read the full record before it writes code against it.</span></li>
<li style="font-weight: 400"><b>Results can lag a change.</b><span style="font-weight: 400"> Something you deployed a moment ago can take time to appear. See the “What&#8217;s behind the tools” section for how refresh works.</span></li>
<li style="font-weight: 400"><b>The set of types is broad but not complete.</b><span style="font-weight: 400"> Use </span><span style="font-weight: 400">describe_metadata</span><span style="font-weight: 400"> to see exactly which types your org exposes, and don&#8217;t treat a search as an exhaustive inventory.</span></li>
<li style="font-weight: 400"><b>It&#8217;s read-only.</b><span style="font-weight: 400"> To change your org, pair it with the Headless 360 MCP Server or your existing deployment tools.</span></li>
<li style="font-weight: 400"><b>It returns metadata, not source code.</b><span style="font-weight: 400"> The agent can find the Apex class or component and see its properties. To read or edit the code, it retrieves that one item with the Salesforce CLI or the Metadata API.</span></li>
<li style="font-weight: 400"><b>Billing.</b><span style="font-weight: 400"> Metadata Grounding usage, including the underlying Data 360 consumption, is metered in production orgs and unmetered in pre-production orgs such as sandboxes and Developer Edition. See </span><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_salesforce_catalog_billing_considerations.htm&amp;type=5"><b>Billing Considerations for Salesforce Catalog</b></a> <span style="font-weight: 400">for details.</span></li>
</ul>
<h2><span style="font-weight: 400">What&#8217;s next</span></h2>
<p><span style="font-weight: 400">We&#8217;re working on two additions that build on this foundation. AI-Generated Metadata Descriptions will make metadata more meaningful to agents, so searches like the menu example get sharper even when a component has no description today. The Metadata Graph will let an agent use dependency analysis to see what could break before it acts. Want to try them early? </span><a href="https://docs.google.com/forms/d/e/1FAIpQLSc3X7ujzcjVViQQKAv7f5Bj19vz-cPfCqKvaMH80hB7bjkcEw/viewform"><span style="font-weight: 400">Sign up for early access.</span></a></p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">Coding agents are only as good as their view of your org. Skills tell them how to build, and the Headless 360 MCP Server lets them act. Metadata Grounding lets them see, so they extend what you have rather than rebuild it.</span></p>
<p><span style="font-weight: 400">A good place to start: activate the Metadata Grounding MCP Server in a sandbox that has Salesforce Catalog enabled, add the project rule above, and give your agent a ticket for a feature you suspect already exists somewhere in the org. We welcome </span><a href="https://forms.gle/G3Xuto6tMAUfhA1K7"><span style="font-weight: 400">your feedback</span></a><span style="font-weight: 400"> to help shape what ships next.</span></p>
<p><span style="font-weight: 400">Watch the </span><a href="https://developer.salesforce.com/"><span style="font-weight: 400">Salesforce Developers website</span></a><span style="font-weight: 400"> for documentation, release notes, and hands-on content.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_headless360_mcp_servers_metadata_grounding.htm&amp;release=264&amp;type=5"><span style="font-weight: 400">Discover Org Metadata with the Metadata Grounding MCP Server (Beta)</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/metadata-grounding.html"><span style="font-weight: 400">Metadata Grounding MCP Server (Beta) Dev Documentation</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/setup-overview.html"><span style="font-weight: 400">Platform Hosted MCP Server Setup Guide</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/platform-metadata-grounding-get"><span style="font-weight: 400">Metadata Grounding Skill (sf-skills)</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/platform-metadata-discovery-get"><span style="font-weight: 400">Metadata Discovery Skill (sf-skills)</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_cdp_2026_winter_salesforce_catalog_ga.htm&amp;release=264&amp;type=5"><span style="font-weight: 400">Ground Users and Agents in Your Metadata with Salesforce Catalog (Generally Available)</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_salesforce_catalog.htm&amp;type=5"><span style="font-weight: 400">Salesforce Catalog Documentation</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/blogs/2026/07/announcing-the-headless-360-mcp-server-beta"><span style="font-weight: 400">Announcing the Headless 360 MCP Server Beta</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><a href="mailto:ggambino@salesforce.com"><span style="font-weight: 400">Gonzalo Gambino</span></a><span style="font-weight: 400"> is a Senior Director of Product Management at Salesforce and the product domain lead for Platform Metadata and Schema. Gonzalo&#8217;s domain covers the schema and metadata foundation for the platform, including Metadata Grounding.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/metadata-grounding-let-your-coding-agent-search-your-org-by-intent">Metadata Grounding: Let Your Coding Agent Search Your Org by Intent</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/11/metadata-grounding-let-your-coding-agent-search-your-org-by-intent/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207282</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261104084827/Generic-A-11-e1793810926525.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261104084827/Generic-A-11-e1793810926525.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Runtime Behavior Anti-Patterns in Lightning Web Components</title>
		<link>https://developer.salesforce.com/blogs/2026/11/runtime-behavior-anti-patterns-in-lightning-web-components</link>
		<comments>https://developer.salesforce.com/blogs/2026/11/runtime-behavior-anti-patterns-in-lightning-web-components#respond</comments>
		<pubDate>Wed, 04 Nov 2026 16:00:34 +0000</pubDate>
		<dc:creator><![CDATA[Tim Dionne]]></dc:creator>
				<category><![CDATA[App Development]]></category>
		<category><![CDATA[Lightning Web Components]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Component Lifecycle]]></category>
		<category><![CDATA[Front-End Performance]]></category>
		<category><![CDATA[lightning web security]]></category>
		<category><![CDATA[lwc]]></category>
		<category><![CDATA[LWC Best Practices]]></category>
		<category><![CDATA[Salesforce Development]]></category>
		<category><![CDATA[Shadow DOM]]></category>
		<category><![CDATA[web development]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207278</guid>
		<description><![CDATA[<p>Learn how to avoid the DOM-access, timing, lifecycle, and performance mistakes that make Lightning Web Components behave unpredictably, and why some base components silently fail outside Lightning Experience.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/runtime-behavior-anti-patterns-in-lightning-web-components">Runtime Behavior Anti-Patterns in Lightning Web Components</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><span style="font-weight: 400">A component can get its security model right and its data flow right, and still misbehave. It queries an element that isn&#8217;t there yet. It races a render cycle with a timeout. It works perfectly on desktop and renders blank on mobile. These bugs don&#8217;t come from misunderstanding a single API; they come from misunderstanding </span><i><span style="font-weight: 400">when</span></i><span style="font-weight: 400"> things happen: when the DOM exists, when a render has actually completed, and which runtime a component is executing in.</span></p>
<p><span style="font-weight: 400">This is the third and final post in our series on Lightning Web Component (LWC) anti-patterns. The </span><a href="https://developer.salesforce.com/blogs/2026/06/security-anti-patterns-in-lightning-web-components"><span style="font-weight: 400">first post</span></a><span style="font-weight: 400"> covered security and platform compatibility. The </span><a href="https://developer.salesforce.com/blogs/2026/08/data-and-communication-anti-patterns-in-lightning-web-components"><span style="font-weight: 400">second post</span></a><span style="font-weight: 400"> covered the wire service, public properties, and events. In this post, you&#8217;ll learn the most common mistakes in </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-dom.html"><span style="font-weight: 400">DOM access</span></a><span style="font-weight: 400">, asynchronous timing, the </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-lifecycle-hooks.html"><span style="font-weight: 400">component lifecycle</span></a><span style="font-weight: 400">, performance, and base component usage.</span></p>
<h2><span style="font-weight: 400">DOM access anti-patterns</span></h2>
<p><span style="font-weight: 400">LWC renders each component into its own </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-dom-intro.html"><span style="font-weight: 400">shadow DOM</span></a><span style="font-weight: 400">. A component should only read or modify elements it owns. The patterns below cover the ways that boundary gets crossed, accidentally or not.</span></p>
<h3><span>Don&#8217;t query the global </span><code>document</code></h3>
<p>Calling <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector"><u><code>document.querySelector()</code></u></a> or <code>document.getElementById()</code> searches the entire page instead of your component&#8217;s shadow tree. Inside Lightning Web Security, these calls are scoped to your sandbox and won&#8217;t find elements owned by other components. Outside that environment (in a Jest test without proper mocking, for instance) they may appear to work but behave differently in production.</p>
<pre language="javascript">renderedCallback() {
    // Searches the global document — won't find elements in another component's shadow
    const field = document.querySelector('.my-input-field');
    field?.focus();
}
</pre>
<p><span style="font-weight: 400">Scope the query to your own template instead:</span></p>
<pre language="javascript">renderedCallback() {
    const field = this.template.querySelector('.my-input-field');
    field?.focus();

}
</pre>
<p>For a cleaner approach that avoids selector fragility altogether, use <a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-components-dom-work.html#refs"><u><code>lwc:ref</code></u></a>:</p>
<pre language="html">&lt;template&gt;
    &lt;input lwc:ref=&quot;inputField&quot; type=&quot;text&quot; /&gt;
&lt;/template&gt;
</pre>
<pre language="javascript">renderedCallback() {
    this.refs.inputField.focus();
}
</pre>
<h3><span>Don&#8217;t set </span><code>innerHTML</code></h3>
<p><span style="font-weight: 400">Setting innerHTML is an XSS risk whenever any part of the string comes from user input, and it bypasses LWC&#8217;s reactive rendering pipeline; a later template re-render can overwrite or conflict with markup you injected imperatively. Lightning Web Security sanitizes innerHTML on shared elements such as &lt;body&gt;, but it doesn&#8217;t protect the markup inside your own component, so that responsibility stays with you. The </span><a href="https://github.com/salesforce/eslint-plugin-lwc/blob/master/docs/rules/no-inner-html.md"><span style="font-weight: 400">no-inner-html</span></a><span style="font-weight: 400"> rule in the LWC ESLint plugin flags this pattern automatically.</span></p>
<pre language="javascript">renderedCallback() {
    const el = this.template.querySelector('.output');
    el.innerHTML = `&lt;strong&gt;${this.label}&lt;/strong&gt;`;
}
</pre>
<p><span style="font-weight: 400">Express the markup declaratively in the template instead:</span></p>
<pre language="html">&lt;template&gt;
    &lt;div class=&quot;output&quot;&gt;&lt;strong&gt;{label}&lt;/strong&gt;&lt;/div&gt;
&lt;/template&gt;
</pre>
<p>If you need to render arbitrary HTML from a trusted source, such as rich text from a CMS, use the <code>lightning-formatted-rich-text</code> base component, which applies platform-level sanitization.</p>
<h3><span>Don&#8217;t use ID selectors in </span><code>querySelector</code></h3>
<p>LWC rewrites <code>id</code> attributes in templates into globally unique values at render time, to prevent collisions across components on the same page. The <code>id</code> you wrote in your HTML won&#8217;t match what&#8217;s in the rendered DOM, so <code>this.template.querySelector('#myId')</code> is unreliable.</p>
<pre language="html">&lt;template&gt;
    &lt;input id=&quot;nameInput&quot; type=&quot;text&quot; /&gt;
&lt;/template&gt;
</pre>
<pre language="javascript">renderedCallback() {
    // The rendered id will be something like "nameInput-123", not "nameInput"
    const input = this.template.querySelector('#nameInput');
}
</pre>
<p>Use class selectors, data attributes, or <code>lwc:ref</code> instead:</p>
<pre language="html">&lt;template&gt;
    &lt;input lwc:ref=&quot;nameInput&quot; type=&quot;text&quot; /&gt;
&lt;/template&gt;
</pre>
<pre language="javascript">renderedCallback() {
    const input = this.refs.nameInput;
}
</pre>
<h2><span style="font-weight: 400">Asynchronous timing anti-patterns</span></h2>
<p><code>setTimeout</code> shows up as a timing workaround in two very different LWC scenarios, and both are fragile for the same underlying reason: they couple your logic to an arbitrary wall-clock delay instead of to the thing you&#8217;re actually waiting for.</p>
<h3><span>Don&#8217;t use </span><code>setTimeout</code><span> to wait for async data</span></h3>
<p><span style="font-weight: 400">Wrapping data processing in a timeout, hoping a fetch or state update has &#8220;finished&#8221; by the time it fires, is unreliable. The timeout can fire before or after the data is ready depending on load, and there&#8217;s no signal telling you it actually completed.</span></p>
<pre language="javascript">handleUpdate() {
    this.isLoading = true;
    // Hoping 100ms is enough for the data to arrive
    setTimeout(() =&gt; {
        this.processData();
        this.isLoading = false;
    }, 100);
}
</pre>
<p>Use <code>async</code>/<code>await</code> so the work runs exactly when the data is ready:</p>
<pre language="javascript">data; // holds the loaded data
async handleUpdate() {
    this.isLoading = true;
    try {
        this.data = await loadData();
    } finally {
        this.isLoading = false;
    }
}
</pre>
<h3><span>Don&#8217;t use </span><code>setTimeout</code><span> to wait for a re-render</span></h3>
<p><span>Setting a reactive property and immediately trying to read the updated DOM also fails, because LWC batches and applies DOM updates asynchronously. </span><code><span>setTimeout(..., 0)</span></code><span> often appears to work in testing, but it isn&#8217;t guaranteed because it depends on timing rather than a signal from the engine, and it breaks under load.</span></p>
<pre language="javascript">handleShowPanel() {
    this.showPanel = true;
    // DOM hasn't updated yet — querySelector returns null
    setTimeout(() =&gt; {
        this.template.querySelector('.panel input').focus();
    }, 0);
}
</pre>
<p>Use <a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-lifecycle-hooks-rendered.html"><u><code>renderedCallback()</code></u></a> instead. The engine calls it after every render cycle, guaranteeing the DOM reflects the latest state:</p>
<pre language="javascript">_shouldFocusInput = false;

handleShowPanel() {
    this.showPanel = true;
    this._shouldFocusInput = true;
}

renderedCallback() {
    if (this._shouldFocusInput) {
        this._shouldFocusInput = false;
        const input = this.template.querySelector('.panel input');
        input?.focus();
    }
}
</pre>
<h2><span style="font-weight: 400">Lifecycle anti-patterns</span></h2>
<p><span style="font-weight: 400">Several APIs are unavailable, or unreliable, at specific points in a component&#8217;s </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-lifecycle-hooks.html"><span style="font-weight: 400">lifecycle</span></a><span style="font-weight: 400">. Most lifecycle bugs come from reaching for one of these APIs one hook too early.</span></p>
<h3><span>Don&#8217;t access </span><code>@api</code><span> properties in the constructor</span></h3>
<p><span>The </span><code><span>constructor()</span></code><span> runs </span><i><span>before</span></i><span> the parent has set any </span><code><span>@api</span></code><span> property values. Reading one in the constructor returns </span><code><span>undefined</span></code><span>, which leads to incorrect initialization.</span></p>
<pre language="javascript">export default class MyComponent extends LightningElement {
    @api recordId;

    constructor() {
        super();
        // recordId has not been set yet — this.recordId is undefined
        this.fetchData(this.recordId);
    }
}
</pre>
<p>To be clear about what <i>is</i> fine in a constructor: setting default values for your own class properties is a perfectly normal use of it. The restriction is specifically on reading <code>@api</code> properties (their values haven&#8217;t been set yet) and touching the DOM (nothing has rendered; see the next pattern). For initialization logic that depends on an <code>@api</code> value, use <code>connectedCallback()</code>, or better, drive it with <code>@wire</code>, which re-runs automatically when the reactive property changes:</p>
<pre language="javascript">export default class MyComponent extends LightningElement {
    @api recordId;

    @wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD] })
    record;
}
</pre>
<h3><span>Don&#8217;t access the DOM in the constructor or </span><code>connectedCallback()</code></h3>
<p>The rendering pipeline is asynchronous. When <code>constructor()</code> runs, nothing has been rendered yet, so <code>this.template.querySelector()</code> returns <code>null</code>. <code>connectedCallback()</code> fires when the component is inserted into the DOM tree, but its children have not rendered yet either. On first connection, a query in either hook returns null, so the code either throws a TypeError or, if you use optional chaining, silently does nothing.</p>
<pre language="javascript">export default class MyComponent extends LightningElement {
    constructor() {
        super();
        // DOM not rendered yet — returns null
        const input = this.template.querySelector('input');
        input.focus(); // throws: Cannot read properties of null
    }

    connectedCallback() {
        // Child elements don't exist yet, so this returns null
        const panel = this.template.querySelector('.panel');
        panel.classList.add('active'); // throws: Cannot read properties of null
    }
}
</pre>
<p>Use <code>renderedCallback()</code> for anything that reads or modifies rendered DOM elements, guarded with a flag if the setup should run only once:</p>
<pre language="javascript">export default class MyComponent extends LightningElement {
    _initialized = false;

    renderedCallback() {
        if (this._initialized) return;
        this._initialized = true;

        const input = this.template.querySelector('input');

        input?.focus();

    }
}
</pre>
<p><code>renderedCallback()</code> fires after every render, not just the first, so the guard matters. For DOM updates that should track ongoing data changes rather than run once, let the template handle them declaratively instead of reaching for imperative DOM code at all.</p>
<h3><span>Don&#8217;t access </span><code>this.template.children</code></h3>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/children"><u><code>this.template.children</code></u></a> returns the immediate children of your component&#8217;s template root, but the LWC engine makes no guarantee about their structure or order. Reading them directly ties your logic to rendered DOM structure instead of component intent, and that structure can shift with unrelated template changes.</p>
<pre language="javascript">renderedCallback() {
    const firstChild = this.template.children[0];
    firstChild.classList.add('active');
}
</pre>
<p>Use <code>this.template.querySelector()</code> with a stable selector, or <code>lwc:ref</code>, to reference elements by intent rather than by position:</p>
<pre language="html">&lt;template&gt;
    &lt;div class=&quot;main-panel&quot; lwc:ref=&quot;panel&quot;&gt;&lt;/div&gt;
&lt;/template&gt;
</pre>
<pre language="javascript">renderedCallback() {
    this.refs.panel.classList.add('active');
}
</pre>
<h2><span style="font-weight: 400">Performance anti-patterns</span></h2>
<p><span style="font-weight: 400">Lightning Web Security introduces proxy overhead at the boundary between the host environment and your component&#8217;s sandbox. For most components, this overhead is negligible, but it becomes measurable when a component processes large volumes of data or crosses that boundary repeatedly in a tight loop.</span></p>
<h3><span style="font-weight: 400">Clone host-environment data before processing it in a loop</span></h3>
<p><span style="font-weight: 400">Data returned by Apex controllers and Lightning Data Service (LDS) wire adapters originates outside your component&#8217;s sandbox. Every property access on that data crosses the proxy boundary. For a handful of fields, this cost is imperceptible. Over thousands of rows, across every sort, filter, and render cycle, it adds up to noticeably degraded responsiveness.</span></p>
<pre language="javascript">@wire(getOpportunities)
wiredOpps({ data }) {
    if (data) {
        // Each access to opp.Amount and opp.StageName crosses the proxy boundary
        this.total = data.reduce((sum, opp) =&gt; sum + opp.Amount, 0);
        this.openOpps = data.filter(opp =&gt; opp.StageName !== 'Closed Won');
    }
}
</pre>
<p><span style="font-weight: 400">Clone the data into your own namespace once, on arrival, and process the local copy from then on:</span></p>
<pre language="javascript">@wire(getOpportunities)
wiredOpps({ data }) {
    if (data) {
        // Clone once on arrival so subsequent processing uses local objects
        const local = JSON.parse(JSON.stringify(data));
        this.total = local.reduce((sum, opp) =&gt; sum + opp.Amount, 0);
        this.openOpps = local.filter(opp =&gt; opp.StageName !== 'Closed Won');
    }
}
</pre>
<p><span style="font-weight: 400">Where possible, push the aggregation itself server-side into Apex, so the client receives a precomputed result instead of a large collection to iterate over.</span></p>
<h3><span>Prefer plain objects and arrays over </span><code>Set</code><span> and </span><code>Map</code><span> for large collections</span></h3>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set"><u><code><span>Set</span></code></u></a><span>, </span><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map"><u><code><span>Map</span></code></u></a><span>, and typed arrays carry the same proxy overhead as host-environment data when accessed inside a namespace sandbox. Processing tens of thousands of items through these structures produces performance degradation you wouldn&#8217;t see in an equivalent non-sandboxed environment.</span></p>
<pre language="javascript">processIds(records) {
    // Map and Set carry proxy overhead per operation at scale
    const seen = new Set(); // passed through the loop
    const index = new Map();
    records.forEach(r =&gt; {
        seen.add(r.Id);
        index.set(r.Id, r);
    });
    return index;
}
</pre>
<p><span style="font-weight: 400">Use plain objects and arrays for large collections instead:</span></p>
<pre language="javascript">processIds(records) {
    const seen = {}; // passed through the loop
    const index = {};
    records.forEach(r =&gt; {
        seen[r.Id] = true;
        index[r.Id] = r;
    });
    return index;
}
</pre>
<p>Reserve <code>Set</code> and <code>Map</code> for small collections, where the overhead is negligible, or for cases where their semantics (non-string keys, insertion-order iteration) are genuinely required.</p>
<h2><span style="font-weight: 400">Base component usage anti-patterns</span></h2>
<p><span style="font-weight: 400">Base Lightning components behave differently across the runtimes your component can end up in, including Lightning Experience, Lightning Web Runtime (LWR) sites, Experience Cloud, and the Salesforce Mobile App. Several of the following failures are silent: nothing throws, the component just doesn&#8217;t do what you expect.</span></p>
<h3><span>Don&#8217;t use </span><code>platformShowToastEvent</code><span> outside Lightning Experience</span></h3>
<p><a href="https://developer.salesforce.com/docs/component-library/bundle/lightning-platform-show-toast-event/documentation"><u><code><span>lightning/platformShowToastEvent</span></code></u></a><span> dispatches a toast using an event-based mechanism that&#8217;s only supported in Salesforce Classic, Lightning Experience, and the mobile app. It&#8217;s a silent no-op in LWR sites and Experience Cloud. No error; no toast.</span></p>
<pre language="javascript">import { ShowToastEvent } from 'lightning/platformShowToastEvent';

handleSuccess() {
    // Silent no-op in LWR and Experience Cloud
    this.dispatchEvent(new ShowToastEvent({
        title: 'Success',
        message: 'Record saved.',
        variant: 'success'
    }));
}
</pre>
<p>Use <a href="https://developer.salesforce.com/docs/component-library/bundle/lightning-toast/documentation"><u><code>lightning/toast</code></u></a> instead, which works across all supported containers:</p>
<pre language="javascript">import LightningToast from 'lightning/toast';

handleSuccess() {
    LightningToast.show({
        label: 'Success',
        message: 'Record saved.',
        variant: 'success'
    });
}
</pre>
<h3><span style="font-weight: 400">Don&#8217;t extend unsupported base components</span></h3>
<p>Only two base components officially support class extension: <a href="https://developer.salesforce.com/docs/component-library/bundle/lightning-modal/documentation"><u><code>lightning/modal</code></u></a> and <a href="https://developer.salesforce.com/docs/component-library/bundle/lightning-datatable/documentation"><u><code>lightning/datatable</code></u></a>. Extending any other base component relies on internal implementation details that can change in any release without notice.</p>
<pre language="javascript">import LightningCombobox from 'lightning/combobox';

// Extending an unsupported base component
export default class CustomCombobox extends LightningCombobox {
    connectedCallback() {
        super.connectedCallback();
        this.doCustomSetup();
    }
}
</pre>
<p>Wrap the base component instead, and delegate to it through <code>@api</code> properties and events:</p>
<pre language="javascript">export default class CustomCombobox extends LightningElement {
    @api label;
    @api options;
    @api value;

    handleChange(event) {
        this.dispatchEvent(new CustomEvent('change', {
            detail: { value: event.detail.value }
        }));
    }
}
</pre>
<pre language="html">&lt;template&gt;
    &lt;lightning-combobox
        label={label}
        options={options}
        value={value}
        onchange={handleChange}&gt;
    &lt;/lightning-combobox&gt;
&lt;/template&gt;
</pre>
<p>Consuming components use <code>c-custom-combobox</code> exactly as they would the base component. The wrapper&#8217;s <code>@api</code> properties and <code>change</code> event give it the same surface:</p>
<pre language="html">&lt;c-custom-combobox
    label=&quot;Status&quot;
    options={statusOptions}
    value={status}
    onchange={handleStatusChange}&gt;
&lt;/c-custom-combobox&gt;
</pre>
<h3><span>Don&#8217;t read the record ID from the </span><code>submit</code><span> event</span></h3>
<p><span>When </span><code><span>lightning-record-form</span></code><span> or </span><code><span>lightning-record-edit-form</span></code><span> creates a new record, the record&#8217;s ID doesn&#8217;t exist yet at </span><code><span>submit</span></code><span> time. Reading </span><code><span>event.detail.id</span></code><span> in an </span><code><span>onsubmit</span></code><span> handler returns </span><code><span>undefined</span></code><span> silently, with no error.</span></p>
<pre language="javascript">handleSubmit(event) {
    // undefined for new records — the record hasn't been created yet
    const newRecordId = event.detail.id;
    this.navigateToRecord(newRecordId);
}
</pre>
<p>Use the <code>success</code> event instead, which fires after the record is saved and the server has assigned an ID:</p>
<pre language="javascript">handleSuccess(event) {
    const newRecordId = event.detail.id;
    this.dispatchEvent(new CustomEvent('recordcreated', {
        detail: { recordId: newRecordId }
    }));
}
</pre>
<pre language="html">&lt;lightning-record-form
    object-api-name=&quot;Account&quot;
    fields={fields}
    onsuccess={handleSuccess}&gt;
&lt;/lightning-record-form&gt;
</pre>
<p><code>submit</code> is still the right event for intercepting or modifying a form submission before it goes out; just not for reading the ID that comes back.</p>
<h3><span style="font-weight: 400">Don&#8217;t override a base component&#8217;s internal SLDS styles</span></h3>
<p>Base components like lightning-button and lightning-input render their internal markup inside the LWC shadow DOM. Their internal class names, such as <code>.slds-button_brand</code><span>, </span><code>.slds-input</code><span>, and similar</span><span>, are implementation details, not a public API. CSS that reaches into a base component&#8217;s shadow to target these classes is unsupported, and it breaks silently whenever SLDS changes internal markup or styling, including changes made for accessibility fixes or SLDS token updates.</span></p>
<pre language="css">/* Targeting an internal SLDS class inside lightning-button — unsupported */
.slds-button_brand {
    background-color: #e07d00 !important;
}
</pre>
<p>Use the public <a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-components-css-custom-properties.html"><u>styling hooks</u></a> (<code>--slds-*</code> CSS custom properties) that the component exposes as its actual styling API. For example, to restyle a brand <a href="https://developer.salesforce.com/docs/component-library/bundle/lightning-button/documentation"><u>lightning-button</u></a>, set its component hooks (which currently apply only to SLDS 1) from your own stylesheet:</p>
<pre language="css">:host {
    --slds-c-button-brand-color-background: var(--slds-g-orange-70);
    --slds-c-button-brand-color-border: var(--slds-g-orange-70);
}
</pre>
<p>Stick to the public <code>--slds-*</code> hooks. The older <code>--lwc-</code>* hooks are legacy and discouraged, and overriding them can trigger validation warnings. If the component doesn&#8217;t expose a hook for what you need, wrap it and style the wrapper, or build the element from scratch if the customization runs deep enough.</p>
<h3><span>Check mobile support before using a base component on a mobile-facing page</span></h3>
<p><span>The Salesforce Mobile App uses a different rendering pipeline than the desktop browser, and some base components have no mobile implementation, or a reduced one. Using one on a page that also renders in the mobile app fails silently. The component renders incompletely or blank, with no error.</span></p>
<p>The most common case is <code>lightning-datatable</code>, which is <b>not supported in the Salesforce Mobile App</b>. It renders correctly in a desktop browser and even in a mobile browser&#8217;s responsive mode, which makes the failure easy to miss until it&#8217;s tested in the native app itself.</p>
<pre language="html">&lt;!-- lightning-datatable is not supported in the Salesforce Mobile App --&gt;
&lt;template&gt;
    &lt;lightning-datatable
        key-field=&quot;id&quot;
        data={products}
        columns={columns}
        onrowaction={handleRowAction}&gt;
    &lt;/lightning-datatable&gt;
&lt;/template&gt;
</pre>
<p><span style="font-weight: 400">Detect the form factor at runtime and render a mobile-appropriate alternative:</span></p>
<pre language="javascript">import FORM_FACTOR from '@salesforce/client/formFactor';

export default class MyProductList extends LightningElement {
    isMobile = FORM_FACTOR === 'Small';
}
</pre>
<pre language="html">&lt;template lwc:if={isMobile}&gt;
    &lt;template for:each={products} for:item=&quot;product&quot;&gt;
        &lt;c-product-tile key={product.id} record={product}&gt;&lt;/c-product-tile&gt;
    &lt;/template&gt;
&lt;/template&gt;
&lt;template lwc:else&gt;
    &lt;lightning-datatable
        key-field=&quot;id&quot;
        data={products}
        columns={columns}
        onrowaction={handleRowAction}&gt;
    &lt;/lightning-datatable&gt;
&lt;/template&gt;
</pre>
<p>If the mobile and desktop experiences differ substantially, consider <a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-render.html"><u>rendering multiple templates</u></a> instead of stacking lwc:if blocks. This approach lets a component ship separate templates and choose between them in a <code>render() </code>method.</p>
<p>Before deploying a custom component to a page that appears in the mobile app, check the <a href="https://developer.salesforce.com/docs/component-library"><u>Component Reference</u></a> for mobile support, and test in the native app, since desktop responsive mode doesn&#8217;t accurately simulate the mobile runtime.</p>
<h2>Conclusion</h2>
<p>The patterns in this post share a common root: assuming something is ready before it actually is. The DOM isn&#8217;t there yet in the constructor. A render hasn&#8217;t finished just because you set a property. An <code>@api</code> value isn&#8217;t set until the parent sets it. A base component that works in Lightning Experience isn&#8217;t guaranteed to work in LWR, Experience Cloud, or the mobile app.</p>
<p>Across this series, the underlying lesson has been the same: LWC guarantees are specific and narrower than they first appear. Security layers namespace and sanitize more than they block. The wire service is a subscription, not a fetch. And the runtime a component renders in shapes what&#8217;s actually available to it. Once those models are correct, most of the anti-patterns in this series stop looking like traps and start looking obvious.</p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-lifecycle-hooks.html"><span style="font-weight: 400">Component lifecycle documentation</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-dom.html"><span style="font-weight: 400">Create DOM elements</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-components-dom-work.html#refs"><span style="font-weight: 400">lwc:ref documentation</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/component-library/bundle/lightning-toast/documentation"><span style="font-weight: 400">lightning/toast documentation</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-client-form-factor.html"><span style="font-weight: 400">@salesforce/client/formFactor</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/salesforce/eslint-plugin-lwc"><span style="font-weight: 400">@lwc/eslint-plugin-lwc rules</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Tim Dionne</b><span style="font-weight: 400"> is a Principal Member of Technical Staff (PMTS) on the Customer Centric Engineering team. He&#8217;s worked on many UI features of Salesforce over the years, starting with VisualForce, Aura Components, and Lightning Web Components with an emphasis on Lightning Web Security and Lightning Data Service.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/runtime-behavior-anti-patterns-in-lightning-web-components">Runtime Behavior Anti-Patterns in Lightning Web Components</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/11/runtime-behavior-anti-patterns-in-lightning-web-components/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207278</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261029132431/Generic-C-9-e1793305490174.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261029132431/Generic-C-9-e1793305490174.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Protect Your Salesforce Commerce API (SCAPI) from Bots and AI Agents with eCDN</title>
		<link>https://developer.salesforce.com/blogs/2026/11/protect-your-salesforce-commerce-api-scapi-from-bots-and-ai-agents-with-ecdn</link>
		<comments>https://developer.salesforce.com/blogs/2026/11/protect-your-salesforce-commerce-api-scapi-from-bots-and-ai-agents-with-ecdn#respond</comments>
		<pubDate>Tue, 03 Nov 2026 16:00:54 +0000</pubDate>
		<dc:creator><![CDATA[Sarah Welker]]></dc:creator>
				<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[Salesforce Releases]]></category>
		<category><![CDATA[Trust, Security, and Accessibility]]></category>
		<category><![CDATA[AI Agents]]></category>
		<category><![CDATA[AI Crawlers]]></category>
		<category><![CDATA[API Security]]></category>
		<category><![CDATA[B2C Commerce]]></category>
		<category><![CDATA[Bot Mitigation]]></category>
		<category><![CDATA[eCDN]]></category>
		<category><![CDATA[Managed Runtime]]></category>
		<category><![CDATA[Salesforce Commerce API]]></category>
		<category><![CDATA[SCAPI]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207267</guid>
		<description><![CDATA[<p>Learn how to secure your Salesforce Commerce API (SCAPI) with B2C Commerce eCDN. Explore how to restrict API access to trusted integrations, strengthen SLAS security, and manage AI crawler and shopping agent traffic at the edge.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/11/protect-your-salesforce-commerce-api-scapi-from-bots-and-ai-agents-with-ecdn">Protect Your Salesforce Commerce API (SCAPI) from Bots and AI Agents with 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">Bots increasingly skip the storefront and call the </span><a href="https://developer.salesforce.com/docs/commerce/commerce-api/guide/scapi-get-started.html"><span style="font-weight: 400">B2C Commerce API (SCAPI)</span></a><span style="font-weight: 400"> directly. This is common with headless and composable storefronts, and with bots that chase high-demand inventory through the API instead of the UI. At the same time, AI crawlers and shopping agents are now a regular part of the traffic mix. While most of that traffic is legitimate, at volume it drives up search and large language model (LLM) costs and skews your analytics.</span></p>
<p><span style="font-weight: 400">The October 2026 release of B2C Commerce gives you the tools to see and control both. The SCAPI zone (the eCDN zone in front of your Commerce API traffic) is now manageable in Business Manager and the CDN API. The new SCAPI Identity Header (SIH) lets you accept Shopper API traffic only from Managed Runtime (MRT). Verified-bot fields let you identify AI crawlers and agents on any eCDN zone.</span></p>
<p><span style="font-weight: 400">This post covers:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">What the SCAPI zone is and what you can control there</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">How to restrict Shopper API traffic to Managed Runtime traffic with the SIH</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">How to allow your other legitimate API clients</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">How to harden SLAS, the login service behind SCAPI</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">How to identify AI crawlers and shopping agents, and manage them with rate limits and SEO controls</span></li>
</ul>
<h2><span style="font-weight: 400">The SCAPI zone and what you can control there</span></h2>
<p><span style="font-weight: 400">The SCAPI zone is provisioned automatically. You can&#8217;t create it or change its hostnames, and it appears alongside your storefront zones in the zone list. One SCAPI zone is shared across your Development, Staging, and Production instances, and you manage it from Production only. Business Manager shows a shared-zone banner, and some Salesforce-managed rules are locked.</span></p>
<p><b>What you can control:</b><span style="font-weight: 400"> custom firewall rules, rate limiting, IP access lists, HTTP and security analytics, and cache purge.</span></p>
<p><b>What isn&#8217;t available:</b><span style="font-weight: 400"> WAF managed rulesets, mutual TLS (mTLS), TLS 1.3 settings, HTTP Strict Transport Security (HSTS), Page Shield, and </span><a href="https://help.salesforce.com/s/articleView?id=cc.b2c_enable_under_attack_mode.htm&amp;type=5"><span style="font-weight: 400">Under Attack Mode</span></a><span style="font-weight: 400">. The Crypto tab is read-only.</span></p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207270" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261029104928/image3_19333d-e1793296218636.png" class="postimages" width="1000" height="649" alt="Salesforce Business Manager displaying SCAPI zone analytics." /><br />
			  </span></p>
<h2><span style="font-weight: 400">Restrict Shopper API traffic to Managed Runtime with the SCAPI Identity Header</span></h2>
<p><span style="font-weight: 400">If all your Shopper API traffic comes from MRT (Composable Storefront or Storefront Next), you can close the direct-to-API path that bots use to get around your CDN and MRT.</span></p>
<p>First, turn on the <b>Managed Runtime SCAPI Request Identification</b> feature in each instance you want to protect. Managed Runtime then attaches the SIH, a shared secret that Salesforce generates and rotates for you, to every request it forwards to SCAPI. A locked, Salesforce-managed allow rule appears in the SCAPI zone. It&#8217;s scoped to the instance by an org-specific path segment, for example <code>f_ecom_abcd_prd</code> for Production.</p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207271" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261029105052/image1_f0a32c-e1793296273712.png" class="postimages" width="1000" height="564" alt="Screenshot of the Salesforce-managed allow rule for the SCAPI Identity Header in eCDN." /><br />
			  </span></p>
<p><span style="font-weight: 400">Next, add a custom firewall rule on the SCAPI zone&#8217;s Security Rules tab that blocks everything else for the same path segment:</span></p>
<pre language="text"># Block direct Shopper API traffic for the Production instance
Expression: URI Path contains "f_ecom_abcd_prd" and URI Path contains "/shopper"
Action:     Log first to verify matches, then Block
</pre>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207272" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261029105333/image2_00ffbb-e1793296430482.png" class="postimages" width="1000" height="569" alt="Screenshot of the SCAPI block rule." /><br />
			  </span></p>
<p><span style="font-weight: 400">Start with the Log action and confirm what traffic matches. Then switch to Block and drag the rule directly after that instance&#8217;s managed allow rule. Rules run in order, so the allow rule passes legitimate Managed Runtime traffic before your block rule runs.</span></p>
<h2><span style="font-weight: 400">Allow your other API clients</span></h2>
<p><span style="font-weight: 400">A custom Backend for Frontend (BFF), mobile app backend, or partner integration may call the Shopper API directly by design. It can&#8217;t present the Managed Runtime SIH, so the block rule will catch it too. Give each integration its own allow rule, placed above the block rule:</span></p>
<pre language="text"># Allow a custom BFF integration for Production
Expression: URI Path contains "f_ecom_abcd_prd" and URI Path contains "/shopper"
            and HTTP Request Header "X-Integration-Secret" equals "&lt;your-shared-secret&gt;"
Action:     Skip (remaining custom rules)
</pre>
<p>Generate a long random value for <code>&lt;your-shared-secret&gt;</code>, store it server-side, rotate it regularly, and treat it like any other API credential. Never send it from a browser or mobile client, because anyone can read it there. Use a separate secret and allow rules for each integration.</p>
<h2><span style="font-weight: 400">Harden SLAS</span></h2>
<p><span style="font-weight: 400">Every Shopper API call needs a shopper access token, and SLAS (Shopper Login and API Access Service) issues those tokens. A bot that can&#8217;t get a token can&#8217;t call Shopper API, so attackers go after SLAS itself, for example by running credential stuffing against its </span><span style="font-weight: 400">/login</span><span style="font-weight: 400"> endpoint. Harden SLAS along with the SCAPI zone.</span></p>
<p><b>Turn on Strict Client Auth for private clients.</b> SLAS then requires every <code>/login</code> and <code>/authorize</code> request to carry this header:</p>
<pre language="text">x-slas-client-auth: Base64(client_id:client_secret)
</pre>
<p>A missing or wrong secret gets a 401, so a leaked or guessed <code>client_id</code> alone can no longer drive your login endpoints. Keep private-client secrets server-side.</p>
<p><span style="font-weight: 400">Compatibility: Composable Storefront (PWA Kit) and Storefront Next don&#8217;t send this header yet. If you turn on Strict Client Auth for a private client either storefront uses, /login and /authorize return 401 and shoppers can&#8217;t log in. Until support ships, enable it only for private clients called from your own server-side code, such as a custom BFF. Check </span><a href="https://developer.salesforce.com/docs/commerce/commerce-api/guide/slas-best-practices.html#strict-client-auth"><span style="font-weight: 400">SLAS Best Practices</span></a><span style="font-weight: 400"> for current status.</span></p>
<p><b>Let the refresh-token throttle work for you.</b> SLAS allows 3 refresh-token exchanges per 60 seconds and returns a 429 with a <code>Retry-After</code> header beyond that. Reused, revoked, or unknown tokens count toward the same limit.</p>
<p><b>Forward </b><b><code>c_*</code></b><b> headers into your bot logic.</b> SLAS passes custom headers prefixed with <code>c_</code> from the <code>/login</code> request through to B2C Commerce. Use them to give a third-party bot manager or your own origin checks per-request context.</p>
<p><span style="font-weight: 400">For client types, guest-token rotation, and token validation, see </span><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><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400">Identify AI crawlers and shopping agents</span></h2>
<p><span style="font-weight: 400">Blocking unwanted API access is one half of the problem. The other half is traffic you may want, just not at any volume: AI crawlers, AI search, and shopping agents acting for shoppers. eCDN gives you two fields to identify it, with no extra entitlement, on every eCDN zone, both storefront and SCAPI:</span></p>
<ul>
<li><b>Known Bot</b> (<code>cf.client.bot</code>): true when the request comes from a bot Cloudflare has verified, such as a search engine crawler or AI agent.</li>
<li><b>Verified Bot Category</b> (<code>cf.verified_bot_category</code>): sorts verified bots into categories, so you can tell a model-training crawler from a shopping agent or a search indexer.</li>
</ul>
<p><span style="font-weight: 400">Use them alongside signals you already see in logs and analytics, such as a rising share of &#8220;direct&#8221; traffic with no referrer or UTM landing straight on product detail pages, or near-identical queries repeating at machine speed.</span></p>
<p><span style="font-weight: 400">To act on them, add a condition to a custom firewall rule, for example:</span></p>
<pre language="text">cf.verified_bot_category in {"AI Crawler" "AI Search"}
</pre>
<p><span style="font-weight: 400">Then choose an action such as Log, Managed Challenge, Block, or Skip.</span></p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207273" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261029105424/image4_c3d641-e1793296481822.png" class="postimages" width="1000" height="497" alt="Screenshot of the Verified Bot Category rule." /><br />
			  </span></p>
<p><span style="font-weight: 400">The Verified Bot Category field covers 17 categories. Here are the most relevant ones:</span></p>
<table>
<thead>
<tr>
<th><b>Category</b></th>
<th><b>What it is</b></th>
<th><b>Example bots</b></th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="font-weight: 400">AI Assistant</span></td>
<td><span style="font-weight: 400">AI bot acting on a user&#8217;s request</span></td>
<td><span style="font-weight: 400">Perplexity-User, DuckAssistBot</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">AI Crawler</span></td>
<td><span style="font-weight: 400">Crawls content to train AI models</span></td>
<td><span style="font-weight: 400">GPTBot, ClaudeBot</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">AI Search</span></td>
<td><span style="font-weight: 400">Powers AI-driven search experiences</span></td>
<td><span style="font-weight: 400">OAI-SearchBot</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Search Engine Crawler</span></td>
<td><span style="font-weight: 400">Indexes pages for search results</span></td>
<td><span style="font-weight: 400">Googlebot, Bingbot</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Search Engine Optimization</span></td>
<td><span style="font-weight: 400">Analyzes sites to improve ranking</span></td>
<td><span style="font-weight: 400">Google Lighthouse, GTmetrix</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">Treating categories differently is the point. For example, you can let Search Engine Crawler traffic index your catalog while rate limiting AI Crawler and AI Search traffic on your most expensive endpoints.</span></p>
<h2><span style="font-weight: 400">Act on AI traffic: rate limit, then steer</span></h2>
<p><span style="font-weight: 400">Some AI training controls exist only in robots.txt. For example, Google-Extended controls whether Google uses your content for Gemini, but Google crawls with Googlebot, so an edge rule can&#8217;t single it out. Once you can identify AI traffic, rate limit it on your most expensive endpoints instead of blocking it outright. Search and recommendation calls are the usual cost drivers. Here&#8217;s an example rate-limiting rule that works on a storefront zone or the SCAPI zone:</span></p>
<pre language="text"># Rate limit AI crawlers and AI search on product search
Expression: cf.verified_bot_category in {"AI Crawler" "AI Search"}
            and URI Path contains "/product-search"
Counting:   per IP
Threshold:  30 requests per 60 seconds   (example; tune from your logs)
Action:     Log first, then Block for 10 minutes
</pre>
<p>On the SCAPI zone, which Development, Staging, and Production share, add <code>and URI Path contains "f_ecom_abcd_prd" (your org+instance-specific path segment) </code>to scope the rule to one instance. Storefront zones belong to a single instance, so they don&#8217;t need this condition.</p>
<p><span style="font-weight: 400">For Storefront Reference Architecture (SFRA) storefronts, scope the path to your search controller instead, for example Search-Show. Run the rule in Log mode and check the matches in Security Analytics before you enforce it. Set thresholds well above real shopper behavior; a shopper applying eight refinements, for example, shouldn&#8217;t trip a limit meant for agents.</span></p>
<p><span style="font-weight: 400">Match your edge rules to what you publish: use robots directives and crawler settings to tell crawlers what they may access, and let eCDN rules enforce it. See </span><a href="https://help.salesforce.com/s/articleView?id=cc.b2c_seo_overview.htm&amp;type=5"><span style="font-weight: 400">SEO &amp; Discoverability for B2C Commerce</span></a><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">Known Bot and Verified Bot Category only identify bots that Cloudflare has verified. To catch unverified or spoofed automation, you need behavioral detection such as bot scores and fingerprinting. That&#8217;s available from a third-party bot mitigation partner, a stacked CDN, or your own Cloudflare zone with Bot Management in front of eCDN. 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 the options.</span></p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">Bots that skip the storefront and AI agents that don&#8217;t need to are two sides of the same shift. With the October 2026 release you can see your SCAPI traffic, accept the Shopper API traffic only from Managed Runtime and the integrations you trust, harden SLAS, and decide how much AI traffic each endpoint should take. Start every new rule in Log mode, confirm what it catches, then enforce. For edge protection of your login forms, see our companion post, </span><a href="https://developer.salesforce.com/blogs/2026/10/stop-credential-stuffing-with-b2c-commerce-ecdn"><span style="font-weight: 400">Stop Credential Stuffing at the Edge with B2C Commerce eCDN</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://help.salesforce.com/s/articleView?id=cc.b2c_scapi_zone.htm&amp;type=5"><span style="font-weight: 400">SCAPI Zone overview</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/scapi-request-identification.html"><span style="font-weight: 400">Restrict SCAPI Access to Managed Runtime Traffic (SCAPI Identity Header)</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/security.html"><span style="font-weight: 400">SCAPI Security</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_b2c_ecdn_verified_bots.htm&amp;release=262&amp;type=5"><span style="font-weight: 400">Allow Traffic from Search Engine and Other Verified Bots with eCDN Rules</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=cc.b2c_create_eCDN_firewall_rule.htm&amp;language=en_US&amp;type=5"><span style="font-weight: 400">Create a Custom Firewall Rule for an eCDN Zone</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=cc.b2c_eCDN_create_rate_limiting_rule.htm&amp;language=en_US&amp;type=5"><span style="font-weight: 400">Create a Rate Limiting Rule for an eCDN Zone</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=cc.b2c_seo_overview.htm&amp;type=5"><span style="font-weight: 400">SEO &amp; Discoverability for B2C Commerce Overview</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=000391633&amp;type=1"><span style="font-weight: 400">Knowledge Article: Mitigating Scrapers and AI Crawlers</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=005460913&amp;type=1"><span style="font-weight: 400">Knowledge Article: AI Crawler and Agent Traffic</span></a></li>
<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>
</ul>
<h2><span style="font-weight: 400">About the Author</span></h2>
<p><b>Raghu Venkatraman</b><span style="font-weight: 400"> is a Director of Product Management at Salesforce, where he drives product and bot mitigation strategy for B2C Commerce. With extensive experience in enterprise software and a passion for building user-centric solutions, Raghu focuses on delivering products that help organizations work more efficiently and effectively. Follow him on </span><a href="https://www.linkedin.com/in/raghu-venkatraman-b6bb02/"><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/11/protect-your-salesforce-commerce-api-scapi-from-bots-and-ai-agents-with-ecdn">Protect Your Salesforce Commerce API (SCAPI) from Bots and AI Agents with 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/11/protect-your-salesforce-commerce-api-scapi-from-bots-and-ai-agents-with-ecdn/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207267</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261029112113/Generic-D-8-e1793298086874.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261029112113/Generic-D-8-e1793298086874.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Build Trusted Conversational Analytics with AIforce, Data 360, and Tableau Semantics</title>
		<link>https://developer.salesforce.com/blogs/2026/10/build-trusted-conversational-analytics-with-aiforce-data-360-and-tableau-semantics</link>
		<comments>https://developer.salesforce.com/blogs/2026/10/build-trusted-conversational-analytics-with-aiforce-data-360-and-tableau-semantics#respond</comments>
		<pubDate>Thu, 29 Oct 2026 15:15:49 +0000</pubDate>
		<dc:creator><![CDATA[Ravindra Varshney]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Data 360]]></category>
		<category><![CDATA[AIforce]]></category>
		<category><![CDATA[tableau]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207254</guid>
		<description><![CDATA[<p>Learn how to create shared business definitions that help AI agents deliver consistent, context-aware insights across conversational experiences.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/build-trusted-conversational-analytics-with-aiforce-data-360-and-tableau-semantics">Build Trusted Conversational Analytics with AIforce, Data 360, and Tableau Semantics</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">AI agents need shared business context to deliver trusted insights. Without consistent definitions for metrics such as “revenue” and “pipeline,” business logic remains scattered across queries, reports, and applications, creating repeated work and potentially conflicting answers. A shared semantic layer gives people and AI agents a common foundation for interpreting data and calculating results.</span></p>
<p><span style="font-weight: 400">Salesforce </span><a href="https://developer.salesforce.com/developer-centers/data-cloud"><span style="font-weight: 400">Data 360</span></a><span style="font-weight: 400"> provides the foundation for bringing data, semantics, and AI-powered analytics together on the Salesforce Platform. </span><a href="https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_concepts_data_cloud_objects.htm"><span style="font-weight: 400">Data lake objects</span></a><span style="font-weight: 400"> (DLOs) hold ingested data, and data model objects (DMOs) organize it into a consistent structure. A</span><a href="https://developer.salesforce.com/docs/data/semantic-layer/guide/tableau-semantics-concepts.html"> <span style="font-weight: 400">Tableau semantic data model</span></a><span style="font-weight: 400"> (SDM) enriches that data with reusable business definitions, relationships, and calculations. Agentforce Agent uses this shared foundation to answer analytical questions, while Model Context Protocol (MCP) extends access to connected AI clients. Together, these capabilities keep data and business logic on one platform while supporting consistent answers across conversational experiences.</span></p>
<p><span style="font-weight: 400">By bringing Data 360, Tableau Semantics, and AIforce together, you can:</span></p>
<ul>
<li style="font-weight: 400"><b>Keep metrics consistent:</b><span style="font-weight: 400"> Reuse the same calculations across reports, applications, and agent responses.</span></li>
<li style="font-weight: 400"><b>Ask questions in business terms:</b><span style="font-weight: 400"> Explore data using familiar language without needing to know underlying object and field names.</span></li>
<li style="font-weight: 400"><b>Ground AI answers in business context:</b><span style="font-weight: 400"> Give agents shared definitions to help interpret questions and return relevant results.</span></li>
<li style="font-weight: 400"><b>Reduce development effort:</b><span style="font-weight: 400"> Reuse established relationships and calculations instead of rebuilding logic for each experience.</span></li>
<li style="font-weight: 400"><b>Maintain definitions in one place:</b><span style="font-weight: 400"> Update shared business logic as requirements evolve.</span></li>
<li style="font-weight: 400"><b>Keep data, semantics, and analytics on one platform:</b><span style="font-weight: 400"> Bring together your Data 360 data, Tableau semantic model, and Agentforce analytics experience without adding a separate analytics platform.</span></li>
<li style="font-weight: 400"><b>Apply governance across your analytics foundation:</b><span style="font-weight: 400"> Use </span><a href="https://help.salesforce.com/s/articleView?id=analytics.tua_governance.htm&amp;language=en_US&amp;type=5"><span style="font-weight: 400">Data 360 classifications, governance tags, and policies </span></a><span style="font-weight: 400">to help control access to sensitive data and support governed analytical experiences.</span></li>
</ul>
<p><span style="font-weight: 400">This post explores how AIforce, Data 360, and Tableau Semantics work together to ground conversational analytics in shared business definitions and deliver more consistent, trusted answers.</span></p>
<h2><span style="font-weight: 400">How Data 360 and Tableau Semantics power conversational analytics</span></h2>
<p><span style="font-weight: 400">Delivering trusted conversational analytics requires data ingestion, shared business definitions, and end-user experiences to work together.</span></p>
<p><span style="font-weight: 400">The workflow begins in Data 360. </span><a href="https://trailhead.salesforce.com/content/learn/projects/explore-data-cloud-core-functionality/create-data-streams-1"><span style="font-weight: 400">Data streams</span></a><span style="font-weight: 400"> map Salesforce CRM objects and fields to corresponding data lake objects (DLOs) and fields, then ingest CRM records into those DLOs.</span></p>
<p><span style="font-weight: 400">Next, you define the business context in an</span><a href="https://help.salesforce.com/s/articleView?id=analytics.c360_a_sl_osi_support_tab_semantics.htm&amp;language=en_US&amp;type=5"> <span style="font-weight: 400">Open Semantic Interchange (OSI) specification</span></a><span style="font-weight: 400"> that references the ingested DLOs and their fields. The</span><a href="https://github.com/apache/ossie/tree/main/converters/salesforce"> <span style="font-weight: 400">Salesforce converter</span></a><span style="font-weight: 400"> translates supported definitions into an initial Tableau semantic data model (SDM) definition. You then complete any missing relationships, metrics, and calculations before creating and validating the SDM in Tableau Semantics.</span></p>
<p><span style="font-weight: 400">Applications query the SDM through the</span><a href="https://developer.salesforce.com/docs/data/semantic-layer/guide/query-api-in-depth-overview.html"> <span style="font-weight: 400">Semantic Query API</span></a><span style="font-weight: 400">. An MCP server exposes this capability to connected AI clients, while Agentforce agents can also use the same SDM to answer business questions. These experiences draw on shared business definitions and calculations, helping users get consistent answers across applications and conversational interfaces.</span></p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207256" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261028225842/Salesforce-CRM-data-flows-into-Data-360-DLOs-and-the-Tableau-Next-SDM-supporting-access-through-Tableau-Agent-and-MCP-enabled-applications.png" class="postimages" width="828" height="382" alt="Salesforce CRM data flows into Data 360 DLOs and the Tableau Next SDM, supporting access through Tableau Agent and MCP-enabled applications" /><br />
			  </span></p>
<h2><span style="font-weight: 400">Defining business meaning with OSI YAML</span></h2>
<p><span style="font-weight: 400">To build the semantic model, you first create an OSI YAML specification that captures the business meaning of the data. Its object and field references match the data available in Data 360, and it brings together four elements:</span></p>
<table>
<thead>
<tr>
<th><span style="font-weight: 400">Element</span></th>
<th><span style="font-weight: 400">What it captures</span></th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="font-weight: 400">Datasets</span></td>
<td><span style="font-weight: 400">The sources and what each record represents.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Fields</span></td>
<td><span style="font-weight: 400">Business attributes and their meaning.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Metrics</span></td>
<td><span style="font-weight: 400">Measures and how they are calculated.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Business context</span></td>
<td><span style="font-weight: 400">Reporting scope and interpretation rules.</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">For example, “pipeline value” could mean all open deals, deals expected to close this quarter, or their probability-weighted value. The business chooses the meaning that fits its reporting needs, and you capture it in the YAML specification so connected experiences use the same definition.</span></p>
<p><span style="font-weight: 400">You then use this YAML file as the input to the Salesforce conversion workflow. After importing the supported definitions into Tableau Semantics, you complete and validate any calculations and metrics that aren’t carried through conversion.</span></p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207258" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261028225938/A-screenshot-illustrating-an-OSI-metric-definition-and-its-business-description-e1793253592464.png" class="postimages" width="1000" height="448" alt="A screenshot illustrating an OSI metric definition and its business description" /><br />
			  </span></p>
<h2><span style="font-weight: 400">How to create and validate a semantic data model</span></h2>
<p><span style="font-weight: 400">With the OSI YAML specification in place, you can turn the business definitions into a semantic data model that applications and agents can query. The workflow below takes you from preparing the data in Data 360 to converting the specification, creating and validating the model, and checking its results.</span></p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207259" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261028230011/A-diagram-illustrating-the-OSI-conversion-workflow-for-creating-a-Tableau-Next-SDM.png" class="postimages" width="828" height="587" alt="A diagram illustrating the OSI conversion workflow for creating a Tableau Next SDM" /><br />
			  </span></p>
<p><span style="font-weight: 400">The following steps use Salesforce APIs to create, validate, and query the semantic model. These API calls can also be incorporated into scripts or deployment workflows to automate the process.</span></p>
<h3><span style="font-weight: 400">1. Prepare the data in Data 360</span></h3>
<p><span style="font-weight: 400">Connect</span><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_salesforce_crm_connector.htm&amp;type=5"> <span style="font-weight: 400">Salesforce CRM</span></a><span style="font-weight: 400"> to Data 360 and ingest the required records through data streams into DLOs. Before converting the specification, confirm that the YAML’s object and field references match the corresponding Data 360 object API names, field names, and object types (DLO or DMO). Add the required Data 360 metadata to the YAML—including the data space API name, source object API name, object type, and field data types—before running the converter, as described in the</span><a href="https://help.salesforce.com/s/articleView?id=analytics.c360_a_sl_osi_workflow.htm&amp;language=en_US&amp;type=5"> <span style="font-weight: 400">OSI workflow</span></a><span style="font-weight: 400">.</span></p>
<h3><span style="font-weight: 400">2. Convert the OSI YAML to SDM JSON</span></h3>
<p><span style="font-weight: 400">Use the</span><a href="https://github.com/apache/ossie/tree/main/converters/salesforce#usage"> <span style="font-weight: 400">Salesforce Ossie converter</span></a><span style="font-weight: 400"> to translate supported YAML definitions into a Tableau Next SDM definition in JSON. After setting up the converter, run:</span></p>
<pre language="java">java -jar &lt;converter.jar&gt; toSF &lt;specification.yaml&gt;
</pre>
<p>The command generates a JSON file named after the model’s API name, such as <code>sales_pipeline_health_and_deal_risk.json</code>. This file provides the starting definition for creating the SDM in Data 360.</p>
<h3><span style="font-weight: 400">3. Complete and create the semantic model</span></h3>
<p><span style="font-weight: 400">Review the generated JSON to confirm that it references the correct Data 360 objects, and complete any missing calculations, metrics, and descriptions. Then submit the completed definition to create the semantic model:</span></p>
<pre language="shell">sf api request rest /services/data/v65.0/ssot/semantic/models \
  --target-org '&lt;alias&gt;' --method POST \
  --body @sales_pipeline_health_and_deal_risk.json
</pre>
<h3><span style="font-weight: 400">4. Validate the semantic model</span></h3>
<p><span style="font-weight: 400">Validate the saved model to check its definitions and references:</span></p>
<pre language="shell">sf api request rest \
  /services/data/v65.0/ssot/semantic/models/sales_pipeline_health_and_deal_risk/validate \
  --target-org '&lt;alias&gt;'
</pre>
<p>Review any validation errors and correct the model as needed. Confirm that validation returns <code>isValid: true</code> before checking the model’s query results.</p>
<h3><span style="font-weight: 400">5. Query the model and verify results</span></h3>
<p><span style="font-weight: 400">Create a query file that references your saved semantic model and selects the measures you want to retrieve, as shown below:</span></p>
<pre language="json">{
  "semanticModelApiName": "sales_pipeline_health_and_deal_risk",
  "structuredSemanticQuery": {
    "fields": [{
      "expression": {
        "semanticField": { "name": "open_pipeline_amount" }
      },
      "alias": "open_pipeline_amount"
    }]
  }
}
</pre>
<pre language="shell">sf api request rest /services/data/v65.0/semantic-engine/gateway \
  --target-org '&lt;alias&gt;' --method POST \
  --body @&lt;query-file.json&gt;
</pre>
<p><span style="font-weight: 400">Check that the response includes the requested measures and that the results match the underlying data for the same filters and business definitions.</span></p>
<h2><span style="font-weight: 400">How business preferences guide the answer</span></h2>
<p><span style="font-weight: 400">Along with relationships and calculations, the deployed SDM includes </span><a href="https://help.salesforce.com/s/articleView?id=analytics.c360_a_sl_biz_preferences_set.htm&amp;type=5"><span style="font-weight: 400">business preferences</span></a><span style="font-weight: 400"> that guide how agents interpret and explain results. These preferences can guide agents to:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Use the organization’s business terminology.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">State the reporting period and scope.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Highlight missing data and explain relevant context.</span></li>
</ul>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207260" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261028230045/A-screenshot-illustrating-how-business-preferences-guide-the-agents-interpretation-of-questions-and-explanation-of-answers-e1793253657538.png" class="postimages" width="1000" height="486" alt="A screenshot illustrating how business preferences guide the agent’s interpretation of questions and explanation of answers" /><br />
			  </span></p>
<h2><span style="font-weight: 400">How verified questions guide answers</span></h2>
<p><span style="font-weight: 400">After deploying the SDM, you manually create verified questions through</span><a href="https://help.salesforce.com/s/articleView?id=analytics.tua_data_sdm_qa_calibration.htm&amp;language=en_US&amp;type=5"> <span style="font-weight: 400">Q&amp;A Calibration</span></a><span style="font-weight: 400">. You enter representative business questions, review the generated answers, and verify that they use the intended metrics, filters, and business definitions. These verified examples help the agent answer similar questions more consistently.</span></p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207261" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261028230122/A-screenshot-illustrating-adding-a-pipeline-health-question-to-QA-Calibration-for-review-e1793253693792.png" class="postimages" width="1000" height="383" alt="A screenshot illustrating adding a pipeline-health question to Q&amp;A Calibration for review" /><br />
			  </span></p>
<h2><span style="font-weight: 400">Conversational analytics with Agentforce and Claude (via MCP)</span></h2>
<p><span style="font-weight: 400">The following examples show an Agentforce agent and Claude (connected through MCP) answering the same business question:</span></p>
<p><b>“How much open pipeline do we have for Q4 2026, and how many opportunities does it include?”</b></p>
<p><span style="font-weight: 400">The following experiences use the</span><a href="https://developer.salesforce.com/docs/data/semantic-layer/guide/query-api-in-depth-overview.html"> <span style="font-weight: 400">Semantic Query API</span></a><span style="font-weight: 400"> to query the Tableau Next SDM for the quarter’s open pipeline amount and opportunity count.</span></p>
<p><b>In Agentforce,</b><span style="font-weight: 400"> the agent uses the semantic model to retrieve the requested metrics and explain the results.</span></p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207262" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261028230153/A-screenshot-illustrating-the-SDMs-four-data-objects-and-a-Tableau-Agent-response-e1793253729359.png" class="postimages" width="1000" height="504" alt="A screenshot illustrating the SDM’s four data objects and a Tableau Agent response" /><br />
			  </span></p>
<p><b>In Claude Code,</b><span style="font-weight: 400"> the client accesses the same semantic model through </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/tableau-next.html"><span style="font-weight: 400">Tableau Next MCP</span></a><span style="font-weight: 400">, using the same business definitions to answer the question.</span></p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207263" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261028230225/Claude-Code-using-Tableau-Next-MCP-to-retrieve-and-explain-pipeline-metrics-from-the-semantic-model.png" class="postimages" width="909" height="538" alt="Claude Code using Tableau Next MCP to retrieve and explain pipeline metrics from the semantic model" /><br />
			  </span></p>
<p><b>In Claude Desktop,</b><span style="font-weight: 400"> the client accesses the same semantic model through </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/tableau-next.html"><span style="font-weight: 400">Tableau Next MCP</span></a><span style="font-weight: 400">, using the same business definitions to answer the question.</span></p>
<p>			  <span class="postimagessection_specify alignnone size-full wp-image-207264" ><br />
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20261028230300/Claude-Desktop-presenting-pipeline-metrics-in-a-conversational-interface-using-Tableau-Next-MCP-e1793253796627.png" class="postimages" width="1000" height="778" alt="Claude Desktop presenting pipeline metrics in a conversational interface using Tableau Next MCP" /><br />
			  </span></p>
<h1><b>Conclusion</b></h1>
<p><span style="font-weight: 400">With your data in Data 360, Tableau Semantics and Agentforce bring data, business definitions, and conversational analytics together on the same platform. You can build on this foundation without adding a separate analytics platform, while MCP extends access to the same semantic model through clients such as Claude Code.</span></p>
<p><span style="font-weight: 400">OSI helps capture reusable semantic definitions, and the SDM puts those definitions to work through shared relationships and calculations. Model validation, business preferences, and verified questions help align analytical results and explanations with business expectations. Data 360 classifications, governance tags, and policies complement this foundation by helping control access to sensitive data.</span></p>
<p><span style="font-weight: 400">Together, these capabilities let you reuse business logic across experiences, reduce duplicated development effort, and maintain definitions as requirements evolve. Start with a business question, establish what its terms mean, and validate the results. Then, extend that shared foundation to more questions and agents.</span></p>
<h1><b>Resources</b></h1>
<ul>
<li style="font-weight: 400"><a href="https://github.com/apache/ossie"><span style="font-weight: 400">OSI / Apache Ossie project</span></a><span style="font-weight: 400"> — Open semantic metadata specification and conversion tooling.</span></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/data/semantic-layer/guide/tableau-semantics-concepts.html"><span style="font-weight: 400">Tableau Semantics concepts</span></a><span style="font-weight: 400"> — Models, dimensions, measures, and relationships.</span></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/data/semantic-layer/guide/query-api-in-depth-overview.html"><span style="font-weight: 400">Semantic Query API overview</span></a><span style="font-weight: 400"> — Querying a model through named fields and query context.</span></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/data/semantic-layer/guide/query-api-in-depth-aggregation.html"><span style="font-weight: 400">Aggregation and totals</span></a><span style="font-weight: 400"> — Aggregation behavior and relationship cardinality.</span></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/data/semantic-layer/guide/connect-api-in-depth-entities.html"><span style="font-weight: 400">Semantic authoring API entities</span></a><span style="font-weight: 400"> — Metadata used to define Tableau Next SDMs.</span></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=analytics.c360_a_sl_biz_preferences_bestp.htm&amp;language=en_US&amp;type=5"><span style="font-weight: 400">Business preferences best practices</span></a><span style="font-weight: 400"> — Business context and guidance for analytical answers.</span></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=analytics.tua_ai_optimize_model.htm&amp;language=en_US&amp;type=5"><span style="font-weight: 400">Optimize and test a semantic model</span></a><span style="font-weight: 400"> — Testing, calibration, and verified questions.</span></li>
<li style="font-weight: 400"><a href="https://github.com/apache/ossie/tree/main/converters/salesforce#usage"><span style="font-weight: 400">Salesforce OSI converter</span></a><span style="font-weight: 400"> — Setup and commands for converting OSI YAML into SDM JSON.</span></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=analytics.c360_a_sl_osi_workflow.htm&amp;language=en_US&amp;type=5"><span style="font-weight: 400">OSI workflow</span></a><span style="font-weight: 400"> — Conversion requirements and steps for importing semantic definitions.</span></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/tableau-next.html"><span style="font-weight: 400">Tableau Next MCP reference</span></a><span style="font-weight: 400"> — Connecting AI clients to Tableau Next analytics capabilities.</span></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_before_you_begin_data_gov.htm&amp;language=en_US&amp;type=5"><span style="font-weight: 400">Data 360 governance considerations</span></a><span style="font-weight: 400"> — Support and considerations for governance tags and policies.</span></li>
</ul>
<h1><b>About the authors</b></h1>
<p><b>Ravindra Varshney</b><span style="font-weight: 400"> is a Senior Director, Software Engineering at Salesforce. You can find him on </span><a href="https://www.linkedin.com/in/ravindra-varshney"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p><b>Gaurav Tewari</b><span style="font-weight: 400"> is a Senior Product Manager at Salesforce. You can find him on </span><a href="https://www.linkedin.com/in/tewarigaurav/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p><b>Chandan Agarwal</b><span style="font-weight: 400"> is a Principal Member of Technical Staff at Salesforce. You can find him on </span><a href="https://www.linkedin.com/in/chandan-agarwal-1080a220/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">Special shoutout for their contribution to this post: </span><b>Sonu Sharma</b><span style="font-weight: 400"> is a Senior Director, Software Engineering at Salesforce. You can find him on </span><a href="https://www.linkedin.com/in/sonuksharma/"><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/build-trusted-conversational-analytics-with-aiforce-data-360-and-tableau-semantics">Build Trusted Conversational Analytics with AIforce, Data 360, and Tableau Semantics</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/build-trusted-conversational-analytics-with-aiforce-data-360-and-tableau-semantics/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207254</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261028231000/Generic-B-7-1-e1793254214616.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261028231000/Generic-B-7-1-e1793254214616.png?w=1000" medium="image" />
	</item>
		<item>
		<title>The TDX 2027 Call for Participation Is Now Open</title>
		<link>https://developer.salesforce.com/blogs/2026/10/the-tdx-2027-call-for-participation-is-now-open</link>
		<comments>https://developer.salesforce.com/blogs/2026/10/the-tdx-2027-call-for-participation-is-now-open#respond</comments>
		<pubDate>Wed, 28 Oct 2026 16:00:20 +0000</pubDate>
		<dc:creator><![CDATA[Akshata Sawant]]></dc:creator>
				<category><![CDATA[Announcements]]></category>
		<category><![CDATA[Salesforce Events]]></category>
		<category><![CDATA[Call for participation]]></category>
		<category><![CDATA[cfp]]></category>
		<category><![CDATA[TDX]]></category>
		<category><![CDATA[TDX 2027]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207230</guid>
		<description><![CDATA[<p>The TDX 2027 CFP is open. Get developer-focused tips on titles, abstracts, session formats, what to avoid, and the submission deadline.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/the-tdx-2027-call-for-participation-is-now-open">The TDX 2027 Call for Participation Is Now Open</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 TDX 2027 </span><a href="https://reg.salesforce.com/flow/plus/tdx27/cfp"><span style="font-weight: 400">Call for Participation</span></a><span style="font-weight: 400"> (CFP) is officially open.</span></p>
<p><span style="font-weight: 400">TDX is Salesforce’s developer conference where thousands of builders come together to explore the latest innovations in enterprise AI and development.Presenting is a powerful way to share your technical expertise, build your personal brand, and position yourself as a leader in the Salesforce ecosystem. Help us co-create the TDX session experience by submitting your proposals for compelling, high-quality content that will inspire and teach others by showcasing your expertise through deep technical dives, real-world implementations, and innovative solutions.</span></p>
<p><span style="font-weight: 400">Whether you are an experienced speaker or new to the stage, we invite you to share your unique perspective and contribute to the community’s collective knowledge. In this post, we’ll share tips on putting together a standout CFP for TDX 2027 to increase your chances of being selected. </span><b>The submission deadline is November 16, 2026.</b></p>
<h2><span style="font-weight: 400">Session topics that resonate with the developer community</span></h2>
<p><span style="font-weight: 400">We’ve reviewed the highest-performing sessions from TDX 2026 to understand what resonated most with our developer community and the biggest takeaway is: </span><b>developers want authentic, hands-on, code-first, AI-native content.</b></p>
<p><span style="font-weight: 400">The most successful sessions went beyond high-level concepts and dove deep into the technical details. They showcased real-world implementations, explored architectural trade-offs, and shared practical insights from debugging and solving complex challenges.</span></p>
<p><span style="font-weight: 400">When submitting a developer session, show us the code (Apex, LWC, Python, Agent Script, etc.), dive into the architecture, and highlight practical, innovative solutions that attendees can learn from and put into practice.</span></p>
<h2><span style="font-weight: 400">Choose your TDX 2027 session format</span></h2>
<p><span style="font-weight: 400">When submitting your proposal, you can choose from the following session formats:</span></p>
<ul>
<li style="font-weight: 400"><b>Theater Sessions (20 minutes):</b><span style="font-weight: 400"> Fast-paced presentations in a high-energy, open theater space. Ideal for quick technical solutions, live demos, career lessons, or thought leadership. There’s no audience Q&amp;A during the session, but speakers will have opportunities to engage with attendee questions in a separate space afterward. Maximum of two presenters</span></li>
<li style="font-weight: 400"><b>Breakout Sessions (40 minutes):</b><span style="font-weight: 400"> Held in dedicated rooms, these longer sessions are ideal for deep technical dives and in-depth explorations of architecture or governance topics. We recommend allocating 35 minutes for the presentation and reserving the final 5 minutes for live audience Q&amp;A. Maximum of three presenters.</span></li>
<li style="font-weight: 400"><b>Community Networking (45 minutes):</b><span style="font-weight: 400"> Community-led networking and peer-learning sessions designed to foster meaningful connections and knowledge sharing. Through guided discussions, Trailblazers exchange ideas, tackle shared challenges, and learn from diverse perspectives across roles and career stages.</span></li>
</ul>
<p><b>Note:</b> <span style="font-weight: 400">You may share your preferred session format and length upon submitting, however, Salesforce reserves the right to incorporate more than one session proposal into a confirmed TDX 2027 session and change the session type/length.</span></p>
<h2><span style="font-weight: 400">How to craft a compelling submission</span></h2>
<p><span style="font-weight: 400">To maximize your chances of being selected for the TDX 2027 Developer Track, your proposal should clearly outline the technical lessons the audience will learn. Follow the below developer-focused best practices:</span></p>
<p>&nbsp;</p>
<ul>
<li style="font-weight: 400"><b>Craft a memorable title.</b><span style="font-weight: 400"> Start with a strong verb and explicitly name the product or technology your session will cover (e.g., Build with Salesforce&#8217;s New Dev Plugin for Claude Code, Scale API Governance with Lessons from Salesforce, Automate Search and Retrieval Deployments via DataKits). Be specific about what attendees will learn. </span>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Tip: Be careful with AI-generated titles and abstracts as they often feel overly generic and inauthentic, especially when combined with excessive em dashes.</span></li>
</ul>
</li>
<li style="font-weight: 400"><b>Write a clear, actionable abstract.</b><span style="font-weight: 400"> Structure your abstract around three key questions: What technical challenge are you addressing? What specific solution will you demonstrate? What practical, actionable takeaways will attendees walk away with?</span></li>
<li style="font-weight: 400"><b>Be agent-native.</b><span style="font-weight: 400"> Show an agent doing real work or agent development best practices. Cover evals, traces, guardrails, costs, and what happened when things went wrong. </span></li>
<li style="font-weight: 400"><b>Bring in the new stuff.</b><span style="font-weight: 400"> Audiences love it when you pair what’s new in the industry with what’s new at Salesforce. For example, show how you build an app in Claude using Salesforce Skills, or how an MCP-connected agent takes action on Salesforce data. </span></li>
<li style="font-weight: 400"><b>Focus on depth over breadth.</b><span style="font-weight: 400"> Don&#8217;t try to cover the entire Salesforce Platform in 40 minutes. Pick a specific topic and go deep (e.g., utilizing parallel subscriptions for Agent actions or optimizing Data 360 queries).</span></li>
<li style="font-weight: 400"><b>Share real-world experience.</b><span style="font-weight: 400"> Features in Pilot and Beta are highly valued, as are innovative ways of using established technologies. </span></li>
<li style="font-weight: 400"><b>Use cross-cloud use cases.</b><span style="font-weight: 400"> Showcase real-world solutions that span multiple Salesforce clouds and products, such as Salesforce Platform and Slack. Examples include: Integrate Slackbot with Agentforce via MCP and Automate Data 360 Pipelines via MuleSoft and Slack.</span></li>
<li style="font-weight: 400"><b>Show something you built.</b><span style="font-weight: 400"> Focus on a solution you’ve personally built, sharing real-world lessons, technical decisions, and honest trade-offs. Practical, experience-driven sessions resonate more than feature tours. For example: Observe Agent Behavior with Traces, Evals, and Alerts.</span></li>
<li style="font-weight: 400"><b>Bring working proof.</b><span style="font-weight: 400"> A repo, a skill, or an MCP tool that attendees can reuse makes your session more useful.</span></li>
</ul>
<h2><span style="font-weight: 400">Best practices for your submission</span></h2>
<p><span style="font-weight: 400">Here are some best practices and guidelines for your TDX CFP proposal:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Write a one-line title (within 60 characters) that clearly states the session outcome.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Begin the abstract (within 120 characters) with a challenge, then state what attendees will learn.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">List three takeaways and name the products, APIs, or tools you will use.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Add a short speaker bio with proof of experience, such as past talks, community work, or code.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Submit more than one idea (maximum of three). Different angles raise your odds of being accepted.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Ask for a peer review before you submit.</span></li>
</ul>
<h3><span style="font-weight: 400">Common submission pitfalls to avoid</span></h3>
<p><span style="font-weight: 400">Based on what we’ve learned from past submissions, here are some things to avoid:</span></p>
<ul>
<li style="font-weight: 400"><b>Generic marketing topics, sales pitches, and roadmap talk.</b><span style="font-weight: 400"> Keep sessions technical and focused on what developers can build today.</span></li>
<li style="font-weight: 400"><b>Company or organization-specific product pitches.</b><span style="font-weight: 400"> Showcase how you’ve used Salesforce technologies to build unique solutions and solve real-world challenges. Focus on the technical approach and practical takeaways, not just your employer&#8217;s story.</span></li>
<li style="font-weight: 400"><b>Broad titles like &#8220;Intro to Agentforce&#8221;.</b><span style="font-weight: 400"> Name the specific outcome, tool, or skill.</span></li>
<li style="font-weight: 400"><b>Abstracts with no demo, code, or practical takeaways.</b><span style="font-weight: 400"> Tell attendees what they will see, learn,  and be able to do afterward.</span></li>
<li style="font-weight: 400"><b>Retired product names.</b><span style="font-weight: 400"> Check the latest naming before you submit.</span></li>
</ul>
<h2><span style="font-weight: 400">Access speaker support and resources</span></h2>
<p><span style="font-weight: 400">Once you submit your proposal, you&#8217;ll receive a confirmation email. All submissions will be reviewed after the CFP closes, and acceptance decisions will be communicated in December 2026.</span></p>
<p><b>If your proposal is accepted</b><span style="font-weight: 400">, you’ll become the content owner for your session and receive instructions for next steps. A Salesforce session owner will also be assigned to help you build your content and prepare for your TDX session(s).</span></p>
<p><b>If your proposal isn&#8217;t accepted</b><span style="font-weight: 400">,</span> <span style="font-weight: 400">your submission will be kept on file for future event consideration. You can also explore opportunities to share your expertise at other Salesforce events, including</span><a href="https://trailhead.salesforce.com/community/conferences"> <span style="font-weight: 400">Trailblazer Community conferences</span></a><span style="font-weight: 400"> and</span><a href="https://trailblazercommunitygroups.com/"> <span style="font-weight: 400">Trailblazer Community group meetings</span></a><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400">Ready to submit your proposal?</span></h2>
<p><span style="font-weight: 400">Before submitting your session proposal, review the </span><a href="https://reg.salesforce.com/flow/plus/tdx27/cfp/page/cfpfaq"><span style="font-weight: 400">CFP FAQ</span></a><span style="font-weight: 400"> and choose a preferred session format, prepare your title, abstract, three concrete takeaways, and a short speaker bio.</span></p>
<p><span style="font-weight: 400">Submit your proposal </span><a href="https://reg.salesforce.com/flow/plus/tdx27/cfp"><span style="font-weight: 400">here</span></a><span style="font-weight: 400"> by </span><b>November 16, 2026</b><span style="font-weight: 400">. We look forward to seeing the creativity and innovation you&#8217;ll bring to TDX 2027.</span></p>
<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><span style="font-weight: 400"><br />
</span><b>Alba Rivas</b><span style="font-weight: 400"> works as a Principal Developer Advocate at Salesforce. You can follow her on <a href="https://www.linkedin.com/in/alba-rivas/">Linkedin</a>.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/10/the-tdx-2027-call-for-participation-is-now-open">The TDX 2027 Call for Participation Is Now Open</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/the-tdx-2027-call-for-participation-is-now-open/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207230</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20261027120213/TDX27_OrganicSocial_CFP_Blog_1920x1080-e1793127747529.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20261027120213/TDX27_OrganicSocial_CFP_Blog_1920x1080-e1793127747529.png?w=1000" medium="image" />
	</item>
	</channel>
</rss>
