<?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, 04 Jun 2026 16:33:40 +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>Simplify Your SOQL Queries Using SOQL FORMULA() in WHERE</title>
		<link>https://developer.salesforce.com/blogs/2026/06/simplify-your-soql-queries-using-soql-formula-in-where</link>
		<comments>https://developer.salesforce.com/blogs/2026/06/simplify-your-soql-queries-using-soql-formula-in-where#respond</comments>
		<pubDate>Thu, 04 Jun 2026 16:35:02 +0000</pubDate>
		<dc:creator><![CDATA[Dikshita Patel]]></dc:creator>
				<category><![CDATA[Apex]]></category>
		<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Salesforce Releases]]></category>
		<category><![CDATA[Data Modeling]]></category>
		<category><![CDATA[Database Performance]]></category>
		<category><![CDATA[ISV App Development]]></category>
		<category><![CDATA[Query Optimization]]></category>
		<category><![CDATA[SOQL]]></category>
		<category><![CDATA[Summer 26]]></category>
		<category><![CDATA[WHERE Clause]]></category>

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

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

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

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

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206373</guid>
		<description><![CDATA[<p>Stop logging into Lightning Experience for daily operations. Learn how to securely configure external tools like Claude Code to interact directly with the Salesforce Platform using standard and custom Hosted MCP Servers.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/connect-claude-with-salesforce-hosted-mcp-servers">Connect Claude with Salesforce Hosted MCP Servers</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">What if you could act on Salesforce records and run business logic without having to log into Lightning Experience? For example, in the context of a hotel management application, you could use an agent with tools that load upcoming reservations, analyze popular events around the hotel’s location and then create personalized marketing campaigns around those events without having to leave the chat interface of the agent. Now, you can do so with </span><a href="https://www.salesforce.com/news/stories/salesforce-headless-360-announcement/"><span style="font-weight: 400">Headless 360</span></a><span style="font-weight: 400"> using agentic tools like Claude Desktop or Claude Code to interact with the Salesforce Platform through the </span><a href="https://modelcontextprotocol.io/docs/getting-started/intro"><span style="font-weight: 400">Model Context Protocol</span></a><span style="font-weight: 400"> (MCP).</span></p>
<p><span style="font-weight: 400">In this post, we&#8217;ll explore the benefits of working with MCP servers, then we’ll focus specifically on how to configure Claude for MCP, and finally, we’ll share some sample use cases.</span></p>
<h2><span style="font-weight: 400">What are Salesforce Hosted MCP Servers?</span></h2>
<p><span style="font-weight: 400">The Model Context Protocol (MCP) is an open standard that lets agentic tools like </span><a href="https://www.postman.com/salesforce-developers/salesforce-developers/collection/689673309a230a0874d2fb86"><span style="font-weight: 400">Postman</span></a><span style="font-weight: 400"> or AI agents like </span><a href="https://claude.com/product/overview"><span style="font-weight: 400">Claude</span></a><span style="font-weight: 400"> interact with external tools and data sources from various vendors. In the context of Salesforce, it means that you can run SOQL queries, modify records, and execute actions directly from a third-party agent. In other words, you don’t have to log into Salesforce and you’re no longer limited to using Agentforce to interact with your data from agents.</span></p>
<p><span style="font-weight: 400">In order to perform operations on Salesforce with agents, you use MCP servers that expose tools and prompts. You can configure and host your own Salesforce MCP Server or use the </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/references/reference/servers-reference.html"><span style="font-weight: 400">Salesforce Hosted MCP Servers</span></a><span style="font-weight: 400"> that are </span><a href="https://developer.salesforce.com/blogs/2026/04/salesforce-hosted-mcp-servers-are-now-generally-available"><span style="font-weight: 400">now generally available</span></a><span style="font-weight: 400">. We&#8217;ll only focus on the latter in this post since this approach saves you from the burden of hosting, securing, and maintaining the server.</span></p>
<h3><span style="font-weight: 400">Standard Salesforce Hosted MCP Servers</span></h3>
<p><span style="font-weight: 400">In addition to the </span><a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_mcp.htm"><span style="font-weight: 400">Salesforce DX MCP Server</span></a><span style="font-weight: 400"> used for development purposes, Salesforce offers a number of </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/references/reference/servers-reference.html"><span style="font-weight: 400">standard hosted MCP servers</span></a><span style="font-weight: 400"> out of the box for daily operations. These allow agents to access our Headless 360 portfolio ranging from the Salesforce Platform, Data 360, Tableau, and MuleSoft. You can also connect directly to the </span><a href="https://docs.slack.dev/ai/slack-mcp-server/connect-to-claude"><span style="font-weight: 400">Slack hosted MCP server</span></a><span style="font-weight: 400">.</span></p>
<h3><span style="font-weight: 400">Custom Salesforce Hosted MCP Servers</span></h3>
<p><span style="font-weight: 400">If the standard hosted MCP servers do not fit your needs, you can also create custom hosted MCP servers. This option gives you granular control over the tools and prompts that the server exposes, and lets you add custom tools and prompts.</span></p>
<p><span style="font-weight: 400">You can build </span><b>custom MCP tools</b><span style="font-weight: 400"> from the following types:</span></p>
<ul>
<li style="font-weight: 400"><b>Apex Action</b><span style="font-weight: 400">: Expose invocable </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/references/reference/invocable-actions.html"><span style="font-weight: 400">Apex Actions</span></a><span style="font-weight: 400"> (</span><span style="font-weight: 400"><code>@InvocableMethod</code> </span><span style="font-weight: 400">annotated Apex methods) as MCP tools</span></li>
<li style="font-weight: 400"><b>Lightning Flow</b><span style="font-weight: 400">s: Expose </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/references/reference/flows.html"><span style="font-weight: 400">autolaunched flows</span></a><span style="font-weight: 400"> as MCP tools</span></li>
<li style="font-weight: 400"><b>Apex REST</b><span style="font-weight: 400">: Expose </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_rest.htm"><span style="font-weight: 400">custom Apex REST endpoints</span></a><span style="font-weight: 400"> as MCP tools.</span></li>
<li style="font-weight: 400"><b>AuraEnabled</b><span style="font-weight: 400">: Expose </span><span style="font-weight: 400"><code>@AuraEnabled</code></span><span style="font-weight: 400"> (see </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_AuraEnabled.htm"><span style="font-weight: 400">docs</span></a><span style="font-weight: 400">) annotated Apex methods as MCP tools</span></li>
<li style="font-weight: 400"><b>Named Query API</b><span style="font-weight: 400">: Expose </span><a href="https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_named_query_intro.htm"><span style="font-weight: 400">Named Query APIs</span></a><span style="font-weight: 400"> as MCP tools to provide access to specific parametrized SOQL queries</span></li>
</ul>
<p><span style="font-weight: 400">You can also create </span><b>custom MCP prompts</b><span style="font-weight: 400"> from </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/references/reference/prompt-builder.html"><span style="font-weight: 400">Flex prompt templates</span></a><span style="font-weight: 400"> built with Prompt Builder. Using MCP prompts in an agent lets you call those parametrized prompt templates as commands. For example, if you expose a “Generate Personalized Schedule” prompt template, you can run the following command in Claude (you don’t have to type the full tool name, autocomplete provides the prompt description).</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206377" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260522154910/Screenshot-of-Claude-Code-autocompleting-a-custom-Salesforce-MCP-prompt-command.png?w=853" class="postimages" width="853" height="94" alt="Screenshot of Claude Code autocompleting a custom Salesforce MCP prompt command." />
			  </span>
			</p>
<p><span style="font-weight: 400">Now that we’ve seen the benefits of working with the Salesforce Hosted MCP Servers, let’s look at how to configure Claude to leverage them.</span></p>
<h2><span style="font-weight: 400">Configure Claude to work with Salesforce Hosted MCP Servers</span></h2>
<p><span style="font-weight: 400">Configuring Claude to connect with the hosted MCP servers is a three-step process:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Activate Salesforce Hosted MCP Servers</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Create an external client app</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Connect Claude to the Salesforce Hosted MCP Servers</span></li>
</ol>
<p><b>Note:</b><span style="font-weight: 400"> Claude is available in different surfaces. In this post, we’ll cover Claude Code (the CLI experience) and Claude Desktop (the desktop app).</span></p>
<p><span style="font-weight: 400">As part of the setup, you&#8217;ll need to set up an </span><a href="https://help.salesforce.com/s/articleView?id=xcloud.external_client_apps.htm&amp;type=5"><span style="font-weight: 400">external client app</span></a><span style="font-weight: 400"> (ECA) so that Claude can authenticate via OAuth 2.0 with Salesforce to use the hosted MCP servers. An ECA tells Salesforce which external applications are allowed to connect to your org and what they&#8217;re allowed to do. Standard Salesforce user access permissions apply in addition to the app-level access checks.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206375" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260522154904/Schema-illustrating-how-the-Claude-MCP-client-authenticates-with-the-external-client-app-and-calls-Salesforce-Hosted-MCP-Servers-e1779490298827.png?w=1000" class="postimages" width="1000" height="485" alt="Schema illustrating how the Claude MCP client authenticates with the external client app and calls Salesforce Hosted MCP Servers" />
			  </span>
			</p>
<h3><span style="font-weight: 400">Step 1: Activate Salesforce Hosted MCP Servers</span></h3>
<p><span style="font-weight: 400">The standard Salesforce Hosted MCP Servers are inactive by default. Follow these steps to activate them:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">In </span><b>Setup</b><span style="font-weight: 400">, search for </span><b>MCP Servers</b><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Click the </span><b>Salesforce Servers</b><span style="font-weight: 400"> tab.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Repeat the following steps for each server that you want to activate:</span>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Click on the server name.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Click </span><b>Activate</b><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400">Copy the server&#8217;s <b style="color: #4a4a4a">API Name</b><span style="font-weight: 400"> without the <code>platform</code></span><span style="font-weight: 400">.</span><span style="font-weight: 400"> suffix and the </span><b style="color: #4a4a4a">Server URL</b><span style="font-weight: 400">. We&#8217;ll need those in the next step.</span></li>
</ol>
</li>
</ol>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206381" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260522154919/Screenshot-showing-a-list-of-Salesforce-Hosted-MCP-Servers-with-some-active-e1779490206908.png?w=1000" class="postimages" width="1000" height="496" alt="Screenshot showing a list of Salesforce Hosted MCP Servers with some active" />
			  </span>
			</p>
<h3><span style="font-weight: 400">Step 2: Create an external client app</span></h3>
<p><span style="font-weight: 400">Follow these steps to create an ECA:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">In </span><b>Setup</b><span style="font-weight: 400">, search for </span><b>External Client App Manager</b><span style="font-weight: 400"> and click </span><b>New External Client App</b><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400">Under <b style="color: #4a4a4a">Basic Information</b><span style="font-weight: 400"><span style="font-weight: 400">, fill in the required fields:<br />
</span></span></p>
<table>
<thead>
<tr>
<th><b>Field name</b></th>
<th><b>Field Value</b></th>
</tr>
</thead>
<tbody>
<tr>
<td><b>App Name</b></td>
<td><span style="font-weight: 400"><code>Claude MCP Client</code></span></td>
</tr>
<tr>
<td><b>API Name</b></td>
<td><span style="font-weight: 400"><code>Claude_MCP_Client</code></span><span style="font-weight: 400"> (auto-filled)</span></td>
</tr>
<tr>
<td><b>Contact Email</b></td>
<td><span style="font-weight: 400">Your email address</span></td>
</tr>
</tbody>
</table>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">Expand the </span><b>API (Enable OAuth Settings)</b><span style="font-weight: 400"> section, check </span><b>Enable OAuth</b><span style="font-weight: 400"> and configure the following app settings:</span>
<ol>
<li style="font-weight: 400"><b>Callback URL</b><span style="font-weight: 400"> with either:</span>
<ul>
<li><code>https://claude.ai/api/mcp/auth_callback</code> for Claude Desktop</li>
<li><code>http://localhost:38000/callback</code> for Claude Code. Use this if you only have an Anthropic API key provisioned for you by your enterprise. Otherwise, we recommend setting up connectors in your <a href="http://Claude.ai">Claude.ai</a> account, then <a href="https://code.claude.com/docs/en/mcp#use-mcp-servers-from-claude-ai"><u>using them in Claude Code</u></a>.</li>
</ul>
</li>
<li style="font-weight: 400"><b>Selected OAuth scopes:</b>
<ul>
<li>Perform requests at any time (<code>refresh_token, offline_access</code>)</li>
<li>Access Salesforce Hosted MCP Servers (<code>mcp_api</code>)</li>
</ul>
</li>
</ol>
</li>
<li>Under <strong>Security</strong>, apply the following:
<ol>
<li>U<span style="font-weight: 400">ncheck these boxes:</span>
<ul>
<li>Require <span style="font-weight: 400">secret for Web Server Flow</span></li>
<li><span style="font-weight: 400">Require secret for Refresh Token Flow</span></li>
</ul>
</li>
<li>Check these boxes:
<ul>
<li>Require <span style="font-weight: 400">Proof Key for Code Exchange (PKCE) extension for Supported Authorization Flows</span></li>
<li>Issue <span style="font-weight: 400"><span style="font-weight: 400"><span style="font-weight: 400"><span style="font-weight: 400">JSON Web Token (JWT)-based access tokens for named users</span></span></span></span>&nbsp;
<p><span style="font-weight: 400">At this point, your ECA configuration should look like this:<br />
</span>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206380" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260522154917/Screenshot-of-the-ECA-configuration-e1779490271629.png?w=857" class="postimages" width="857" height="1000" alt="Screenshot of the ECA configuration" />
			  </span>
			</li>
</ul>
</li>
</ol>
</li>
<li>Click<span style="font-weight: 400"> </span><b>Create</b><span style="font-weight: 400"> to save the app.</span></li>
</ol>
<p><span style="font-weight: 400">Now that the app is created, you need to retrieve the connection information:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Navigate to the </span><b>Settings</b><span style="font-weight: 400"> tab and expand </span><b>OAuth Settings</b><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Click </span><b>Consumer Key and Secret</b><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Complete the authentication dialog with the verification code.</span></li>
<li style="font-weight: 400">Copy the <b style="color: #4a4a4a">Consumer Key</b><span style="font-weight: 400"> and </span><b style="color: #4a4a4a">Consumer Secret</b><span style="font-weight: 400">.</span></li>
</ol>
<h3><span style="font-weight: 400">Step 3: Connect Claude to the Salesforce Hosted MCP Servers</span></h3>
<p><span style="font-weight: 400">The last step of the integration process is to configure the Salesforce MCP Server connections in Claude.</span></p>
<p><span style="font-weight: 400">The instructions differ depending on whether you use Claude Code or Desktop (in particular the ECA callback URL). We&#8217;ll dive into how to configure Claude Code here, and you can find </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/claude.html"><span style="font-weight: 400">instructions for Claude Desktop</span></a><span style="font-weight: 400"> in the docs.</span></p>
<h4><span style="font-weight: 400">Instructions for Claude Code</span></h4>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400"><span style="font-weight: 400"><span style="font-weight: 400">Run the following command in a terminal where:<br />
&#8211; <code>MY_MCP_SERVER_NAME</code> is replaced by the name of the server that you&#8217;re connecting to. You&#8217;re free to enter any value, but we recommend that you stick to the following convention: <code>salesforce-</code> followed by the API name of the MCP server. For example: <code>salesforce-sobject-all</code> for the <code>sobject-all</code> server.<br />
&#8211;<code>MY_MCP_SERVER_URL</code> is replaced by the MCP server URL that you copied earlier. Pay attention to the fact that server URLs differ depending on whether you are connecting to a production or sandbox/scratch org.<br />
&#8211;<code>MY_ECA_CONSUMER_KEY</code> is replaced by the ECA consumer key you&#8217;ve obtained in the previous step.</span></span></span></p>
<pre language="shell">claude mcp add --transport http MY_MCP_SERVER_NAME MY_MCP_SERVER_URL --callback-port 38000 --client-id "MY_ECA_CONSUMER_KEY" --client-secret
</pre>
<p><span style="font-weight: 400"><span style="font-weight: 400">For example, to install the <code>sobject-all</code> MCP server from a scratch org, run:</span></span></p>
<pre language="shell">claude mcp add --transport http salesforce-sobject-all https://api.salesforce.com/platform/mcp/v1/sandbox/platform/sobject-all --callback-port 38000 --client-id "XXX" --client-secret
</pre>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">When prompted to do so, paste the ECA consumer secret you&#8217;ve obtained previously.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Launch Claude Code with the <code>claude</code></span><span style="font-weight: 400"> terminal command.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Run </span><span style="font-weight: 400"><code>/mcp</code></span><span style="font-weight: 400">to list the MCP servers.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Select your newly added MCP server from the list (it is annotated with a “needs authentication” message).</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Select </span><b>Authenticate</b><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">When prompted, allow access to the MCP server.</span></li>
</ol>
<ul></ul>
<ol></ol>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206379" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260522154915/Screenshot-of-the-dialog-asking-to-grant-access-to-the-MCP-server.png?w=438" class="postimages" width="438" height="733" alt="Screenshot of the dialog asking to grant access to the MCP server" />
			  </span>
			</p>
<p><span style="font-weight: 400">Once you authorize the connection, Claude Code lists your MCP server as “connected.”</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206378" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260522154913/Screenshot-of-Claude-Code-showing-the-newly-added-MCP-server-in-the-connected-state-e1779490245662.png?w=1000" class="postimages" width="1000" height="317" alt="Screenshot of Claude Code showing the newly added MCP server in the “connected” state" />
			  </span>
			</p>
<h2><span style="font-weight: 400">Some practical examples of Claude working with Salesforce Hosted MCP Servers</span></h2>
<p><span style="font-weight: 400">Let’s assume that you work at </span><a href="https://github.com/trailheadapps/coral-cloud"><span style="font-weight: 400">Coral Cloud Resorts</span></a><span style="font-weight: 400">, a fictitious resort that offers tailored experience to their guests using Headless 360. You can use Claude and our MCP servers for a number of tasks.</span></p>
<h3><span style="font-weight: 400">Analyzing daily bookings</span></h3>
<p><span style="font-weight: 400">Let’s imagine that as the resort manager, you want to review customer bookings on a weekly basis.</span></p>
<p><span style="font-weight: 400">In traditional Salesforce operations, you would build a report and then generate a dashboard out of it. To access it, you would need tens of clicks in Lightning Experience.</span><span style="font-weight: 400"><br />
</span><span style="font-weight: 400"><br />
</span><span style="font-weight: 400">With Claude, all you have to do is use natural language, such as “Show me a report of daily bookings. Use the start date to group bookings,” to get the trend of your bookings with some additional insights that would require that you set up Tableau reports. You can also drill down by interacting further with Claude with follow up questions.</span></p>
<pre>❯ Show me a report of daily bookings. Use the start date to group bookings.                                                                                      

&#x23fa; I'll query the bookings grouped by start date now.
  
  Called salesforce-sobject-all (ctrl+o to expand)

&#x23fa; Let me find the correct booking object name in this org.

  Called salesforce-sobject-all 2 times (ctrl+o to expand)

&#x23fa; The date field is Date__c. Let me query the bookings grouped by that field.

  Called salesforce-sobject-all (ctrl+o to expand)

&#x23fa; Here's the daily bookings report:

  ┌────────────┬──────────┬──────────────┬───────────────┐
  │    Date    │ Bookings │ Total Guests │ Total Revenue │
  ├────────────┼──────────┼──────────────┼───────────────┤
  │ 2026-05-10 │ 347      │ 1,096        │ $144,918      │
  ├────────────┼──────────┼──────────────┼───────────────┤
  │ 2026-05-11 │ 316      │ 1,026        │ $132,874      │
  ├────────────┼──────────┼──────────────┼───────────────┤
  │ 2026-05-12 │ 325      │ 996          │ $131,169      │
  ├────────────┼──────────┼──────────────┼───────────────┤
  │ 2026-05-13 │ 347      │ 1,050        │ $135,670      │
  ├────────────┼──────────┼──────────────┼───────────────┤
  │ 2026-05-14 │ 285      │ 887          │ $106,410      │
  ├────────────┼──────────┼──────────────┼───────────────┤
  │ Total      │ 1,620    │ 5,055        │ $651,041      │
  └────────────┴──────────┴──────────────┴───────────────┘

  A 5-day window with fairly consistent volume (~315–347/day), except May 14 which is notably lower at 285 bookings and $106K revenue — about 18% below the average of the other days.
</pre>
<p><span style="font-weight: 400">Reading data is nice, but agents can take things further with MCP and take action.</span></p>
<h3><span style="font-weight: 400">Mass cancelling bookings using a custom server and a flow</span></h3>
<p><span style="font-weight: 400">Running a resort is no simple feat, and there can be unforeseen events (such as jellyfish invasions) that lead to booking cancellations. Should such unlikely scenarios occur, the resort staff is left scrambling to mass cancel bookings with hundreds of clicks, which is a repetitive and error-prone process.</span></p>
<p><span style="font-weight: 400">Instead, you can leverage Headless 360 and expose our existing Cancel Booking flow as a custom hosted MCP server.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206376" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260522154907/Screenshot-of-a-custom-hosted-MCP-server-exposing-a-Cancel-Booking-flow-e1779490220752.png?w=1000" class="postimages" width="1000" height="421" alt="Screenshot of a custom hosted MCP server exposing a Cancel Booking flow" />
			  </span>
			</p>
<p><b>Note:</b><span style="font-weight: 400"> in this basic example, you use a flow to cancel a single booking at a time. However, in production, we recommend creating a dedicated flow to process cancellations in bulk to reduce API calls and scale.</span></p>
<p><span style="font-weight: 400">Once the custom MCP server is added to Claude, you can run the cancellations with a prompt that combines multiple calls to our various MCP servers. Note how the prompt is structured to keep the human in the loop: you validate the data before running the cancellation batch.</span></p>
<pre>❯ Identify all bookings for tomorrow morning for the "Surf's Up Lesson" and "Tropical Snorkel Adventure" experience.            
  Let me verify the list then cancel them with the following cancellation reason: "Dear guest, we're sorry to cancel your aquatic experience due to a jellyfish invasion. You'll be refunded shortly."                                                  

&#x23fa; Let me query tomorrow's morning bookings for those two experiences.

  Called salesforce-sobject-all (ctrl+o to expand)

&#x23fa; Here are the 12 bookings found for tomorrow morning (May 13) — please verify before I proceed with cancellations:

  Surf's Up Lesson (9:00 AM – 10:00 AM) — 5 bookings

  ┌─────┬────────────┬───────────────────┬──────────────────────────────┬────────┐
  │  #  │  Booking   │       Guest       │            Email             │ Guests │
  ├─────┼────────────┼───────────────────┼──────────────────────────────┼────────┤
  │ 1   │ B-00001602 │ Ferne Ackwood     │ ferneackwood@example.com     │ 1      │
  ├─────┼────────────┼───────────────────┼──────────────────────────────┼────────┤
  │ 2   │ B-00001603 │ Deeyn Dumsday     │ deeyndumsday@example.com     │ 2      │
  ├─────┼────────────┼───────────────────┼──────────────────────────────┼────────┤
  │ 3   │ B-00001604 │ Alessandro Krates │ alessandrokrates@example.com │ 2      │
  ├─────┼────────────┼───────────────────┼──────────────────────────────┼────────┤
  │ 4   │ B-00001605 │ Quinlan Soppit    │ quinlansoppit@example.com    │ 2      │
  ├─────┼────────────┼───────────────────┼──────────────────────────────┼────────┤
  │ 5   │ B-00001606 │ Osborn Libbe      │ osbornlibbe@example.com      │ 3      │
  └─────┴────────────┴───────────────────┴──────────────────────────────┴────────┘

  Tropical Snorkel Adventure (9:00 AM – 1:00 PM) — 7 bookings

  ┌─────┬────────────┬────────────────────┬───────────────────────────────┬────────┐
  │  #  │  Booking   │       Guest        │             Email             │ Guests │
  ├─────┼────────────┼────────────────────┼───────────────────────────────┼────────┤
  │ 6   │ B-00000972 │ Gay Ragge          │ gayragge@example.com          │ 1      │
  ├─────┼────────────┼────────────────────┼───────────────────────────────┼────────┤
  │ 7   │ B-00000973 │ Baxie Donaher      │ baxiedonaher@example.com      │ 5      │
  ├─────┼────────────┼────────────────────┼───────────────────────────────┼────────┤
  │ 8   │ B-00000974 │ Munmro Semken      │ munmrosemken@example.com      │ 3      │
  ├─────┼────────────┼────────────────────┼───────────────────────────────┼────────┤
  │ 9   │ B-00000975 │ Ira Kemble         │ irakemble@example.com         │ 1      │
  ├─────┼────────────┼────────────────────┼───────────────────────────────┼────────┤
  │ 10  │ B-00000976 │ Suzie Patience     │ suziepatience@example.com     │ 3      │
  ├─────┼────────────┼────────────────────┼───────────────────────────────┼────────┤
  │ 11  │ B-00000977 │ Stephine Suttill   │ stephinesuttill@example.com   │ 1      │
  ├─────┼────────────┼────────────────────┼───────────────────────────────┼────────┤
  │ 12  │ B-00000978 │ Christyna Gianelli │ christynagianelli@example.com │ 1      │
  └─────┴────────────┴────────────────────┴───────────────────────────────┴────────┘

  Total: 12 bookings, 25 guests affected.

  All are currently Confirmed. Confirm and I'll cancel all 12 with the jellyfish invasion message.

✻ Cogitated for 18s

❯ confirmed                                                                                                                      

&#x23fa; Let me load the Cancel Booking flow tool and cancel all 12 bookings in parallel.

&#x23fa; Cancelling all 12 bookings in parallel now.

  Called salesforce-coral-cloud-flows 12 times (ctrl+o to expand)

&#x23fa; All 12 bookings cancelled successfully. Here's the summary:

  Surf's Up Lesson — 5 cancelled

  ┌────────────┬───────────────────┐
  │  Booking   │       Guest       │
  ├────────────┼───────────────────┤
  │ B-00001602 │ Ferne Ackwood     │
  ├────────────┼───────────────────┤
  │ B-00001603 │ Deeyn Dumsday     │
  ├────────────┼───────────────────┤
  │ B-00001604 │ Alessandro Krates │
  ├────────────┼───────────────────┤
  │ B-00001605 │ Quinlan Soppit    │
  ├────────────┼───────────────────┤
  │ B-00001606 │ Osborn Libbe      │
  └────────────┴───────────────────┘

  Tropical Snorkel Adventure — 7 cancelled

  ┌────────────┬────────────────────┐
  │  Booking   │       Guest        │
  ├────────────┼────────────────────┤
  │ B-00000972 │ Gay Ragge          │
  ├────────────┼────────────────────┤
  │ B-00000973 │ Baxie Donaher      │
  ├────────────┼────────────────────┤
  │ B-00000974 │ Munmro Semken      │
  ├────────────┼────────────────────┤
  │ B-00000975 │ Ira Kemble         │
  ├────────────┼────────────────────┤
  │ B-00000976 │ Suzie Patience     │
  ├────────────┼────────────────────┤
  │ B-00000977 │ Stephine Suttill   │
  ├────────────┼────────────────────┤
  │ B-00000978 │ Christyna Gianelli │
  └────────────┴────────────────────┘

  All 25 affected guests have been notified and will be refunded.
</pre>
<p><span style="font-weight: 400">These are two basic examples of what you can do with Claude and Salesforce Hosted MCP Servers, ranging from standard actions to custom scenarios.</span></p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">You’ve now had a glimpse at how Salesforce Hosted MCP Servers let you use agents like Claude to interact with Headless 360 without requiring users to log into Lightning Experience. You’ve learned how to configure Claude Code and Claude Desktop to connect with Salesforce Hosted MCP Servers, and you saw some practical examples of what can be done with those servers.</span></p>
<p><span style="font-weight: 400">Now, it’s your turn to put Headless 360 to the test by building custom tools and configuring MCP servers to power your business.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation: </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/references/reference/servers-reference.html"><span style="font-weight: 400">Salesforce Hosted MCP Servers</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Documentation: </span><a href="https://code.claude.com/docs/en/mcp#installing-mcp-servers"><span style="font-weight: 400">Claude Code</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Postman collection: </span><a href="https://www.postman.com/salesforce-developers/salesforce-developers/collection/689673309a230a0874d2fb86"><span style="font-weight: 400">Salesforce Hosted MCP Servers</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the Author</span></h2>
<p><b>Philippe Ozil</b><span style="font-weight: 400"> is a Principal Developer Advocate at Salesforce, where he focuses on the Salesforce Platform. He writes technical content and speaks frequently at conferences. He is a full-stack developer and enjoys working with APIs, DevOps, robotics, and VR projects. Follow him on </span><a href="https://x.com/PhilippeOzil"><span style="font-weight: 400">X</span></a><span style="font-weight: 400">, </span><a href="https://www.linkedin.com/in/philippeozil/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">, and </span><a href="https://bsky.app/profile/pozil.bsky.social"><span style="font-weight: 400">Bluesky</span></a><span style="font-weight: 400">, and check out his </span><a href="https://github.com/pozil"><span style="font-weight: 400">GitHub projects</span></a><span style="font-weight: 400">.</span></p>
<ol></ol>
<ol></ol>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/connect-claude-with-salesforce-hosted-mcp-servers">Connect Claude with Salesforce Hosted MCP Servers</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/05/connect-claude-with-salesforce-hosted-mcp-servers/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206373</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260522152913/SingleHeadshot-1-e1779488980107.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260522152913/SingleHeadshot-1-e1779488980107.png?w=1000" medium="image" />
	</item>
		<item>
		<title>What Salesforce Headless 360 Means For Developers</title>
		<link>https://developer.salesforce.com/blogs/2026/05/headless-360-what-it-means-for-developers</link>
		<comments>https://developer.salesforce.com/blogs/2026/05/headless-360-what-it-means-for-developers#respond</comments>
		<pubDate>Thu, 21 May 2026 15:02:33 +0000</pubDate>
		<dc:creator><![CDATA[René Winkelmeyer]]></dc:creator>
				<category><![CDATA[Announcements]]></category>
		<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[New Developments]]></category>
		<category><![CDATA[apis]]></category>
		<category><![CDATA[Coding agents]]></category>
		<category><![CDATA[Developers]]></category>
		<category><![CDATA[Governance]]></category>
		<category><![CDATA[Headless 360]]></category>
		<category><![CDATA[MCP tools]]></category>
		<category><![CDATA[Salesforce Platform]]></category>
		<category><![CDATA[System design]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206326</guid>
		<description><![CDATA[<p>Headless 360 exposes every Salesforce capability as an API, MCP tool, or CLI command. Learn why your platform expertise matters more in an agentic world.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/headless-360-what-it-means-for-developers">What Salesforce Headless 360 Means For Developers</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<h2>Get hands-on at the first-ever Salesforce Headless 360 workshop on June 3</h2>
<p><span>Join Salesforce Developer Advocates</span><span class="white-space-pre"> </span><span><a href="https://www.linkedin.com/in/stephanwgarcia/" id="ember2045" class="ember-view">Stephan Chandler-Garcia</a></span><span class="white-space-pre"> </span><span>and</span><span class="white-space-pre"> </span><span><a href="https://www.linkedin.com/in/philippeozil/" id="ember2046" class="ember-view">Philippe Ozil</a></span><span class="white-space-pre"> </span><span>to learn how to build with Salesforce Headless 360 and get hands-on with APIs, MCP tools, and agent-ready experiences that extend Salesforce beyond the platform.</span><span><br />
</span><span><br />
</span><span><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f5d3.png" alt="🗓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> June 3 at 10:00 a.m. EDT</span><span><br />
</span><span><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Register for free:</span><span class="white-space-pre"> </span><a class="vuTzYwVyCxtbVzvrqIdIfPsXoNUtMFeaRuX " target="_self" href="https://sforce.co/42MBvMA" data-test-app-aware-link="">https://sforce.co/42MBvMA</a></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206411" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260527103159/Hands-On-with-Salesforce-Headless-360-Workshop-e1779903130111.jpeg?w=1000" class="postimages" width="1000" height="563" alt="Hands On with Salesforce Headless 360 Workshop" />
			  </span>
			</p>
<p><span style="font-weight: 400">Salesforce Headless 360 makes every major Salesforce capability available as an API, Model Context Protocol (MCP) tool, or CLI command. MCP is a standardized protocol that lets AI models discover and call external tools, APIs, and data sources at runtime, so any compatible client can connect to any compatible server without custom integration code. This post covers what that means when you build on the platform, and why the fundamentals you&#8217;ve been learning matter more than ever.</span></p>
<p><span style="font-weight: 400">As the Salesforce Developer Advocacy team lead, I</span> <a href="https://developer.salesforce.com/blogs/2025/03/future-salesforce-developer-agentic-ai"><span style="font-weight: 400">recently posted on the future of the Salesforce Developer role</span></a><span style="font-weight: 400">. In that post, I argued that AI is reshaping our work around system design, quality engineering, and cross-system thinking. Headless 360 is the platform shift that turns those arguments into daily practice.</span></p>
<h2><span style="font-weight: 400">The platform provides context and capability</span></h2>
<p><span style="font-weight: 400">The LLM provides intelligence. The platform provides everything else: identity, access, capabilities, governance, and the context that makes intelligence useful. When an agent calls an MCP tool, it&#8217;s invoking accumulated state. Without it, the agent has nothing to reason over, no permissions to inherit, no business process to follow.</span></p>
<p><span style="font-weight: 400">This was always true. Every customer record, every workflow, every approval chain, every permission boundary your org has accumulated &#8211; that&#8217;s not overhead. That&#8217;s the business itself, encoded in a system that enforces it.</span></p>
<p><span style="font-weight: 400">That combination of intelligence from the model and everything else from the platform is what separates a prototype from a production system. It&#8217;s also what makes Headless 360 different from “just expose some APIs”.</span></p>
<h2><span style="font-weight: 400">Two kinds of MCP tools</span></h2>
<p><span style="font-weight: 400">When we talk about MCP tools in the Salesforce ecosystem, we mean two things:</span></p>
<ul>
<li style="font-weight: 400"><b>MCP tools for coding agents.</b><span style="font-weight: 400"> These enable developers to connect Claude Code, Cursor, or any MCP-compatible coding agent to their org and read metadata, generate Apex, run tests, or deploy through the pipeline. The consumer is a </span><i><span style="font-weight: 400">developer building software</span></i><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><b style="color: #4a4a4a">MCP tools for business agents.</b><span style="font-weight: 400"> These enable </span><span style="font-weight: 400">users to access Salesforce capabilities from any MCP-compatible client: including Slack, ChatGPT, and Claude, among others. These MCP tools also allow agents to access Salesforce capabilities autonomously</span><span style="font-weight: 400">. The consumer is an </span><i style="color: #4a4a4a"><span>agent serving an end user</span></i><span style="font-weight: 400">.</span></li>
</ul>
<p><span style="font-weight: 400">Both kinds of MCP tools share the same open surface and the same trust layer. The difference is who&#8217;s calling and why. Headless 360 covers both, and both have implications for the expertise you&#8217;ve built.</span></p>
<p><span style="font-weight: 400">Headless 360 changes how the platform can be consumed. Coding agents can now reach into your org through MCP tools to build and deploy. Business agents can call the same platform to serve customers in Slack, ChatGPT, Claude, or any MCP-compatible client. The Salesforce browser-based UI is one surface among many.</span><i><span style="font-weight: 400">.</span></i></p>
<h2><span style="font-weight: 400">How software is built, delivered, and used is changing all at once</span></h2>
<p><span style="font-weight: 400">Agentic AI is changing three things simultaneously.</span></p>
<p><b>How software is written.</b><span style="font-weight: 400"> Agents are becoming the development team. The first draft of an Apex class, an LWC, a test suite, or an Agentforce agent arrives in seconds. Your job shifts from writing code to specifying intent, evaluating output, and deciding what fits your existing system design.</span></p>
<p><b>How quickly it is delivered.</b><span style="font-weight: 400"> Implementations compress from months to days. When an agent can execute against a development org, with human review gates before anything reaches production, the bottleneck moves from execution to clarity of intent.</span></p>
<p><b>How it is used.</b><span style="font-weight: 400"> Humans will use Salesforce from anywhere, not necessarily the Salesforce UI. A sales rep gets a customer summary in Slack. A support agent resolves a case in Teams. And increasingly, the consumer isn&#8217;t a human at all; it&#8217;s a business agent reasoning over your data. The destination isn&#8217;t a Lightning Experience tab. It&#8217;s wherever the work is already happening.</span></p>
<p><span style="font-weight: 400">These three changes are happening simultaneously, and they put pressure on the platform itself. </span><b>Headless 360 is the platform shift designed to address all three at once.</b></p>
<table>
<thead>
<tr>
<th><b>Dimension</b></th>
<th><b>Traditional Salesforce</b></th>
<th><b>Headless 360</b></th>
</tr>
</thead>
<tbody>
<tr>
<td><b>Who writes code</b></td>
<td><span style="font-weight: 400">Human developer, start to finish</span></td>
<td><span style="font-weight: 400">Human + coding agent via MCP tools; developer specifies intent and reviews output</span></td>
</tr>
<tr>
<td><b>Implementation timeline</b></td>
<td><span style="font-weight: 400">Weeks to months across multiple roles</span></td>
<td><span style="font-weight: 400">Days; agent executes against development org with human review gates</span></td>
</tr>
<tr>
<td><b>Primary consumer</b></td>
<td><span style="font-weight: 400">Human navigating Lightning UI</span></td>
<td><span style="font-weight: 400">Any authenticated caller &#8211; human, agent, app, or CLI</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">In addition to these three core dimensions, Headless 360 also changes how governance is enforced and how you think about channel strategy. When the UI is no longer the primary surface, you can&#8217;t rely on a mix of UI constraints and schema rules for governance; instead, full schema-level enforcement (via validation rules, your sharing model, and Apex) is required. And rather than building per-channel UIs (for Lightning, mobile, and community, for example), with Headless 360 you define a capability once and let it render wherever it&#8217;s called, including Slack, ChatGPT, mobile, or web. </span></p>
<h2><span style="font-weight: 400">What Headless 360 actually is</span></h2>
<p><span style="font-weight: 400">Headless 360 makes every major Salesforce capability openly composable by agents, not just by code paths a human developer wrote in advance. The full surface, including 60+ MCP tools, 30+ coding skills, 4,000+ existing APIs, and 220+ CLI commands, is accessible to any authenticated caller that your trust layer authorizes.</span></p>
<p><span style="font-weight: 400">This isn&#8217;t new territory for Salesforce developers. You&#8217;ve been building headless for years. This includes Experience Cloud sites with React frontends, Node.js apps over REST APIs, and mobile apps via the Mobile SDK. What&#8217;s new is that AI models can now dynamically discover, use, and compose capabilities at runtime, without custom integration code written in advance for each one. </span></p>
<p><span style="font-weight: 400">When we say “openly composable by agents” the </span><i><span style="font-weight: 400">openly</span></i><span style="font-weight: 400"> matters. Openness is the default posture. Governance is what makes it safe.</span></p>
<h2><span style="font-weight: 400">Headless 360 in practice</span></h2>
<p><span style="font-weight: 400">The capabilities Headless 360 unlocks aren&#8217;t abstract. Here&#8217;s what they look like in practice. </span></p>
<h3><span style="font-weight: 400">Build with any coding agent</span></h3>
<p><span style="font-weight: 400">The </span><a href="https://www.salesforce.com/news/stories/salesforce-headless-360-announcement/"><span style="font-weight: 400">60+ MCP tools and 30+ preconfigured coding skills</span></a><span style="font-weight: 400"> give any compatible coding agent live access to your org. </span><a href="https://www.salesforce.com/agentforce/developers/vibe-coding/"><span style="font-weight: 400">Agentforce Vibes 2.0</span></a><span style="font-weight: 400"> brings that natively inside Salesforce, but the MCP surface is the same regardless of which agent you choose.</span></p>
<p><span style="font-weight: 400">Here is a typical session: your coding agent connects to a scratch org via MCP, reads the object model, scaffolds an Apex service class with governor-limit-aware test coverage, and pushes it through your DevOps pipeline. Same deployment gates, same quality checks, same review process as code written by hand. The pipeline doesn&#8217;t care who initiated it. It cares whether the gates pass.</span></p>
<p><span style="font-weight: 400">The platform doesn&#8217;t privilege one agent over another. Your org&#8217;s capabilities are the constant. The tooling around it is your choice. For more on the MCP tools and how they work, see our post </span><a href="https://developer.salesforce.com/blogs/2025/06/introducing-mcp-support-across-salesforce"><span style="font-weight: 400">Introducing MCP Support Across Salesforce</span></a><span style="font-weight: 400">.</span></p>
<h3><span style="font-weight: 400">Render in any UI framework</span></h3>
<p><span style="font-weight: 400">Headless decouples capability from rendering. In practice, this means the platform no longer constrains your UI framework. A clear example of this is </span><a href="https://developer.salesforce.com/blogs/2026/04/build-with-react-run-on-salesforce-introducing-salesforce-multi-framework"><span style="font-weight: 400">multi-framework support</span></a><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">With multi-framework support, React developers can now build on Salesforce using the same patterns they already know, with no requirement to use LWC. A front-end engineer who&#8217;s never touched Salesforce can build a governed, data-connected application using familiar tools, because the platform adapts to their stack.</span></p>
<p><span style="font-weight: 400">That hiring constraint where you needed someone who knew LWC or Aura? It&#8217;s no longer a hard constraint. The platform meets developers where they already are.</span></p>
<h3><span style="font-weight: 400">Deploy on any surface</span></h3>
<p><span style="font-weight: 400">The </span><a href="https://www.salesforce.com/platform/orchestration-platform/"><span style="font-weight: 400">Headless Experience Layer</span></a><span style="font-weight: 400"> is a new runtime that decouples capability definitions from their rendering surface. You define a UI fragment and the Headless Experience Layer (HXL) handles rendering it as a Slack block, a mobile card, a ChatGPT response, or a voice interaction. Business logic, data, and permissions stay separated from the screen, so you define intent once and render natively everywhere, including Slack, mobile, ChatGPT, Claude, Teams, or any MCP-compatible client.</span></p>
<p><span style="font-weight: 400">This is where the two MCP lanes start to converge. The same openness that lets a coding agent build against your org will also let a business agent serve your customers from it. Today, the build-time surface is mature. The runtime surface already supports straightforward use cases, such as a support agent pulling a case summary inside a Slack thread. The broader vision is that the same capability can be delivered across surfaces such as voice interactions or partner mobile apps, and that ultimately capabilities can be made available on any surface.</span></p>
<h3><span style="font-weight: 400">Protect with built-in governance</span></h3>
<p><a href="https://www.salesforce.com/blog/headless-trust-model-agentic-architecture/"><span style="font-weight: 400">Headless doesn&#8217;t mean ungoverned</span></a><span style="font-weight: 400">. Governance is what makes it safe to build with any coding agent, render in any UI framework, and deploy on any surface. </span></p>
<p><span style="font-weight: 400">When a coding agent pushes an Apex class, it goes through the same validation, tests, and deployment gates as code written by a human. When an agent queries customer data through MCP, it hits the same sharing rules and field-level security as a user in Lightning.</span></p>
<p><span style="font-weight: 400">Four things are enforced on every API call, every MCP tool invocation, and every CLI command:</span></p>
<ul>
<li style="font-weight: 400"><b>Identity.</b><span style="font-weight: 400"> The agent acts as a specific user, not an anonymous caller. Permissions are scoped to that identity.</span></li>
<li style="font-weight: 400"><b>Access.</b><span style="font-weight: 400"> Sharing rules, field-level security, and permission sets enforce what the caller can see and do.</span></li>
<li style="font-weight: 400"><b>Invocation scope.</b><span style="font-weight: 400"> The agent can invoke only those tools and actions that have been explicitly exposed.</span></li>
<li style="font-weight: 400"><b>Governance.</b><span style="font-weight: 400"> Validation rules, triggers, approval chains, and governor limits all fire regardless of entry point.</span></li>
</ul>
<p><span style="font-weight: 400">The platform is open about </span><i><span style="font-weight: 400">what</span></i><span style="font-weight: 400"> can be accessed. It is not open about </span><i><span style="font-weight: 400">who</span></i><span style="font-weight: 400"> can access it, or </span><i><span style="font-weight: 400">how</span></i><span style="font-weight: 400">. That&#8217;s what makes it safe for </span><i><span style="font-weight: 400">openness</span></i><span style="font-weight: 400"> to be the default posture.</span></p>
<h2><span style="font-weight: 400">Why most agentic prototypes don&#8217;t reach production</span></h2>
<p><span style="font-weight: 400">If you&#8217;ve tried to ship an agent, you&#8217;ve likely encountered this problem. The demo works. The stakeholder asks a question that breaks it. For example:</span><i></i></p>
<ul>
<li style="font-weight: 400"><i><span style="font-weight: 400">&#8220;Will it respect the sharing rule on this object?&#8221;</span></i></li>
<li style="font-weight: 400"><i><span style="font-weight: 400">&#8220;What happens if the user doesn&#8217;t have permission to update that field?&#8221;</span></i></li>
<li style="font-weight: 400"><i><span style="font-weight: 400">&#8220;How does it know which approval policy applies?&#8221;</span></i></li>
</ul>
<p><span style="font-weight: 400">The LLM alone is not enough. The hard part is everything around it: business context, identity-aware permissions, policy enforcement, and the operational guarantees that make a system safe to run autonomously.</span></p>
<p><b>That gap is exactly what the Salesforce platform fills.</b><span style="font-weight: 400"> The context an agent needs didn&#8217;t show up because someone wrote a clever prompt. It accumulated over years, in your data model, your validation rules, your sharing architecture, your trigger framework. Headless 360 makes that platform accessible to agents through a governed surface. </span><b>You don&#8217;t rebuild context, permissions, or policy for your agents. They inherit it.</b></p>
<p><span style="font-weight: 400">Testing is part of this too. Testing agents is different from testing deterministic code. You&#8217;re evaluating whether an agent&#8217;s reasoning led to an acceptable outcome across a distribution of inputs. Session tracing, custom scoring evals, and the Agentforce Testing Center are how you build confidence that an agent does the right thing in production. The discipline is familiar if you&#8217;ve built test suites for complex Apex, but the techniques are new.</span></p>
<h2><span style="font-weight: 400">What comes next</span></h2>
<p><span style="font-weight: 400">Headless 360 ships with MCP tools for coding and business agents, coding skills, multi-framework support, and the Experience Layer. That&#8217;s its surface. What makes it work is everything underneath: the state, the governance, the accumulated business logic of a platform you already know.</span></p>
<p><span style="font-weight: 400">On the </span><a href="https://developer.salesforce.com/blogs"><span style="font-weight: 400">Salesforce Developer Blog</span></a><span style="font-weight: 400"> and </span><a href="https://www.youtube.com/@salesforcedevs"><span style="font-weight: 400">YouTube</span></a><span style="font-weight: 400">, we&#8217;re going deep on every building block, including skills for coding agents, multi-framework walkthroughs, trust and governance deep dives, testing and observability patterns, and more. Follow along and tell us where to go deeper.</span></p>
<p><span style="font-weight: 400">The platform is open. Build on it.</span></p>
<h2><span style="font-weight: 400">Related Reading</span></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/blogs/2026/03/future-salesforce-developer-agentic-ai"><span style="font-weight: 400">The Future of the Salesforce Developer in the Agentic AI Era</span></a></li>
<li style="font-weight: 400"><a href="https://www.salesforce.com/news/stories/salesforce-headless-360-announcement/"><span style="font-weight: 400">Introducing Salesforce Headless 360</span></a></li>
<li style="font-weight: 400"><a href="https://www.salesforce.com/agentforce/developers/vibe-coding/"><span style="font-weight: 400">Agentforce Vibes 2.0</span></a></li>
<li style="font-weight: 400"><a href="https://www.salesforce.com/platform/orchestration-platform/"><span style="font-weight: 400">Headless Experience Layer</span></a></li>
<li style="font-weight: 400"><a href="https://www.salesforce.com/platform/multi-framework/"><span style="font-weight: 400">Salesforce Multi-Framework</span></a></li>
<li style="font-weight: 400"><a href="https://labs.agentforce.com/"><span style="font-weight: 400">Agentforce Labs</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/forcedotcom/afv-library/tree/main/skills/developing-agentforce"><span style="font-weight: 400">Open Source Agentforce Skills</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the Author</span></h2>
<p><b>Rene Winkelmeyer</b><span style="font-weight: 400"> leads the Developer Advocacy team at Salesforce. His team focuses on Agentforce, Data 360, and the Salesforce Platform. In his spare time, you might find him </span><a href="https://github.com/muenzpraeger"><span style="font-weight: 400">still coding on GitHub</span></a><span style="font-weight: 400">. Follow Rene on </span><a href="https://www.linkedin.com/in/muenzpraeger"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/headless-360-what-it-means-for-developers">What Salesforce Headless 360 Means For Developers</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/05/headless-360-what-it-means-for-developers/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206326</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260521080801/1000x563-B-2.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260521080801/1000x563-B-2.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Conduct End-to-End Testing for Salesforce Omnistudio-based UIs</title>
		<link>https://developer.salesforce.com/blogs/2026/05/conduct-end-to-end-testing-for-salesforce-omnistudio-based-uis</link>
		<comments>https://developer.salesforce.com/blogs/2026/05/conduct-end-to-end-testing-for-salesforce-omnistudio-based-uis#respond</comments>
		<pubDate>Wed, 20 May 2026 15:00:35 +0000</pubDate>
		<dc:creator><![CDATA[Sarah Welker]]></dc:creator>
				<category><![CDATA[App Development]]></category>
		<category><![CDATA[Automation]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Lightning Web Components]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[app development]]></category>
		<category><![CDATA[automation]]></category>
		<category><![CDATA[developer tooling]]></category>
		<category><![CDATA[devops]]></category>
		<category><![CDATA[Trust Security and Accessibility]]></category>
		<category><![CDATA[tutorials]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206339</guid>
		<description><![CDATA[<p>Stop broken deployments. Learn how to implement stable end-to-end testing for Salesforce Omnistudio UI, leveraging UTAM, Playwright, and specific scenario testing for Omniscripts and Flexcards.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/conduct-end-to-end-testing-for-salesforce-omnistudio-based-uis">Conduct End-to-End Testing for Salesforce Omnistudio-based UIs</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">Testing user interfaces (UIs) can feel like trying to hit a moving target, especially when you are working with </span><a href="https://www.salesforce.com/products/omnistudio/overview/"><span style="font-weight: 400">Salesforce Omnistudio components</span></a><span style="font-weight: 400">. You know the drill: you pour hours into crafting an Omniscript for customer onboarding or a Flexcard dashboard for sales teams, and it looks perfect in preview. But then you run user tests and — </span><i><span style="font-weight: 400">bam!</span></i><span style="font-weight: 400"> — stuff breaks in ways you never saw coming.</span></p>
<p><span style="font-weight: 400">Picture your customer rolling out a Flexcard for quick account overviews. It’s looking great on desktop, but on mobile? Actions don&#8217;t fire, data doesn&#8217;t render, and users bounce in frustration. These aren&#8217;t rare moments; they&#8217;re everyday headaches that slow deployments, breaking trust. End-to-end (E2E) testing fixes this mess by mimicking real journeys, so you can catch issues early and deploy with confidence.</span></p>
<h2><b>Why E2E testing matters for Omnistudio</b></h2>
<p><span style="font-weight: 400">So, why do we need to test the entire journey rather than just checking if a button works? Testing the full user journey is critical because </span><a href="https://help.salesforce.com/s/articleView?id=xcloud.os_omniscripts_8355.htm&amp;type=5"><span style="font-weight: 400">Omniscripts</span></a><span style="font-weight: 400">, </span><a href="https://help.salesforce.com/s/articleView?id=xcloud.os_omnistudio_flexcards_24388.htm&amp;type=5"><span style="font-weight: 400">Flexcards</span></a><span style="font-weight: 400">, and </span><a href="https://help.salesforce.com/s/articleView?id=xcloud.os_omnistudio_dataraptors_45587.htm&amp;type=5"><span style="font-weight: 400">Data Mappers</span></a><span style="font-weight: 400"> interact across boundaries where unit tests cannot validate the complete process. E2E testing plays out the full story of what a real user does, from start to finish across your components, like a demo of an actual </span><a href="https://www.salesforce.com/marketing/customer-journey/"><span style="font-weight: 400">customer journey</span></a><span style="font-weight: 400">. </span></p>
<p><span style="font-weight: 400">E2E testing also helps you:</span></p>
<ul>
<li style="font-weight: 400"><b>Simulate full user journeys across Omnistudio components:</b><span style="font-weight: 400"> E2E testing plays out that whole flow, just like a real person clicking through components, spotting weird hiccups along the way.</span></li>
<li style="font-weight: 400"><b>Catch integration issues early for partners building solutions: </b><span style="font-weight: 400">E2E grabs those sneaky spots where Data Mappers don&#8217;t feed data right into an Omniscript, saving you from frantic fixes right before demo day.</span></li>
<li style="font-weight: 400"><b>Reduce post-go-live bugs:</b><span style="font-weight: 400"> E2E irons out production surprises, reducing post-deployment defect tickets for your teams, giving you greater confidence in deployments.</span></li>
<li style="font-weight: 400"><b>Validate real-world flows to record creation:</b><span style="font-weight: 400"> Unit tests poke single buttons (like does this button click? Does that data pull?). E2E runs the full race, like submitting a lead form, watching it zip through conditions, and landing as a shiny new record in Salesforce.</span></li>
<li style="font-weight: 400"><b>Find the real problem:</b><span style="font-weight: 400"> When tests flake, E2E pinpoints the issue past the pretty interface and discovers whether or not the backend logic is actually the part causing the crash.</span></li>
<li style="font-weight: 400"><b>Confirm that the handoff works:</b><span style="font-weight: 400"> Picture Omniscript passing data to a Flexcard. E2E checks that the pass doesn&#8217;t drop, enabling easy jumps between your UI pieces.</span></li>
<li style="font-weight: 400"><b>Manage your own path: </b><span style="font-weight: 400">Users don&#8217;t always go straight. E2E tests the sunny &#8220;submit and succeed&#8221; path plus detours like errors or save-for-later resumes, covering all those branching stories.</span></li>
</ul>
<h2><b>High risk components to test first</b></h2>
<p><span style="font-weight: 400">When figuring out where to focus your testing energy, it helps to know which parts of the system are most likely to break. Based on what teams see in the real world, here are the high-risk areas you need to watch.</span></p>
<p><b>Omniscripts</b><span style="font-weight: 400"> account for approximately 50% of reported Omnistudio UI bugs, making them the highest-priority target for E2E testing. Because this is the layer the user interacts with directly and is also most visible, it typically receives the highest number of reported bugs. They juggle steps, conditions, inputs, and data swaps with Data Mappers all in one go. Mess up here, and your whole customer journey, like a service request form, grinds to a halt.</span></p>
<p><span style="font-weight: 400">Next is </span><b>Flexcards</b><span style="font-weight: 400"> because these cards are used to display critical data and handle user actions, and any glitch here is immediately obvious to the user. </span></p>
<p><span style="font-weight: 400">Finally, we have </span><b>Data Mappers</b><span style="font-weight: 400">. If mappings fail (say, wrong field names or null values), Omniscripts starve for information, also breaking Flexcards downstream.</span></p>
<p><b>Note:</b><span style="font-weight: 400"> All data presented above is derived from internal testing.</span></p>
<h2><b>Tools for Omnistudio test automation: UTAM, Playwright, and Selenium</b></h2>
<p><a href="https://www.salesforce.com/artificial-intelligence/what-is-automation/"><span style="font-weight: 400">Automation</span></a><span style="font-weight: 400"> sounds cool, but here&#8217;s the headache: </span><a href="https://www.salesforce.com/industries/omnistudio/omnistudio-on-core-guide/"><span style="font-weight: 400">Omnistudio</span></a><span style="font-weight: 400"> pieces shift a lot. You might spend days building a perfect test script, and it runs beautifully, until the next release drops. Suddenly, everything breaks. Why? The main culprit here is dynamic element IDs. Because Omniscripts and Flexcards are built on </span><a href="https://developer.salesforce.com/developer-centers/lightning-web-components"><span style="font-weight: 400">Lightning Web Components (LWC)</span></a><span style="font-weight: 400">, they sit inside a </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-dom.html"><span style="font-weight: 400">Shadow DOM</span></a><span style="font-weight: 400">. So your usual CSS or XPath selectors become fragile and start failing whenever Salesforce moves with seasonal updates. It breaks everything at the backend. One day your test clicks Submit just fine, and the next, it hunts for a button that moves and your whole pipeline stalls.</span></p>
<p><span style="font-weight: 400">To solve this problem, Salesforce created the </span><a href="https://utam.dev/"><span style="font-weight: 400">UI test automation model (UTAM)</span></a><span style="font-weight: 400">, a proprietary tool designed specifically for LWC in Omnistudio. Omnistudio components are wrapped in Shadow Roots (that&#8217;s the Shadow DOM), which blocks standard Selenium selectors like CSS or XPath from reaching elements reliably, especially after Salesforce updates.</span></p>
<pre class="wp-block-code language-xml">&lt;!-- Source: https://mvnrepository.com/artifact/com.salesforce.utam/salesforce-pageobjects --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.salesforce.utam&lt;/groupId&gt;
    &lt;artifactId&gt;salesforce-pageobjects&lt;/artifactId&gt;
    &lt;version&gt;12.0.0&lt;/version&gt;
    &lt;scope&gt;compile&lt;/scope&gt;
&lt;/dependency&gt;</pre>
<p><span style="font-weight: 400">Instead of relying on brittle selectors in scripts, UTAM uses JSON-based page objects. When a button’s location or structure changes in an Omnistudio update, you just update the </span><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_activation_toolkit_isv.htm&amp;type=5"><span style="font-weight: 400">JSON metadata</span></a><span style="font-weight: 400"> once, and all your tests are instantly back on track. It creates a stable layer and lets scripts identify UI components consistently, no matter what changes underneath.</span>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206351" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260515104345/Screenshot-of-example-UTAM-code-e1778867167835.png?w=1000" class="postimages" width="1000" height="583" alt="Screenshot of example UTAM code" />
			  </span>
			</p>
<h3><span style="font-weight: 400">Choosing between UTAM, Playwright, and Selenium</span></h3>
<p><span style="font-weight: 400">Instead of relying on fragile identifiers, a smart move would be to grab the right tools that don&#8217;t flake. Use a combination of industry-standard tools and Salesforce-specific frameworks. Here&#8217;s when to use what:</span></p>
<ul>
<li style="font-weight: 400"><b>UTAM:</b><span style="font-weight: 400"> Best for long-term maintenance and Salesforce-native components, giving you those stable JSON selectors that survive Omnistudio updates.</span></li>
<li style="font-weight: 400"><b>Playwright:</b> <a href="https://playwright.dev/"><span style="font-weight: 400">Playwright</span></a><span style="font-weight: 400"> is best for speed and headless </span><a href="https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/ci-cd.html"><span style="font-weight: 400">CI/CD</span></a><span style="font-weight: 400"> pipelines. Fire it up without a visible browser to debug fast across browsers. </span></li>
<li style="font-weight: 400"><b>Cucumber with Selenium:</b> <a href="https://cucumber.io/"><span style="font-weight: 400">Cucumber</span></a><span style="font-weight: 400"> with</span><a href="https://www.selenium.dev/"> <span style="font-weight: 400">Selenium</span></a><span style="font-weight: 400"> is best for business stakeholder sign-off (BDD). You write tests in plain English and your team reads and approves it.</span></li>
</ul>
<p><span style="font-weight: 400">Launch with tweaks like test parameters (</span><a href="https://www.salesforce.com/in/platform/sandboxes-environments/"><span style="font-weight: 400">sandbox</span></a><span style="font-weight: 400"> mode? Slow it down?). Then poke issues using element properties. Is it visible? Enabled? That&#8217;s how you spot and squash the real culprits without endless rewrites.</span></p>
<table>
<tbody>
<tr>
<td><b>Feature</b></td>
<td><b>UTAM (UI Test Automation Model)</b></td>
<td><b>Playwright</b></td>
<td><b>Cucumber + Selenium</b></td>
</tr>
<tr>
<td><b>Primary Use Case</b></td>
<td><span style="font-weight: 400">Salesforce-specific UI testing and complex Shadow DOMs</span></td>
<td><span style="font-weight: 400">Modern web apps (React, Vue, etc.) and high-speed CI/CD</span></td>
<td><span style="font-weight: 400">Legacy apps and BDD (Business Driven Development)</span></td>
</tr>
<tr>
<td><b>Philosophy</b></td>
<td><span style="font-weight: 400">Model-based: Decouples UI via JSON-based page objects</span></td>
<td><span style="font-weight: 400">Modern/native: Direct browser control via WebSockets</span></td>
<td><span style="font-weight: 400">Driver-based: Uses WebDriver to remote-control the browser</span></td>
</tr>
<tr>
<td><b>Wait Handling</b></td>
<td><span style="font-weight: 400">Explicit waits designed into JSON page objects</span></td>
<td><span style="font-weight: 400">Auto-waiting: Automatically waits for elements to be actionable</span></td>
<td><span style="font-weight: 400">Manual: Requires explicit/implicit wait statements (often flaky)</span></td>
</tr>
<tr>
<td><b>Learning Curve</b></td>
<td><span style="font-weight: 400">High: Requires learning UTAM JSON grammar and compiler</span></td>
<td><span style="font-weight: 400">Low: Very beginner-friendly with excellent documentation</span></td>
<td><span style="font-weight: 400">Moderate: Large community, but setup is complex</span></td>
</tr>
<tr>
<td><b>Language Support</b></td>
<td><span style="font-weight: 400">Java, JavaScript, TypeScript</span></td>
<td><span style="font-weight: 400">JS, TS, Python, Java, .NET</span></td>
<td><span style="font-weight: 400">Almost any (Java, Python, C#, Ruby, PHP, Perl)</span></td>
</tr>
<tr>
<td><b>Parallelization</b></td>
<td><span style="font-weight: 400">Managed by the runner (e.g., WebdriverIO/JUnit)</span></td>
<td><span style="font-weight: 400">Native: Extremely fast via isolated Browser Contexts</span></td>
<td><span style="font-weight: 400">Requires Selenium Grid (resource-heavy)</span></td>
</tr>
<tr>
<td><b>Standout Feature</b></td>
<td><span style="font-weight: 400">Shadow DOM support: Pierces Salesforce&#8217;s complex DOM layers</span></td>
<td><span style="font-weight: 400">Trace Viewer: Time-travel debugging with video and logs</span></td>
<td><span style="font-weight: 400">Gherkin Syntax: Tests written in plain English (Given/When/Then)</span></td>
</tr>
</tbody>
</table>
<h2><b>Testing scenarios by Omnistudio component</b></h2>
<p><span style="font-weight: 400">Now that we&#8217;ve covered the hurdles, let&#8217;s get practical: what exact scenarios should you click, check, and validate in your Omnistudio flows? Since Omnistudio is made up of different building blocks, we need to look at them individually. Here are the recommended testing scenarios to keep your application running smoothly.</span></p>
<p><span style="font-weight: 400">These are the must-hit spots to make sure everything hangs together for real users.</span></p>
<h3><span style="font-weight: 400">Omniscript testing</span></h3>
<p><span style="font-weight: 400">These are your flow bosses, so test the moves that users make every day.</span></p>
<ul>
<li style="font-weight: 400"><b>Check step transitions &amp; logic: </b><span style="font-weight: 400">Don&#8217;t just check if the Next button works. You need to validate the conditional visibility. This is the if/else logic, if a user selects Option A, does the form show them the correct follow-up question? If they select Option B, does it take them down the alternative path?<br />

			  <span class="postimagessection_specify alignnone size-medium wp-image-206349" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260515104341/Omniscript-configuration-panel-showing-Conditional-View-settings-e1778867141557.png?w=1000" class="postimages" width="1000" height="189" alt="Omniscript configuration panel showing Conditional View settings." />
			  </span>
			<br />
</span></li>
<li><b>Validation rules:</b><span style="font-weight: 400"> Try to break it. Enter the wrong date format or leave a required field blank. Does the system stop you with a helpful error message (validation failure), or does it let you proceed and crash later?<br />

			  <span class="postimagessection_specify alignnone size-medium wp-image-206341" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260515104321/Omniscript-property-panel-highlighting-a-required-text-field-e1778867033289.png?w=1000" class="postimages" width="1000" height="163" alt="Omniscript property panel highlighting a required text field." />
			  </span>
			<br />
</span></li>
<li style="font-weight: 400"><b>Save and resume: </b><span style="font-weight: 400">This is a big one. For long applications, users might not finish in one sitting. You must test the &#8220;Save for Later&#8221; functionality to ensure that their data is actually saved and can be resumed exactly where they left off without data loss.<br />

			  <span class="postimagessection_specify alignnone size-medium wp-image-206350" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260515104343/Omniscript-form-preview-featuring-a-Save-for-later-button-e1778867153783.png?w=1000" class="postimages" width="1000" height="804" alt="Omniscript form preview featuring a Save for later button." />
			  </span>
			<br />
</span></li>
<li style="font-weight: 400"><b>The ID trap: </b><span style="font-weight: 400">When automating these tests, avoid using dynamic IDs generated by the browser as these change frequently. Instead, use </span><span style="font-weight: 400">data-testid</span><span style="font-weight: 400"> or stable CSS selectors. This keeps your test from breaking every time Salesforce updates the platform.<br />

			  <span class="postimagessection_specify alignnone size-medium wp-image-206345" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260515104329/Developer-tools-inspecting-an-Omniscripts-nested-LWC-DOM-structure-e1778867088676.png?w=1000" class="postimages" width="1000" height="718" alt="Developer tools inspecting an Omniscript's nested LWC DOM structure." />
			  </span>
			<br />
</span></li>
</ul>
<h3><span style="font-weight: 400">Flexcard testing</span></h3>
<p><span style="font-weight: 400">If Omniscript is the form, the Flexcard is the dashboard that needs to look sharp and act right. It displays data and offers quick actions.</span></p>
<ul>
<li style="font-weight: 400"><b>Validate data rendering: </b><span style="font-weight: 400">First, perform simple visual checks. Is the data showing up? Whether it is static info or contextual data pulled from the system, it needs to render correctly on the screen.<br />

			  <span class="postimagessection_specify alignnone size-medium wp-image-206346" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260515104331/Flexcard-datatable-rendering-various-complaint-records-e1778867100422.png?w=1000" class="postimages" width="1000" height="80" alt="Flexcard datatable rendering various complaint records." />
			  </span>
			<br />
</span></li>
<li style="font-weight: 400"><b>Action buttons:</b><span style="font-weight: 400"> Flexcards aren&#8217;t just for looking, they have buttons. Test these actions specifically. Does clicking the button successfully launch the correct Omniscript or navigate the user to the right page?<br />

			  <span class="postimagessection_specify alignnone size-medium wp-image-206347" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260515104337/Flexcard-designer-displaying-an-action-button-configured-for-Omnianalytics-e1778867114967.png?w=1000" class="postimages" width="1000" height="347" alt="Flexcard designer displaying an action button configured for Omnianalytics." />
			  </span>
			<br />
</span></li>
<li style="font-weight: 400"><b>Conditional states:</b><span style="font-weight: 400"> Just like Omniscripts, Flexcards can change based on data. Test these conditional states, such as hover effects or expanded details, to ensure that the card looks different when it is supposed to.<br />

			  <span class="postimagessection_specify alignnone size-medium wp-image-206348" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260515104339/Flexcard-property-panel-detailing-custom-condition-rules-e1778867128147.png?w=1000" class="postimages" width="1000" height="569" alt="Flexcard property panel detailing custom condition rules." />
			  </span>
			<br />
</span></li>
<li style="font-weight: 400"><b>Selectors:</b><span style="font-weight: 400"> Stick to </span><a href="https://developer.salesforce.com/developer-centers/integration-apis"><span style="font-weight: 400">application programming interface (API)</span></a><span style="font-weight: 400"> name–based selectors for your automation to keep things stable. They stay reliable when Salesforce tweaks the UI.</span></li>
</ul>
<h3><span style="font-weight: 400">Data Mapper testing</span></h3>
<p><span style="font-weight: 400">Now we are moving backstage. Data Mappers are responsible for moving data in and out of Salesforce. Even if the UI looks fine, a broken Data Mapper means the process fails.</span></p>
<ul>
<li style="font-weight: 400"><b>Test separately: </b><span style="font-weight: 400">You don&#8217;t always need the UI to test these. Isolate them to see if they work on their own since they&#8217;re data pipes.<br />

			  <span class="postimagessection_specify alignnone size-medium wp-image-206342" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260515104322/Data-Mapper-debug-interface-showing-a-successful-JSON-execution-e1778867047945.png?w=1000" class="postimages" width="1000" height="196" alt="Data Mapper debug interface showing a successful JSON execution." />
			  </span>
			<br />
</span></li>
<li style="font-weight: 400"><b>Input and mappings:</b><span style="font-weight: 400"> Check the input payload correctness. If you send data in, does it map to the correct fields in the database?<br />

			  <span class="postimagessection_specify alignnone size-medium wp-image-206344" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260515104327/Data-Mapper-screen-displaying-JSON-input-to-output-mappings-e1778867074871.png?w=1000" class="postimages" width="1000" height="390" alt="Data Mapper screen displaying JSON input to output mappings." />
			  </span>
			<br />
</span></li>
<li style="font-weight: 400"><b>Edge cases:</b><span style="font-weight: 400"> What happens if a field is missing or comes through as null? You need to test these edge cases to make sure that the Data Mapper doesn&#8217;t just crash when the data isn&#8217;t perfect.<br />

			  <span class="postimagessection_specify alignnone size-medium wp-image-206343" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260515104325/Data-Mapper-options-dialog-highlighting-null-value-handling-e1778867060927.png?w=1000" class="postimages" width="1000" height="553" alt="Data Mapper options dialog highlighting null value handling." />
			  </span>
			<br />
</span></li>
</ul>
<h3><span style="font-weight: 400">Integration Procedures (IPs) testing </span></h3>
<p><span style="font-weight: 400">Omnistudio’s</span> <a href="https://developer.salesforce.com/docs/atlas.en-us.industries_reference.meta/industries_reference/omnistudio_integration_procedure_apis.htm"><span style="font-weight: 400">Integration Procedures </span></a><span style="font-weight: 400">is like an engine room that handles multiple steps and actions at once.</span></p>
<ul>
<li style="font-weight: 400"><b>Orchestration sequence:</b><span style="font-weight: 400"> Verify that the sequence fires in the right order without skips. Does the Integration Procedure call the Data Mapper, then the external API, and then send the email?<br />

			  <span class="postimagessection_specify alignnone size-medium wp-image-206340" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260515104317/Integration-Procedure-canvas-displaying-a-successful-sequential-flow-of-actions-e1778867019298.png?w=1000" class="postimages" width="1000" height="521" alt="Integration Procedure canvas displaying a successful sequential flow of actions." />
			  </span>
			<br />
</span></li>
<li style="font-weight: 400"><b>Error branching and timeouts:</b><span style="font-weight: 400"> It’s critical to test system failures.&#8221; What happens if an external service takes too long to respond (timeout handling)? Does the IP handle the error gracefully, or does the whole screen freeze?</span></li>
<li style="font-weight: 400"><b>Mock services:</b><span style="font-weight: 400"> In your lower testing environments (like sandbox), use mock services to simulate external responses. This lets you test the logic without relying on (or waiting for) real-world third-party systems.</span></li>
</ul>
<h2><b>Pretesting checklist for Omnistudio </b></h2>
<p><span style="font-weight: 400">Before you even write your first test script or click Run, there are some best practices that you should keep in mind. Testing isn&#8217;t just about finding bugs; it’s about making sure that the design actually works for the human using it.</span></p>
<p><span style="font-weight: 400">Here is a checklist of what to consider before you start testing.</span></p>
<h3><span style="font-weight: 400">Verify backend object updates</span></h3>
<p><span style="font-weight: 400">Don&#8217;t get too distracted by the flashy buttons just yet. Start with the win you want. Your primary goal is to verify that the business actually got what it needed. We call this testing the &#8220;happy path,&#8221; the core journey where everything goes right.</span></p>
<p><span style="font-weight: 400">Ask yourself: When the user hits Submit, did the data actually land where it was supposed to? You need to confirm that the backend S-objects or business objects were updated correctly. If the form looks great but the database is empty, the test has failed.</span></p>
<h3><span style="font-weight: 400">Test dynamic UI state changes</span></h3>
<p><span style="font-weight: 400">Omnistudio interfaces are dynamic. They change based on who is looking at them. For example, a Flexcard might display different contextual data depending on the customer.</span></p>
<p><span style="font-weight: 400">Before testing, verify that the UI visibility logic is sound. Does the screen state change correctly when new info is added? Are buttons going gray when they&#8217;re not ready? You need to ensure that the interface is displaying the right information at the right time.</span></p>
<h3><span style="font-weight: 400">Validate responsive UX</span></h3>
<p><span style="font-weight: 400">Step back and think about the user: does the flow feel natural on a phone or desktop? Test responsive tweaks and little touches like smooth transitions — no janky jumps that make users ditch the app mid-task. The Omniscript is the &#8220;face&#8221; of your application, and because it is the part that users touch, it usually receives the highest number of bug reports.</span></p>
<p><span style="font-weight: 400">However, remember that a bad UX isn&#8217;t always a code error, sometimes it&#8217;s just a confusing design. When planning your tests, think about the flow. Is it intuitive? Since the root cause of a UI bug is often actually a backend configuration issue, ensuring a smooth UX means making sure that the backend supports the frontend easily.</span></p>
<h3><span style="font-weight: 400">Test error messaging paths</span></h3>
<p><span style="font-weight: 400">What happens when things go wrong? Your testing plan needs to cover more than just success stories. You must account for &#8220;unhappy paths,&#8221; such as validation failures where a user enters the wrong data.</span></p>
<p><span style="font-weight: 400">You also need to verify how the system handles big failures, like an Integration Procedure timeout or an </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_intro_what_is_apex.htm"><span style="font-weight: 400">Apex</span></a><span style="font-weight: 400"> exception. Does the user get a helpful error message, or does the screen just freeze? Check that success pop-ups say things like &#8220;Claim submitted, thanks!&#8221; and errors spell out next steps, like &#8220;Missing phone number, add it here,&#8221; instead of vague tech jargon.</span></p>
<h3><span style="font-weight: 400">Accessibility verification</span></h3>
<p><span style="font-weight: 400">Don&#8217;t leave anyone behind — this is non-negotiable. When designing and testing Omniscripts, you must consider accessibility because the UI needs to work for everyone.</span></p>
<p><span style="font-weight: 400">Your tests should ensure that the application caters to all customers, including those who may be visually impaired. Test for screen readers announcing fields correctly, keyboard-only navigation through your Flexcards, and color contrasts that pop for all eyes.</span></p>
<h3><span style="font-weight: 400">Conditional navigation outcomes</span></h3>
<p><span style="font-weight: 400">Finally, where does the user go next? Omnistudio relies heavily on conditional logic, basically if/else statements that send users down different paths based on their choices.</span></p>
<p><span style="font-weight: 400">You need to verify these alternative paths. If a user clicks Save for Later, does the system actually let them resume later without losing data? If they answer Yes to a specific question, does the navigation direct them to the correct follow-up screen? Make sure that every road leads to the right destination.</span></p>
<h2><span style="font-weight: 400">Next steps</span></h2>
<p><span style="font-weight: 400">There you have it. Tackling Omnistudio testing in the simplest way possible without it being a nightmare of flaky scripts and hidden bugs. Start with your highest-traffic Omniscript. Write one UTAM page object for its first step, validate a single field input and step transition, and run it against your sandbox. Once that passes reliably across two Salesforce releases, expand coverage to the scenarios listed above. Build rock-solid flows with clear messaging and confident deployments that your customers love.</span></p>
<h2><b>Resources</b></h2>
<p><span style="font-weight: 400">Video: </span><a href="https://www.youtube.com/watch?v=FV7u7s7i5CY"><span style="font-weight: 400">FlexCards and OmniScripts | Salesforce Industries</span></a><span style="font-weight: 400"> </span></p>
<p><span style="font-weight: 400">Trailhead: </span><a href="https://trailhead.salesforce.com/content/learn/modules/omnistudio-development-essentials"><span style="font-weight: 400">Omnistudio Development Essentials</span></a><span style="font-weight: 400"> </span></p>
<p><span style="font-weight: 400">Trailhead: </span><a href="https://trailhead.salesforce.com/content/learn/modules/omnistudio-omniscript-fundamentals"><span style="font-weight: 400">Omnistudio Omniscript Fundamentals</span></a></p>
<p><span style="font-weight: 400">Video: </span><a href="https://youtu.be/x7kwVCFpKyM?si=k7V_kyNXkUpMv1q4"><span style="font-weight: 400">Build Low-Code Digital Experiences with All-New OmniStudio  | TDX Bengaluru</span></a><span style="font-weight: 400"> </span></p>
<h2><b>About the authors</b></h2>
<p><b>Anjali Gupta</b><span style="font-weight: 400"> is a Senior Product Manager at Salesforce who is specialised in horizontal cloud and platform. She is focused on Omnistudio roadmap and strategies ensuring customer growth and success. Follow her on </span><a href="https://in.linkedin.com/in/anjigupta"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p><b>Payal Verma</b><span style="font-weight: 400"> is a Content Marketing Analyst at Salesforce with over four years of professional experience in product, brand, and content marketing. She has worked across both B2B and B2C markets, developing and executing content strategies for a wide range of formats, including blogs, social media, scripts, eBooks, and guides. Follow her on </span><a href="https://www.linkedin.com/in/payal-verma-writer-marketer/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/conduct-end-to-end-testing-for-salesforce-omnistudio-based-uis">Conduct End-to-End Testing for Salesforce Omnistudio-based UIs</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/05/conduct-end-to-end-testing-for-salesforce-omnistudio-based-uis/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206339</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260515142805/Generic-B-e1778880497342.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260515142805/Generic-B-e1778880497342.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Use Custom Lightning Types in Agent Script for Rich Agent UI</title>
		<link>https://developer.salesforce.com/blogs/2026/05/use-custom-lightning-types-in-agent-script-for-rich-agent-ui</link>
		<comments>https://developer.salesforce.com/blogs/2026/05/use-custom-lightning-types-in-agent-script-for-rich-agent-ui#respond</comments>
		<pubDate>Tue, 19 May 2026 15:00:49 +0000</pubDate>
		<dc:creator><![CDATA[Mohith Shrivastava]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Apex]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Lightning Web Components]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Agent Script]]></category>
		<category><![CDATA[Agent Script Decoded]]></category>
		<category><![CDATA[Agentic UI]]></category>
		<category><![CDATA[Custom Lightning Types]]></category>
		<category><![CDATA[Headless Experience Layer]]></category>
		<category><![CDATA[lwc]]></category>
		<category><![CDATA[salesforce developers]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206313</guid>
		<description><![CDATA[<p>Use Custom Lightning Types to embed LWCs directly into Agentforce. Build validated forms and rich cards to handle complex enterprise workflows with ease, ensuring a structured and high-fidelity user experience.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/use-custom-lightning-types-in-agent-script-for-rich-agent-ui">Use Custom Lightning Types in Agent Script for Rich Agent UI</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">Agentforce is conversational by default. Users type a message and the agent responds with text. This works great for most use cases, but many enterprise workflows demand more. Businesses need forms with dropdowns, input validation, and structured output, and they need it right inside the agent conversation.</span></p>
<p><a href="https://developer.salesforce.com/docs/platform/lightning-types/guide/lightning-types-core.html"><span style="font-weight: 400">Custom Lightning types</span></a><span style="font-weight: 400"> (CLTs) meet this need. They let you render </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide"><span style="font-weight: 400">Lightning Web Components </span></a><span style="font-weight: 400">(LWC) directly within Agentforce experiences in Lightning Experience, </span><a href="https://help.salesforce.com/s/articleView?id=service.enhanced_chat_v2_intro.htm&amp;type=5"><span style="font-weight: 400">Enhanced Chat v2</span></a><span style="font-weight: 400">, and Experience Builder. You can collect structured input through a custom form and display results as rich, styled cards or in your own custom-designed user interface. CLTs are also the foundation of </span><b>Headless</b><a href="https://www.salesforce.com/platform/orchestration-platform/"><b> Experience Layer</b></a><span style="font-weight: 400">, announced at TDX 2026, which extends rich agentic UI beyond Salesforce — to Slack, ChatGPT, and more. </span><b>Define Once, Deploy Anywhere, Secure Everywhere</b><span style="font-weight: 400"> — that&#8217;s the headless experience layer vision. Building with CLTs today means you&#8217;re already working with the primitives that power the next generation of multi-surface agentic experiences.</span></p>
<p><span style="font-weight: 400">This post walks through how to work with CLTs in Agent Script to render a rich experience within Agentforce. All code examples come from the </span><a href="https://github.com/trailheadapps/agent-script-recipes/tree/main/force-app/main/02_actionConfiguration/customLightningTypes"><span style="font-weight: 400">Agent Script Custom Lightning Type</span></a><span style="font-weight: 400"> example recipe.</span></p>
<h2><b>What are custom Lightning types in Agentforce?</b></h2>
<p><span style="font-weight: 400">Custom Lightning types create a bridge between your Agent Script, your actions backed by Apex or flows, and your LWC components. They override the default text-based UI with custom LWC components for both input and output.</span></p>
<p><span style="font-weight: 400">Below is an example of custom Lightning types in action within the Agentforce experience.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206315" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260513112452/image4.png?w=586" class="postimages" width="586" height="1000" alt="Lightning types in the Agentforce experience" />
			  </span>
			</p>
<p><span style="font-weight: 400">The connection flows through three layers:</span></p>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/blogs/2026/02/agent-script-decoded-intro-to-agent-script-language-fundamentals"><b>Agent Script</b></a><span style="font-weight: 400"> declares which action parameters need custom UI</span></li>
<li style="font-weight: 400"><b>A Lightning Type Bundle</b><span style="font-weight: 400"> maps each parameter type to an Apex data shape and an LWC component</span></li>
<li style="font-weight: 400"><b>An LWC</b><span style="font-weight: 400"> renders the user interface inside the conversation</span></li>
</ul>
<p><span style="font-weight: 400">This three-layer wiring enables you to keep a clean separation of concerns. Your Agent Script handles orchestration, Apex handles the data shape definition and the business logic, and LWC handles the final user experience.</span><span style="font-weight: 400"><br />
</span><span style="font-weight: 400"></span></p>
<p><span style="font-weight: 400">The following diagram shows how a custom Lightning type is mapped with Agent Script and LWCs.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206316" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260513112527/image2_45401c-e1778696741486.png?w=1000" class="postimages" width="1000" height="546" alt="Custom Lightning type mapping with Agent Script action and LWCs" />
			  </span>
			</p>
<p><span style="font-weight: 400">The following sections describe how to build and wire this connection step-by-step.</span></p>
<h2><b>Step 1: Prepare and deploy your Apex and LWC </b></h2>
<h3><b>Defining the Apex data shape</b></h3>
<p>Each CLT needs an Apex class that defines the data structure. Use inner classes within your service class, with each field annotated with <code>@InvocableVariable</code>. The Lightning Type Bundle references these inner classes as the schema source.</p>
<pre language="apex">public class CaseInput {
    @InvocableVariable(label='Subject' required=true)
    public String subject;

    @InvocableVariable(label='Priority')
    public String priority;

    @InvocableVariable(label='Description')
    public String description;
}
</pre>
<p>One important detail: the wrapper class field names must exactly match the action parameter names from your Agent Script. If your action input is called <code>case_data</code>, the request wrapper field must also be named <code>case_data</code>. A mismatch here silently breaks the entire CLT connection.</p>
<p><b>View the code</b><b>:</b> See the full<a href="https://www.google.com/search?q=https://github.com/trailheadapps/agent-script-recipes"> </a><a href="https://github.com/trailheadapps/agent-script-recipes/blob/main/force-app/main/02_actionConfiguration/customLightningTypes/classes/CaseSubmissionService.cls"><u>CaseSubmissionService.cls</u></a> for the complete Apex implementation including the request/response wrappers.</p>
<h3><b>Building the editor and renderer LWC</b></h3>
<p>The editor LWC collects user input inside the chat. Two things make it work with Agentforce: the meta XML must target <code>lightning__AgentforceInput</code> with a <code>targetType</code> matching the CLT name, and the component must dispatch a <code>valuechange</code> event whenever the user modifies the form.</p>
<pre class="wp-block-code language-xml"> &lt;target&gt;lightning__AgentforceInput&lt;/target&gt;
 &lt;targetType name="c__caseInput" /&gt;
</pre>
<p>The <code>valuechange</code> event payload field names must match the Apex CaseInput class fields. The platform listens for this event to capture and forward the form data to your Apex action.</p>
<p>Here is the simple example snippet showing the <code>valuechange</code> event in practice:</p>
<pre language="js">import { LightningElement } from 'lwc';

export default class CustomCaseForm extends LightningElement {
    // Local state variables for the form
    subject = '';
    priority = 'Medium';
    description = '';

    // Handler triggered when the user types in the Subject input
    handleSubjectChange(event) {
        // 1. Update the specific field that changed
        this.subject = event.target.value;

        // 2. Dispatch the FULL state of the form
        this.dispatchFormState();
    }

    // You would have similar handlers for Priority and Description...

    // Centralized method to dispatch the complete current state
    dispatchFormState() {
        this.dispatchEvent(
            new CustomEvent('valuechange', {
                detail: {
                    value: {
                        // ALWAYS include all fields to match the Apex CaseInput class
                        subject: this.subject,
                        priority: this.priority,
                        description: this.description
                    }
                }
            })
        );
    }
}
</pre>
<p>The renderer LWC displays the result card after the action completes. It targets <code>lightning__AgentforceOutput</code> with a <code>sourceType</code> (not <code>targetType</code>). The component receives the full result object via <code>@api value</code> and uses getters to render the fields with Salesforce Lightning Design System styling.</p>
<pre class="wp-block-code language-xml">&lt;target&gt;lightning__AgentforceOutput&lt;/target&gt;
&lt;sourceType name="c__caseResult" /&gt;</pre>
<p><b>View the code</b><b>:</b><span style="font-weight: 400"> See the full </span><a href="https://github.com/trailheadapps/agent-script-recipes/tree/main/force-app/main/02_actionConfiguration/customLightningTypes/lwc/caseInputEditor"><span style="font-weight: 400">caseInputEditor</span></a><span style="font-weight: 400"> and </span><a href="https://github.com/trailheadapps/agent-script-recipes/tree/main/force-app/main/02_actionConfiguration/customLightningTypes/lwc/caseResultRenderer"><span style="font-weight: 400">caseResultRenderer</span></a> <span style="font-weight: 400">components in the repo.</span></p>
<h3><b>Wiring Lightning type bundles</b></h3>
<p>The Lightning Type Bundle is a folder containing JSON configuration files. The <code>schema.json</code> file points to your Apex inner class using <code>$</code> syntax. For example, <code>c__CaseSubmissionService$CaseInput</code> references the <code>CaseInput</code> inner class.</p>
<pre language="json">{
    "title": "Case Input",
    "lightning:type": "@apexClassType/c__CaseSubmissionService$CaseInput"
}
</pre>
<p>A second file tells the platform which LWC component to render. Input types use an <code>editor.json</code> file. Output types use a <code>renderer.json</code> file. The <code>"$"</code> root key means “override the UI for the entire type”.</p>
<pre language="json">{
    "editor": {
        "componentOverrides": {
            "$": { "definition": "c/caseInput" }
        }
    }
}
</pre>
<p><b>View the code</b><b>:</b><span style="font-weight: 400"> See the complete</span> <a href="https://github.com/trailheadapps/agent-script-recipes/tree/main/force-app/main/02_actionConfiguration/customLightningTypes/lightningTypes"><span style="font-weight: 400">Lightning Type Bundle files</span></a><span style="font-weight: 400"> in the repository</span></p>
<h2><b>Step 2: Create the agent action asset</b></h2>
<p>The <code>GenAiFunction</code> is the metadata layer that registers your action with Agentforce. Without it, Agentforce cannot resolve your CLT bindings. Every CLT-based action needs a corresponding <code>GenAiFunction</code> in the <code>genAiFunctions</code> folder.</p>
<p>The function metadata XML points to your Apex invocable class:</p>
<pre class="wp-block-code language-xml">&lt;GenAiFunction xmlns="http://soap.sforce.com/2006/04/metadata"&gt;
    &lt;developerName&gt;Submit_Case&lt;/developerName&gt;
    &lt;invocationTarget&gt;CaseSubmissionService&lt;/invocationTarget&gt;
    &lt;invocationTargetType&gt;apex&lt;/invocationTargetType&gt;
    &lt;masterLabel&gt;Submit Case&lt;/masterLabel&gt;
&lt;/GenAiFunction&gt;</pre>
<p>Alongside this XML, you define input and output schema JSON files. These schemas declare the <code>lightning:type</code> for each parameter, which is how the platform knows to look up your Lightning Type Bundle.</p>
<p>The input schema marks <code>case_data</code> as a user input with the CLT type <code>c__caseInput</code>:</p>
<pre language="json">{
    "properties": {
        "case_data": {
            "lightning:type": "c__caseInput",
            "copilotAction:isUserInput": true
        }
    }
}
</pre>
<p>The output schema marks <code>case_result</code> as displayable with the CLT type <code>c__caseResult</code>:</p>
<pre language="json">{
    "properties": {
        "case_result": {
            "lightning:type": "c__caseResult",
            "copilotAction:isDisplayable": true
        }
    }
}
</pre>
<p><span style="font-weight: 400">See the full </span><a href="https://github.com/trailheadapps/agent-script-recipes/tree/main/force-app/main/02_actionConfiguration/customLightningTypes/genAiFunctions/Submit_Case"><span style="font-weight: 400">GenAI Function files</span></a><span style="font-weight: 400"> in the repository.</span></p>
<p><span style="font-weight: 400">You can build the Agent action metadata directly in the Salesforce user interface. </span></p>
<p><span style="font-weight: 400">To do this, navigate from Setup to Agentforce Assets, then select Actions and create a new agent action by following below steps.</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Setup → Agentforce Assets → Click on Actions → Click on New Agent Action</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Fill in the agent action details like Inputs and Outputs description and data types.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Ensure you are setting up the Input Rendering and Output Rendering with the right custom Lightning types</span></li>
</ol>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206317" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260513112615/image3_5c288a-e1778696801549.png?w=1000" class="postimages" width="1000" height="621" alt="The UI for creating an Agentforce action under Agentforce Assets" />
			  </span>
			</p>
<h2><b>Step 3: Define the action in Agent Script</b></h2>
<p><span style="font-weight: 400">While the following sections cover </span><span style="font-weight: 400">action definition</span><span style="font-weight: 400"> via metadata in detail, the Agentforce Builder UI enforces proper naming constraints automatically and is the recommended starting point for most teams.</span></p>
<p>Everything hinges on the action definition in your Agent Script. Two properties control CLT rendering: <code>complex_data_type_name</code> links a parameter to a Lightning type, and <code>is_user_input</code> or <code>is_displayable</code> tells the platform to render the custom component.</p>
<pre language="yaml">actions:
   submit_case:
      inputs:
         case_data: object
            is_user_input: True
            complex_data_type_name: "c__caseInput"
      outputs:
         case_result: object
            complex_data_type_name: "c__caseResult"
            is_displayable: True
      target: "apex://CaseSubmissionService"
      source: "Submit_Case"
</pre>
<p>Notice the <code>source</code> property at the bottom. This is a critical piece that connects Agent Script to an Agentforce action (represented as <a href="https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_genaifunction.htm"><u><code>GenAiFunction</code></u></a><b> </b>in the metadata as discussed in step 2<b>)</b>. The value <code>“</code><code>Submit_Case</code><code>"</code> must match the developer name of a <code>GenAiFunction</code> metadata file in your project.</p>
<h3><b>Triggering the CLT editor in Agent Script</b></h3>
<p>One detail that is easy to miss: your Agent Script subagent instructions must include the <code>user_input</code><code> </code>keyword when calling the action. This tells the platform to render the CLT editor component instead of collecting input through plain text.</p>
<pre language="yaml">reasoning:
   instructions: -&gt;
      | Call {!@actions.submit_case} action's user_input tool to collect the case details.
</pre>
<p><span style="font-weight: 400">Without this phrase, the platform falls back to text-based input collection. The </span><span style="font-weight: 400">user_input</span><span style="font-weight: 400"> keyword is what activates the entire CLT editor rendering pipeline.</span></p>
<p><b>Important note:</b><span style="font-weight: 400"> In Agent Script the subagent’s action name and the subagent reasoning action name should be exactly the same.</span></p>
<p><b>View the code</b><b>:</b><span style="font-weight: 400"> See the full instructions for the</span> <a href="https://github.com/trailheadapps/agent-script-recipes/blob/main/force-app/main/02_actionConfiguration/customLightningTypes/aiAuthoringBundles/CustomLightningTypes/CustomLightningTypes.agent"><span style="font-weight: 400">Agent Script</span></a><span style="font-weight: 400"> in the repository.</span></p>
<h2><span style="font-weight: 400">The alignment checklist</span></h2>
<p><span style="font-weight: 400">CLTs require precise naming across multiple files. If any link in the chain breaks, the platform silently falls back to the default text UI with no error message. The following table shows what must stay aligned:</span></p>
<table>
<thead>
<tr>
<th><span style="font-weight: 400">What</span></th>
<th><span style="font-weight: 400">Where</span></th>
<th><span style="font-weight: 400">Must Match</span></th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="font-weight: 400">Type name</span></td>
<td><span style="font-weight: 400">Agent Script </span><span style="font-weight: 400">complex_data_type_name</span></td>
<td><span style="font-weight: 400">Lightning type folder name</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Source name</span></td>
<td><span style="font-weight: 400">Agent Script </span><span style="font-weight: 400">source</span></td>
<td><span style="font-weight: 400">GenAI Function </span><span style="font-weight: 400">developerName</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Apex class</span></td>
<td><span style="font-weight: 400">Lightning type </span><span style="font-weight: 400">schema.json</span></td>
<td><span style="font-weight: 400">Inner class in your service</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">LWC name</span></td>
<td><span style="font-weight: 400">editor.json</span><span style="font-weight: 400"> or </span><span style="font-weight: 400">renderer.json</span></td>
<td><span style="font-weight: 400">Your LWC component name</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">LWC target</span></td>
<td><span style="font-weight: 400">Meta XML </span><span style="font-weight: 400">targetType</span><span style="font-weight: 400"> / </span><span style="font-weight: 400">sourceType</span></td>
<td><span style="font-weight: 400">Lightning type name</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Wrapper fields</span></td>
<td><span style="font-weight: 400">Apex request/response classes</span></td>
<td><span style="font-weight: 400">Agent Script action parameter names</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">Check these connections first when debugging. The silent fallback behavior makes misalignment hard to spot.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206318" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260513112711/image1_d51cd1-e1778696844677.png?w=1000" class="postimages" width="1000" height="600" alt="User interface for building an agent with custom Lightning types in the new Agentforce Builder to adhere to precise naming conventions" />
			  </span>
			</p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">Custom Lightning types bring the power of Lightning Web Components into Agentforce conversations. You can collect precise, validated data through custom forms and present results in rich, branded cards, all without leaving the Agentforce experience.</span></p>
<p><span style="font-weight: 400">The key pieces are the Agent Script action definition and the action in the Agentforce assets, the Lightning Type Bundle, and the LWC components. Each layer has a specific job, and the naming alignment across all four layers is what makes the whole system work.</span></p>
<p><span style="font-weight: 400">This pattern opens up new possibilities for building enterprise-grade agent experiences. Forms with dropdowns and validation, structured result cards with conditional styling, and multistep workflows can all be rendered inline within the conversation.</span></p>
<p><span style="font-weight: 400">The complete working example is available in the </span><a href="https://github.com/trailheadapps/agent-script-recipes"><span style="font-weight: 400">Agent Script Recipes</span></a><span style="font-weight: 400"> repository under the </span><a href="https://github.com/trailheadapps/agent-script-recipes/tree/main/force-app/main/02_actionConfiguration/customLightningTypes"><span style="font-weight: 400">customLightningTypes</span></a><span style="font-weight: 400"> recipe. Clone the repo, deploy to a scratch org or Developer Edition org, and start building your own CLT-powered Agentforce agents.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/lightning-types/overview"><span style="font-weight: 400">Lightning Types Developer Guide</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/trailheadapps/agent-script-recipes"><span style="font-weight: 400">Agent Script Recipes Sample App</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/ai/agentforce/guide/agent-script.html"><span style="font-weight: 400">Agent Script Documentation</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the authors</span></h2>
<p><b>Mohith Shrivastava</b><span style="font-weight: 400"> is a Principal Developer Advocate at Salesforce with 15 years of experience building enterprise-scale products on the Agentforce 360 Platform. Mohith is currently among the lead contributors on Salesforce Stack Exchange, a developer forum where Salesforce Developers can ask questions and share knowledge. You can follow him on </span><a href="https://www.linkedin.com/in/mohith-shrivastava-9a36464a/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p><b>Parvinder Singh</b><span style="font-weight: 400"> is a Senior Forward Deployed Engineer at Salesforce with deep expertise in Agentforce and Agent Script, specializing in building intelligent, customized agent experiences for enterprise customers. He is passionate about sharing developer insights with the community and bringing practical, field-tested patterns to life through hands-on content.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/use-custom-lightning-types-in-agent-script-for-rich-agent-ui">Use Custom Lightning Types in Agent Script for Rich Agent UI</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/05/use-custom-lightning-types-in-agent-script-for-rich-agent-ui/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206313</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260513110616/Thumbnail_Headshot-2-e1778695588316.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260513110616/Thumbnail_Headshot-2-e1778695588316.png?w=1000" medium="image" />
	</item>
		<item>
		<title>The New Agentforce Metadata and Development Lifecycle</title>
		<link>https://developer.salesforce.com/blogs/2026/05/new-agentforce-metadata-and-development-lifecycle</link>
		<comments>https://developer.salesforce.com/blogs/2026/05/new-agentforce-metadata-and-development-lifecycle#respond</comments>
		<pubDate>Thu, 14 May 2026 19:55:24 +0000</pubDate>
		<dc:creator><![CDATA[Alba Rivas]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Salesforce Releases]]></category>
		<category><![CDATA[Agent Script]]></category>
		<category><![CDATA[alm]]></category>
		<category><![CDATA[CI/CD]]></category>
		<category><![CDATA[GenAiPlannerBundle]]></category>
		<category><![CDATA[Metadata API]]></category>
		<category><![CDATA[salesforce developers]]></category>
		<category><![CDATA[Spring '26]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206288</guid>
		<description><![CDATA[<p>Streamline your CI/CD pipelines by leveraging the new versioned metadata model and human-readable Agent Scripts. Learn how bundleized assets eliminate "global ripples" and simplify peer reviews for more reliable agent deployments.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/new-agentforce-metadata-and-development-lifecycle">The New Agentforce Metadata and Development Lifecycle</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://help.salesforce.com/s/articleView?id=ai.agent_builder_intro.htm&amp;type=5"><span style="font-weight: 400">new Agentforce Builder</span></a><span style="font-weight: 400"> (GA Spring &#8217;26) introduces a completely revamped experience for authoring and testing AI agents. It provides a high-velocity, &#8220;safe-to-fail&#8221; environment where you can iterate on complex logic without worrying about breaking your live production agents.</span></p>
<p><span style="font-weight: 400">As a Salesforce developer, you know that a new building experience usually means changes under the hood. To support this faster, safer way of working, the underlying metadata had to evolve. This post walks through the new developer workflow, why the metadata changed, and how to structure your packages for deployment.</span></p>
<h2><b>The new authoring workflow: from script to metadata</b></h2>
<p><span style="font-weight: 400">To understand the metadata changes, it helps to understand the new mental model for building agents. The new Agentforce Builder introduces a clear separation between your &#8220;intent&#8221; (what you want the agent to do) and the engine&#8217;s &#8220;execution&#8221; (the active metadata).</span></p>
<p><span style="font-weight: 400">Here is how the lifecycle works:</span></p>
<ol>
<li><b><span>Write and build:</span></b><span> You author your agent using the Agentforce Builder UI or directly via an </span><b><span>Agent Script</span></b><span>. This human-readable script is saved as a single </span><code><span>.agent</span></code><span> file within a new </span><code><span>AiAuthoringBundle</span></code><span> metadata type, representing your agent’s configuration. At this stage, your work is a draft.</span></li>
<li><b><span>Iterate and preview:</span></b><span> You test your draft using preview modes (like Simulation mode, which safely isolates your tests from org data).</span></li>
<li><b><span>Publish:</span></b><span> When you are happy with the draft, you </span><i><span>commit</span></i><span> your version. </span><b><span>This is the crucial moment.</span></b><span> It is only during commit that the platform translates your </span><code>.agent</code><span> script into the active </span><code>Bot</code><span> and </span><code>GenAiPlannerBundle</code><span> metadata that is actually used to run your agent.</span></li>
</ol>
<h2><b>Why the metadata changed: bundling local assets</b></h2>
<p>So, why did we change the <a href="https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_genaiplannerbundle.htm"><u><code>GenAiPlannerBundle</code><span> metadata</span></u></a><span> structure for this new builder? The answer comes down to isolation and deployment reliability.</span><br />
<span></span></p>
<p><span>Historically, Agentforce assets were global. If you modified a shared subagent for one agent, you triggered a &#8220;global ripple&#8221; that instantly impacted every other agent using that subagent. To solve this, Salesforce introduced </span><b><span>Local Assets</span></b><span> (including local subagents and local actions), which clone a global asset and anchor it specifically to one agent version. Once local, you can edit it safely without breaking anything else.</span><br />
<span></span></p>
<p><span>While Local Assets have been available for a while, the </span><code>GenAiPlannerBundle</code> metadata needed to be updated to fully support them in CI/CD pipelines. Previously, agent metadata was distributed across metadata types. The main change in the new <code>GenAiPlannerBundle</code> is <i>bundleization</i><b>.</b> In short, we restructured the metadata to ensure that <i>all</i> dependent agent assets related to any given version are deployed and available in one centralized folder structure. This prevents deployment errors and ensures your agent has everything it needs to function exactly as it did in preview.</p>
<h2><b>Under the hood: the new metadata structure</b></h2>
<p><span style="font-weight: 400">Instead of scattered files, the directory structure now cleanly encapsulates everything specific to each agent version.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206308" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260513105223/image3_7fea0a.png?w=594" class="postimages" width="594" height="1000" alt="Diagram showing the GenAiPlannerBundle directory structure with planner file, localActions subfolder, and input/output schema.json files" />
			  </span>
			</p>
<p><code>AiAuthoringBundles</code><span> is a directory that contains all the design-time artifacts for the agent. The bundle supports multiple versioned subdirectories (e.g., </span><code>AgentName_1</code><span>, </span><code>AgentName_2</code><span>, etc.), each with its own </span><code>.agent</code><span> file and </span><code>.bundle-meta.xml</code><span> descriptor</span><span>. These correspond to different lifecycle states such as draft and committed versions of the agent. Within each version:</span></p>
<ul>
<li><span>The </span><code>.agent</code><span> file holds the agent&#8217;s configuration written in Agent Script: name, label, description, system instructions, subagent definitions, reasoning instructions, variables, conditionals, and tool/action references.</span></li>
<li><span>The metadata descriptor (</span><code>.bundle-meta.xml)</code><span> declares the bundleType (e.g., AGENT) and the target agent version it points to.</span></li>
</ul>
<p><code><span>Bot</span></code><span> contains information common to all versions of the agent, such as the agent type and useful settings like whether to log private data. It also holds agent-level context variables, which are distinct from the conversation variables that live inside each </span><code><span>BotVersion</span></code><span>. </span><code><span>BotVersion </span></code><span>details include conversation variables, welcome message, and transfer conversation message.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206310" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260513105333/image1_7095ac.png?w=958" class="postimages" width="958" height="626" alt="Diagram showing plannerAction directory content alongside the directory structure of the planner bundle" />
			  </span>
			</p>
<p><code></code><code>GenAiPlannerBundle</code> contains all data relevant to each published version of an agent. Within each version you can find:</p>
<ul>
<li><code>.genAiPlannerBundle</code>, which is the main metadata file containing the agent&#8217;s runtime configuration: subagents, instructions, actions, and orchestration logic.</li>
<li><code>agentGraph</code>, a <code>.json</code> file encoding all transitions and conditions between subagents.</li>
<li><code>agentScript</code>, an encoded copy of the version agent script. This is a snapshot kept for sync-detection purposes, so Agent Builder can tell the user if the script is out of sync with the published/committed metadata.</li>
<li><code>plannerActions</code><span style="color: #4a4a4a;font-family: 'Salesforce Sans', Arial, sans-serif">, agent-level actions (not tied to any specific subagent).</span></li>
<li><code>localActions</code>, subagent-scoped actions, nested under their subagent folder. The <code>input/schema.json</code> and <code>output/schema.json</code> files define the typed parameters for each action.</li>
</ul>
<p>Note: <code>GenAiPlannerBundle</code> versions are auto-incremented in commit order, so they won&#8217;t necessarily match <code>AiAuthoringBundle</code> version numbers.</p>
<p>Remember that both <code>Bot</code> and <code>GenAiPlannerBundle</code> metadata are created automatically when a version is committed, and <code>AiAuthoringBundle</code>, <code>agentScript</code><span>,</span><span> and </span><code>agentGraph</code> are only present on the new Agentforce Builder agent metadata.</p>
<h3><b>Example: </b><b><code>GenAiPlannerBundle</code></b><b> XML</b></h3>
<p><span style="font-weight: 400">When you look at the XML, you&#8217;ll see it now clearly reflects these localized subagents and actions:</span></p>
<pre class="wp-block-code language-xml">&lt;GenAiPlannerBundle&gt;
   &lt;description&gt;New agent description&lt;/description&gt;
   &lt;localTopicLinks&gt;
       &lt;genAiPluginName&gt;Retrieve_data_from_the_Knowledge_Base_16jxx0000001234&lt;/genAiPluginName&gt;
   &lt;/localTopicLinks&gt;
   &lt;localTopics&gt;
       &lt;fullName&gt;Retrieve_data_from_the_Knowledge_Base_16jxx0000001234&lt;/fullName&gt;
       &lt;description&gt;Use the retriever action&lt;/description&gt;
       &lt;pluginType&gt;Topic&lt;/pluginType&gt;
       &lt;masterLabel&gt;Retrieve data from Knowledge Base&lt;/masterLabel&gt;
       &lt;genAiPluginInstructions&gt;
           &lt;description&gt;You are an AI Agent.&lt;/description&gt;
           &lt;sortOrder&gt;1&lt;/sortOrder&gt;
       &lt;/genAiPluginInstructions&gt;
       &lt;localActionLinks&gt;
            &lt;functionName&gt;File_test_retriever_179xx0000001234&lt;/functionName&gt;
       &lt;/localActionLinks&gt;
       &lt;localActions&gt;
           &lt;fullName&gt;File_test_retriever_179xx0000001234&lt;/fullName&gt;
           &lt;invocationTarget&gt;File_test_retriever_1234&lt;/invocationTarget&gt;
           &lt;invocationTargetType&gt;apex:testRetriever&lt;/invocationTargetType&gt;
           &lt;source&gt;File_test_retriever&lt;/source&gt;
       &lt;/localActions&gt;
   &lt;/localTopics&gt;
   &lt;plannerType&gt;AiCopilot__ReAct&lt;/plannerType&gt;
&lt;/GenAiPlannerBundle&gt;</pre>
<p>Each <code>&lt;localTopics&gt;</code> block defines a subagent&#8217;s full configuration: its unique API name, description, plugin type, deterministically ordered instructions, and all the local actions available to it. Optionally, a reference to the source subagent may also be present.</p>
<p>Each <code>&lt;localActions&gt;</code> block defines the action&#8217;s name, master label, invocation target and type, and optionally a <code>source</code> reference to the global action it was cloned from. Additional UX-related settings, such as confirmation behavior and loading text configuration, may also be included.</p>
<h3><b>Sample package.xml with metadata of new model</b></h3>
<p>To successfully retrieve and deploy your agents built with the new Agentforce Builder, ensure your <code>package.xml</code> includes the new bundle types. Below is a sample manifest designed for the Spring ’26 metadata structure.</p>
<pre class="wp-block-code 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;*&lt;/members&gt;
        &lt;name&gt;GenAiFunction&lt;/name&gt;
    &lt;/types&gt;
    &lt;types&gt;
        &lt;members&gt;*&lt;/members&gt;
        &lt;name&gt;Bot&lt;/name&gt;
    &lt;/types&gt;
    &lt;types&gt;
        &lt;members&gt;*&lt;/members&gt;
        &lt;name&gt;GenAiPlannerBundle&lt;/name&gt;
    &lt;/types&gt;
    &lt;types&gt;
        &lt;members&gt;*&lt;/members&gt;
        &lt;name&gt;GenAiPlugin&lt;/name&gt;
    &lt;/types&gt;
    &lt;types&gt;
        &lt;members&gt;*&lt;/members&gt;
        &lt;name&gt;AiAuthoringBundle&lt;/name&gt;
    &lt;/types&gt;
    &lt;version&gt;66.0&lt;/version&gt;
&lt;/Package&gt;</pre>
<p><span style="font-weight: 400">Note: This manifest uses API version 66.0, which is required to support GenAiPlannerBundle and the new agent metadata types in this example.</span></p>
<h3><b>Developer tips for the new Agentforce Builder</b></h3>
<p><span style="font-weight: 400">Here are two ways to take advantage of development lifecycle improvements enabled by this new architecture:</span></p>
<ul>
<li><b>Use human-readable script files to streamline code reviews.</b> In the legacy Agentforce Builder world, reviewing agent changes meant comparing complex, scattered XML files in your pull requests. Now, you can compare the human-readable <code>.agent</code> script files. This makes peer reviews significantly faster and easier to understand.</li>
<li><b>Use </b><code>AiAuthoringBundle</code><span> </span><b><span>to</span></b><span> </span><b><span>simplify CI/CD.</span></b><span> For most pipelines, you can keep just the </span><code>AiAuthoringBundle</code><span> </span><span><span><span>in your source control. When you need the agent in a new org, you deploy it, version commit to generate the runtime metadata, and finally activate it. This can be done automatically using the following commands:</span></span></span><code>sf project deploy start --metadata AiAuthoringBundle --target-org my-org</code><code>sf agent publish authoring-bundle --api-name MyAuthoringBundle --target-org my-org<br />
</code><code><br />
sf agent activate --api-name MyAuthoringBundle --version 2 --target-org my-org</code>If your team wants a fully automated pipeline with no manual steps, you can maintain both <code>AiAuthoringBundle</code> and <code>GenAiPlannerBundle</code><span> in your source control and deploy them</span> simultaneously, achieving a committed state. The system recognizes the incoming script alongside its pre-realized metadata and skips the manual commit step entirely, so your agent is live immediately after deployment.<code>sf project deploy start --metadata AiAuthoringBundle,GenAiPlannerBundle --target-org my-org</code></li>
</ul>
<h2><b>Conclusion</b></h2>
<p><span style="font-weight: 400">The bundleized metadata structure is what makes the isolated, version-safe authoring possible in the new Agentforce Builder. With it, your metadata is cleaner, your code reviews are easier, and your deployments are more reliable. </span></p>
<p><span style="font-weight: 400">Get started with Agent Script and the new Agentforce Builder by completing </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">this Trailhead module</span></a><span style="font-weight: 400">, reading more </span><a href="https://developer.salesforce.com/blogs/2026/02/agent-script-decoded-intro-to-agent-script-language-fundamentals"><span style="font-weight: 400">on our blog</span></a><span style="font-weight: 400">, watching our </span><a href="https://www.youtube.com/playlist?list=PLgIMQe2PKPSJNLEQTPkr06gKhjnHgI1Jb"><span style="font-weight: 400">Agent Script decoded playlist on YouTube</span></a><span style="font-weight: 400">, or registering for one of the upcoming </span><a href="https://developer.salesforce.com/events#upcomingevents"><span style="font-weight: 400">Agentforce Builder workshops</span></a><span style="font-weight: 400">.</span></p>
<h2><b>Resources</b></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Release Notes:</span>
<ul>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=ai.agent_versions_lifecycle.htm&amp;type=5"><span style="font-weight: 400">Versioning and Editing Agents</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_einstein_agentforce_versioning_improvements.htm&amp;release=256&amp;type=5"><span style="font-weight: 400">Test and Customize your Agents with Improved Agent Versions</span></a></li>
</ul>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">Help Docs:</span>
<ul>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=ai.copilot_actions_edit_reference.htm&amp;type=5"><span style="font-weight: 400">Editing Standard Agent Action Reference Actions </span></a></li>
</ul>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">Packaging:</span>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/atlas.en-us.pkg2_dev.meta/pkg2_dev/dev2gp_packageable_agentforce_md.htm"><span style="font-weight: 400">Package Agentforce Metadata Components</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/atlas.en-us.pkg2_dev.meta/pkg2_dev/dev2gp_package_agent_templates.htm"><span style="font-weight: 400">Develop and Package Agent Templates Using Scratch Orgs </span></a></li>
</ul>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">Metadata updates:</span>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_genaiplannerbundle.htm"><span style="font-weight: 400">GenAiPlannerBundle</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_genaifunction.htm"><span style="font-weight: 400">GenAiFunction</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_genaiplugin.htm"><span style="font-weight: 400">GenAiPlugin</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_genaiplugininstructiondef.htm"><span style="font-weight: 400">GenAiPluginInstructionDef</span></a></li>
</ul>
</li>
</ul>
<p><b>Shreyas</b><span style="font-weight: 400"> is a Lead Software Engineer at Salesforce AI in San Francisco and a Fellow of the Institute of Analytics (FIoA). As a lead member of the Agentforce Core Platform team, he co-designed and led the Agentforce Versioning System and Local Assets Configuration changes to bring enterprise-grade ALM to Agentforce. </span><span style="font-weight: 400">You can follow him on</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><b>Alba Rivas</b><span style="font-weight: 400"> works as a Principal Developer Advocate at Salesforce. You can follow her on</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/05/new-agentforce-metadata-and-development-lifecycle">The New Agentforce Metadata and Development Lifecycle</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/05/new-agentforce-metadata-and-development-lifecycle/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206288</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260514111122/DoubleHeadshot-4-1-e1778782304152.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260514111122/DoubleHeadshot-4-1-e1778782304152.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Expose Custom Apex as a Hosted MCP Tool for Agents</title>
		<link>https://developer.salesforce.com/blogs/2026/05/expose-custom-apex-as-a-hosted-mcp-tool-for-agents</link>
		<comments>https://developer.salesforce.com/blogs/2026/05/expose-custom-apex-as-a-hosted-mcp-tool-for-agents#respond</comments>
		<pubDate>Wed, 13 May 2026 16:56:25 +0000</pubDate>
		<dc:creator><![CDATA[Mohith Shrivastava]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Apex]]></category>
		<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[New Developments]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[Developer Preview]]></category>
		<category><![CDATA[Headless 360]]></category>
		<category><![CDATA[Integrations]]></category>
		<category><![CDATA[MCP]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206291</guid>
		<description><![CDATA[<p>Shift from UI-first to agent-first with Headless 360. Learn how to expose your Apex logic as Salesforce Hosted MCP tools, allowing AI agents like Claude or Cursor to discover and invoke your business intelligence directly via the Model Context Protocol.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/expose-custom-apex-as-a-hosted-mcp-tool-for-agents">Expose Custom Apex as a Hosted MCP Tool for Agents</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><span style="font-weight: 400">Salesforce has always been a business process platform. For over two decades, developers have encoded complex business logic, such as deal scoring, customer segmentation, and approval routing, into Apex classes that power the world’s largest enterprises. But that logic has historically been consumed through Salesforce’s own UI layers: Lightning pages, flows, and Visualforce. The business intelligence lives on the platform, locked behind a browser tab.</span></p>
<p><a href="https://www.salesforce.com/news/stories/salesforce-headless-360-announcement/"><span style="font-weight: 400">Headless 360</span></a><span style="font-weight: 400"> and </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide"><span style="font-weight: 400">Salesforce Hosted MCP Servers</span></a><span style="font-weight: 400"> introduce a shift from UI-first to agent-first. Every capability on the platform — data access, business logic, automation — becomes consumable through CLIs, APIs, and the </span><a href="https://modelcontextprotocol.io/docs/getting-started/intro"><span style="font-weight: 400">Model Context Protocol (MCP)</span></a><span style="font-weight: 400">, an open standard that lets AI agents discover and call tools hosted on external servers. AI agents running in Slack, Claude, ChatGPT, Cursor, or any MCP-compatible client can now reach into Salesforce and invoke your business logic directly. No login screen. No navigation. No UI at all.</span></p>
<p><span style="font-weight: 400">In this blog post, we’ll show you how to write custom Apex business logic, expose it as a Hosted MCP Server on Salesforce, deploy the necessary metadata to configure authentication, and connect an external AI agent like Claude to invoke your logic.</span></p>
<h2><b>What makes Salesforce Hosted MCP different</b></h2>
<p><span style="font-weight: 400">A Salesforce Hosted MCP Server is a platform-managed endpoint that exposes Apex actions, Apex REST, and external APIs as discoverable tools for AI agents through the Model Context Protocol. Unlike self-hosted MCP servers, it runs on Salesforce infrastructure handling authentication, tool discovery, and request routing without external deployment.</span></p>
<p><span style="font-weight: 400">A Salesforce Hosted MCP tool delivers three things that no raw database can match out-of-the-box: </span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Pre-built business object graphs (e.g., Account → Opportunity → Product → Revenue) with relationships already modeled </span></li>
<li style="font-weight: 400"><span style="font-weight: 400">A record-level sharing model that automatically scopes results to what this specific caller is allowed to see </span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Decades of accumulated business process metadata that each customer’s org has already encoded over years of operation</span></li>
</ul>
<h2><b>Example: Pipeline intelligence</b></h2>
<p><span style="font-weight: 400">The example exposes a pipeline intelligence tool built in Apex. An AI agent passes an account name, and the tool returns a complete analysis: open deals with risk levels, weighted pipeline value, product line items, and an overall health assessment.</span></p>
<p>The Apex <code>@InvocableMethod</code> (see <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_InvocableMethod.htm"><u>docs</u></a>) that powers flows and Agentforce actions now also serves external AI agents through MCP. Write the logic once; it runs everywhere.</p>
<p>The Apex class uses global access (required for MCP tool discovery) and structures the method as a deep module, absorbing interpretive logic so that the Agent receives insight, not raw data.</p>
<p>The method starts by resolving the account using fuzzy matching: trying an exact name match first, then falling back to a LIKE pattern.</p>
<pre language="apex">global with sharing class AccountPipelineService {
    @InvocableMethod(
        label='Get Account Pipeline'
        description='Returns open opportunities with line items and computed business intelligence (risk levels, weighted pipeline, health summary) for a given account name. Supports fuzzy matching.'
    )
global static List getAccountPipeline(List requests) {
  // All private methods is invoked here..
}
private static Account resolveAccount(String accountName, PipelineResult result) {
     List accounts = [
         SELECT Id, Name, Industry, AnnualRevenue
         FROM Account
         WHERE Name = :accountName
         WITH USER_MODE
         LIMIT 1
     ];

     if (!accounts.isEmpty()) {
         result.matchType = 'exact';
         return accounts[0];
     }

     String fuzzyPattern = '%' + accountName + '%';
     accounts = [
         SELECT Id, Name, Industry, AnnualRevenue
         FROM Account
         WHERE Name LIKE :fuzzyPattern
         WITH USER_MODE
         LIMIT 1
     ];

     if (!accounts.isEmpty()) {
         result.matchType = 'partial';
         return accounts[0];
     }

     result.accountFound = false;
     result.message = 'No account found matching: ' + accountName;
     return null;
 }

}

</pre>
<p>When no account matches, the tool returns a structured response with <code>accountFound: false</code> and a descriptive message, giving the AI agent a clear signal to ask the user for clarification rather than failing silently.</p>
<p>Once the account is resolved, a single SOQL query traverses the full business object graph.</p>
<pre language="apex">List opportunities = [
     SELECT Id, Name, Amount, StageName, Probability, CloseDate,
         Owner.Name,
         (SELECT Id, Product2.Name, Quantity, UnitPrice, TotalPrice
          FROM OpportunityLineItems)
     FROM Opportunity
     WHERE AccountId = :acc.Id
         AND IsClosed = false
     WITH USER_MODE
     ORDER BY Amount DESC NULLS LAST
 ];
</pre>
<p><span style="font-weight: 400">Each deal is then scored for risk based on probability and proximity to close date.</span></p>
<pre language="apex">private static String assessRisk(Decimal probability, Integer daysUntilClose) {
     if (daysUntilClose &lt;= 30 &amp;&amp; (probability == null || probability &lt; 60)) {
         return 'High';
     }
     if (probability == null || probability &lt; 50) {
         return 'Medium';
     }
     return 'Low';
 }</pre>
<p>The overall pipeline health is a multi-factor assessment combining high-risk ratio and weighted conversion strength.</p>
<pre language="apex">private static String assessPipelineHealth(
     Integer totalDeals, Integer highRiskCount,
     Decimal weightedValue, Decimal totalValue
 ) {
     if (totalDeals == 0) {
         return 'Empty — no open deals';
     }
     Decimal highRiskRatio = (Decimal) highRiskCount / totalDeals;
     Decimal conversionStrength = totalValue &gt; 0 ? weightedValue / totalValue : 0;

     if (highRiskRatio &gt; 0.5) {
         return 'Weak — majority of deals are high risk';
     }
     if (conversionStrength &lt; 0.4) {
         return 'At Risk — low weighted conversion across pipeline';
     }
     if (highRiskRatio &lt;= 0.2 &amp;&amp; conversionStrength &gt;= 0.6) {
         return 'Strong — high confidence, low risk';
     }
     return 'Moderate — mixed signals, review recommended';
 }
</pre>
<p>Key design decisions:</p>
<ul>
<li><b>Fuzzy matching</b>: The tool resolves partial account names (“Acme” finds “Acme Corporation”), reducing errors for AI agents that may not know exact names.</li>
<li><b>Computed fields</b>: <code>riskLevel</code>, <code>weightedPipelineValue</code>, and <code>pipelineHealthSummary</code><span> </span><span>are calculated inside the method. The AI agent never needs to interpret raw probabilities or close dates.</span></li>
<li><b><code>WITH USER_MODE</code></b>: SOQL queries enforce the running user’s field-level and object-level security automatically.</li>
<li><b style="color: #4a4a4a;font-family: 'Salesforce Sans', Arial, sans-serif">Cross-object traversal</b><span style="color: #4a4a4a;font-family: 'Salesforce Sans', Arial, sans-serif">: This is a single tool call that spans Account, Opportunity, OpportunityLineItem, Product2, and User.</span></li>
</ul>
<h2><b>Registering the Hosted MCP Server</b></h2>
<p><span style="font-weight: 400">With the Apex class deployed, the next step is the MCP Server Definition, the metadata that maps your Apex action to a named tool and controls how AI agents discover it.</span></p>
<p><span style="font-weight: 400">Below is an example deployable metadata for registering the hosted MCP server. The metadata can be source controlled and CI/CD deployed alongside the Apex class.</span></p>
<pre>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;McpServerDefinition xmlns="http://soap.sforce.com/2006/04/metadata"&gt;
    &lt;description&gt;Analyzes account pipeline health with deal risk scoring
    and weighted forecasts by traversing Salesforce's native business
    object graph.&lt;/description&gt;
    &lt;masterLabel&gt;Pipeline Intelligence&lt;/masterLabel&gt;
    &lt;tools&gt;
        &lt;apiDefinition&gt;
            &lt;apiIdentifier&gt;aa:apex-AccountPipelineService&lt;/apiIdentifier&gt;
            &lt;apiSource&gt;API_CATALOG&lt;/apiSource&gt;
            &lt;operation&gt;AccountPipelineService&lt;/operation&gt;
        &lt;/apiDefinition&gt;
        &lt;descriptionOverride&gt;Returns open opportunities with line items
        and computed business intelligence (risk levels, weighted pipeline,
        health summary) for a given account name.
        Supports fuzzy matching.&lt;/descriptionOverride&gt;
        &lt;toolName&gt;getAccountPipeline&lt;/toolName&gt;
        &lt;toolTitle&gt;Get Account Pipeline&lt;/toolTitle&gt;
    &lt;/tools&gt;
&lt;/McpServerDefinition&gt;</pre>
<p><span style="font-weight: 400">Note: MCP Registration can also be done via Salesforce User Interface. The steps are documented in the official </span><a href="https://help.salesforce.com/s/articleView?language=en_US&amp;id=platform.api_catalog_manage_mcp_servers.htm&amp;type=5"><span style="font-weight: 400">documentation</span></a><span style="font-weight: 400">.</span></p>
<h3><b>Why every field here matters</b></h3>
<p>AI agents don’t read your code. When an MCP client calls <code>tools/list</code>, the agent sees exactly four things per tool: a name, a description, an <code>inputSchema</code>, and an <code>outputSchema</code>. That’s the entire basis for deciding whether to call your tool, what to pass, and how to interpret the response.</p>
<p>Each metadata field controls part of that picture.</p>
<table>
<tbody>
<tr>
<td><b>Metadata Field</b></td>
<td><b>Maps To</b></td>
<td><b>Role</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">toolName</span></td>
<td><span style="font-weight: 400">MCP name</span></td>
<td><span style="font-weight: 400">Identifier that the agent uses to invoke the tool.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">descriptionOverride</span></td>
<td><span style="font-weight: 400">MCP description</span></td>
<td>The tool’s pitch — drives whether the agent selects it. Falls back to <code>@InvocableMethod</code> description if empty.</td>
</tr>
<tr>
<td><span style="font-weight: 400">apiDefinition</span></td>
<td><span style="font-weight: 400">MCP inputSchema + outputSchema</span></td>
<td>Points to the API Catalog entry. Salesforce generates both JSON Schemas from your <code>@InvocableVariable</code> annotations.</td>
</tr>
<tr>
<td><span style="font-weight: 400">description (server-level)</span></td>
<td><span style="font-weight: 400">Server description</span></td>
<td><span style="font-weight: 400">Helps agents decide whether to explore this server’s tools at all.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">toolTitle</span></td>
<td><span style="font-weight: 400">Display label</span></td>
<td><span style="font-weight: 400">Human-readable name in client UIs. Not used for tool selection.</span></td>
</tr>
</tbody>
</table>
<h3><b>From Apex annotations to agent-readable schemas</b></h3>
<p>The <code>apiDefinition</code> is the bridge. It references the API Catalog entry auto-created from your global class. Salesforce reads the <code>@InvocableVariable</code> annotations on your input and output classes and generates the <code>inputSchema</code> and <code>outputSchema</code> that agents see.</p>
<pre language="apex">// Input class → becomes inputSchema (wrapped in an inputs[] array)
 @InvocableVariable(required=true description='The name of the Account to look up pipeline for. Supports partial matching.')
 global String accountName;

 // Output class → becomes outputSchema (nested inside a response envelope as outputValues)
 @InvocableVariable(description='Overall pipeline health: Strong, Moderate, At Risk, or Weak')
 global String pipelineHealthSummary;

 @InvocableVariable(description='Pipeline value weighted by probability — realistic expected revenue')
 global Decimal weightedPipelineValue;
</pre>
<p>The <code>description</code> text you write in each <code>@InvocableVariable</code> annotation becomes the documentation that the agent reads. Without it, the agent sees field types but not meaning. Write them with the agent as your audience for example:</p>
<ul>
<li><b>Input fields</b>: What to provide. “Supports partial matching” tells the agent it doesn’t need an exact name.</li>
<li><b>Output fields</b>: How to interpret. “Negative means overdue” on <code>daysUntilClose</code> prevents misreading.</li>
<li><b>Enumerated values</b>: List them. “Strong, Moderate, At Risk, or Weak” — no guessing.</li>
</ul>
<h3><b>Writing an effective </b><b><code>descriptionOverride</code></b></h3>
<p>The <code>descriptionOverride</code> drives tool selection for the Agent. A good description answers three questions:</p>
<ol>
<li style="font-weight: 400"><b>What does it return?</b><span style="font-weight: 400">  “Returns open opportunities with line items and computed business intelligence (risk levels, weighted pipeline, health summary)”</span></li>
<li style="font-weight: 400"><b>What input does it need?</b><span style="font-weight: 400">  “For a given account name”</span></li>
<li style="font-weight: 400"><b style="color: #4a4a4a;font-family: 'Salesforce Sans', Arial, sans-serif">Any behavioral nuances?</b><span style="font-weight: 400">  “Supports fuzzy matching”</span></li>
</ol>
<h4><b>Anti-patterns to avoid:</b></h4>
<ol>
<li><b>Too vague: </b><span style="font-weight: 400">A description like “Gets pipeline data” gives the agent no signal about what makes this tool different from a generic query tool. The agent is unlikely to select it when multiple tools are available</span></li>
<li><b>Too implementation-focused: </b><span style="font-weight: 400">A description like “Executes SOQL against Opportunity with USER_MODE” tells the agent how the tool is built rather than what it delivers. The agent needs to know what business value the tool provides, not the internal mechanics.</span></li>
<li><b>Missing input guidance: </b><span style="font-weight: 400">If the description does not mention that the tool expects an account name, the agent has no way to know whether it should pass an account ID, an opportunity name, or something else entirely.</span></li>
</ol>
<h3><b>Activate the MCP Servers</b></h3>
<p><span style="font-weight: 400">After deploying, verify the server appears in </span><b>Setup → MCP Servers</b><span style="font-weight: 400"> and activate it. The server must be active before any MCP client can connect.</span></p>
<h2><b>Setting up the External Client App</b></h2>
<p><span style="font-weight: 400">For an MCP client like Claude to authenticate, an External Client App with OAuth is required. This is configured through Setup as follows:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Navigate to </span><b>Setup → External Client App Manager → New External Client App</b></li>
<li style="font-weight: 400"><span style="font-weight: 400">Provide a label, description, and contact email</span></li>
<li style="font-weight: 400">Enable <b style="color: #4a4a4a;font-family: 'Salesforce Sans', Arial, sans-serif">OAuth</b><span style="font-weight: 400"> with the following settings:</span></li>
</ol>
<table>
<tbody>
<tr>
<td><b>Setting</b></td>
<td><b>Value</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">Callback URL</span></td>
<td><span style="font-weight: 400">https://claude.ai/api/mcp/auth_callback</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">OAuth Scopes</span></td>
<td><span style="font-weight: 400">mcp_api, refresh_token</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">JWT-based access tokens</span></td>
<td><span style="font-weight: 400">Enabled</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">PKCE</span></td>
<td><span style="font-weight: 400">Required</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">All other security options</span></td>
<td><span style="font-weight: 400">Unchecked</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">Once saved, the Consumer Key becomes available under </span><b>Settings → Consumer Key and Secret</b><span style="font-weight: 400">. This is the client identifier that Claude uses to initiate the OAuth flow.</span></p>
<p><span style="font-weight: 400">Note: External Client Apps can take up to 30 minutes to become fully active after creation. If Claude’s connection fails immediately after setup, wait and retry.</span></p>
<h2><b>Connecting Claude</b></h2>
<p><span style="font-weight: 400">Connecting Claude to a Hosted MCP Server requires a server URL and an OAuth Client ID — nothing else.</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">In </span><a href="http://claude.ai"><span style="font-weight: 400">Claude.ai</span></a><span style="font-weight: 400"> on the web, navigate to </span><b>Settings → Connectors → Add custom connector</b><span style="font-weight: 400"><span style="font-weight: 400"><span style="font-weight: 400">.</span></span></span>Note this assumes you have a Claude account and you are signed in. If you are signed in you can also <a href="https://claude.ai/settings/connectors?modal=add-custom-connector">directly add a connector</a>.</li>
<li style="font-weight: 400"><span style="font-weight: 400">Copy the </span><b>Server URL</b><span style="font-weight: 400"> from Salesforce Setup:</span></li>
</ol>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Navigate to </span><b>Setup → Integrations → MCP Servers</b></li>
<li style="font-weight: 400"><span style="font-weight: 400">Click the </span><b>MCP Server name</b><span style="font-weight: 400"> (e.g., “Pipeline Intelligence”)</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Under </span><b>Authentication Details</b><span style="font-weight: 400">, copy the </span><b>Server URL</b></li>
</ul>
<p><span style="font-weight: 400">The URL follows the pattern below:</span></p>
<table>
<tbody>
<tr>
<td><b>Org Type</b></td>
<td><b>URL Pattern</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">Sandbox / Scratch</span></td>
<td><span style="font-weight: 400">https://api.salesforce.com/platform/mcp/v1/sandbox/custom/{MCP_SERVER_API_NAME}</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Production / Developer Edition</span></td>
<td><span style="font-weight: 400">https://api.salesforce.com/platform/mcp/v1/custom/{MCP_SERVER_API_NAME}</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">For this project, the URL is </span><a href="https://api.salesforce.com/platform/mcp/v1/sandbox/Pipeline_Intelligence"><span style="font-weight: 400">https://api.salesforce.com/platform/mcp/v1/sandbox/Pipeline_Intelligence</span></a></p>
<p><span style="font-weight: 400">Note: You can also copy this from the user interface by navigating to the following: </span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Set the </span><b>Server UR</b><span style="font-weight: 400">L in Claude by pasting the URL from Step 2</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Paste the </span><b>OAuth Consumer Key </b><span style="font-weight: 400">from the External Client App settings</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Click </span><b>Connect</b><span style="font-weight: 400"> and authenticate with the Salesforce org credentials</span></li>
</ol>
<p><span style="font-weight: 400">Once connected, Claude can invoke the tool naturally:</span></p>
<blockquote><p><span style="font-weight: 400">“What’s happening with Acme’s pipeline?”</span></p></blockquote>
<p><span style="font-weight: 400">The response includes the matched account, open deals with per-deal risk levels, weighted pipeline value, product line items, and an overall health assessment — all computed by the Apex running on Salesforce, all respecting the user’s security context.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206297" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260513085745/image1_c35b5c-e1778687878862.png?w=1000" class="postimages" width="1000" height="625" alt="Claude invoking Salesforce Hosted MCP built with Apex" />
			  </span>
			</p>
<p><span style="font-weight: 400">Note: If you are having issues connecting to MCP servers refer to the troubleshooting section in the </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/troubleshooting-overview.html"><span style="font-weight: 400">official docs</span></a><span style="font-weight: 400">.</span></p>
<h2><b>The Headless 360 shift</b></h2>
<p><span style="font-weight: 400">For Apex developers, Headless 360 is a fundamental shift in how you design code. The consumer is no longer a human clicking through a UI; it’s an AI agent that reads tool descriptions, passes structured inputs, and reasons over structured outputs.</span></p>
<p><span style="font-weight: 400">Designing for agents means returning computed insight (not raw fields), writing precise descriptions (not UI labels), and building tools that are self-contained (not steps in a wizard).</span></p>
<p><span style="font-weight: 400">For AI developers exploring MCP, Salesforce is a uniquely deep server: a pre-modeled business graph, governed security, and computed intelligence — not just another database behind an API.</span></p>
<h2><b>A note on tool governance</b></h2>
<p><span style="font-weight: 400">Too many MCP tools overwhelm AI agents, they struggle to pick the right one, leading to irrelevant calls or confused responses. As more teams expose Apex logic as MCP tools, governance becomes important. The Salesforce </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/best-practices.html"><span style="font-weight: 400">Hosted MCP Servers Best Practices</span></a><span style="font-weight: 400"> guide recommends keeping tools self-contained (each returns a useful result on its own), avoiding both over-granular tools that require prescribed sequences and over-broad tools that bundle unrelated operations. Give each tool</span></p>
<h2><b>Get started</b></h2>
<p><span style="font-weight: 400">The complete source code for this blog post, setup scripts, and sample data are available on GitHub:</span><a href="https://github.com/msrivastav13/headless-apex-mcp-tool"> <span style="font-weight: 400">headless-apex-mcp-tool</span></a><span style="font-weight: 400">. </span></p>
<p><span style="font-weight: 400">While this post demonstrates the connection with Claude, MCP is an open protocol. Any MCP-compatible agent — Slack, ChatGPT, or your own custom agent — can connect to the same Hosted MCP Server using the same URL and OAuth flow. Check the resources below to learn more.</span></p>
<h2><b>Resources</b></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/client-connection-overview.html"><span style="font-weight: 400">Connect MCP Clients</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/create-external-client-app.html"><span style="font-weight: 400">External Client App Setup for MCP</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/claude.html"><span style="font-weight: 400">Connecting Claude to Salesforce MCP</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/custom-servers.html"><span style="font-weight: 400">Hosted MCP Servers Developer Guide</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/msrivastav13/headless-apex-mcp-tool"><span style="font-weight: 400">GitHub: Headless Apex MCP Tool</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Mohith Shrivastava</b><span style="font-weight: 400"> is a Principal Developer Advocate at Salesforce with 15 years of experience building enterprise-scale products on the Agentforce 360 Platform. Mohith is currently among the lead contributors on Salesforce Stack Exchange, a developer forum where Salesforce Developers can ask questions and share knowledge. You can follow him on </span><a href="https://www.linkedin.com/in/mohith-shrivastava-9a36464a/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/05/expose-custom-apex-as-a-hosted-mcp-tool-for-agents">Expose Custom Apex as a Hosted MCP Tool for Agents</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/05/expose-custom-apex-as-a-hosted-mcp-tool-for-agents/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206291</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260512151244/SingleHeadshot-e1778623995941.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260512151244/SingleHeadshot-e1778623995941.png?w=1000" medium="image" />
	</item>
	</channel>
</rss>
