<?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>Mon, 14 Sep 2026 17:42:12 +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>Build Custom Salesforce Connect Adapters Smarter with Salesforce Skills</title>
		<link>https://developer.salesforce.com/blogs/2026/09/build-custom-salesforce-connect-adapters-smarter-with-salesforce-skills</link>
		<comments>https://developer.salesforce.com/blogs/2026/09/build-custom-salesforce-connect-adapters-smarter-with-salesforce-skills#respond</comments>
		<pubDate>Mon, 14 Sep 2026 17:43:41 +0000</pubDate>
		<dc:creator><![CDATA[snakra]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Apex]]></category>
		<category><![CDATA[APIs and Integrations]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[APIs & Integrations]]></category>
		<category><![CDATA[developer tooling]]></category>
		<category><![CDATA[External Objects]]></category>
		<category><![CDATA[Headless 360]]></category>
		<category><![CDATA[Salesforce Connect]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=207004</guid>
		<description><![CDATA[<p>Learn how the Salesforce Connect adapter generation skill helps coding agents build a working custom Apex adapter from a plain-language API description.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/09/build-custom-salesforce-connect-adapters-smarter-with-salesforce-skills">Build Custom Salesforce Connect Adapters Smarter with Salesforce Skills</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><a href="https://help.salesforce.com/s/articleView?id=platform.platform_connect_about.htm&amp;type=5"><span style="font-weight: 400">Salesforce Connect</span></a><span style="font-weight: 400"> brings external data into Salesforce without copying it. Live API calls replace ETL and sync jobs, and the data stays where it is. For sources reachable through a built-in adapter, such as OData services, Snowflake, or Athena, a standard adapter can handle the connection. But when your data lives behind a REST API that none of the built-in adapters speak, developers have had to </span><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_connector_start.htm"><span style="font-weight: 400">write the integration by hand</span></a><span style="font-weight: 400">, often requiring hundreds of lines of Apex and deep framework knowledge to build a working </span><a href="https://help.salesforce.com/s/articleView?id=platform.apex_adapter_setup.htm&amp;type=5"><span style="font-weight: 400">custom Salesforce Connect Apex adapter</span></a><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">AI agents can write this code on behalf of the developer, but without deep Salesforce Connect context, AI agents hallucinate. They write Apex that looks correct but misses required columns, uses wrong field types, or returns a malformed </span><span style="font-weight: 400">TableResult</span><span style="font-weight: 400">. What promised speed becomes hours of debugging </span><a href="https://help.salesforce.com/s/articleView?id=platform.ext_data_sync_database.htm&amp;type=5"><span style="font-weight: 400">validate and sync</span></a><span style="font-weight: 400"> failures.</span></p>
<p><span style="font-weight: 400">That changes with the </span><b>platform-salesforce-connect-adapter-generate</b><span style="font-weight: 400"> skill.</span></p>
<p><span style="font-weight: 400">Developers can now use a coding agent to carry out the entire end-to-end integration workflow and build a complete, working adapter, including Apex classes.</span></p>
<p><span style="font-weight: 400">This blog post covers what the skill does, how it structures the generated code, and how it uses the Salesforce </span><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/headless-360-mcp.html"><span style="font-weight: 400">Headless 360 MCP Server</span></a><span style="font-weight: 400"> to register your adapter, reducing the manual work required in Salesforce Setup to a single click</span></p>
<h2><span style="font-weight: 400">What is a custom Apex adapter in Salesforce Connect?</span></h2>
<p><span style="font-weight: 400">The Salesforce Connect custom Apex adapter type lets you build your </span><a href="https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_externaldatasource.htm"><span style="font-weight: 400">external data source</span></a><span style="font-weight: 400"> by connecting with any REST API as a first-class </span><a href="https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_external_objects.htm"><span style="font-weight: 400">external object.</span></a><span style="font-weight: 400"> External objects look and behave like native Salesforce records where they show up in related lists; work in SOQL, flows, reports; and support platform features like sharing and field-level security. The data is never stored in Salesforce. Every query fires a live callout.</span></p>
<p>To build one, you implement two Apex classes from the <a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_namespace_DataSource.htm"><u><code>DataSource</code></u></a> namespace:</p>
<ul>
<li><a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_DataSource_Provider.htm"><b><u><code>DataSource.Provider</code></u></b></a>: This class declares what your adapter supports: which objects it exposes, which query capabilities it has, and which authentication method it uses.</li>
<li><a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_DataSource_Connection.htm"><b><u><code>DataSource.Connection</code></u></b></a>: This class handles the actual API calls. It receives a <code>QueryContext</code>, calls your external system, maps the response to Salesforce field types, and returns a <code>TableResult</code>.</li>
</ul>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207005" >
			    <img fetchpriority="high" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260914035354/image3-e1789383246283.jpg?w=1000" class="postimages" width="1000" height="747" alt="Architecture diagram showing the runtime flow: a Salesforce client request triggers a custom Apex Adapter, which queries an external REST API and maps JSON responses into external objects." />
			  </span>
			</p>
<p><span style="font-weight: 400">The interface is well-defined and public. But writing it correctly requires knowing the right method signatures, valid field types, pagination contracts, and error handling patterns. All of that is knowledge that takes time to build.</span></p>
<h2><span style="font-weight: 400">Introducing platform-salesforce-connect-adapter-generate</span></h2>
<p><a href="https://www.salesforce.com/headless/agent-skills-plugins/"><span style="font-weight: 400">Salesforce skills </span></a><span style="font-weight: 400">are specialized AI capabilities that ground Agentforce Vibes and other dev agents with deep, domain-specific knowledge. They&#8217;re open-source, available as sf-skills, preloaded in Agentforce Vibes, and installable in any third-party AI tool. You describe your API in plain language. The agent does the rest.</span></p>
<p><span style="font-weight: 400">When the </span><b>platform-salesforce-connect-adapter-generate</b><span style="font-weight: 400"> skill is loaded, your dev agent knows:</span></p>
<ul>
<li>The exact method signatures for <code>DataSource.Provider</code> and <code>DataSource.Connection</code></li>
<li>Correct <code>DataSource.DataType</code> mappings for your API&#8217;s field types. The skill knows which enum value to use for text, numbers, URLs, and more</li>
<li>That <code>ExternalId</code> and <code>DisplayUrl</code> columns are required on every table definition</li>
<li>That every table needs <span>a column designated as its “name” column</span><span> or the validate and sync operation fails</span></li>
<li>How to structure <code>TableResult</code>, <code>QueryUtils</code>, and <code>SearchUtils</code></li>
<li><a href="https://help.salesforce.com/s/articleView?id=xcloud.named_credentials_about.htm&amp;type=5"><u>Named credential</u></a> patterns so no credentials end up hardcoded</li>
</ul>
<p>Without this skill, any dev agent produces plausible Apex that fails on first sync. With it, the agent gets the contract right on the first try.</p>
<h2><span style="font-weight: 400">Real-world use case: iRail Belgian Rail API</span></h2>
<p><span style="font-weight: 400">A developer at a travel company needs live data from 714 Belgian train stations in Salesforce for their customer service agents. The </span><a href="https://docs.irail.be/"><span style="font-weight: 400">iRail API</span></a><span style="font-weight: 400"> has this data: a public REST endpoint, free to use, returning clean JSON. No built-in adapter covers this endpoint and a custom Apex adapter is the only path.</span></p>
<p><span style="font-weight: 400">With the </span><b>platform-salesforce-connect-adapter-generate</b><span style="font-weight: 400"> skill loaded, the developer describes the API in plain language:</span></p>
<p><span style="font-weight: 400">&#8220;I have a public REST API for Belgian train stations at GET https://api.irail.be/v1/stations. It returns 714 stations with id, name, locationX, and locationY fields. Create a custom Salesforce Connect Apex adapter.&#8221;</span></p>
<p>The agent generates both Apex classes <code>DataSource.Provider</code> and <code>DataSource.Connection</code> with correct field types, required columns, and named credential auth wired in:</p>
<pre language="apex">public class TrainTravelDataSourceConnection extends DataSource.Connection {
    public override DataSource.TableResult query(DataSource.QueryContext context) {
        HttpRequest req = new HttpRequest();
        // context unused here — iRail returns all stations; use QueryUtils.filter/sort for filtered queries

req.setEndpoint('callout:iRail_Named_Credential/v1/stations?format=json&amp;lang=en');
        req.setMethod('GET');
        HttpResponse res = new Http().send(req);
        Map&lt;String, Object&gt; body = (Map&lt;String, Object&gt;) JSON.deserializeUntyped(res.getBody());
        List&lt;Object&gt; stations = (List&lt;Object&gt;) body.get('station');
        List&lt;Map&lt;String, Object&gt;&gt; rows = new List&lt;Map&lt;String, Object&gt;&gt;();
        for (Object s : stations) {
            Map&lt;String, Object&gt; station = (Map&lt;String, Object&gt;) s;
            Map&lt;String, Object&gt; row = new Map&lt;String, Object&gt;();
            row.put('ExternalId', (String) station.get('@id'));
 row.put('DisplayUrl', 'https://api.irail.be/v1/stations?format=json&amp;lang=en&amp;id=' + (String) station.get('@id'));
            row.put('Name', (String) station.get('name'));
            row.put('LocationX', Double.valueOf((String) station.get('locationX')));
            row.put('LocationY', Double.valueOf((String) station.get('locationY')));
            rows.add(row);
        }
        return DataSource.TableResult.get(context, rows);
    }
}
</pre>
<p><span style="font-weight: 400">The code above shows the Connection class, which handles query logic. The full adapter also includes a DataSource.Provider class that declares adapter capabilities and returns the connection instance. See the complete code at </span><a href="http://github.com/shra1904/dreamforce-demo"><span style="font-weight: 400">https://github.com/shra1904/dreamforce-demo</span></a><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">Here&#8217;s an example of how to run such a prompt with Claude Code:</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207006" >
			    <img decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260914035502/image1_cdabe6-e1789383316694.png?w=1000" class="postimages" width="1000" height="685" alt="Screenshot showing an agent&apos;s terminal output where it has written four files for the &apos;TrainTravelDataSource&apos;, including the provider and connection classes, along with key decisions like mapping the &apos;Name&apos; column and using &apos;DataSource.SearchUtils.searchByName()&apos;." />
			  </span>
			</p>
<h2><span style="font-weight: 400">This skill gives your agent the edge</span></h2>
<p><span style="font-weight: 400">Some of the key things the skill gets right automatically:</span></p>
<ul>
<li><span>Every custom adapter must declare a DataSource.Column named ExternalId, and the skill enforces this</span></li>
<li>Field types are deterministic and mapped accurately to avoid field type mismatches, which are the most common failure mode</li>
<li>The callout uses named credentials via the <code>callout:</code> prefix so no hardcoded URLs, no secrets in code</li>
<li>The return type is <b><code>DataSource.TableResult</code></b>, which is the exact contract Salesforce expects</li>
</ul>
<p>When both classes are deployed to the org via <span>sf project deploy start,</span><span> the adapter code is live. The next step is registering it in Salesforce as an external data source, traditionally a multistep Setup wizard. With the Salesforce Headless 360 MCP Server connected to your coding agent, that step becomes an API call too.</span></p>
<h1><span style="font-weight: 400">Registering without going to Setup in Salesforce</span></h1>
<p>The agent invokes the <b><code>external-data-source-setup</code></b> platform metadata operation directly thanks to an <a href="https://github.com/forcedotcom/mcp-hosted?tab=readme-ov-file"><u>MCP tool:</u></a></p>
<pre language="shell"># Coding agent generates and deploys the adapter, then calls:
# Headless 360 MCP → external-data-source-setup
# → External Data Source created without opening Setup
</pre>
<p><span style="font-weight: 400">The MCP server exposes Salesforce configuration operations as structured tools an AI agent can call so the same operations you&#8217;d perform through the UI are accessible programmatically.</span></p>
<p><span style="font-weight: 400">This is the direction the platform is heading. Every Setup operation becomes an API call that an agent can invoke. Salesforce Connect is one of the first areas where we&#8217;re making this real.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-207007" >
			    <img decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260914035542/image2_665045-e1789383355910.png?w=1000" class="postimages" width="1000" height="563" alt="Terminal screenshot showing an agent creating an iRail Train Travel external data source in Salesforce, followed by a list of instructions for the user to manually perform Validate &amp; Sync in the Salesforce Setup UI to complete the process." />
			  </span>
			</p>
<p><span style="font-weight: 400">The final step is validate and sync, which creates the external object by querying the adapter for its schema. That is one click in Salesforce Setup. The result: live station data in Salesforce, queryable in SOQL and available across all platform features like flows, reports, and so on.</span></p>
<h1><span style="font-weight: 400">Getting started</span></h1>
<p>The skill is available now in <a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/platform-salesforce-connect-adapter-generate"><u>sf-skills</u></a> as <b><code>platform-salesforce-connect-adapter-generate</code></b>. You&#8217;ll need a Salesforce Connect license, Salesforce CLI v2+, and a coding agent like Agentforce Vibes, Claude Code, or Cursor. If you use Agentforce Vibes, the skills are already loaded, but if you are working with a third-party agent, you&#8217;ll need to install the skill by running <span>npx skills add forcedotcom/sf-skills</span><span> in your project directory. Once the skills are available, describe your API to your agent in natural language.</span></p>
<p>The skill handles any REST API that returns a structured JSON response, whether the API is authenticated or public, and whether the response is paginated or returned as a single response. Tell the agent what fields you need, what types they are, and how authentication works. The generated code is a working first pass. Add error handling and a test class before deploying to production. Apex requires test coverage to deploy to a production org.</p>
<h1><span style="font-weight: 400">Conclusion</span></h1>
<p>Custom Apex adapters have always been capable. The barrier was the knowledge required to write them correctly. The <b><code>platform-salesforce-connect-adapter-generate</code></b> skill removes that barrier because your coding agent now knows the full <code>DataSource</code> framework and can generate a working adapter from a plain-language description.</p>
<p>Combined with the Salesforce Headless 360 MCP server, virtually the entire workflow, including code generation, deployment, and org registration, can be completed without leaving your terminal. That&#8217;s the foundation we&#8217;re building toward: agentic Salesforce configuration, end to end.</p>
<p>In Winter &#8217;27, we&#8217;re extending this further with full headless external data source lifecycle management and Salesforce Connect extensibility for standard adapters. <i>(This represents current product direction and is subject to change. Please base purchasing decisions on currently available features.)</i></p>
<p><span>Have a data source you&#8217;d like to connect to Salesforce? Share your ideas and vote for related requests on </span><span> </span><a href="https://ideas.salesforce.com/s/search#q=salesforce+connect&amp;t=All&amp;sortField=points&amp;sortOrder=desc&amp;categories=platform%7Cdata-import-integration"><u>IdeaExchange</u></a><span>.</span></p>
<h1><span style="font-weight: 400">Resources</span></h1>
<ul>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=platform.salesforce_connect_apex.htm&amp;type=5"><span style="font-weight: 400">Salesforce Connect Custom Apex Adapter documentation</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/forcedotcom/sf-skills/tree/main/skills/platform-salesforce-connect-adapter-generate"><span style="font-weight: 400">platform-salesforce-connect-adapter-generate skill on sf-skills</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_namespace_DataSource.htm"><span style="font-weight: 400">DataSource Namespace reference</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/headless-360-mcp.html"><span style="font-weight: 400">Headless360 MCP documentation</span></a></li>
<li style="font-weight: 400"><a href="https://docs.irail.be/"><span style="font-weight: 400">iRail API (demo target used in this post)</span></a></li>
</ul>
<h1><span style="font-weight: 400">About the author</span></h1>
<p><b>Shraddha Nakra</b><span style="font-weight: 400"> is a Senior Product Manager at Salesforce on the Platform Connectivity Solutions team, where she owns Salesforce Connect and Private Connect. She focuses on making external data integration secure, faster, and developer-friendly from zero-copy connectivity to agentic configuration. Find her on </span><a href="https://www.linkedin.com/in/shraddhanakra/"><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/09/build-custom-salesforce-connect-adapters-smarter-with-salesforce-skills">Build Custom Salesforce Connect Adapters Smarter with Salesforce Skills</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/09/build-custom-salesforce-connect-adapters-smarter-with-salesforce-skills/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">207004</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260914035909/Generic-C-7-e1789383567614.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260914035909/Generic-C-7-e1789383567614.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Run Complex Data Transformations in Data 360 with Code Extension</title>
		<link>https://developer.salesforce.com/blogs/2026/09/build-deploy-run-and-troubleshoot-complex-transformations-with-python-and-pyspark-while-keeping-execution-governed-by-data-360</link>
		<comments>https://developer.salesforce.com/blogs/2026/09/build-deploy-run-and-troubleshoot-complex-transformations-with-python-and-pyspark-while-keeping-execution-governed-by-data-360#respond</comments>
		<pubDate>Thu, 10 Sep 2026 15:00:03 +0000</pubDate>
		<dc:creator><![CDATA[Chandan Agarwal]]></dc:creator>
				<category><![CDATA[App Development]]></category>
		<category><![CDATA[Data 360]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[Batch Data Transform]]></category>
		<category><![CDATA[Code Extension]]></category>
		<category><![CDATA[Data Engineering]]></category>
		<category><![CDATA[ETL]]></category>
		<category><![CDATA[PySpark]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[Salesforce Data Cloud]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206976</guid>
		<description><![CDATA[<p>Build, deploy, run, and troubleshoot complex transformations with Python and PySpark while keeping execution governed by Data 360.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/09/build-deploy-run-and-troubleshoot-complex-transformations-with-python-and-pyspark-while-keeping-execution-governed-by-data-360">Run Complex Data Transformations in Data 360 with Code Extension</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">Data 360 provides </span><a href="https://trailhead.salesforce.com/content/learn/modules/batch-data-transforms-in-data-cloud-quick-look/get-started-with-batch-data-transforms-in-data-360"><span style="font-weight: 400">batch data transforms</span></a><span style="font-weight: 400"> for governed data preparation. When a customer needs a complex transformation implemented with custom code, Code Extension provides a Python and PySpark authoring option that can be managed in source control, validated locally, and run through Data 360 without exporting governed data to a separate processing platform.</span></p>
<p><span style="font-weight: 400">You can package Python code as a script, deploy it into Data 360, and invoke it through a batch data transform. The transform remains the operational control point, while the script contains the customer&#8217;s processing logic.</span></p>
<h2><span style="font-weight: 400">The Northstar Outfitters customer scenario</span></h2>
<p><span style="font-weight: 400">Northstar Outfitters receives product data from several commerce systems. Brand names, currencies, units, required attributes, and product categories are represented differently across those systems. These inconsistencies affect product search and produce uneven analytics across channels.</span></p>
<p><span style="font-weight: 400">Northstar needed one trusted product catalog in Data 360. The transformation had to:</span></p>
<ul>
<li><span style="font-weight: 400"> Standardize product names, brands, currencies, and units.</span></li>
<li><span style="font-weight: 400"> Enrich products by mapping them to the approved category reference.</span></li>
<li><span style="font-weight: 400"> Validate required attributes and identify invalid records.</span></li>
<li><span style="font-weight: 400"> Calculate a product-completeness score using custom business rules.</span></li>
</ul>
<p><span style="font-weight: 400">Native batch data transforms support common operations through visual nodes and formulas. The complexity of Northstar’s transformation logic made a code-based approach more practical, so the team implemented it as modular, unit-tested, and source-controlled Python and PySpark with Code Extension.</span></p>
<p><span style="font-weight: 400">A batch data transform remained the operational control point, providing execution, scheduling, status, and run history for the deployed script.</span></p>
<h2><span style="font-weight: 400">How Data 360 Code Extension fits the solution architecture</span></h2>
<p><span style="font-weight: 400">Northstar selected Code Extension so its production transformation could run on Salesforce-managed infrastructure within Data 360 using Spark for distributed processing. Developers used their local IDE only to validate the logic against sampled data. Full-scale processing began when the batch data transform invoked the deployed script.</span></p>
<p><span style="font-weight: 400">This model provided:</span></p>
<ul>
<li><span style="font-weight: 400"> </span><b>In-platform processing.</b><span style="font-weight: 400"> Custom Python logic executes within Salesforce-managed infrastructure instead of a separate customer-managed processing pipeline.</span></li>
<li><span style="font-weight: 400"> </span><b>Distributed execution.</b><span style="font-weight: 400"> Spark distributes PySpark DataFrame operations across Salesforce-managed compute resources.</span></li>
<li><span style="font-weight: 400"> </span><b>Isolated, temporary runtime.</b><span style="font-weight: 400"> Tenant isolation, runtime sandboxing, network controls, and ephemeral compute help protect the platform and other workloads.</span></li>
<li><span style="font-weight: 400"> </span><b>Managed operations.</b><span style="font-weight: 400"> Data 360 manages deployment, invocation, scheduling, execution status, logs, and run history.</span></li>
</ul>
<p><span style="font-weight: 400">The batch data transform starts the deployed Code Extension. The script uses the</span><a href="https://pypi.org/project/salesforce-data-customcode/"> <span style="font-weight: 400">Data Custom Code SDK</span></a><span style="font-weight: 400"> to read permitted DLOs as Spark DataFrames and write to the permitted target DLO. This scenario illustrates a DLO-to-DLO transform. Code Extension scripts also support DMO-to-DMO transforms, but a script must read from and write to the same object type—it can’t mix DLOs and DMOs.</span></p>
<p><span style="font-weight: 400">For more about the architecture and security model, see </span><a href="https://www.salesforce.com/blog/power-data-360-code-extension/"><span style="font-weight: 400">How to Power Data 360 with Code Extension</span></a><span style="font-weight: 400"> and </span><a href="https://engineering.salesforce.com/securely-running-python-with-data-clouds-new-bring-your-own-code-architecture/"><span style="font-weight: 400">Securely Running Python with Data Cloud&#8217;s Bring Your Own Code Architecture</span></a><span style="font-weight: 400">.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206982" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260909155740/image3_e05368-e1788994679801.png?w=1000" class="postimages" width="1000" height="563" alt="A batch transform invokes the script. The script reads only the DLOs granted in `config.json`, processes Spark DataFrames, and writes the curated product catalog." />
			  </span>
			</p>
<p><span style="font-weight: 400">Northstar&#8217;s object map was:</span></p>
<table>
<tbody>
<tr>
<td><b>Role</b></td>
<td><b>DLO API name</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">Raw product feed</span></td>
<td><span style="font-weight: 400">Product_Catalog_Raw__dll</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Approved category reference</span></td>
<td><span style="font-weight: 400">Product_Category_Reference__dll</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Execution logs</span></td>
<td><span style="font-weight: 400">DataCustomCodeLogs__dll</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Curated product target</span></td>
<td><span style="font-weight: 400">Product_Catalog_Curated__dll</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">The DLO names in the package constants matched this access model.</span></p>
<h2><span style="font-weight: 400">How to structure a Data 360 Code Extension package</span></h2>
<p><span style="font-weight: 400">The Code Extension script used the following structure:</span></p>
<pre language="text">my_package/
├── payload/
│   ├── config.json
│   └── entrypoint.py
├── requirements.txt
└── tests/
    ├── test_deploy_config.py
    └── test_entrypoint.py
</pre>
<p>Because <code>my_package/</code> contains <code>payload/</code>, the team used it as the deployment root and passed <code>--package-dir ./payload</code> to the packaging and deployment commands.</p>
<h2><span style="font-weight: 400">The package access model</span></h2>
<p>In <code>config.json</code>, Northstar identified the SDK version, entry point, data space, and DLOs that the package could read or write.</p>
<pre language="json">{
  "sdkVersion": "6.1.0",
  "entryPoint": "entrypoint.py",
  "dataspace": "default",
  "permissions": {
    "read": {
      "dlo": [
        "Product_Catalog_Raw__dll",
        "Product_Category_Reference__dll"
      ]
    },
    "write": {
      "dlo": [
        "Product_Catalog_Curated__dll"
      ]
    }
  }
}
</pre>
<p><span style="font-weight: 400">The curated target remained write-only, keeping read and write responsibilities separate. The explicit permission list also gave reviewers a clear view of the package&#8217;s intended data access.</span></p>
<p><span style="font-weight: 400">Before deployment, the team confirmed that each DLO existed in the configured data space and that the schemas contained every column used by the program. This caught naming and environment differences before transform creation.</span></p>
<h2><span style="font-weight: 400">What the Python and PySpark entrypoint script does</span></h2>
<p><span style="font-weight: 400">The entry point followed a simple read, transform, and write flow:</span></p>
<pre language="python">products_df = client.read_dlo(PRODUCT_SOURCE_DLO)
categories_df = client.read_dlo(CATEGORY_REFERENCE_DLO)

changes = transform_products(products_df, categories_df)

client.write_to_dlo(
    FINAL_DLO,
    changes,
    write_mode=WriteMode.MERGE)
</pre>
<p>Managed execution supplied the authenticated SDK context and enforced the permissions declared in <code>config.json</code>; the script did not hardcode credentials. The transformation function validated and standardized product data, matched approved categories, and calculated the required output. Spark distributed this processing across the managed execution environment.</p>
<p><span style="font-weight: 400">The script logged failures with their processing context and rethrew exceptions so the data transform could report the run as failed.</span></p>
<h2><span style="font-weight: 400">How to validate a Code Extension script locally</span></h2>
<p><span style="font-weight: 400">Before deployment, Northstar ran the script locally against a Data 360 sandbox. Local execution read a sample of up to 1,000 records and sent output to the console without modifying Data 360 objects.</span></p>
<p><span style="font-weight: 400">The team used this for functional validation. They measured performance and scale after deploying the script to the managed Data 360 runtime.</span></p>
<h3><span style="font-weight: 400">Local setup</span></h3>
<p><span style="font-weight: 400">The local toolchain included Salesforce CLI, Python 3.11, the SDK version declared in </span><span style="font-weight: 400">config.json</span><span style="font-weight: 400">, and the Salesforce CLI Code Extension plugin.</span></p>
<pre language="shell">sf update
sf plugins install @salesforce/plugin-data-code-extension
python3.11 -m pip install salesforce-data-customcode==6.1.0

sf org login web \
  --alias myorg \
  --instance-url https://test.salesforce.com

sf org display --target-org myorg
</pre>
<p>With the sandbox authenticated as <code>myorg</code>, the team ran the entry point from the repository root:</p>
<pre language="shell">sf data-code-extension script run \
  --entrypoint ./my_package/payload/entrypoint.py \
  --config-file ./my_package/payload/config.json \
  --target-org myorg
</pre>
<p><span style="font-weight: 400">They ran the unit tests separately:</span></p>
<pre language="shell">cd my_package
python3.11 -m pytest tests -q
</pre>
<p><span style="font-weight: 400">Northstar&#8217;s test suite covered schema handling, normalization, category enrichment, quality-score calculation, invalid and empty inputs, write behavior, structured logging, and disjoint read/write permissions.</span></p>
<p><span style="font-weight: 400">The team used the </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/quickstart.html"><span style="font-weight: 400">Code Extension quick start</span></a><span style="font-weight: 400"> as its reference for prerequisites and sandbox setup.</span></p>
<h2><span style="font-weight: 400">How to deploy a Code Extension with Setup or Salesforce CLI</span></h2>
<h3><span style="font-weight: 400">Deploying through Setup</span></h3>
<p><span style="font-weight: 400">In </span><b>Setup</b><span style="font-weight: 400">, Northstar entered </span><b>Code Extension</b><span style="font-weight: 400"> in </span><b>Quick Find</b><span style="font-weight: 400"> and opened </span><b>Code Extension</b><span style="font-weight: 400">. The page displayed existing deployments, including their code type, owner, deployment time, and current status.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206983" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260909155843/image1_13a6db-e1788994735747.png?w=1000" class="postimages" width="1000" height="177" alt="The Code Extension page in Setup lists each deployment, its code type, who it was deployed by, the deployment time, and the current deployment status." />
			  </span>
			</p>
<p><span style="font-weight: 400">After selecting </span><b>New</b><span style="font-weight: 400">, the team uploaded the ZIP package and selected </span><b>Python</b><span style="font-weight: 400">, </span><b>Script</b><span style="font-weight: 400">, </span><b>Batch Transform</b><span style="font-weight: 400">, and the required compute size before submitting the </span><b>Code Extension</b><span style="font-weight: 400">.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206984" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260909155922/image4_cfa0e3-e1788994776924.png?w=1000" class="postimages" width="1000" height="461" alt="The New Code Extension page captures the source ZIP, language, code type, target feature, and compute size before submission." />
			  </span>
			</p>
<h3><span style="font-weight: 400">Deploying with Salesforce CLI</span></h3>
<p><span style="font-weight: 400">For its repeatable developer workflow, Northstar used the Code Extension plugin to package and deploy the script:</span></p>
<pre language="shell">cd my_package

sf data-code-extension script zip \
  --package-dir ./payload

sf data-code-extension script deploy \
  --name Northstar_Product_Catalog_V1 \
  --package-version 1.0.0 \
  --description "Product catalog standardization and enrichment" \
  --package-dir ./payload \
  --target-org myorg \
  --cpu-size CPU_2XL
</pre>
<p>The available CPU flags were <code>CPU_L</code>, <code>CPU_XL</code>, <code>CPU_2XL</code>, and <code>CPU_4XL</code>. Northstar began with <code>CPU_2XL</code>, measured the managed run, and used its timing logs to decide whether a different size was justified.</p>
<p>The deployment name was metadata supplied by the command, not a value that had to appear in <code>entrypoint.py</code>. Northstar later used that same name to select the Code Extension for its transform and filter execution logs.</p>
<p><span style="font-weight: 400">The team also accounted for the documented CLI limitation for DMO-to-DMO transforms, where the UI deployment path is required. The current behavior is described in </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/deploy-custom-script-cli.html"><span style="font-weight: 400">Deploy a Custom Script to Data 360 Sandbox by Using CLI</span></a><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">After upload, the package passed through platform security validation before execution. Managed runs used isolated, temporary compute resources, and Northstar planned to migrate the validated Code Extension and dependencies through a </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/migrate-code-to-prod.html"><span style="font-weight: 400">DevOps data kit</span></a><span style="font-weight: 400"> for its production release.</span></p>
<h2><span style="font-weight: 400">How to connect a Code Extension to a batch data transform</span></h2>
<p><span style="font-weight: 400">Deployment made Northstar&#8217;s script available, but did not execute it. The team connected the deployed Code Extension to a batch data transform, which became the invocation and scheduling layer.</span></p>
<h2><span style="font-weight: 400">Creating the transform in the UI</span></h2>
<p><span style="font-weight: 400">In Data 360, Northstar opened </span><b>Data Transforms</b><span style="font-weight: 400">, selected </span><b>Create from Code Extension</b><span style="font-weight: 400">, entered a unique label and API name, and selected the deployed Code Extension. It saved the transform and used </span><b>Run Now</b><span style="font-weight: 400"> for the first managed validation.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206985" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260909155959/image2_313926-e1788994809887.png?w=1000" class="postimages" width="1000" height="390" alt="Northstar selected Create from Code Extension to connect the deployed script to a new batch Data Transform." />
			  </span>
			</p>
<p><span style="font-weight: 400">The team kept the API name within the documented character, length, and uniqueness rules described in </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/create-batch-transform.html"><span style="font-weight: 400">Invoke Code Extension by Creating a Batch Data Transform</span></a><span style="font-weight: 400">.</span></p>
<h3><span style="font-weight: 400">Automating transform creation with the Connect API</span></h3>
<p>To automate transform creation, Northstar captured the Connect API request as <code>transform-request.json</code>. The following abridged excerpt highlights the relationship between the target DLO, input DLOs, data space, and deployed Code Extension:</p>
<pre language="json">{
  "definition": {
    "type": "DCSQL",
    "manifest": {
      "nodes": {
        "node1": {
          "relation_name": "Product_Catalog_Curated__dll",
          "config": { "materialized": "table" },
          "compiled_code": ""
        }
      },
      "sources": {
        "source1": {
          "relation_name": "Product_Catalog_Raw__dll"
        },
        "source2": {
          "relation_name": "Product_Category_Reference__dll"
        }
      },
      "macros": {
        "macro.byoc": {
          "arguments": [
            {
              "name": "Northstar_Product_Catalog_V1",
              "type": "BYOC_SCRIPT"
            }
          ]
        }
      }
    },
    "version": "56.0"
  },
  "label": "Northstar Product Catalog Transform V1",
  "name": "Northstar_Product_Transform_V1",
  "description": "Invokes the Northstar custom Code Extension",
  "type": "BATCH",
  "dataSpaceName": "default"
}
</pre>
<p><span style="font-weight: 400">The team submitted the payload with Salesforce CLI&#8217;s REST client:</span></p>
<pre language="shell">sf api request rest \
  /services/data/v67.0/ssot/data-transforms \
  --method POST \
  --header "Content-Type: application/json" \
  --body @transform-request.json \
  --target-org myorg
</pre>
<p>Because the excerpt is intentionally abbreviated, Northstar used the complete manifest required by the current <a href="https://developer.salesforce.com/docs/data/connectapi/references/spec"><u>Data 360 Connect REST API reference</u></a> in <code>transform-request.json</code>.</p>
<p>During testing, transform creation sometimes outlived the UI or CLI wait window. Instead of immediately resubmitting, the team queried <code>MktDataTransform</code> and checked the Data Transforms UI to determine whether backend creation was still progressing.</p>
<h2><span style="font-weight: 400">How to run and monitor a batch data transform</span></h2>
<p><span style="font-weight: 400">For its on-demand validation, Northstar invoked the transform through the API:</span></p>
<pre language="shell">sf api request rest \
  /services/data/v67.0/ssot/data-transforms/Northstar_Product_Transform_V1/actions/run \
  --method POST \
  --body '{}' \
  --target-org myorg
</pre>
<p><span style="font-weight: 400">The operations team retrieved transform details and run history with the companion endpoints:</span></p>
<pre language="shell">sf api request rest \
  /services/data/v67.0/ssot/data-transforms/Northstar_Product_Transform_V1 \
  --method GET \
  --target-org myorg

sf api request rest \
  /services/data/v67.0/ssot/data-transforms/Northstar_Product_Transform_V1/run-history \
  --method GET \
  --target-org myorg
</pre>
<p><span style="font-weight: 400">Northstar waited to schedule recurring production runs until it had measured duration, verified input and output counts, and understood billing for the selected compute size.</span></p>
<h2><span style="font-weight: 400">How to troubleshoot a failed Code Extension run</span></h2>
<p>Northstar checked three separate signals: Code Extension deployment in <code>DataCustomCode</code>, data transform status in <code>MktDataTransform</code>, and Python execution messages in <code>DataCustomCodeLogs__dll</code>.</p>
<h3><span style="font-weight: 400">Verifying the Code Extension record</span></h3>
<pre language="shell">sf data query \
  --target-org myorg \
  --query "SELECT Id, Name, CodeType, DeploymentStatus, Status, DeploymentFailureCode, DeploymentFailureReason, CreatedDate, LastModifiedDate FROM DataCustomCode WHERE Name = 'Northstar_Product_Catalog_V1' ORDER BY CreatedDate DESC"
</pre>
<p>The team treated <code>Status = Active</code> and <code>DeploymentStatus = Deployed</code> as separate signals. <code>DeploymentStatus</code> showed whether package deployment had completed successfully.</p>
<h3><span style="font-weight: 400">Verifying the data transform record</span></h3>
<p><span style="font-weight: 400">This query confirms the transform exists and shows its current status and most recent execution details.</span></p>
<pre language="shell">sf data query \
  --target-org myorg \
  --query "SELECT Id, Name, DataTransformStatus, LastRunStatus, LastRunTime, CreationSource, Type, CreatedDate, LastModifiedDate FROM MktDataTransform WHERE Name = 'Northstar_Product_Transform_V1' ORDER BY CreatedDate DESC"
</pre>
<h3><span style="font-weight: 400">Inspecting execution logs</span></h3>
<p>Application logs are stored in the <code>DataCustomCodeLogs__dll</code> DLO, so Northstar queried them in Data Explorer using Data 360 SQL. Because log ingestion is asynchronous, messages may appear shortly after an execution completes.</p>
<h3><span style="font-weight: 400">The latest execution messages</span></h3>
<p><span style="font-weight: 400">Run this query to review the latest execution messages and correlate them with a specific Code Extension run.</span></p>
<pre language="sql">SELECT
    "Timestamp__c",
    "Message__c",
    "CorrelationId__c",
    "ExecutionId__c",
    "ProcessDefinitionName__c",
    "DataCustomCodeName__c"
FROM "DataCustomCodeLogs__dll"
WHERE "DataCustomCodeName__c" =
      'Northstar_Product_Catalog_V1'
ORDER BY "Timestamp__c" DESC
LIMIT 200;
</pre>
<h3><span style="font-weight: 400">How the script produced application logs</span></h3>
<p>Northstar used Python&#8217;s standard <code>logging</code> module inside <code>entrypoint.py</code>:</p>
<pre language="python">import logging

log = logging.getLogger(__name__)
log.info(
    "PRODUCT_PIPELINE | phase=PROCESS | status=SUCCESS | records_written=%s",
    records_written,
)
</pre>
<p>During managed execution, Data 360 captured this application message and ingested it into <code>DataCustomCodeLogs__dll</code>. The script did not call a separate API or write directly to the log DLO.</p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">Data 360 Code Extension brings the flexibility of </span><a href="https://engineering.salesforce.com/securely-running-python-with-data-clouds-new-bring-your-own-code-architecture/"><span style="font-weight: 400">Bring Your Own Code</span></a><span style="font-weight: 400"> to complex data transformations. Teams can use Python and PySpark for modular and testable logic, multiple data inputs, custom validation, specialized libraries, and distributed processing within a governed Data 360 environment. Combined with batch data transforms for execution, scheduling, monitoring, and run history, it provides a scalable way to operationalize sophisticated data processing without managing a separate external processing platform.</span></p>
<h2><span style="font-weight: 400">Resources</span></h2>
<ul>
<li style="list-style-type: none">
<ul>
<li>YouTube series: <a href="https://www.youtube.com/playlist?list=PLACV3VmGs318">Extend Data 360 with Code Extension</a></li>
<li><span style="font-weight: 400"> </span><a href="https://www.salesforce.com/blog/power-data-360-code-extension/"><span style="font-weight: 400">How to Power Data 360 with Code Extension</span></a></li>
<li><span style="font-weight: 400"> </span><a href="https://engineering.salesforce.com/securely-running-python-with-data-clouds-new-bring-your-own-code-architecture/"><span style="font-weight: 400">Securely Running Python with Data Cloud&#8217;s Bring Your Own Code Architecture</span></a></li>
<li><span style="font-weight: 400"> </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/use-custom-code.html"><span style="font-weight: 400">Code Extension in Data 360</span></a></li>
<li><span style="font-weight: 400"> </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/quickstart.html"><span style="font-weight: 400">Code Extension quick start</span></a></li>
<li><span style="font-weight: 400"> </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/deploy-custom-script-cli.html"><span style="font-weight: 400">Deploy a custom script using Salesforce CLI</span></a></li>
<li><span style="font-weight: 400"> </span><a href="https://developer.salesforce.com/docs/data/data-cloud-code-ext/guide/create-batch-transform.html"><span style="font-weight: 400">Create a batch Data Transform from Code Extension</span></a></li>
<li><span style="font-weight: 400"> </span><a href="https://developer.salesforce.com/docs/data/connectapi/references/spec"><span style="font-weight: 400">Data 360 Connect REST API reference</span></a></li>
<li><span style="font-weight: 400"> </span><a href="https://pypi.org/project/salesforce-data-customcode/"><span style="font-weight: 400">Salesforce Data Custom Code SDK on PyPI</span></a></li>
<li><span style="font-weight: 400"> </span><a href="https://github.com/salesforcecli/plugin-data-code-extension"><span style="font-weight: 400">Salesforce CLI Code Extension plugin</span></a></li>
</ul>
</li>
</ul>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">About the authors</span></h2>
<p><b>Ravindra Varshney</b> <span style="font-weight: 400">is a</span><span style="font-weight: 400"> Senior Director, Software Engineering </span><span style="font-weight: 400">at Salesforce.</span> <span style="font-weight: 400">You can find him on </span><a href="https://www.linkedin.com/in/ravindra-varshney"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span><span style="font-weight: 400">   </span></p>
<p><b>Gaurav Tewari</b> <span style="font-weight: 400">is a </span><span style="font-weight: 400">Senior Product Manager </span><span style="font-weight: 400">at Salesforce.</span> <span style="font-weight: 400">You can find him on </span><a href="https://www.linkedin.com/in/tewarigaurav/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p><b>Chandan Agarwal </b><span style="font-weight: 400">is a Principal Member of Technical Staff at Salesforce. You can find him on  </span><a href="https://www.linkedin.com/in/chandan-agarwal-1080a220/"><span style="font-weight: 400">LinkedIn</span></a><span style="font-weight: 400">.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/09/build-deploy-run-and-troubleshoot-complex-transformations-with-python-and-pyspark-while-keeping-execution-governed-by-data-360">Run Complex Data Transformations in Data 360 with Code Extension</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/09/build-deploy-run-and-troubleshoot-complex-transformations-with-python-and-pyspark-while-keeping-execution-governed-by-data-360/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206976</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260909164152/Generic-D-8-e1788997323200.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260909164152/Generic-D-8-e1788997323200.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Announcing General Availability: Web Console – Your Modern In-Browser IDE</title>
		<link>https://developer.salesforce.com/blogs/2026/09/announcing-general-availability-web-console-your-modern-in-browser-ide</link>
		<comments>https://developer.salesforce.com/blogs/2026/09/announcing-general-availability-web-console-your-modern-in-browser-ide#respond</comments>
		<pubDate>Tue, 08 Sep 2026 15:32:51 +0000</pubDate>
		<dc:creator><![CDATA[Karen Fidelak]]></dc:creator>
				<category><![CDATA[Announcements]]></category>
		<category><![CDATA[Apex]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Developer Tooling]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Lightning Web Components]]></category>
		<category><![CDATA[debugging]]></category>
		<category><![CDATA[Developer Tools]]></category>
		<category><![CDATA[lwc]]></category>
		<category><![CDATA[salesforce developers]]></category>
		<category><![CDATA[testing]]></category>
		<category><![CDATA[Visual Studio Code]]></category>
		<category><![CDATA[Web Console]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206963</guid>
		<description><![CDATA[<p>Learn how the generally available Web Console brings a modern, browser-based IDE to Salesforce so you can edit, debug, test, and validate without leaving your org.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/09/announcing-general-availability-web-console-your-modern-in-browser-ide">Announcing General Availability: Web Console – Your Modern In-Browser IDE</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">We are thrilled to announce that </span><b>Web Console is now Generally Available (GA)</b><span style="font-weight: 400">!</span></p>
<h2><b>What is Web Console?</b></h2>
<p><span style="font-weight: 400">Web Console is a modern, browser-based IDE designed to help developers work faster inside the platform they already know, without switching tools just to inspect code, run queries, debug an issue, or make a quick fix. It’s built on a proven foundation of VS Code for the Web with our existing </span><a href="https://developer.salesforce.com/docs/platform/sfvscode-extensions/overview"><span style="font-weight: 400">Salesforce Extensions for Visual Studio Code</span></a><span style="font-weight: 400">, delivering a seamless in-browser experience for Salesforce developers. This provides the robustness of a modern IDE in a fast, easy-to-access experience like you had with Developer Console. Yes, this is your modern alternative to Developer Console!</span></p>
<p><span style="font-weight: 400">You may also be wondering about how Web Console differs from </span><a href="https://developer.salesforce.com/docs/platform/code-builder/guide/codebuilder-overview.html"><span style="font-weight: 400">Agentforce Vibes IDE</span></a><span style="font-weight: 400">, since they are both dev environments accessible from the browser. In short, Web Console is a lightweight environment optimized for quick file editing and development tasks within a single org, while Agentforce Vibes IDE is a full development environment backed by a virtual machine (VM), providing additional compute, AI, integration capabilities, and multi-org connections. They really serve two different needs, and depending on what you’re looking to do you can select the appropriate tool for the job. </span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206967" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260908081727/image3_e80192-e1788880660506.png?w=1000" class="postimages" width="1000" height="553" alt="Web Console screenshot showing the Org Browser panel and an LWC file being edited." />
			  </span>
			</p>
<p><span style="font-weight: 400">Web Console provides the same trusted functionality you’ve become accustomed to in Developer Console, but through a more modern experience. Additionally, Web Console provides new capabilities that have not been available in Developer Console, including support for Lightning Web Components (LWC). </span></p>
<p><span style="font-weight: 400">Below is a table highlighting key capabilities of Web Console, with Quick Start instructions for using them in Web Console (and links to full documentation for each). A star indicates features you have access to in Web Console that you didn’t have access to in Developer Console.</span></p>
<table>
<tbody>
<tr>
<td><b>Feature/Capability</b></td>
<td><b>Quick Start &#8211; How to Access Web Console</b></td>
</tr>
<tr>
<td><b>Quick, in-context Apex viewing/editing</b></td>
<td>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Click Edit on any Apex Class, Trigger, or Scheduled Job from within the org</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Edit as desired, then Save</span></li>
</ol>
</td>
</tr>
<tr>
<td><b>View/edit Lightning Web Component (LWC) files</b><span style="font-weight: 400"> <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></span></td>
<td>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Open Org Browser (cloud icon) in the left-side Activity Bar</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Navigate the Metadata tree, then click Retrieve Metadata</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Edit as desired, then Save</span></li>
</ol>
<p><a href="https://developer.salesforce.com/docs/platform/webconsole/guide/org-browser.html"><span style="font-weight: 400">Learn More</span></a></td>
</tr>
<tr>
<td><b>Run Anonymous Apex</b></td>
<td>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">From Command Palette (Ctrl+Shift+P</span> <span style="font-weight: 400">on Windows or Cmd+Shift+P on macOS), run SFDX: Create Anonymous Apex Script</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Edit the script, then right-click in the script file -&gt; SFDX: Execute Anonymous Apex</span></li>
</ol>
<p><a href="https://developer.salesforce.com/docs/platform/webconsole/guide/webconsole-apex-anonymous.html"><span style="font-weight: 400">Learn More</span></a></td>
</tr>
<tr>
<td><b>Generate/run SOQL queries, optionally with declarative SOQL Builder <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" />, and optimize performance by generating a query plan</b></td>
<td>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">From Command Palette*, run SFDX: Create Query in SOQL Builder (or Create SOQL Query)</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Edit the query, then select Run Query</span></li>
</ol>
<p><a href="https://developer.salesforce.com/docs/platform/webconsole/guide/query-records.html"><span style="font-weight: 400">Learn More</span></a></td>
</tr>
<tr>
<td><b>Run Apex tests and view test results</b></td>
<td>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Open Testing (beaker icon) from the left-side Activity Bar</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Select a test method, test class, or all Apex tests. </span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Run test(s)</span></li>
</ol>
<p><a href="https://developer.salesforce.com/docs/platform/webconsole/guide/webconsole-apex-tests.html"><span style="font-weight: 400">Learn More</span></a></td>
</tr>
<tr>
<td><b>Debug Apex with trace flags and log levels</b></td>
<td><span style="font-weight: 400">View Logs:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Command Palette* -&gt; SFDX: Get Apex Debug Logs</span></li>
</ol>
<p><span style="font-weight: 400">Trace/Log Levels</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Command Palette* -&gt; SFDX: Create Trace Flag for Current User</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Adjust Log Levels as desired</span></li>
</ol>
<p><a href="https://developer.salesforce.com/docs/platform/webconsole/guide/debug-your-code.html"><span style="font-weight: 400">Learn More</span></a></td>
</tr>
<tr>
<td><b>Browse all metadata in the org <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" />, and optionally retrieve for editing/redeploy with the Org Browser</b></td>
<td>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Open Org Browser (cloud icon) in left-side Activity Bar</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Browse metadata in the org</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Optionally Retrieve, Edit, Save</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Optionally open Explorer (files icon) in left-side Activity Bar to browse/edit/deploy retrieved metadata</span></li>
</ol>
<p><a href="https://developer.salesforce.com/docs/platform/webconsole/guide/org-browser.html"><span style="font-weight: 400">Learn More</span></a></td>
</tr>
<tr>
<td><b>Log Analysis</b></td>
<td><span style="font-weight: 400">Coming soon via Certinia Apex Log Analyzer!</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">* To open Command Palette use Ctrl+Shift+P on Windows or Cmd+Shift+P on macOS. </span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206968" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260908081812/image5_1a755d-e1788880721204.png?w=1000" class="postimages" width="1000" height="624" alt="Web Console screenshot showing the Testing panel with Apex tests being run with inline errors." />
			  </span>
			</p>
<h2><b>Connected workflow example: Debugging an Apex job failure</b></h2>
<p><span style="font-weight: 400">Apex job failures are a good example of where Web Console can make an immediate difference. A developer can begin in the Apex Jobs page, where a failed or long-running Apex job is already visible. From there, they can open the related Apex class in Web Console, enable logging, rerun the scenario, inspect the execution path, jump from the stack trace to the problematic line of code, make the fix, run tests, and validate that the job now completes successfully. They can do all of that without leaving Salesforce or bouncing between disconnected tools.</span></p>
<p><span style="font-weight: 400">This streamlined flow is more efficient because it mirrors how real troubleshooting actually happens. The work does not begin in a file tree. It begins with a problem. Web Console is designed so the IDE meets the developer at that point of failure, then stays with them through diagnosis, code change, and validation.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206969" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260908081929/image2_cac728.png?w=1000" class="postimages" width="1000" height="288" alt="Diagram showing five-step process for debugging and fixing a failed Apex job." />
			  </span>
			</p>
<p><span style="font-weight: 400">This is one of the clearest ways Web Console improves usability. Instead of forcing the developer to reconstruct the situation across separate tools, it keeps runtime evidence, source code, and validation steps close together, shortening the distance between “What just happened?” and “Did my fix resolve the issue?”</span></p>
<h2><b>Production versus non-production: Web Console guardrails</b></h2>
<p><span style="font-weight: 400">Web Console is built with the realities of Salesforce development in mind. In </span><b>production orgs</b><span style="font-weight: 400">, Apex editing is </span><b>read-only</b><span style="font-weight: 400">, letting developers inspect and understand code safely without any risk to live orgs. In </span><b>sandboxes and other non-production environments</b><span style="font-weight: 400">, developers can make inline edits and save changes as part of a faster path-to-fix workflow.</span></p>
<p><span style="font-weight: 400">That balance is important. A modern experience should make developers faster, but not by weakening platform guardrails. Web Console keeps the guardrails intact while still streamlining inspection, debugging, and validation.</span></p>
<h2><b>How to launch Web Console</b></h2>
<p><span style="font-weight: 400">Web Console is now enabled by default for users with admin permissions. </span><span style="font-weight: 400">Getting started with Web Console requires no local command-line setup or extension installation. Once it is enabled, you can simply launch it directly from the Setup (gear) menu within your Salesforce org or refer to the official guide on </span><a href="https://developer.salesforce.com/docs/platform/webconsole/guide/launch.html"><span style="font-weight: 400">How to launch Web Console</span></a><span style="font-weight: 400">. </span><span style="font-weight: 400">Global access can be disabled or enabled through Setup -&gt; Development -&gt; Web Console.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206970" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260908082006/image4_06d418.png?w=342" class="postimages" width="342" height="221" alt="Screenshot showing how to launch Web Console from the Setup menu." />
			  </span>
			</p>
<h2><b>What about Developer Console?</b></h2>
<p><span style="font-weight: 400">While we believe that Web Console will provide you with the functionality that you need and an improved experience, if you still prefer to use Developer Console you can easily re-enable it through Setup -&gt; Development -&gt; Web Console, then toggle Enable Developer Console. </span></p>
<p><span style="font-weight: 400">If you or your team find yourselves needing to revert to the legacy Developer Console, we would love to know why! Please </span><a href="https://github.com/forcedotcom/web-console-feedback/issues"><span style="font-weight: 400">share your feedback</span></a><span style="font-weight: 400"> so we can ensure Web Console continues to satisfy all your development needs.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206971" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260908082113/image1_dca47f-e1788880887804.png?w=1000" class="postimages" width="1000" height="445" alt="Screenshot showing how to enable or disable Web Console and Developer Console in Setup." />
			  </span>
			</p>
<h2><b>Conclusion</b></h2>
<p><span style="font-weight: 400">We’re excited to bring Web Console to general availability, providing a quick, easy, and streamlined experience for your everyday development needs. Whether it’s making a quick edit to an Apex class, modifying an LWC component, testing and debugging your code, querying data in your org, running an Apex script, or just browsing your org’s metadata, Web Console makes it easy. </span></p>
<p><span style="font-weight: 400">And it’s only a click away: try Web Console today at Setup Menu (gear icon) -&gt; Web Console!</span></p>
<h2><b>Resources</b></h2>
<ul>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/webconsole/guide"><span style="font-weight: 400">Web Console documentation</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/forcedotcom/web-console-feedback"><span style="font-weight: 400">Feedback/Issues repository on GitHub</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">AMA: Oct 28, 2026 at 9 a.m. PT</span></li>
</ul>
<h2><b>About the author</b></h2>
<p><b>Karen Fidelak</b><span style="font-weight: 400"> is a Sr. Director Product Management at Salesforce, focused on bringing robust developer tooling and experiences to our developer community. She leads product teams responsible for our IDEs, VS Code extensions, CLI, Agentforce Vibes, DevOps Center, DX Inspector, and Code Analyzer. You can follow her on </span><a href="https://www.linkedin.com/in/karen-fidelak-8ba8248/"><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/09/announcing-general-availability-web-console-your-modern-in-browser-ide">Announcing General Availability: Web Console – Your Modern In-Browser IDE</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/09/announcing-general-availability-web-console-your-modern-in-browser-ide/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206963</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260908082812/Generic-B-6-e1788881307316.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260908082812/Generic-B-6-e1788881307316.png?w=1000" medium="image" />
	</item>
		<item>
		<title>Improve Enterprise RAG Accuracy in Data 360</title>
		<link>https://developer.salesforce.com/blogs/2026/09/improve-enterprise-rag-accuracy-in-data-360</link>
		<comments>https://developer.salesforce.com/blogs/2026/09/improve-enterprise-rag-accuracy-in-data-360#respond</comments>
		<pubDate>Wed, 02 Sep 2026 17:00:22 +0000</pubDate>
		<dc:creator><![CDATA[Barnali Patnaik]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Data 360]]></category>
		<category><![CDATA[New Developments]]></category>
		<category><![CDATA[AI & Machine Learning]]></category>
		<category><![CDATA[architecture]]></category>
		<category><![CDATA[Data Science]]></category>
		<category><![CDATA[Enterprise Search]]></category>
		<category><![CDATA[new developments]]></category>
		<category><![CDATA[tutorials]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206946</guid>
		<description><![CDATA[<p>Learn how to improve enterprise RAG accuracy in Data 360 with three configuration changes that help AI agents deliver more accurate answers without custom code or model training.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/09/improve-enterprise-rag-accuracy-in-data-360">Improve Enterprise RAG Accuracy in Data 360</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">Retrieval Augmented Generation (RAG) is the architecture behind most enterprise Artificial Intelligence (AI) agents&#8217; answers. It retrieves relevant documents and feeds them to a large language model (LLM) to generate a response. </span></p>
<p><span style="font-weight: 400">If AI agents are returning confident but wrong answers from enterprise documents, the problem probably isn&#8217;t your LLM. It&#8217;s your </span><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_rag_overview.htm&amp;type=5"><span style="font-weight: 400">RAG</span></a><span style="font-weight: 400"> pipeline, and you can fix it without writing code.</span></p>
<p><span style="font-weight: 400">We validated the approach described in this post on four public, reproducible benchmarks. Three configuration changes in Data 360 improved accuracy from 62.5% to 92.5% on knowledge-base question answering. On complex enterprise PDFs with tables, charts, and formulas, the same pipeline achieved 86.8% accuracy end to end.</span></p>
<p><span style="font-weight: 400">In this post, we&#8217;ll cover where your pipeline is breaking, three configuration steps to fix it, how to validate improvements, and what&#8217;s coming next.</span></p>
<h2><span style="font-weight: 400">The four stages where RAG pipelines fail</span></h2>
<p><span style="font-weight: 400">RAG failures compound silently across four stages. Before tuning anything, identify your bottleneck:</span></p>
<ol>
<li style="font-weight: 400"><b>Content processing:</b><span style="font-weight: 400"> Charts, diagrams, and tables lose structure during extraction. A pricing matrix becomes garbled text; a wiring diagram disappears entirely.</span></li>
<li style="font-weight: 400"><b>Content splitting:</b><span style="font-weight: 400"> A 15-step procedure gets split at an arbitrary token boundary. Your agent returns steps 1–7 and misses the safety warning on step 12.</span></li>
<li style="font-weight: 400"><b>Search:</b><span style="font-weight: 400"> The right content exists but the wrong content surfaces. Your user asks about &#8220;firmware reset&#8221; but the document says &#8220;factory restore procedure.&#8221;</span></li>
<li style="font-weight: 400"><b>Generation:</b><span style="font-weight: 400"> The retrieved context is correct but the LLM produces a wrong or incomplete answer anyway.</span></li>
</ol>
<p><span style="font-weight: 400">Fixing one stage doesn&#8217;t help if another is the actual bottleneck. On our benchmarks, the remaining errors after full optimization break down as follows: </span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">40% generation</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">26% content processing</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">18% search</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">16% content splitting</span></li>
</ul>
<p><span style="font-weight: 400">Your distribution will vary by document type.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206948" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260902093635/image5-e1788367008597.png?w=1000" class="postimages" width="1000" height="370" alt="Pipeline diagram showing four RAG stages with their respective error contribution percentages." />
			  </span>
			</p>
<p><span style="font-weight: 400">Here&#8217;s what fixing these stages looks like in practice across different industries and for your business:</span></p>
<ul>
<li style="font-weight: 400"><b>Insurance and financial services:</b><span style="font-weight: 400"> Multipage policy tables become queryable with correct cell-level extraction. Finance-domain accuracy improved by over 27.9 percentage points, the largest increase of any domain tested.</span></li>
<li style="font-weight: 400"><b>Manufacturing and field service:</b><span style="font-weight: 400"> Schematics, wiring diagrams, and safety documentation become searchable by meaning, not just filename. Technical/product manual-domain accuracy: from 77% to 88.7%.</span></li>
<li style="font-weight: 400"><b>Customer support and service:</b><span style="font-weight: 400"> Optimized configuration delivers 92.5% accuracy on knowledge-base question answering. Your agents get complete procedures, not fragments that leave customers waiting.</span></li>
<li style="font-weight: 400"><b>Enterprise-wide search: </b><span style="font-weight: 400">82.1% accuracy with confluence documents. Your agents find information regardless of where it lives.</span></li>
</ul>
<h2><span style="font-weight: 400">Optimize your RAG pipeline</span></h2>
<h3><span style="font-weight: 400">Enable intelligent parsing for rich documents</span></h3>
<p><span style="font-weight: 400">The first thing you can do to increase accuracy is to enable </span><b>intelligent parsing (expected November 2026)</b><span style="font-weight: 400"> for rich documents. Intelligent parsing analyzes each page and routes it to the right extraction method. Text-heavy pages get fast, structure-preserving parsing. Pages with charts, diagrams, or schematics get vision-model interpretation that converts visual content into searchable text.</span></p>
<p><span style="font-weight: 400">This matters because standard parsing skips images entirely and breaks tables across rows. Intelligent parsing keeps tables intact, preserves section hierarchy during chunking, and makes visual content retrievable.</span></p>
<p><span style="font-weight: 400">To enable it, select Intelligent Parsing as the document processing method in your Data 360 intelligent context. No additional setup is required; page-level routing happens automatically.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206949" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260902093747/image1-e1788367081118.png?w=1000" class="postimages" width="1000" height="637" alt="Intelligent parsing routing logic showing how text-heavy pages and visual pages are processed differently before entering the same search index." />
			  </span>
			</p>
<p><span style="font-weight: 400">The following table shows the measured impact of intelligent parsing on </span><a href="https://arxiv.org/pdf/2412.02592"><span style="font-weight: 400">OHR-Bench </span></a><span style="font-weight: 400">(Office-domain Hybrid Retrieval Benchmark) v2 (International Conference on Computer Vision [ICCV] 2025, enterprise PDF questions):</span></p>
<table>
<tbody>
<tr>
<td><b>Content type</b></td>
<td><b>Before (Data 360 RAG Pipeline)</b></td>
<td><b>After ( Data 360 RAG Pipeline)</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">Charts and diagrams</span></td>
<td><span style="font-weight: 400">57.9%</span></td>
<td><span style="font-weight: 400">76.3%</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Reading order (multicolumn, multipage)</span></td>
<td><span style="font-weight: 400">11.9%</span></td>
<td><span style="font-weight: 400">67.1%</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Tables</span></td>
<td><span style="font-weight: 400">Fragmented</span></td>
<td><span style="font-weight: 400">Intact with cell-level accuracy</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">End-to-end accuracy</span></td>
<td><span style="font-weight: 400">65%</span></td>
<td><span style="font-weight: 400">84.4%</span></td>
</tr>
</tbody>
</table>
<h3><span style="font-weight: 400">Turn on enriched indexing</span></h3>
<p><span style="font-weight: 400">Another way to improve accuracy is to enable </span><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_search_index_enriched_index.htm&amp;type=5"><b>enriched indexing</b><span style="font-weight: 400">.</span></a><span style="font-weight: 400"> For every chunk in your index, enriched indexing generates two additional representations automatically:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Rich metadata: titles, keywords, entities, summaries, and topic labels extracted from the content</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Question chunks: the natural-language questions this content is likely to answer, phrased the way your users actually search</span></li>
</ul>
<p><span style="font-weight: 400">At retrieval time, a query matches against all three surfaces simultaneously: raw content, metadata, and anticipated questions.</span></p>
<p><span style="font-weight: 400">This matters because your users search with different vocabulary than your documents use. A service agent asks &#8220;How do I reset the configuration after a firmware update?&#8221; but the document says &#8220;Restore factory settings post-upgrade.&#8221; Enriched indexing bridges that gap without manual tagging.</span></p>
<p><span style="font-weight: 400">To enable it, toggle Enriched Indexing on in Search Index Setup. The enrichment runs during indexing;  no changes are needed at query time.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206950" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260902093840/image4-e1788367134200.png?w=1000" class="postimages" width="1000" height="517" alt="Diagram showing how enriched indexing creates three searchable representations from each content chunk, enabling broader query matching." />
			  </span>
			</p>
<p><span style="font-weight: 400">The </span><b>measured impact of enriched indexing </b><span style="font-weight: 400">was +2.4 percentage points on top of intelligent parsing by itself (84.4% to 86.8% on OHR-Bench v2). No manual content curation is required.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206951" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260902093922/image3-e1788367174400.png?w=1000" class="postimages" width="1000" height="127" alt="Data 360 RAG pipeline showing how errors at each stage are addressed by a specific capability" />
			  </span>
			</p>
<h3><span style="font-weight: 400">Tune chunk size, search depth, and generation prompts</span></h3>
<p><span style="font-weight: 400">This is likely where you will see the largest single improvement. On </span><a href="https://arxiv.org/abs/2505.08643"><span style="font-weight: 400">WixQA</span></a><span style="font-weight: 400"> (arXiv 2025, real help articles with expert-written questions), configuration tuning alone delivered a 30 percentage-point improvement, with no code changes and no custom models.</span></p>
<p><span style="font-weight: 400">The three levers are:<br />
</span></p>
<table>
<tbody>
<tr>
<td><b>Setting</b></td>
<td><b>Default</b></td>
<td><b>Optimized</b></td>
<td><b>What it controls</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">Chunk size</span></td>
<td><span style="font-weight: 400">512 tokens</span></td>
<td><span style="font-weight: 400">2048 tokens</span></td>
<td><span style="font-weight: 400">Whether procedures and tables stay intact</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Search/Retrieval depth (top-k, the number of top-ranked chunks returned)</span></td>
<td><span style="font-weight: 400">10 chunks</span></td>
<td><span style="font-weight: 400">30 chunks</span></td>
<td><span style="font-weight: 400">How much relevant content surfaces</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Generation prompt</span></td>
<td><span style="font-weight: 400">Generic</span></td>
<td><span style="font-weight: 400">Domain-tuned</span></td>
<td><span style="font-weight: 400">Whether the LLM produces full answers or fragments</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">Adjust these configurations in your search index and RAG pipeline settings. If your documents contain multistep procedures or tables, start with the chunk size, increasing it to 512 or higher.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206952" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260902094002/image2-e1788367236648.png?w=1000" class="postimages" width="1000" height="497" alt="" />
			  </span>
			</p>
<p><b>The measured impact of this tuning was</b><span style="font-weight: 400"> 62.5% to 92.5% accuracy on the same benchmark, same platform, and same data, with configuration changes only.</span></p>
<h2><span style="font-weight: 400">What&#8217;s next: Knowledge Graph</span></h2>
<p><span style="font-weight: 400">Some questions can&#8217;t be answered from a single page or even a single document. &#8220;What safety certifications apply to this component given recent regulatory changes?&#8221; requires connecting information across multiple sources.</span></p>
<p><span style="font-weight: 400">Knowledge Graph (Generally Available November 2026) builds entity-relationship connections across your entire document corpus. Instead of retrieving isolated content, the system traverses relationships. It follows a policy clause to its definitions. It connects a financial line item to its footnoted methodology. It links a product specification to safety constraints in a separate document.</span></p>
<p><span style="font-weight: 400">This is our path to 90%+ accuracy on multipage reasoning (the same level we&#8217;ve achieved on single-document benchmarks).</span></p>
<h2><span style="font-weight: 400">Your action plan</span></h2>
<table>
<tbody>
<tr>
<td><b>If your problem is&#8230;</b></td>
<td><b>Do this&#8230;</b></td>
<td><b>Expected improvement</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">Charts/diagrams invisible to search</span></td>
<td><span style="font-weight: 400">Enable Intelligent Parsing</span></td>
<td><span style="font-weight: 400">+17 percentage points (pp) </span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Users can&#8217;t find content with different wording</span></td>
<td><span style="font-weight: 400">Enable Enriched Indexing</span></td>
<td><span style="font-weight: 400">+2.4 pp end-to-end</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Procedures and tables fragmented</span></td>
<td><span style="font-weight: 400">Enable Intelligent Parsing &amp; increase chunk size</span></td>
<td><span style="font-weight: 400">Contributes to + 17 pp config improvement</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Agents hallucinating instead of saying I don&#8217;t know</span></td>
<td><span style="font-weight: 400">Tune the generation prompt &amp; generation model</span></td>
<td><span style="font-weight: 400">+5 pp improvement </span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Chunking/retrieval for text based knowledge articles</span></td>
<td><span style="font-weight: 400">Fully optimize the configuration (Tune chunk size, search depth, and generation prompts)</span></td>
<td><span style="font-weight: 400">62.5% → 92.5% on knowledge-base question answering</span></td>
</tr>
</tbody>
</table>
<h2><span style="font-weight: 400">Validating your improvements</span></h2>
<p><span style="font-weight: 400">You don&#8217;t need to trust our benchmark numbers; you can measure the pipeline against your dataset:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Build your own evaluation dataset of 20-50 questions paired with correct answers from your documents. You can evaluate the answers to each of these questions in </span><a href="https://help.salesforce.com/s/articleView?id=data.c360_a_intelligent_context.htm&amp;type=5"><span style="font-weight: 400">Intelligent Context</span></a><span style="font-weight: 400"> now. In the next version of Intelligent Context (expected November 2026), you will be able to get an aggregate accuracy assessment on your dataset out of the box. </span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Through our benchmarking exercise, we mapped every failure pattern to the specific pipeline configuration that fixes it. We&#8217;re now packaging that into auto-tuning, which is expected to launch in November 2026. It runs accuracy assessments against a set of golden question-answer pairs, diagnoses where answers fail, and automatically applies the right configuration, with no manual experimentation and no RAG expertise required. In early testing, auto-tuning found the optimal configuration in three iterations, replacing months of manual work.</span></li>
</ol>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">Enterprise RAG accuracy is a configuration problem, not a model problem. Three changes (Intelligent Parsing, Enriched Indexing, and tuned pipeline settings) improved accuracy from 62.5% to 92.5% on public benchmarks without custom code or model training.</span></p>
<p><span style="font-weight: 400">For practitioners who want hands-on control, start with the Action Plan table above to identify your highest-impact fix. Then, use the validation steps to measure your own pipeline. For everyone else, we&#8217;re </span><span style="font-weight: 400">introducing auto-tuning and automated accuracy assessments built from the same diagnostic logic our </span><span style="font-weight: 400">benchmarking uncovered so that any practitioner can reach optimal accuracy without needing to understand the details of the RAG system. </span></p>
<p><span style="font-weight: 400">If you have questions or want to share your results, join the conversation on the </span><a href="https://trailhead.salesforce.com/trailbl%20%20%20%20%20%20azer-community/groups/0F94S000000kQixSAE"><span style="font-weight: 400">Trailblazer Community</span></a><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">Ready to get started? Set up a Data 360 search index today and run your first evaluation.</span></p>
<h2><b>Resources</b></h2>
<ul>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?language=en_US&amp;id=data.c360_a_data_cloud.htm&amp;type=5"><span style="font-weight: 400">Data 360 Documentation</span></a><span style="font-weight: 400"> &#8211;  Platform setup and configuration guides</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Benchmarks:</span>
<ul>
<li style="font-weight: 400"><a href="https://arxiv.org/abs/2501.00321"><span style="font-weight: 400">OHR-Bench v2</span></a><span style="font-weight: 400"> (ICCV 2025) &#8211;  Complex enterprise PDF benchmark</span></li>
<li style="font-weight: 400"><a href="https://arxiv.org/abs/2407.01523"><span style="font-weight: 400">MMLongBench-Doc</span></a><span style="font-weight: 400"> (NeurIPS 2024) &#8211;  Long-document reasoning benchmark</span></li>
<li style="font-weight: 400"><a href="https://arxiv.org/abs/2505.08643"><span style="font-weight: 400">WixQA</span></a><span style="font-weight: 400"> (arXiv 2505.08643) &#8211;  Knowledge-base QA benchmark</span></li>
<li style="font-weight: 400"><a href="https://arxiv.org/abs/2605.05253"><span style="font-weight: 400">Enterprise RAG Bench</span></a><span style="font-weight: 400"> &#8211; Multisource enterprise retrieval benchmark</span></li>
</ul>
</li>
<li style="font-weight: 400"><a href="https://trailhead.salesforce.com/data-cloud-trail"><span style="font-weight: 400">Data 360 on Trailhead </span></a><span style="font-weight: 400">&#8211;  Learning paths for Data 360 developers</span></li>
<li style="font-weight: 400"><a href="https://www.salesforce.com/blog/ai/"><span style="font-weight: 400">Salesforce AI Blog</span></a><span style="font-weight: 400"> &#8211; Related posts on enterprise AI architecture</span></li>
</ul>
<h2><b>About the Authors</b></h2>
<p><b>Barnali Patnaik</b><span style="font-weight: 400"> is a Senior Product Manager at Salesforce, focused on enterprise search accuracy and RAG pipeline optimization in Data 360. She leads benchmarking and evaluation strategy for the Data 360 retrieval stack.</span><a href="https://www.linkedin.com/in/barnali-patnaik-507b8055/"> <span style="font-weight: 400">LinkedIn</span></a></p>
<p><b>Aanchal Varma</b><span style="font-weight: 400"> is a Senior Member of Technical Staff (SMTS) in Data Science at Salesforce, specializing in retrieval evaluation and accuracy measurement for enterprise search systems in Data 360. </span><a href="https://www.linkedin.com/in/aanchalvarma511/"><span style="font-weight: 400">LinkedIn</span></a></p>
<p><b>Kartik Muktinutalapati</b><span style="font-weight: 400">, Senior Director of Product Management at Salesforce, leads the Data 360 product vision and strategy for unstructured data processing and retrieval to give AI agents access to enterprise knowledge.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/09/improve-enterprise-rag-accuracy-in-data-360">Improve Enterprise RAG Accuracy in Data 360</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/09/improve-enterprise-rag-accuracy-in-data-360/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206946</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260902095139/Generic-A-8-e1788367941922.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260902095139/Generic-A-8-e1788367941922.png?w=1000" medium="image" />
	</item>
		<item>
		<title>【TDX Tokyo 2026】Lightning MiniHack Live ファイナリスト座談会: 初代チャンピオンと挑戦者たちが語る「最速」の舞台裏と AI 時代の実装力</title>
		<link>https://developer.salesforce.com/blogs/2026/08/tdx-tokyo-2026-lightning-minihack-live</link>
		<comments>https://developer.salesforce.com/blogs/2026/08/tdx-tokyo-2026-lightning-minihack-live#respond</comments>
		<pubDate>Mon, 31 Aug 2026 05:55:13 +0000</pubDate>
		<dc:creator><![CDATA[odasho]]></dc:creator>
				<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Apex]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Flow]]></category>
		<category><![CDATA[Salesforce Events]]></category>
		<category><![CDATA[MiniHacks]]></category>
		<category><![CDATA[TDX26]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206928</guid>
		<description><![CDATA[<p>&#26412;&#35352;&#20107;&#12391;&#12399;&#12289;TDX Tokyo 2026 &#12391;&#38283;&#20652;&#12373;&#12428;&#12383; Lightning MiniHack Live &#12398;&#12501;&#12449;&#12452;&#12490;&#12522;&#12473;&#12488; &#12398;&#30342;&#12373;&#12435;&#12434;&#12362;&#25307;&#12365;&#12375;&#12383;&#24231;&#35527;&#20250;&#12398;&#27096;&#23376;&#12434;&#12362;&#23626;&#12369;&#12375;&#12414;&#12377;&#12290;&#12394;&#12380;&#25361;&#25126;&#12375;&#12383;&#12398;&#12363;&#12289;&#12300;&#26368;&#36895;&#12301;&#12434;&#12393;&#12358;&#20316;&#12426;&#36796;&#12435;&#12384;&#12398;&#12363;&#12289;&#12381;&#12375;&#12390; AI &#26178;&#20195;&#12395;&#12300;&#23455;&#35013;&#21147;&#12301;&#12399;&#12393;&#12371;&#12408;&#21521;&#12363;&#12358;&#12398;&#12363;&#12290;&#19990;&#20195;&#12418;&#12496;&#12483;&#12463;&#12464;&#12521;&#12454;&#12531;&#12489;&#12418;&#30064;&#12394;&#12427; 4 &#21517;&#12398;&#35328;&#33865;&#12395;&#12399;&#12289;&#12371;&#12428;&#12363;&#12425;&#25361;&#25126;&#12377;&#12427;&#12415;&#12394;&#12373;&#12435;&#12408;&#12398;&#12498;&#12531;&#12488;&#12364;&#35440;&#12414;&#12387;&#12390;&#12356;&#12414;&#12375;&#12383;&#12290;</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/08/tdx-tokyo-2026-lightning-minihack-live">【TDX Tokyo 2026】Lightning MiniHack Live ファイナリスト座談会: 初代チャンピオンと挑戦者たちが語る「最速」の舞台裏と AI 時代の実装力</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<h2><b>はじめに</b></h2>
<p><span style="font-weight: 400">みなさん、こんにちは。株式会社セールスフォース・ジャパンの Trailblazer リレーションズチームで、Developer Advocate をしている odasho です。</span></p>
<p><span style="font-weight: 400">2026 年 6 月に開催された TDX Tokyo 2026 (Agentforce World Tour Tokyo) の会場で、Salesforce の実装スピードと正確性を競う「Lightning MiniHack Live」が初開催されました。予選を勝ち抜いた 4 名のファイナリストが 1 vs 1 のトーナメント形式で対決し、初代チャンピオンが誕生しています。</span></p>
<h2><b>ファイナリストのご紹介</b></h2>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206934" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260901093008/Madani.jpg?w=1333" class="postimages" width="1333" height="1000" alt="" />
			  </span>
			</p>
<p><b>Madani Abdullah さん(サークレイス株式会社) — 初代チャンピオン</b><span style="font-weight: 400"> シリア出身。日本のアニメや文化に惹かれて 2019 年に来日し、2025 年 4 月にサークレイス株式会社へ新卒入社。Salesforce デベロッパーとして活動中です。</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206938" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260901093026/Hasegawa.jpg?w=1333" class="postimages" width="1333" height="1000" alt="" />
			  </span>
			</p>
<p><b>Yuki Hasegawa さん (東京都市大学 メディア情報学部 社会メディア学科 3 年)</b><span style="font-weight: 400"> 昨年の Agentforce Hackathon Tokyo で優勝した企業でインターン中の学生。本ハッカソンが Salesforce にほぼ初めて触れる挑戦でした。</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206935" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260901093013/Omuro.jpg?w=1333" class="postimages" width="1333" height="1000" alt="" />
			  </span>
			</p>
<p><b>Tomoki Omuro さん (東京都市大学 メディア情報学部 社会メディア学科 3年)</b><span style="font-weight: 400"> Hasegawa さんと同じ企業 (株式会社 Tekuru) でインターン中。日頃から社長や先輩たちが挑戦する姿を間近で見て、自身も初の大会参加を決意しました。</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206936" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260901093019/Suzuki.jpg?w=1334" class="postimages" width="1334" height="1000" alt="" />
			  </span>
			</p>
<p><b>Tatsuro Suzuki さん (アクセンチュア株式会社)</b><span style="font-weight: 400"> Salesforce 歴 14 年のベテラン。近年はマネジメント側に軸足を移しつつも、現場の第一線であり続けるために挑戦を決めました。当日はオンラインでご参加いただきました。</span></p>
<p><span style="font-weight: 400">聞き手は Salesforce の Kimura と odasho です。</span></p>
<h2><b>なぜ Lightning MiniHack Live に挑んだのか</b></h2>
<p><b>Madani </b><b>さん</b><span style="font-weight: 400">は、昨年のハッカソンにも応募した経験があり、挑戦することそのものに強い哲学を持っています。</span></p>
<p><span style="font-weight: 400">「挑戦しなかったら、あとに残るのは『挑戦しなかった』という結果だけです。生きている間に挑戦できる機会があるなら挑戦したい。それに、コミュニティには技術や実装に長けた方が多いので、そうした方々と競える機会は楽しいんじゃないかと思ったんです」</span></p>
<p><b>Hasegawa さん</b><span style="font-weight: 400">のきっかけは、同じインターン先の Omuro さんからの誘いでした。</span></p>
<p><span style="font-weight: 400">「私は現在大学 3 年生で、就職活動が始まったばかりです。クラウド業界やソフトウェア業界を志望しており、その中でも Salesforce の技術やエコシステムに魅力を感じていました。学生のうちに実践的な技術力を磨き、自分の力を試したいと思っていた中で、この Lightning MiniHack Live の開催を知りました。ひとりだけでは挑戦しなかったかもしれませんが、Omuro さんも挑戦したいということを教えてもらい、挑戦への勇気をもらいました」</span></p>
<p><b>Omuro さん</b><span style="font-weight: 400">は、インターン先の社長の背中を見て、挑戦を決意したそうです。</span></p>
<p><span style="font-weight: 400">「社長が 2025 年に Agentforce Hackathon など様々なイベントや事業に挑戦している姿を日々見ていて、自分もそのような経験がしてみたいと思って応募しました」</span></p>
<p><span style="font-weight: 400">そして </span><b>Suzuki さん</b><span style="font-weight: 400">。14 年のキャリアを持つベテランならではの、少し意外な動機でした。</span></p>
<p><span style="font-weight: 400">「徐々にマネジメント側に移りつつあって、最近は Apex を組む機会も減ってきていたんです。プロジェクトのメンバーには『最新のリリースノートを読み込もう』『最新情報をキャッチアップしよう』と言っている手前、私自身もちゃんとキャッチアップして『我々もいけるんだぞ』というところを見せたい。そんな気持ちで応募しました」</span></p>
<h2><b>「最速」はこうして作られた 〜予選・本選への準備〜</b></h2>
<p><span style="font-weight: 400">予選・本選で問われたのは、スピードと正確性の両立。ファイナリストたちはそれぞれのやり方で「最速」を作り込んでいました。</span></p>
<p><span style="font-weight: 400">Madani さんの練習法は、アスリートさながらの動画分析です。</span></p>
<p><span style="font-weight: 400">「自分の実装を動画に撮って、時間を確認する。『この部分はもっと速くできる』『ここは AI に任せられる』と分析してもう一度練習する。その繰り返しで毎回タイムを縮めて、最速の手順を作り込んでいきました。ベストの動画は残しておいて、AWTT (Agentforce World Tour Tokyo) に向かう移動中もスマホで見返していましたね」</span></p>
<p><span style="font-weight: 400">学生のお二人は、インターン先のメンバーを巻き込んだチーム戦でした。</span></p>
<p><span style="font-weight: 400">「オンラインで社員のみなさんと勉強会をしたり、空いている時間にタイムを計って『何分何秒だった』と社内で共有して競い合ったり励まし合ったりしていました」(Hasegawa さん)</span></p>
<p><span style="font-weight: 400">「『インターンシップの業務時間を使って練習していいよ』と言ってもらえたことが、まず大きかったです。さらに、実装方針を一緒に考えてもらったり、操作方法の練習を見てもらったりと、社長自らサポートしてくれました。ここまで挑戦できたのは、会社全体で背中を押してもらえる環境があったからだと思います」(Omuro さん)</span></p>
<p><span style="font-weight: 400">Suzuki さんは、要件の「見切り」から入る戦略派です。</span></p>
<p><span style="font-weight: 400">「ひととおり試してみて、3 シナリオのうち 2 つは制限時間に収まらないと判断しました。だったら他の方もハマらないはずだと前提を置いて、いかに業務が回る形に要件を絞るかを考える。制限時間内で実現できる最大限のアプリケーションを設計して、あとはひたすら練習。1 分のバッファを残して収まるまで作り込み、その 1 分で加点要件を取りにいくアプローチです」</span></p>
<p><span style="font-weight: 400">ちなみに運営側では、皆さんから提出された予選のプレイを全部絶叫しながら見ていました。予選通過タイム 1 位は Madani さんで、なんと 2 位に約 1 分半の差をつけてのトップ通過でした。</span></p>
<h2><b>四者四様の実装選択 〜入力規則？ フロー？ Apex トリガー？〜</b></h2>
<p><span style="font-weight: 400">本選の課題は、入力規則・フロー・Apex トリガーのいずれでも実装できるバリデーション要件。「緊急フラグがオンなら予算超過チェックをスキップする」という例外ロジックの扱いが勝負どころでした。ここでファイナリストたちの選択が分かれます。</span></p>
<p><span style="font-weight: 400">Suzuki さんの選択は </span><b>Apex トリガー</b><span style="font-weight: 400">でした。</span></p>
<p><span style="font-weight: 400">「フローでもいけると思うんですが、ドラッグ &amp; ドロップやダブルクリックで設定画面を開く操作には、1〜2 秒ずつの積み重ねがあります。一方トリガーなら、最後に一発セーブすればそこでしか PC とサーバー間の通信が発生しない。そこに賭けました」</span></p>
<p><span style="font-weight: 400">Omuro さんは</span><b>フロー</b><span style="font-weight: 400">を選択。理由は「安定性」です。</span></p>
<p><span style="font-weight: 400">「ほかの手段を試したときに読み込みに時間がかかることがあって、録画するタイミングによってタイムがまちまちだったんです。安定しないなと思って、フローだけに絞りました」</span></p>
<p><span style="font-weight: 400">Hasegawa さんは、初心者として「確実性」を最優先しました。</span></p>
<p><span style="font-weight: 400">「カスタムエラーを使いました。奇をてらったやり方だと自分が混乱してしまうと思ったので、速さには欠けても確実にできるものを選びました。とにかく間違えないように、です」</span></p>
<p><span style="font-weight: 400">そして優勝した Madani さんの選択は、フロー。ただしその使い方が徹底されていました。</span></p>
<p><span style="font-weight: 400">「Apex とフロー、どちらも試したうえで、私はフローを構築するほうが速かったんです。ホットキーで速度を上げられますし、数式も使える。そして読み込み時間の間には必ず別の作業をする。フローの弱みを、作業のパラレル化でカバーしていました」</span></p>
<p><span style="font-weight: 400">得意なものを選ぶのではなく、検証したうえで「自分にとっての最速」を選ぶ。全員に共通していたのは、この姿勢でした。</span></p>
<h2><b>AI 時代における「実装力」とは何か</b></h2>
<p><span style="font-weight: 400">今回の運営側のこだわりは、AI で何でもできるようになった時代における「人による実装力」でした。そこで、AI ツールと人が手を動かす力の関係をどう捉えているか、全員に聞いてみました。</span></p>
<p><span style="font-weight: 400">Madani さんの判断基準は明快です。</span></p>
<p><span style="font-weight: 400">「まず自分に問いかけるのは『AI にプロンプトを書くより、自分の手を動かしたほうが速いか？』です。自分の手のほうが速いならプロンプトを書く時間がもったいないし、プロンプトの結果が必ずしも欲しいものになるとは限らない。逆に AI に任せると決めたら、指示をしている間に自分は別のことをする。これからの実装力は『誰が最も正しく AI に説明し、正しい出力を引き出せるか』になっていくと思います」</span></p>
<p><span style="font-weight: 400">Suzuki さんは「対話力」というキーワードを挙げました。</span></p>
<p><span style="font-weight: 400">「これからの実装力は、対話力とニアリーイコールになっていくと思っています。明確な要件があってあとは作るだけ、という領域は AI が担っていく。一方で、コンタクトセンターやフィールドサービス、営業といった現場で汗をかいて、お客様と話しながら『あ、これはこの画面に欲しかったんだ』とサクッと作って当てられる力は、どうしても人間が必要です。Salesforce は画面をドラッグ &amp; ドロップで実装できるので、対話ツールとしても優秀なんですよ」</span></p>
<p><span style="font-weight: 400">学生のお二人は、AI との日々の付き合いから実感を語ってくれました。</span></p>
<p><span style="font-weight: 400">「出てきた成果物を評価して、さらによく改善していくのは人間の楽しみだと思います。曖昧な指示だと想定と全然違う動きになってしまうので、『自分がやりたいことは何か』を自分の中で明確にすることが大事だと感じています」(Omuro さん)</span></p>
<p><span style="font-weight: 400">「私も日常会話くらいの感覚で AI を使うんですが、けっこう間違いが多くて。『ここ違くない？』と言うと『すみません』って、平気で嘘をついてくるんですよ(笑)。だからこそ、AI が生成したものを精査する力が実装力において大切なんじゃないかと思います」(Hasegawa さん)</span></p>
<h2><b>初代チャンピオンの勝ち筋 〜ウィナーズインタビュー〜</b></h2>
<p><span style="font-weight: 400">優勝した Madani さんに、勝因を自己分析してもらいました。</span></p>
<p><span style="font-weight: 400">「横の画面で AI に指示を出して、AI が作っている間に、私は AI にできないことを別の画面で進める。Agentforce Grid など Salesforce の最新機能を使おうという意識もありました。完全に AI に任せるのではなく、指示して、待っている間に他の作業をして、戻ってきて AI の生成物を編集する。このやり方が優勝に導いてくれたと思っています。こうした、要件に応じて最適な実装方法を見極め、AIと自身の技術を使い分けながら素早く形にする力は、実際のお客様のプロジェクトでも生かされています。お客様のご要望に対して、スピードと品質を両立させながら設計やコーディングを進める推進力につながっています」</span></p>
<p><span style="font-weight: 400">「優勝できると思っていましたか？」という質問には、迷いなく「はい」。</span></p>
<p><span style="font-weight: 400">「私は自分が勝つと思って挑戦していました。たとえ勝てなかったとしても『全力を尽くした』と言えるくらいの努力はしたつもりです」</span></p>
<p><span style="font-weight: 400">優勝が決まった瞬間については、こう振り返ります。</span></p>
<p><span style="font-weight: 400">「言葉にできないくらい幸せでした。頑張ったことの価値を感じられる瞬間が、一番幸せなんだと思います。Salesforce の知識が少ないところから増やしながら、それでも『勝ちます』という自信を持って頑張れたことは、次の挑戦への自信にもなります。この結果は自分の努力だけではなく、職場のみなさん、そして妻をはじめ、多くの支えてくれた方々のおかげです。……ただ同時に、練習では時間内に終わらせられたのに、本戦では収めきれなかった悔しさもあるんですけどね」</span></p>
<p><span style="font-weight: 400">チャンピオンはすでに次を見据えていました。</span></p>
<h2><b>挑戦を終えて 〜これから参加するあなたへ〜</b></h2>
<p><span style="font-weight: 400">最後に、この挑戦で得たものを聞きました。</span></p>
<p><span style="font-weight: 400">「Salesforce は名前を聞いたことがある程度の認識だったので、ノーコードでアプリを作れる世界があることをまず知れました。就活の面でも『こういう進路もあるんだ』と視野が広がったのは大きなメリットです。私はもともと人前に出るのがすごく苦手で、予選に通ったときも『みんなの前でやるのか……』と危惧していたくらいなんです。1 vs 1 の対戦では敗れて決勝には進めず悔しかったのですが、足も手も子鹿のように震わせながら(笑)、それでも実力を発揮できた。自分の中で本当にいい経験になりました」(Hasegawa さん)</span></p>
<p><span style="font-weight: 400">「Salesforce は知っていましたが、企業が業務で使うものという格式高いイメージがあって、自分に触れるのかなという気持ちがありました。でも触ってみると、どういうものか分かればできるものなんだなと。知らないから勝手に難しいと思い込んでいただけで、自分の中のハードルがすごく下がりました。今回自信がなくて挑戦しなかった方がいらっしゃれば、難しくとらえすぎず、次回挑戦していただけたらと思います」(Omuro さん)</span></p>
<p><span style="font-weight: 400">「一番よかったのは、新しい知識を得られたことです。普段使っているとはいえ、自分のプロジェクトで触る機能は本当に一部なんですよね。聞いたことはあっても自分の手で作ったことのない機能に触れられましたし、他のみなさんのプレイから『こんな方法があったのか、これならもっと速くなる』と学べたのも、すごく面白い経験でした」(Suzuki さん)</span></p>
<h2><b>おわりに</b></h2>
<p><span style="font-weight: 400">運営としてこの企画には、「AI 時代でも、やろうと思えば人はキャッチアップできる」ということを証明したいという思いがありました。Salesforce にほぼ初めて触れる学生から 14 年のベテランまでが同じ土俵で本気の勝負を見せてくれたことで、それが証明されたと感じています。</span></p>
<p><span style="font-weight: 400">一方で作り手としては、クリアされると難易度を上げたくなってしまうのも正直なところ(笑)。せっかく初代チャンピオンが誕生したので、次回はレギュレーションや名称が変わるかもしれませんが、挑戦してくれたみなさんに報いるような、そして初めての方がもっと参加しやすいような形を用意していきます。</span></p>
<p><span style="font-weight: 400">この記事を読んで「自分もやってみたい」と思った方は、まずは Trailhead で Salesforce に触れてみてください。次はあなたの挑戦をお待ちしています！</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206937" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260901093023/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%8A%E3%83%AA%E3%82%B9%E3%83%88%E5%85%A8%E5%93%A1.jpg?w=1333" class="postimages" width="1333" height="1000" alt="" />
			  </span>
			</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/08/tdx-tokyo-2026-lightning-minihack-live">【TDX Tokyo 2026】Lightning MiniHack Live ファイナリスト座談会: 初代チャンピオンと挑戦者たちが語る「最速」の舞台裏と AI 時代の実装力</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/08/tdx-tokyo-2026-lightning-minihack-live/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206928</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260901093651/2026-09-02_01-36-27.jpg?w=2000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260901093651/2026-09-02_01-36-27.jpg?w=2000" medium="image" />
	</item>
		<item>
		<title>Shift Left Natively: Testing and Quality Gates in DevOps Center</title>
		<link>https://developer.salesforce.com/blogs/2026/08/shift-left-natively-testing-and-quality-gates-in-devops-center</link>
		<comments>https://developer.salesforce.com/blogs/2026/08/shift-left-natively-testing-and-quality-gates-in-devops-center#respond</comments>
		<pubDate>Tue, 25 Aug 2026 15:00:43 +0000</pubDate>
		<dc:creator><![CDATA[spadmannagari]]></dc:creator>
				<category><![CDATA[App Development]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Flow]]></category>
		<category><![CDATA[Agentforce]]></category>
		<category><![CDATA[Apex Testing]]></category>
		<category><![CDATA[Automated Testing]]></category>
		<category><![CDATA[Code Analyzer]]></category>
		<category><![CDATA[Flow Testing]]></category>
		<category><![CDATA[Quality Gates]]></category>
		<category><![CDATA[Salesforce DevOps Center]]></category>
		<category><![CDATA[Shift-Left Testing]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206887</guid>
		<description><![CDATA[<p>Learn how generally available testing in DevOps Center helps you catch issues earlier and ship with confidence by automating tests and enforcing quality gates before changes reach production.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/08/shift-left-natively-testing-and-quality-gates-in-devops-center">Shift Left Natively: Testing and Quality Gates in DevOps Center</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><a href="https://help.salesforce.com/s/articleView?id=platform.devops_center_testing_setup.htm&amp;type=5"><span style="font-weight: 400">DevOps Center Testing</span></a><span style="font-weight: 400"> is now generally available, natively on the platform within DevOps Center. For today’s enterprises, speed means nothing without stability. Yet, the release cycle still hits a familiar bottleneck at the very end: manual or disconnected testing. When a test fails late in the cycle, code gets sent all the way back to the developer, resulting in broken feedback loops and delayed releases. By embedding automated testing directly into the Headless 360 platform, your team can now catch regressions and enforce compliance long before code ever gets near production. In this post, we’ll look at the cost of late-stage testing, how automated quality gates work, the native test providers you can use, and how to scale this with API tools.</span></p>
<h2><b>What is DevOps Center Testing?</b></h2>
<p><span style="font-weight: 400">Testing in DevOps Center brings together Salesforce tools like </span><a href="https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview"><span style="font-weight: 400">Code Analyzer</span></a><span style="font-weight: 400">, Apex unit tests, and flow tests (with other Salesforce tools and partner tools from ACCELQ, Copado, Panaya, Provar, Quality Clouds, and Tricentis coming soon). You can run everything from static code analysis to full regression tests within a single workflow.</span></p>
<p><span style="font-weight: 400">DevOps Center Testing is the single source of truth for test definitions and test results, where you can define your test strategy across multiple tools, run tests automatically, and enforce quality gates to keep only high-quality changes moving forward.</span></p>
<p><span style="font-weight: 400">This means that your teams are empowered to find issues earlier in the development process and fix them well before any bugs make their way to production, where they are expensive to fix. You’ll be able to comprehensively test Apex and flow actions thanks to Code Analyzer v5 and integration with Apex unit tests and flow tests.</span></p>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206889" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260824135928/image2_ce3802-e1787605177517.png?w=1000" class="postimages" width="1000" height="492" alt="A DevOps Center interface displaying a multi-stage testing pipeline." />
			  </span>
			</p>
<h2><b>The cost of the late-stage testing bottleneck</b></h2>
<p><span style="font-weight: 400">&#8220;Shifting left&#8221; is the practice of moving testing, quality checks, and compliance validation as early in the development lifecycle as possible. Catching a bug in a developer sandbox takes minutes to fix. Catching that same bug in production can cost days of downtime and thousands of dollars in remediation.</span></p>
<p><span style="font-weight: 400">Achieving a true shift-left workflow on Salesforce historically required complex CI/CD orchestration, external script maintenance, and heavy third-party syncing tools. DevOps Center eliminates this operational overhead by bringing automated quality frameworks natively on the platform where your metadata and code already live.</span></p>
<h2><b>Quality gates: automated pipeline governance</b></h2>
<p><span style="font-weight: 400">Running tests automatically is only half the battle; the real magic happens when you use those tests to enforce governance.</span> <span style="font-weight: 400">A</span> <i><span style="font-weight: 400">quality gate</span></i><span style="font-weight: 400"> is an automated checkpoint in a CI/CD pipeline that evaluates code against predefined criteria, blocking substandard software from advancing or deploying until it meets the required thresholds. </span><a href="https://help.salesforce.com/s/articleView?id=platform.devops_testing_ensure_high_quality_work_items_with_quality_gate_rules.htm&amp;type=5"><span style="font-weight: 400">Automated quality gates</span></a><span style="font-weight: 400"> are now directly part of the pipeline workflow in DevOps Center. Instead of relying on manual checklists or human oversight to approve a pipeline promotion, you can define strict criteria for specific pipeline stages, such as on pull request creation or pre-promotion.</span></p>
<p><span style="font-weight: 400">Imagine a developer attempting to promote a work item. Behind the scenes, DevOps Center automatically triggers the assigned Apex, Flow, and Code Analyzer test suites. If an Apex test fails, code coverage drops below your organization&#8217;s threshold, or Code Analyzer flags a critical security vulnerability, the pipeline automatically halts. The promotion is blocked, and the developer receives immediate, line-level feedback right inside their work item. The bug is isolated and fixed instantly, keeping the main release branch completely clean.</span></p>
<h2><b>Test providers in native platform automation</b></h2>
<p><span style="font-weight: 400">DevOps Center Testing unifies execution and reporting for your most critical internal test providers right out of the box. From a single centralized hub, you can easily orchestrate a comprehensive testing matrix across programmatic and declarative elements.</span></p>
<ul>
<li style="font-weight: 400"><b>Apex tests:</b><span style="font-weight: 400"> Automatically execute unit tests and track code coverage percentages to ensure backend programmatic logic is thoroughly vetted early.</span></li>
<li style="font-weight: 400"><b>Flow tests:</b><span style="font-weight: 400"> Natively execute declarative flow test cases to verify that your automated business logic behaves precisely as intended before promotion.</span></li>
<li style="font-weight: 400"><b>Salesforce Code Analyzer:</b><span style="font-weight: 400"> Catch security vulnerabilities, detect anti-patterns, and evaluate technical debt before code review even begins by running multi-engine static code analysis including PMD, ESLint, and </span><a href="https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/engine-flow.html"><span style="font-weight: 400">Flow Scanner</span></a><span style="font-weight: 400">.</span></li>
</ul>
<p>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206890" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260824135959/image1_77b037-e1787605210118.png?w=1000" class="postimages" width="1000" height="725" alt="A Configure Test Provider interface showing options for Code Analyzer, Flow Tests, and Apex Unit Tests." />
			  </span>
			</p>
<h2><b>Agentic era: skills for DevOps Center Testing</b></h2>
<p><span style="font-weight: 400">While manual and programmatic gate configuration offers incredible safety, managing a matrix of dozens of test suites, mapping coverage gaps, and diagnosing complex failures can become a full-time task. To simplify this complexity, we have also released </span><b>Skills for Testing</b><span style="font-weight: 400"> within Agentforce Vibes.</span></p>
<p><span style="font-weight: 400">Four new Agentforce skills bring AI-assisted test management directly into your DevOps Center workflow, from configuring providers and quality gates to running suites, analyzing failures, and tracking fixes.</span></p>
<p><span style="font-weight: 400">These skills combine to act as an autonomous copilot for your release operations, allowing DevOps Center Testing managers to control the lifecycle through natural language interaction:</span></p>
<ul>
<li><code><span>dx-devops-test-pipeline-configure</span></code><br />
<b><span>What it does:</span></b><span> Configures test providers, syncs new suites into a pipeline, and assigns quality gates with pass-rate, severity, and essential-test rules to test suites on a pipeline stage. Instead of manually mapping criteria across complex tables, managers can instruct the agent to align the gate logic automatically.</span></li>
<li><code><span>dx-devops-test-suite-assignments-configure</span></code><br />
<b><span>What it does:</span></b><span> Recommends and manages DevOps Center test suite assignments for pipeline stages. Use this skill to recommend suites for a commit, assign or map suites to stages, or add/remove tests in a suite.</span></li>
<li><code><span>dx-devops-test-suite-run</span></code><br />
<b><span>What it does:</span></b><span> Triggers test suite execution on a specific pipeline stage, polls for results, and can automatically re-run a quality gate check after a developer resolves their code coverage gaps. This keeps the deployment train moving efficiently with automated validation loopbacks.</span></li>
<li><code><span>dx-devops-test-failures-analyze</span></code><br />
<b><span>What it does:</span></b><span> Analyzes DevOps Center test failures and Code Analyzer violations in plain language, failure category, offending file/class/method/line, rule violated, fix direction, and prioritized improvement suggestions (test-code vs production-code), then optionally creates a tracked fix WorkItem on explicit request.</span></li>
</ul>
<h2><b>How to get started</b></h2>
<p><span style="font-weight: 400">You can bring automated quality to your release management workflow in a few quick steps:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Locate the Tests tab: The automated testing feature is available within the DevOps Center application. Admins can open their DevOps Center pipeline to find the new native Tests management interface.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Add test providers: Configure one or more of the available test providers: Apex, Flow, and Code Analyzer.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Configure your quality gates: Define your testing thresholds. Decide which pipeline stages require mandatory Apex test passes, flow validations, or Code Analyzer scans.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Automate and scale: Review the developer</span><a href="https://help.salesforce.com/s/articleView?id=platform.devops_center_testing_setup.htm&amp;language=en_US&amp;type=5"><span style="font-weight: 400"> reference documentation</span></a><span style="font-weight: 400"> to connect your CLI scripts, repository webhooks, or Agentforce toolsets to the Headless 360 API.</span></li>
</ol>
<p><span style="font-weight: 400">By automating your testing lifecycle natively within DevOps Center and pairing it with Agentforce, you eliminate release anxiety, secure your digital supply chain, and empower your teams to ship high-quality features faster and smarter than ever before.</span></p>
<h2><b>Conclusion</b></h2>
<p><span style="font-weight: 400">DevOps Center Testing now enforces Apex, Flow, and Code Analyzer checks at each pipeline stage, and you can trigger the same gates from your existing CI tools via the CLI or REST API.</span></p>
<h2><b>Resources</b></h2>
<ul>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=platform.devops_center_testing_setup.htm&amp;type=5"><span style="font-weight: 400">Testing User Guide</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_deployment_devops_center_testing.htm&amp;release=262&amp;type=5"><span style="font-weight: 400">Release Notes</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_list.htm#:~:text=67.0%20and%20later.-,DevopsQualityGateRule,-Defines%20the%20criteria"><span style="font-weight: 400">Object References</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/ci-cd.html#accelerate-your-github-cicd-integration"><span style="font-weight: 400">Code Analyzer</span></a><span style="font-weight: 400"> </span></li>
<li style="font-weight: 400"><a href="https://www.google.com/search?q=https://trailhead.salesforce.com/trailblazer-community/groups/0F94V000000HeS0SAK"><span style="font-weight: 400">DevOps Center Trailblazer Group</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=platform.devops_center_dx_inspector.htm&amp;language=en_US&amp;type=5"><span style="font-weight: 400">DX Inspector Documentation</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference.html"><span style="font-weight: 400">Salesforce CLI Reference</span></a></li>
<li style="font-weight: 400"><a href="https://help.salesforce.com/s/articleView?id=platform.next_generation_devops_center.htm&amp;type=5"><span style="font-weight: 400">Next Generation DevOps Center documentation</span></a></li>
</ul>
<h2><b>About the Author</b></h2>
<p><b>Srikrishna Padmannagari</b><span style="font-weight: 400"> is a Senior Product Manager at Salesforce, where he builds tools that simplify ALM for the global developer community. He leads product for </span><b>Salesforce DX Inspector</b><span style="font-weight: 400">, </span><b>Testing</b><span style="font-weight: 400">, Code Analyzer focusing on making the Salesforce DX ecosystem more efficient, reliable, and developer-friendly. Follow Srikrishna on </span><a href="https://www.linkedin.com/in/srikrishnapadmannagari/"><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/08/shift-left-natively-testing-and-quality-gates-in-devops-center">Shift Left Natively: Testing and Quality Gates in DevOps Center</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/08/shift-left-natively-testing-and-quality-gates-in-devops-center/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206887</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260824140820/Generic-C-6-e1787605714286.png?w=1000" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260824140820/Generic-C-6-e1787605714286.png?w=1000" medium="image" />
	</item>
		<item>
		<title>新しいAgentforceメタデータと開発ライフサイクル</title>
		<link>https://developer.salesforce.com/blogs/2026/08/new-agentforce-metadata-and-development-lifecycle-jp</link>
		<comments>https://developer.salesforce.com/blogs/2026/08/new-agentforce-metadata-and-development-lifecycle-jp#respond</comments>
		<pubDate>Tue, 25 Aug 2026 12:03:40 +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=206904</guid>
		<description><![CDATA[<p>&#26032;&#12375;&#12356;&#12496;&#12540;&#12472;&#12519;&#12531;&#31649;&#29702;&#20184;&#12365;&#12513;&#12479;&#12487;&#12540;&#12479;&#12514;&#12487;&#12523;&#12392;&#12289;&#20154;&#38291;&#12364;&#35501;&#12415;&#12420;&#12377;&#12356;Agent Script&#12434;&#27963;&#29992;&#12375;&#12390;&#12289;CI/CD&#12497;&#12452;&#12503;&#12521;&#12452;&#12531;&#12434;&#21177;&#29575;&#21270;&#12375;&#12414;&#12375;&#12423;&#12358;&#12290;&#12496;&#12531;&#12489;&#12523;&#21270;&#12373;&#12428;&#12383;&#12450;&#12475;&#12483;&#12488;&#12364;&#12300;&#12464;&#12525;&#12540;&#12496;&#12523;&#12394;&#27874;&#21450;&#21177;&#26524;&#12301;&#12434;&#25490;&#38500;&#12375;&#12289;&#12500;&#12450;&#12524;&#12499;&#12517;&#12540;&#12434;&#31777;&#32032;&#21270;&#12377;&#12427;&#12371;&#12392;&#12391;&#12289;&#12424;&#12426;&#20449;&#38972;&#24615;&#12398;&#39640;&#12356;&#12456;&#12540;&#12472;&#12455;&#12531;&#12488;&#12398;&#12487;&#12503;&#12525;&#12452;&#12434;&#23455;&#29694;&#12377;&#12427;&#26041;&#27861;&#12434;&#23398;&#12403;&#12414;&#12375;&#12423;&#12358;&#12290;</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/08/new-agentforce-metadata-and-development-lifecycle-jp">新しいAgentforceメタデータと開発ライフサイクル</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<blockquote><p>※本記事は2026年5月14日に米国で公開された <a href="https://developer.salesforce.com/blogs/2026/05/new-agentforce-metadata-and-development-lifecycle">The New Agentforce Metadata and Development Lifecycle</a>の抄訳です。本記事の正式言語は英語であり、その内容および解釈については英語が優先されます。</p></blockquote>
<p><span style="font-weight: 400"></span><a href="https://help.salesforce.com/s/articleView?id=ai.agent_builder_intro.htm&amp;type=5"><span style="font-weight: 400">新しいAgentforce Builder</span></a><span style="font-weight: 400">（Spring ’26正式リリース）では、AIエージェントの作成とテストのエクスペリエンスが刷新されます。スピーディで「失敗しても安全」な環境が提供され、試行錯誤しながら複雑なロジックを改良できます。本番環境で稼働しているAIエージェントへの影響を心配する必要はありません。</span></p>
<p><span style="font-weight: 400">Salesforce開発者の皆さんなら、構築エクスペリエンスが新しくなると、たいていは内部の仕組みも変わることをご存じでしょう。今回、このスピード感がある、安全な作業環境をサポートするために、基盤となるメタデータも進化させる必要がありました。この記事では、新しい開発者ワークフロー、メタデータが変更された理由、デプロイ用にパッケージを構成する方法について、順を追って説明します。</span></p>
<h2><b>新しい開発ワークフロー – スクリプトからメタデータへ</b></h2>
<p><span style="font-weight: 400">メタデータの変更については、AIエージェント構築の新しい考え方を理解しておくとわかりやすくなるでしょう。新しいAgentforce Builderでは、開発者の「意図」（AIエージェントに実行させたいこと）と、エンジンの「実行」（有効なメタデータ）が明確に分離されます。</span></p>
<p><span style="font-weight: 400">AIエージェントの作成から公開までは、次のような流れで進みます。</span></p>
<ol>
<li><b><span>記述と構築</span></b><span> – Agentforce BuilderのUIを使用するか、</span><b><span>Agent Script</span></b><span>を使って、AIエージェントを作成します。人間が読んで理解できるこのスクリプトは、AIエージェントの設定を示す単一の</span><code><span>.agent</span></code><span>ファイルとして、新しい</span><code><span>AiAuthoringBundle</span></code><span>メタデータ型内に保存されます。この段階では、作業内容はドラフトの状態です。</span></li>
<li><b><span>改良とプレビュー</span></b><span> – プレビューモード（テストを組織のデータから安全に分離するシミュレーションモードなど）を使用して、ドラフトをテストします。</span></li>
<li><b><span>公開</span></b><span> – ドラフトの内容で問題ないと判断したら、そのバージョンをコミットします。</span><b><span>ここが非常に重要なポイントです。</span></b><span>コミットの段階で初めて、プラットフォームは</span><code>.agent</code><span>スクリプトを、AIエージェントの実行に実際に使われる有効な</span><code>Bot</code><span>メタデータと</span><code>GenAiPlannerBundle</code><span>メタデータに変換します。</span><i><span></span></i><span></span></li>
</ol>
<h2><b>メタデータ変更の理由 – ローカルアセットのバンドル化</b></h2>
<p>新しいビルダーで<a href="https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_genaiplannerbundle.htm"><u><code>GenAiPlannerBundle</code><span>メタデータ</span></u></a><span>の構成を変更したのはなぜかというと、分離を確保し、デプロイの信頼性を高める必要があったからです。</span><br />
<span></span></p>
<p><span>これまで、Agentforceアセットはグローバルでした。あるAIエージェントのために共有サブエージェントを変更すると、それが全体に波及し、そのサブエージェントを使用しているすべてのエージェントに即座に影響していました。これを解決するために導入したのが</span><b><span>ローカルアセット</span></b><span>（ローカルサブエージェントとローカルアクションを含む）です。ローカルアセットは、グローバルアセットを複製して作成され、特定のエージェントバージョンにひも付けられます。アセットがローカルになることで、他に影響を与えず安全に編集できます。</span><br />
<span></span></p>
<p><span>ローカルアセットは以前から利用可能でしたが、CI/CDパイプラインで完全にサポートするには、</span><code>GenAiPlannerBundle</code>メタデータを更新する必要がありました。これまで、エージェントメタデータは複数のメタデータ型に分散していました。新しい<code>GenAiPlannerBundle</code>で最も大きな変更は、「バンドル化」です。特定のバージョンに関連し、依存関係にあるすべてのエージェントアセットを、1つのフォルダー構造でデプロイして利用できるように、メタデータを再構成しました。これにより、デプロイエラーを防げるともに、エージェントがプレビュー時とまったく同じように動作するために必要なものを、すべて揃えられます。<i></i><b></b><i></i></p>
<h2><b>内部の仕組み – 新しいメタデータ構造</b></h2>
<p><span style="font-weight: 400">従来のようにファイルが分散することなく、各エージェントバージョンに固有のすべての要素が、ディレクトリ構造の中にすっきりとまとめられています。</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="plannerファイル、localActionsサブフォルダー、入力および出力用のschema.jsonファイルを含む、GenAiPlannerBundleのディレクトリ構造を示す図" />
			  </span>
			</p>
<p><code>AiAuthoringBundle</code><span>は、AIエージェントの設計時のアーティファクトをすべて含むディレクトリです。このバンドルでは、</span><code>AgentName_1</code><span>や</span><code>AgentName_2</code><span>のように複数のバージョン付きサブディレクトリを使用でき、それぞれが独自の</span><code>.agent</code><span>ファイルと</span><code>.bundle-meta.xml</code><span>記述子を持ちます。</span><span>これらのサブディレクトリは、AIエージェントのドラフトやコミット済みバージョンなど、異なるライフサイクル状態に対応します。各バージョンには、次のファイルが含まれます。</span></p>
<ul>
<li><span></span><code>.agent</code><span>ファイルには、Agent Scriptで記述されたAIエージェントの設定が保持されます。これには、名前、ラベル、説明、システム指示、サブエージェント定義、推論指示、変数、条件分岐、ツール/アクション参照が含まれます。</span></li>
<li><span>メタデータ記述子（</span><code>.bundle-meta.xml</code><span>）は、bundleType（例：AGENT）と、その記述子が参照するエージェントバージョンを宣言します。</span></li>
</ul>
<p><code><span>Bot</span></code><span>には、AIエージェントの種類や、プライベートデータをログに記録するかどうかといった設定など、AIエージェントのすべてのバージョンに共通する情報が含まれます。また、Botはエージェントレベルのコンテキスト変数も保持します。これは、各</span><code><span>BotVersion</span></code><span>内に存在する会話変数とは異なります。</span><code><span>BotVersion</span></code><span>の詳細には、会話変数、ウェルカムメッセージ、会話転送時のメッセージが含まれます。</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="plannerバンドルのディレクトリ構造と、plannerActionディレクトリの内容を示す図" />
			  </span>
			</p>
<p><code></code><code>GenAiPlannerBundle</code>は、AIエージェントの公開済みバージョンごとに関連するデータをすべて保持します。各バージョンには、次の要素が含まれます。</p>
<ul>
<li><code>.genAiPlannerBundle</code>は、AIエージェントの実行時設定（サブエージェント、指示、アクション、オーケストレーションロジック）を含む、中核となるメタデータファイルです。</li>
<li><code>agentGraph</code>は、サブエージェント間のすべての遷移と条件を記述した<code>.json</code>ファイルです。</li>
<li><code>agentScript</code>は、そのバージョンのAgent Scriptをエンコードしたコピーです。これは同期検出を目的として保持されるスナップショットです。このスナップショットにより、スクリプトが公開済みまたはコミット済みメタデータと同期していない場合に、Agentforce Builderがユーザーに通知できます。</li>
<li><code>plannerActions</code><span style="color: #4a4a4a;font-family: &apos;Salesforce Sans&apos;, Arial, sans-serif">は、特定のサブエージェントにひも付かない、エージェントレベルのアクションです。</span></li>
<li><code>localActions</code>は、サブエージェント単位でスコープが限定されたアクションで、それぞれのサブエージェントフォルダーの下にネストされます。<code>input/schema.json</code>ファイルと<code>output/schema.json</code>ファイルが、各アクションの型付きパラメーターを定義します。</li>
</ul>
<p>注 – <code>GenAiPlannerBundle</code>のバージョンはコミットされた順に自動で番号が付けられるため、<code>AiAuthoringBundle</code>のバージョン番号と一致するとは限りません。</p>
<p>バージョンをコミットすると、<code>Bot</code>メタデータと<code>GenAiPlannerBundle</code>メタデータはどちらも自動的に作成されます。また、<code>AiAuthoringBundle</code>、<code>agentScript</code><span></span><span>、</span><code>agentGraph</code>は、新しいAgentforce Builderで作成されたAIエージェントのメタデータにのみ含まれます。</p>
<h3><b></b><b><code>GenAiPlannerBundle</code></b><b> XMLの例</b></h3>
<p><span style="font-weight: 400">ローカル化されたサブエージェントとアクションをXMLの例で見てみましょう。</span></p>
<pre class="wp-block-code language-xml">&lt;GenAiPlannerBundle&gt;
   &lt;description&gt;新しいAIエージェントの説明&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;リトリーバーアクションを使用する&lt;/description&gt;
       &lt;pluginType&gt;Topic&lt;/pluginType&gt;
       &lt;masterLabel&gt;ナレッジベースからデータを取得&lt;/masterLabel&gt;
       &lt;genAiPluginInstructions&gt;
           &lt;description&gt;あなたはAIエージェントです。&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><code>&lt;localTopics&gt;</code>ブロックでは、サブエージェントのすべての設定を定義します。一意のAPI名、説明、プラグイン型、決定的な順序で並べられた指示、そのサブエージェントで利用可能なすべてのローカルアクションなどです。また、元のサブエージェントへのリファレンスが含まれる場合もあります。</p>
<p><code>&lt;localActions&gt;</code>ブロックでは、アクション名、マスターラベル、呼び出し先とその種別を定義します。さらに、必要に応じて、クローン元のグローバルアクションへの<code>source</code>リファレンスを含める場合もあります。確認時の動作や読み込み中テキストの設定など、UX関連の追加設定が含まれる場合もあります。</p>
<h3><b>新しいメタデータモデルに対応したpackage.xmlの例</b></h3>
<p>新しいAgentforce Builderで構築したAIエージェントを正常に取得し、デプロイするには、<code>package.xml</code>に新しいバンドル型を含める必要があります。以下は、Spring ’26のメタデータ構造を前提として作成されたマニフェストの例です。</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">注 – このマニフェストではAPIバージョン66.0を使用しています。この例のGenAiPlannerBundleと新しいエージェントメタデータ型をサポートするには、このバージョンが必要です。</span></p>
<h3><b>新しいAgentforce Builderで開発するためのヒント</b></h3>
<p><span style="font-weight: 400">この新しいアーキテクチャーによって改善された開発ライフサイクルを活用する方法を、2つ紹介します。</span></p>
<ul>
<li><b>人間が読めるスクリプトファイルを使って、コードレビューを効率化する。</b>従来のAgentforce Builderでは、AIエージェントの変更をレビューするには、プルリクエスト内に分散した複雑なXMLファイルを見比べる必要がありました。新しいアーキテクチャーでは、人が読んで理解できる<code>.agent</code>スクリプトファイルを比較できます。これにより、ピアレビューが大幅にスピードアップし、内容も理解しやすくなります。</li>
<li><b></b><code>AiAuthoringBundle</code><span></span><b><span></span></b><span></span><b><span>を使用して、CI/CDをシンプルにする。</span></b><span>ほとんどのパイプラインでは、ソース管理に</span><code>AiAuthoringBundle</code><span></span><span><span><span>を保持するだけで済みます。新しい組織でそのAIエージェントが必要になったら、デプロイし、バージョンをコミットして実行時メタデータを生成し、最後に有効化します。これは、次のコマンドを使用して自動的に実行できます。</span></span></span><br />
<code>sf project deploy start --metadata AiAuthoringBundle --target-org my-org</code><br />
<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><br />
手動のステップを必要としない、完全に自動のパイプラインを構築したい場合は、<code>AiAuthoringBundle</code>と<code>GenAiPlannerBundle</code><span>の両方をソース管理に保持し、同時にデプロイすることで、コミット済みの状態にできます。</span>システムは、デプロイされるスクリプトと事前に生成されたメタデータをあわせて認識します。手動のコミット手順がすべてにスキップされ、エージェントはデプロイ直後に稼働状態になります。<br />
<code>sf project deploy start --metadata AiAuthoringBundle,GenAiPlannerBundle --target-org my-org</code></li>
</ul>
<h2><b>まとめ</b></h2>
<p><span style="font-weight: 400">新しいAgentforce Builderでは、メタデータ構造がバンドル化されたことで、影響範囲を分離し、バージョンを安全に維持しながらAIエージェントを作成できるようになりました。この構造により、メタデータが整理され、コードレビューが簡単になり、デプロイの信頼性も向上します。 </span></p>
<p><span style="font-weight: 400">さっそく、Agent Scriptと新しいAgentforce Builderを使ってみましょう。</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">こちらのTrailheadモジュール</span></a><span style="font-weight: 400">では、Agent Scriptを使用したエージェントの作成について学習できます。また、Agent Scriptの基本を解説した</span><a href="https://developer.salesforce.com/blogs/2026/02/agent-script-decoded-intro-to-agent-script-language-fundamentals"><span style="font-weight: 400">ブログ</span></a><span style="font-weight: 400">や、</span><a href="https://www.youtube.com/playlist?list=PLgIMQe2PKPSJNLEQTPkr06gKhjnHgI1Jb"><span style="font-weight: 400">YouTubeのAgent Script Decodedシリーズ</span></a><span style="font-weight: 400">（英語）もご覧ください。</span><a href="https://developer.salesforce.com/events#upcomingevents"><span style="font-weight: 400">Agentforce Builderのワークショップ</span></a><span style="font-weight: 400">（英語）も随時開催していますので、ぜひ参加してみてください。</span></p>
<h2><b>関連情報</b></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">リリースノート</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">エージェントのバージョン管理と編集</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">改良されたエージェントバージョン機能を使用したエージェントのテストとカスタマイズ（英語）</span></a></li>
</ul>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">ヘルプドキュメント</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">標準エージェントアクションの参照アクションの編集 </span></a></li>
</ul>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">パッケージ</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">Agentforceメタデータコンポーネントのパッケージ化（英語）</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">スクラッチ組織を使用したAIエージェントテンプレートの開発とパッケージ化（英語） </span></a></li>
</ul>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">メタデータの更新</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">は、サンフランシスコのSalesforce AIでリードソフトウェアエンジニアを務めており、Institute of Analyticsのフェロー（FIoA）でもあります。Agentforce Core Platformチームの中心メンバーとして、AgentforceにエンタープライズレベルのALMを導入するため、Agentforceのバージョン管理システムとローカルアセット構成の変更を共同で設計し、主導しました。</span><span style="font-weight: 400">LinkedInのプロフィールは</span><a href="https://www.linkedin.com/in/rshreyas2/"><span style="font-weight: 400">こちら</span></a><span style="font-weight: 400">。</span></p>
<p><b>Alba Rivas</b><span style="font-weight: 400">は、Salesforceのプリンシパルデベロッパーアドボケイトです。LinkedInのプロフィールは</span><a href="https://www.linkedin.com/in/alba-rivas/"><span style="font-weight: 400">こちら</span></a><span style="font-weight: 400">。</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/08/new-agentforce-metadata-and-development-lifecycle-jp">新しいAgentforceメタデータと開発ライフサイクル</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/08/new-agentforce-metadata-and-development-lifecycle-jp/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206904</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>Claude CodeでSalesforceエージェントスキルを構築する</title>
		<link>https://developer.salesforce.com/blogs/2026/08/build-a-salesforce-agent-skill-with-claude-code-jp</link>
		<comments>https://developer.salesforce.com/blogs/2026/08/build-a-salesforce-agent-skill-with-claude-code-jp#respond</comments>
		<pubDate>Tue, 25 Aug 2026 10:16:09 +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=206896</guid>
		<description><![CDATA[<p>&#27083;&#36896;&#21270;&#12373;&#12428;&#12383;&#12527;&#12540;&#12463;&#12501;&#12525;&#12540;&#12289;&#12467;&#12540;&#12489;&#12486;&#12531;&#12503;&#12524;&#12540;&#12488;&#12289;&#33258;&#21205;&#26908;&#35388;&#12484;&#12540;&#12523;&#12434;&#27963;&#29992;&#12375;&#12289;Salesforce&#12456;&#12467;&#12471;&#12473;&#12486;&#12512;&#20840;&#20307;&#12391;&#19968;&#36011;&#12375;&#12390;&#39640;&#21697;&#36074;&#12394;&#12467;&#12540;&#12489;&#12434;&#20316;&#25104;&#12377;&#12427;&#12383;&#12417;&#12398;&#12467;&#12540;&#12487;&#12451;&#12531;&#12464;&#12473;&#12461;&#12523;&#12434;&#36523;&#12395;&#12388;&#12369;&#12427;&#26041;&#27861;&#12434;&#23398;&#12403;&#12414;&#12375;&#12423;&#12358;&#12290;</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/08/build-a-salesforce-agent-skill-with-claude-code-jp">Claude CodeでSalesforceエージェントスキルを構築する</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<blockquote><p>※本記事は2026年5月29日に米国で公開された <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>の抄訳です。本記事の正式言語は英語であり、その内容および解釈については英語が優先されます。</p></blockquote>
<p><span style="font-weight: 400">AIにコードを書かせるのは簡単です。とはいえ、セキュリティレビューに合格し、ガバナ制限を守り、指定したフレームワークに従い、静的解析でも高い評価を得られるコードを安定して生成させるとなると、そう簡単にはいきません。</span><a href="https://agentskills.io/home"><span style="font-weight: 400">エージェントスキル</span></a><span style="font-weight: 400">（英語）を使えば、この問題を解決できます。エージェントスキルとは、開発チームが定義する「本番品質」を、エージェントが従い、検証ツールが強制できる形に落とし込んだ、構造化されたプロンプトパッケージです。LLMの出力には確率的なばらつきがあるため、どうしても例外的な結果が生じます。それを想定して検出するのが、このアーキテクチャーです。</span></p>
<p><span style="font-weight: 400">この記事では、Apexを例に、Salesforceエージェントスキルの構造を詳しく見ていきます。わかりやすいように、Claude Codeを使って具体的な例を紹介しますが、ここで取り上げる原則はどのAIエージェントにも適用できます。エージェントスキルとは何か、プロジェクトの構造、最初のスキルの作成方法について、順を追って説明していきます。</span></p>
<h2><span style="font-weight: 400">エージェントスキルとは</span></h2>
<p><span style="font-weight: 400">エージェントスキルとは、Claudeを汎用的なアシスタントではなく、スペシャリストとして使えるようにするために構造化されたプロンプトパッケージです。スキルでは、モデルの学習データだけに頼るのではなく、明確に定義されたワークフロー、リファレンス、コードテンプレート、自動検証ツールを用意します。スキルが有効化されると、これらがコンテキストに読み込まれます。</span></p>
<p><span style="font-weight: 400">Claudeの基盤である大規模言語モデル（LLM）は、Apex、LWC、SOQLといったSalesforceエコシステムのプログラミング言語をすでに習得しています。しかし、プログラミング言語を理解しているからといって、必ずしもベストプラクティスに沿った高品質なコードを安定して生成できるわけではありません。スキルは、これを可能にします。</span></p>
<p><span style="font-weight: 400">各スキルは、次のような一定の構造を持つフォルダー内に配置されます。</span></p>
<pre>skills/authoring-apex/
├── SKILL.md              # 実行ルール
├── references/           # デシジョンツリー、パターン、ガードレール
├── assets/               # サンプルコード
└── hooks/scripts/        # Python検証ツール
</pre>
<p><span>最低限必要なのは、</span><code>SKILL.md</code><span>です。これは、スキルの有効化時にClaudeが最初に参照するファイルです。その他の要素は、スキルの成長に応じて追加する足場となります。まずは</span><a href="http://skill.md"><u><code>SKILL.md</code></u></a><span>と参照ドキュメントを1つずつ用意し、指示だけではなく、仕組みによってルールを徹底したい段階でフックを追加するとよいでしょう。</span></p>
<p><span>スキルは、フロントマターにもとづいて有効化されます。フロントマターは、Markdownファイルの先頭で区切り記号「</span><code>---</code><span>」の間に記述されるYAML形式のメタデータブロックです。フロントマターはコンテンツとして表示されず、機械が読み取れる設定情報として使用されます。フロントマターは、どのような場合にそのスキルを有効にするかをエージェントに示します。nameフィールドでスキルを識別し、descriptionフィールドには、現在のタスクにこのスキルが適しているかどうかを判断するためのトリガールールを記述します。 </span></p>
<p><span>以下の例では、descriptionで</span><code>TRIGGER when</code><span> / </span><code>DO NOT TRIGGER when</code><span>という構造を使用して、スキルを有効にする条件と有効にしない条件の両方をエージェントに示しています。有効化の条件だけをモデルに伝えると、「このSOQLはApexコードの作成作業としてApexスキルで扱うのか、それともSOQLスキルで扱うのか」といったあいまいさが生じ、誤ったスキルが有効化されることがあります。この形式を使えば、そうしたあいまいさを排除できます。</span></p>
<pre language="yaml">---
name: authoring-apex
description: &gt;
  TRIGGER when: ユーザーがSalesforce Apexコードを作成、編集、またはレビューする場合 — 
  対象は、サービスクラス、セレクタークラス、
  トリガーハンドラー、テストクラス、バッチジョブ、Queueableクラス、
  呼び出し可能メソッド、Apex RESTエンドポイントを含む.clsファイルまたは.triggerファイル。
  DO NOT TRIGGER when: ユーザーがLWC JavaScript、Flow XML、
  単独のSOQLクエリーを扱う場合、または既存のテストを実行する場合。
---
</pre>
<p>この例は簡略化されたもので、実際には、descriptionにはもっと包括的な情報を記述します。すべてのクラス種別（<code>Schedulable</code>、<code>AuraEnabled</code>、<code>HttpCalloutMock</code>）を列挙し、除外するケースごとに、どのスキルが処理するかを明記します（LWC JavaScriptには「authoring-lwcを使用」など）。境界を具体的に定義するほど、誤ったスキルの有効化を減らせます。</p>
<p>条件に一致するタスクが検出されると、Claudeは<code>SKILL.md</code>を読み込み、ワークフローに従います。プロンプトで<code>/skill-name</code>を指定して、スキルを手動で有効化することもできます。どちらの場合も、ワークフロー、リファレンス、テンプレートを含むスキルのコンテキスト全体が自動的に組み込まれます。<code>SKILL.md</code>の内容を会話に貼り付ける必要はありません。</p>
<p><span>スキルの形式自体（フロントマターを含む</span><span></span><code>SKILL.md</code><span>、リファレンス、アセット）は、AnthropicがApache 2.0の下で管理している</span><a href="https://agentskills.io/home"><u>オープン仕様</u></a><span>（英語）で、コミュニティも開発に参加できます。作成した</span><span></span><code>SKILL.md</code><span>、リファレンス、テンプレートは、別の環境でも利用できます。フックの仕組み（PostToolUseライフサイクルイベントを含むhooks.yaml）はClaude Code固有のものです。別のエージェントランタイムを使用する場合は、検証を別の方法で組み込むことになりますが、スキルの内容はそのまま移行できます。そのため、スキルは特定の実行環境に依存しない、いわばヘッドレスなものになります。Agentforce Vibes、Claude Code、Cursor、Visual Studio Code、Gemini CLI、OpenAI Codex、Windsurf、Roo Code、Gooseのほか、このオープン仕様をサポートする30以上のエージェントで使用できます。</span></p>
<h2><span style="font-weight: 400">エージェントスキルの構造</span></h2>
<h3><span style="font-weight: 400">SKILL.md – 実行ルール</span></h3>
<p><span style="font-weight: 400">スキルを使用する際に従う重要なワークフローです。手順は変更できず、各フェーズには厳格な終了条件が設定されています。以下に、その構造を示す簡略化した例を紹介します。実際の</span><a href="http://skill.md"><span style="font-weight: 400">SKILL.md</span></a><span style="font-weight: 400">（英語）では各フェーズをさらに詳しく記述しますが、終了条件を伴うフェーズを順番に並べるという構造は同じです。</span></p>
<pre language="markdown">このワークフローを順番どおりに実行してください。ステップの省略、結合、順序変更は禁止します。
行き詰まった場合は停止し、不足しているコンテキストを確認してください。該当なしの場合は、
レポートで「N/A」と記載し、その理由を1行で示してください。

## 必須の入力

作成に入る前に、以下を収集または推測する：

- クラス種別（サービス、セレクター、バッチ、Queueable、Invocable、トリガーハンドラー）
- 対象となるオブジェクトとビジネス目標
- デフォルトの共有設定（理由がある場合を除き`with sharing`）
- すでに使用中のトリガーフレームワーク（または明示的に選択されたもの）

フェーズの構成:

### フェーズ1 — 作成
1. **プロジェクト規約を確認する** — 既存のパターン、トリガー
     フレームワーク、命名スタイルを調べる
2. **要件を満たす最小のパターンを選択する**:

  | 要件                  | パターン                              |
  | ------------------ | -------------------------------------- |
  | ビジネスロジック       | サービスクラス                          |
  | データアクセス         | セレクタークラス                        |
  | トリガーロジック        | トリガーハンドラー（フレームワークが必要）　|
  | Flowとの連携        | @InvocableMethod                       |
  | 一括処理             | Batch Apex                            |
  | 非同期処理          | Queueable                               |

3. 作成に入る前に、`assets/`内の**対応するテンプレートを確認する**
4. **ガードレールに従って作成する** — 以下の「ルール」セクションにあるすべてのルールを適用する
5. **テストクラスを生成する** — テストスキルに委任する

### フェーズ2 — 検証

6. **Code Analyzerを実行する** — すべてのブロッキング違反を修正し、クリーンになるまで再実行する
7. **テストを実行する** — 合否とコードカバー率を記録する

### フェーズ3 — レポート

8. **報告する** — ファイル、設計上の判断、アナライザー出力、テスト結果、デプロイメモ

</pre>
<p><span style="font-weight: 400">フェーズが適用されない場合、ClaudeはそのフェーズをN/Aとして記録し、理由を記す必要があります。これは、「順序や完全性が重要な場合は、番号付きリストを使って、指示を連続したステップとして示す」というAnthropicのベストプラクティスに対応しています。</span></p>
<h3><span style="font-weight: 400">references/: ナレッジライブラリ</span></h3>
<p><span style="font-weight: 400">referencesは、一般的な学習では一貫した対処ができない、特定の失敗モードに対処するための参照ドキュメントです。つい情報を詰め込んでしまいがちなので、以下にAnthropicの</span><a href="https://platform.claude.com/docs/en/docs/build-with-claude/prompt-engineering/claude-prompting-best-practices"><span style="font-weight: 400">ベストプラクティス</span></a><span style="font-weight: 400">をいくつか示します。</span></p>
<table>
<tbody>
<tr>
<td><b>ベストプラクティス</b></td>
<td><b>理由</b></td>
<td><b>Apexの例</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">ファイルサイズ</span></td>
<td><span style="font-weight: 400">ドキュメントが大きくなるほど、Claudeが重視するポイントに偏りが生じます。短く、目的が明確なファイルのほうが、安定して注意を向けられます。</span></td>
<td><span style="font-weight: 400">関心ごとに分けます。コアパターン（Factory、Strategy、セレクター、サービス）は1つのファイルにまとめ、高度なパターン（Unit of Work、ドメインモデル、Facade）は別のファイルに分けます。</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">階層構造</span></td>
<td><span style="font-weight: 400">目次と見出しがあると、Claudeがすべてを読まなくても関連するセクションを見つけられます。 </span></td>
<td><span style="font-weight: 400">SKILL.mdで「作成前にbest-practices.mdを読む」と指示しておくと、Claudeはファイル全体を確認することなく、必要な見出しに移動できます。</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">1セクションにつき1つのルール</span></td>
<td><span style="font-weight: 400">各セクションのポイントを1つに絞ると、適用されるルールからあいまいさを排除できます。</span></td>
<td><span style="font-weight: 400">「SOQL in Loops」という見出しのセクションなら、ループ内のSOQLだけを扱い、DMLや共有設定、null安全性は扱いません。</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">短いセクションを数多く</span></td>
<td><span style="font-weight: 400">各セクションを自己完結させる（約20～40行）ことで、Claudeはドキュメント全体を読み込まずに1つのルールを適用できます。</span></td>
<td><span style="font-weight: 400">アンチパターンであれば、1項目ごとに、見出し、「失敗する理由」の説明、BAD（悪い例）コードブロック、修正方法、GOOD（良い例）コードブロックを1つずつ用意します。</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">抽象的ではなく具体的に</span></td>
<td><span style="font-weight: 400">BAD/GOODのペアは、実質的にfew-shotの例として機能します。Anthropicは、例は「出力を方向付ける最も信頼できる方法の1つ」だと述べています。抽象的なルール（「ガバナ制限を避ける」）だと解釈に幅が出ますが、具体的なペアであれば振る舞いが安定します。</span></td>
<td><span style="font-weight: 400">// BAD: 1回の反復につきSOQLを1回実行。レコード101件目で100件のクエリー制限を超過する。</span></p>
<p><span style="font-weight: 400"></span></p>
<p><span style="font-weight: 400">// GOOD: SOQLの実行は全体で1回。</span></p>
<p><span style="font-weight: 400"></span></td>
</tr>
<tr>
<td><span style="font-weight: 400">命令よりも理由と結果を示す</span></td>
<td><span style="font-weight: 400">「Xは禁止」と伝えるより、「XはYという理由で失敗する」と示すほうが効果的です。Anthropicは、「理由を説明することで、Claudeが未知のケースにも応用できるようになる」と述べています。Claudeは理由がわかれば、例とは異なって見えるパターンにも同じルールを適用します。</span></td>
<td><span style="font-weight: 400">「Salesforceでは、1トランザクションにつきDMLステートメントは150件までしか許可されません。200件のレコードに対するトリガーでループ内にDMLを書くと、151件目のレコードで150件の上限を超過し、バッチ全体がロールバックされます」</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">肯定形で表現する</span></td>
<td><span style="font-weight: 400">「Xをしてはいけない」という表現では、何をすべきかをClaudeが推測しなければなりません。期待する動作を直接示すことで、Claudeが目指すべき目標を1つに絞れます。</span></td>
<td><span style="font-weight: 400">すべてのクラスでsharingを明示的に宣言し、ユーザー向けロジックにはwith sharingを、両方のコンテキストから呼び出されるユーティリティクラスにはinherited sharingを使用します。</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">さまざまな例</span></td>
<td><span style="font-weight: 400">Claudeが特定の形だけに引きずられないよう、エッジケースも扱います。Anthropicは、限定的なパターンマッチングを避けるために、3～5個の多様な例を示すことを推奨しています。</span></td>
<td><code>selector.cls</code><span>では、</span><code>getByIds</code><span>（一括取得）、</span><code>getByName</code><span>（サニタイズを伴うLIKE）、</span><code>getWithContacts</code><span>（親-子サブクエリー）、</span><code>getContactsWithAccount</code><span>（子-親クエリー）の4つのメソッドを示せます。同じパターンを4つの形で示せます。これにより、Claudeは「1つのクエリーメソッド」ではなく、「セレクター」という概念を学びます。</span></td>
</tr>
</tbody>
</table>
<p><code>authoring-apex</code>スキルでは、どのような参照ファイルを含めるとよいかを見てみましょう。</p>
<table>
<tbody>
<tr>
<td><b>リファレンス</b></td>
<td><b>ドキュメントの内容</b></td>
<td><b>この情報がない場合に起こる問題</b></td>
</tr>
<tr>
<td><code>best-practices.md</code></td>
<td><span style="font-weight: 400">コーディング規約（4スペースインデント、120文字以内の行）、命名規則、ApexDoc要件、APIバージョン管理、一般的なガイダンス</span></td>
<td><span style="font-weight: 400">プロジェクト規約とのずれ — Claudeは正しく動くコードを生成しますが、チームの既存のスタイルには合っていません。</span><i><span style="font-weight: 400"></span></i><span style="font-weight: 400"></span></td>
</tr>
<tr>
<td><code>design-patterns.md</code></td>
<td><span style="font-weight: 400">Factory、Strategy、セレクター、サービス、バッチ、Queueable、Unit of Work、ドメインモデルなど、各パターンを選ぶためのデシジョンツリー。各パターンに「使用する場面」の表を用意して、Claudeが最も一般的なパターンをデフォルトで選ばないようにします。</span></td>
<td><span style="font-weight: 400">この情報がないと、セレクターやStrategyのほうが適している場面でも、Claudeはすべてをサービスクラスとして作成する傾向があります。</span></td>
</tr>
<tr>
<td><code>anti-patterns.md</code></td>
<td><span style="font-weight: 400">Apexでよくあるミスをまとめます。各項目には、BAD/GOODのコードペアと、スケール時に失敗する理由を説明します。</span><i><span style="font-weight: 400"></span></i><span style="font-weight: 400"></span></td>
<td><span style="font-weight: 400">Claudeが人間の開発者よりも起こしやすい、一般的なミスを扱います（例：</span><span style="font-weight: 400">AccessLevel</span><span style="font-weight: 400">の指定なしに</span><span style="font-weight: 400">database.query()</span><span style="font-weight: 400">を生成する。</span><span style="font-weight: 400">Assert.areEqual</span><span style="font-weight: 400">ではなく従来の</span><span style="font-weight: 400">System.assertEquals</span><span style="font-weight: 400">を使用するなど）。</span></td>
</tr>
<tr>
<td><code>security-guide.md</code></td>
<td><span style="font-weight: 400"></span><span style="font-weight: 400">USER_MODE</span><span style="font-weight: 400">（API 56以降）を使用したCRUD/FLSの適用、下位互換性のための</span><span style="font-weight: 400">Security.stripInaccessible()</span><span style="font-weight: 400">、SOQLインジェクション対策、XSS対策パターン。</span></td>
<td><span style="font-weight: 400">この情報がないと、Claudeは管理者プロファイルのユーザーでは動作しても、標準ユーザーでは</span><span style="font-weight: 400">INSUFFICIENT_ACCESS</span><span style="font-weight: 400">が発生するコードを生成することがあります。</span></td>
</tr>
<tr>
<td><code>transaction-security-policy.md</code></td>
<td><span style="font-weight: 400">特殊なケース向けのリファレンス。この例では、</span><span style="font-weight: 400">TxnSecurity.EventCondition</span><span style="font-weight: 400">の実装（拡張トランザクションセキュリティ）を扱います。これらのクラスは</span><span style="font-weight: 400">global</span><span style="font-weight: 400">であり、システムコンテキストで実行され、監視イベントを評価します。</span></td>
<td><span style="font-weight: 400">これらは特殊なケースです。共有設定や可視性についての一般的なルールは、これらのクラスには当てはまりません。このリファレンスがないと、Claudeが</span><span style="font-weight: 400">with sharing</span><span style="font-weight: 400">を適用してしまい、こうした種類のクラスが機能しなくなります。</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">このようなリファレンスの構成は、Anthropicが言う「指示の背景にある文脈と動機を提供する」という方法を活用したものです。単に「何をすべきか」だけを示すのではなく、「なぜそうするのか」も説明することで、Claudeが機械的にパターンマッチングするのではなく、未知の状況にも正しく応用できるようにします。</span><i><span style="font-weight: 400"></span></i><span style="font-weight: 400"></span><i><span style="font-weight: 400"></span></i><span style="font-weight: 400"></span></p>
<p><span style="font-weight: 400"><a href="http://anti-patterns.md"><u><code>anti-patterns.md</code></u></a><span></span></span><span style="font-weight: 400">の例を見てみましょう。この構造は、繰り返し問題が起きやすいプラクティスを対象に設計されています。すべてのセクションで同じBAD/GOOD形式を使用しています。Claudeが個別のミスを認識し、回避できるようにするためです。アンチパターンの詳細については、</span><a href="https://architect.salesforce.com/docs/architect/well-architected-tools/guide/anti-patterns.html"><span style="font-weight: 400">こちらのドキュメント</span></a><span style="font-weight: 400">をご覧ください。</span></p>
<pre language="markdown"># Apexのアンチパターン

## 目次

- [ループ内SOQL](#soql-in-loops)
- 

---

## ループ内SOQL
**アンチパターン:** `for`ループまたは`while`ループ内でSOQLクエリーを実行すること。

**失敗する理由:** Salesforceには、1つの同期トランザクションで実行できるSOQLクエリーは
100件までという厳格な制限があります。トリガーは最大200件のレコードをまとめて処理するため、
ループ内でSOQLを1回実行すると、100件を処理しただけで上限に達します。

```apex
// BAD: 1回の反復につきSOQLを1回実行。レコード101件目で100クエリー制限を超過する。
for (Account acc : accounts) {
    List contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
}

修正方法: クエリーは一括で1回だけ実行し、取得した結果を反復処理します。

// GOOD: SOQLの実行は全体で1回。
Map contactsByAccount = new Map();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
    ...
}
// Salesforce Well-Architectedのアンチパターンに合わせて、他の項目を追加します。
</pre>
<p><span style="font-weight: 400">トランザクションセキュリティポリシーのような特殊なケースでは、この構造に沿ってMarkdownを作成し、通常のルールがどれも当てはまらない特定のApex機能を1つ扱うことができます。このMarkdownでは、通常のルールが当てはまらないのはどのような場合か、代わりに何をすべきか、なぜそうするのかをClaudeに教える必要があります。BAD/GOODのペアはありません。この文脈では、「悪い」コード（例：global without sharing）が実際には正しいコードだからです。この例では、assetsフォルダーにある実例を参照していることに注意してください。</span></p>
<pre language="markdown"># トランザクションセキュリティポリシー（Apex条件）

## 目次

- [概要](#overview)
- [クラスの形式](#class-shape)
- [共有設定](#sharing)
- [ガードレール](#guardrails)
- [例](#example)

---

## 概要

条件ビルダーでは不十分な場合、拡張トランザクションセキュリティポリシーはApexを呼び出します。
`TxnSecurity.EventCondition`を実装し、単一の
`evaluate(SObject event)`メソッドを定義します。

## クラスの形式

- クラスは**`global`**として宣言する（［設定］で選択できるようにするために必須）
- `TxnSecurity.EventCondition`を実装する
- シグネチャ: `global Boolean evaluate(SObject event)`

## 共有設定

これらのクラスは、ユーザー向けのコントローラーでは**ありません**。`without sharing`を使用します。
`with sharing`をデフォルトとする通常のルールはここでは適用されません。
適用すると、エラーが明示されないまま、ポリシーが正しく機能しなくなります。

## ガードレール

- 一括SOQLクエリーは最大1つとする（ループ内でSOQLは実行しない。ガバナ制限は引き続き適用される）
- トランザクションをブロックする場合は`true`、許可する場合は`false`を返す

## 例

`assets/transaction-security-policy.cls`を参照してください。

</pre>
<h3><span style="font-weight: 400">assets/：具体例によるfew-shot</span></h3>
<p><span style="font-weight: 400">Anthropicは、Claudeの出力形式、トーン、構造を方向づける最も信頼性の高い方法の1つに、few-shotとして機能する例の使用を挙げています。</span><span style="font-weight: 400">few-shotとは、テンプレートとして使える良い例をいくつか提示するといった意味です。各テンプレートには、必須要件として、ApexDocコメント、バルク対応のロジック、明示的なsharing宣言、CRUD/FLSの適用、テストを容易にするための依存性の注入が組み込まれています。Claudeはテンプレートをもとにコードを作成する際に、テンプレートの構造からこれらの要件を引き継ぎます。学習済みの知識から引き出す必要はありません。</span></p>
<p><span style="font-weight: 400">Apexには幅広い可能性がありますが、ここでは有効なユースケースをいくつか掘り下げてみましょう。</span></p>
<table>
<tbody>
<tr>
<td><b>アセット</b></td>
<td><b>ドキュメントの内容</b></td>
<td><b>この情報がない場合に起こる問題</b></td>
</tr>
<tr>
<td><code>service.cls</code></td>
<td><span style="font-weight: 400">ビジネスロジックを取りまとめる役割。クエリーを委譲し、DML 操作をまとめ、エラーを処理します。</span></td>
<td><span style="font-weight: 400">Claudeが関心の分離を考慮することなく、クエリーロジック、DML、オーケストレーションを1つの巨大なメソッドに入れてしまいます。</span></td>
</tr>
<tr>
<td><code>selector.cls</code></td>
<td><span style="font-weight: 400">SOQLアクセスを一元化します。sObjectごとに1つのセレクターを使用します。</span></td>
<td><span style="font-weight: 400">Claudeが場当たり的にクエリーを書いて、serviceコードやtriggerコードに分散させてしまいます。その結果、コードレビューでセキュリティやパフォーマンスの観点からSOQLを監査しにくくなります。</span></td>
</tr>
<tr>
<td><code>batch.cls</code></td>
<td>大量データの非同期処理（1万件以上のレコード）。<code>Database.Batchable</code>と<code>Database.Stateful</code>。</td>
<td><span style="font-weight: 400">Claudeが、エラーが発生しても適切に報告せず、そのまま処理してしまうバッチジョブを書いたり、失敗を報告する手段がないステートレスなバッチを使用したりしてしまいます。</span></td>
</tr>
<tr>
<td><code>queueable.cls</code></td>
<td><span style="font-weight: 400">オブジェクトを渡し、ジョブをチェーニングする非同期処理。</span></td>
<td><span style="font-weight: 400">Claudeが深さを制限せずにジョブをチェーニングして無限再帰を招いたり、partial DMLを使わないためにエラーを見落としたりしてしまいます。</span></td>
</tr>
<tr>
<td><code>transaction-security-policy.cls</code></td>
<td>拡張トランザクションセキュリティの<code>EventCondition</code>実装。</td>
<td>Claudeは、通常のルール（with sharing、<code>USER_MODE</code>）が当てはまらないクラスに、通常のルールを適用してしまいます。トランザクションセキュリティポリシー（TSP）クラスは、プラットフォームの要件により、global without sharingである必要があります。<i></i></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">各テンプレートは、単に「特定のクラスをどう書くか」を示すものではありません。必須要件を構造に組み込んだ、事前に用意されたfew-shot例です。</span></p>
<h3><span style="font-weight: 400">hooks.yamlと検証スクリプト：セーフティネット</span></h3>
<p>フックは、ライフサイクルの特定のタイミングでClaude Codeが自動実行するシェルコマンドです。フックは<code>hooks.yaml</code>で設定します。スクリプトは、そのコマンドが実行するものです。一般的には、ツールの入出力を検査して構造化されたフィードバックを返す、Pythonの検証スクリプトです。</p>
<p><span style="font-weight: 400">フックによって、人が介入しなくてもフィードバックループが完結します。Claudeがコードなどを生成すると、フックがすぐにそれを評価し、その結果をClaudeが同じ会話ターンの中で受け取ります。Claudeが問題全体を把握している状態のまま、その文脈内で修正が行われます。</span></p>
<p><span style="font-weight: 400">hooksファイルは、検証スクリプトが適切なタイミングで実行されるように設定します。たとえば、ユーザーがプロンプトを送信したタイミングで、事前チェックが実行されます。</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>また、ファイルの書き込みまたは編集が行われるたびに、<code>PostToolUse</code>フックが実行されます。</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は、フックのコンテキストをJSON形式で標準入力（stdin）に渡します。ペイロードの構造はライフサイクルイベントによって異なります。<code>PostToolUse</code>にはツール名、入力パラメーター、出力が含まれ、<code>UserPromptSubmit</code>にはユーザーのプロンプトテキストが含まれます。</p>
<p>開発チーム向けの事前チェックを例に考えてみましょう。<code>SKILL.md</code>のワークフローでは、検証フェーズで「Code Analyzerを実行する」と指示しています。しかし、Code Analyzerがインストールされていなかったり、バージョンが誤っていたりすると、どうなるでしょうか。フックがない場合、Claudeはワークフローの途中でコマンドを実行しようとしてエラーを受け取ります。開発者はエラーの原因がプラグインの不足にあることを突き止めなければなりません。フックを設定しておけば、Apex関連の作業が始まった時点でチェックが実行されます。</p>
<pre language="python">import json, subprocess, sys

MINIMUM_VERSION = "5.5.0"

def parse_version(v):
    """バージョン文字列を比較可能なタプルに変換します。"""
    return tuple(int(x) for x in v.split(".")[:3])

def main():
    hook_input = json.load(sys.stdin)
    prompt = hook_input.get("prompt", "").lower()

    # Apex関連の作業である可能性が高い場合のみチェックする
    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("=== 事前チェック ===")
                        print(f"  [警告] Code Analyzer {version} は、最低対応バージョンである {MINIMUM_VERSION} を満たしていません。")
                        print(f"  インストール: sf plugins install @salesforce/plugin-code-analyzer@latest")
                        sys.exit(0)
    except (FileNotFoundError, subprocess.TimeoutExpired, json.JSONDecodeError):
        pass

    print("=== 事前チェック ===")
    print("  [警告] Salesforce Code Analyzerがインストールされていません。")
    print("  authoring-apex skillでは、検証のためにSalesforce Code Analyzerが必要です。")
    print("  インストール: sf plugins install @salesforce/plugin-code-analyzer")
    sys.exit(0)

if __name__ == "__main__":
    main()

</pre>
<p><span style="font-weight: 400">開発者は、ツールチェーンに不足があることを事前に発見できるので、ワークフローの途中で検証フェーズが失敗してから気づくという事態を避けられます。 </span></p>
<p><span style="font-weight: 400">指示とフックの違いはここにあります。</span><span style="font-weight: 400"><code>SKILL.md</code></span><span style="font-weight: 400">には、「検証フェーズでCode Analyzerを実行する」と書かれています。フックは、ワークフローが始まる前にCode Analyzerを実行できることを確認します。指示は望ましい動作を示すものであり、フックは機械的に実行されるものです。この両方が必要です。</span></p>
<h2><span style="font-weight: 400">エージェントスキルには、なぜ人間と機械の両方による検証が必要になるのか</span></h2>
<p><span style="font-weight: 400">スキルはすべてを解決する切り札ではありません。スキルによってClaudeの出力のばらつきはかなり小さくできますが、それでもClaudeは本質的に非決定論的なシステムです。ここで問題になるのは、次の2点です。</span></p>
<p><b>動作を完全に指定することはできない。</b><span style="font-weight: 400"></span><span style="font-weight: 400"><code>SKILL.md</code></span><span style="font-weight: 400">がどれほど正確であっても、実際の要件にはテンプレートやリファレンスではカバーしきれない、新しい組み合わせが含まれます。それでもClaudeは判断を下さなければなりません。新しいモデルほどプロンプトを文字どおりに解釈するため、記述が少し足りないだけでも結果にばらつきが出ます。</span><span style="font-weight: 400"><code>SKILL.md</code></span><span style="font-weight: 400">でパターンを指定せずに「エラー処理を追加する」とだけ書くと、毎回異なるアプローチが使われることになります。</span></p>
<p><b>出力は確率に左右される。</b><span style="font-weight: 400">プロンプトを構造化し、役割を割り当て、例を提示し、検証フックを使っても、結局は正答率を最適化しているにすぎません。正答率が60%から95%に向上すれば、もちろん大きな改善ではありますが、残りの5%に対応するために検証ツールが必要です。このアーキテクチャーには、予防的対策（構造化されたプロンプト、リファレンス、テンプレート）と検出的対策（自動検証ツール、採点ルーブリック、エラー時のブロック）の両方が含まれます。このスキルは、Claudeが間違えることもあるという前提に立っています。完全な出力が可能であるかのように見なすのではなく、修正プロセスをワークフローに組み込んでいるのです。</span></p>
<h2><span style="font-weight: 400">さっそく始めてみましょう</span></h2>
<p><span style="font-weight: 400">Anthropicは、プロンプトの作成についてこう述べています。「背景情報をほとんど知らない同僚にプロンプトを見せて、それに沿って作業してもらってみてください。その同僚が戸惑うようなら、Claudeも戸惑います」。適切に作成されたエージェントスキルには、人間にとってもモデルにとっても、あいまいな点がありません。</span></p>
<p><span style="font-weight: 400">スキルをゼロから構築する必要はありません。Anthropicが提供する</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">スキル</span></a><span style="font-weight: 400">（英語）を使うと、意図の整理、<code>SKILL.md</code></span><span style="font-weight: 400">の記述、テストケースの作成、evalの実行、そして出力が求める基準に達するまで反復するという一連のプロセスを進められます。skill-creatorスキルをインストールし、作成したいスキルをClaudeに伝えてみてください。skill-creatorスキルが土台を作成し、エッジケースについて聞き取り、実際に動作し、調整可能なドラフトを生成してくれます。</span></p>
<p><span style="font-weight: 400">Salesforceに特化したサンプルを使って始めてみたい場合は、</span><a href="https://github.com/forcedotcom/afv-library"><span style="font-weight: 400">Agentforce Vibesスキルライブラリ</span></a><span style="font-weight: 400">（英語）で、Apex、LWC、Flowなどの本番品質のスキルを利用できます。こうしたスキルをインストールして使い、どのように構成されているかを確認してみてください。この記事で説明したものと同様のパターンに従っています。Agentforce Vibesスキルライブラリでは、ルールと理由を</span><span style="font-weight: 400"><code>SKILL.md</code></span><span style="font-weight: 400">にインラインで記述し、Markdown形式の参照ドキュメントの代わりに、完全なApexソースファイルをテンプレートとして使っています。これにより、少ないファイル数でスキルを自己完結させることができますが、モジュール性は損なわれます。1つのルールを更新するには、メインのワークフローファイルを編集する必要があります。 </span></p>
<p><span style="font-weight: 400">スキルは、ニーズに合わせて成長していきます。まずは</span><span style="font-weight: 400"><code>skill-creator</code></span><span style="font-weight: 400">または既存のスキルから始めてみましょう。プロジェクトに合わない部分をカスタマイズし、既存のスキルでは埋められないギャップが出てきたら、新しいスキルを構築してみるとよいでしょう。</span></p>
<h2><span style="font-weight: 400">関連情報</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のエージェントスキル集（英語）</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">プロンプトの基本事項</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">プロンプトエンジニアリングのテクニック</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">Anthropic Skill Creator（英語）</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">ドキュメント：</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">執筆者について</span></h2>
<p><b>Dave Norris</b><span style="font-weight: 400">は、Salesforceのデベロッパーアドボケイトです。技術的なテーマを、多様な読者に広くわかりやすく説明することに情熱を注いでいます。Salesforceで10年以上勤務し、現在、SalesforceとMuleSoftの認定資格を40以上保有。2013年に、Salesforce認定テクニカルアーキテクトの資格を取得しています。</span></p>
<p>&nbsp;</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/08/build-a-salesforce-agent-skill-with-claude-code-jp">Claude CodeでSalesforceエージェントスキルを構築する</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/08/build-a-salesforce-agent-skill-with-claude-code-jp/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206896</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>Headless Experience Layer によるカスタムウィジェット：MCP 経由で取引先の商談サマリーを AI エージェントアプリに表示する</title>
		<link>https://developer.salesforce.com/blogs/2026/08/headless-experience-layer-custom-ui-widget-jp</link>
		<comments>https://developer.salesforce.com/blogs/2026/08/headless-experience-layer-custom-ui-widget-jp#respond</comments>
		<pubDate>Mon, 24 Aug 2026 16:10:41 +0000</pubDate>
		<dc:creator><![CDATA[Hiroyuki Inaba]]></dc:creator>
				<category><![CDATA[Agentforce Vibes]]></category>
		<category><![CDATA[App Development]]></category>
		<category><![CDATA[Headless 360]]></category>
		<category><![CDATA[hxl]]></category>
		<category><![CDATA[MCP Server]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206871</guid>
		<description><![CDATA[<p>Salesforce &#12398;&#21830;&#35527;&#12487;&#12540;&#12479;&#12434;&#12289;Claude &#12420; ChatGPT &#12395;&#38745;&#30340;&#12469;&#12531;&#12503;&#12523;&#12391;&#12399;&#12394;&#12367;&#23455;&#12487;&#12540;&#12479;&#12398;&#12522;&#12483;&#12481;&#12394; UI &#12459;&#12540;&#12489;&#12391;&#34920;&#31034;&#12290;HXL&#65288;Headless Experience Layer&#65289;&#12392; MCP &#12434;&#32068;&#12415;&#21512;&#12431;&#12379;&#12383; 7 &#12473;&#12486;&#12483;&#12503;&#12398;&#12495;&#12531;&#12474;&#12458;&#12531;&#12434;&#12289;Salesforce &#38283;&#30330;&#32773;&#21521;&#12369;&#12395;&#26085;&#26412;&#35486;&#12391;&#35299;&#35500;&#12375;&#12414;&#12377;&#12290;</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/08/headless-experience-layer-custom-ui-widget-jp">Headless Experience Layer によるカスタムウィジェット：MCP 経由で取引先の商談サマリーを AI エージェントアプリに表示する</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p>みなさん、こんにちは！</p>
<p>TDX 2026 で発表された <strong>HXL（Headless Experience Layer）</strong> ですが、2026年8月後半よりベータ公開となりました。Salesforce のデータを Slackbot、ChatGPT や Claude といった外部の AI エージェントアプリに、リッチなカスタム UI（ウィジェット）として表示できます。しかも表示するのは静的なサンプルではなく、<strong>組織の実データ</strong>です。</p>
<p>本記事では、その HXL ウィジェットを <strong>MCP（Model Context Protocol）サーバー経由</strong>で公開し、AI エージェントアプリに「取引先の商談サマリ」を表示するまでをの流れを解説します。完成すると、AI エージェントアプリで「株式会社アストロエンタープライズの商談状況サマリを教えて」などと話しかけるだけで、次のようなカードが返ってくるようになります。</p>
<figure>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206879" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260824092858/00-hero-card.png?w=1024" class="postimages" width="1024" height="590" alt="" />
			  </span>
			</figure>
<p>HXL の仕様や API の詳細は、公式の <a href="https://developer.salesforce.com/docs/platform/hxl/overview">Headless Experience Layer Developer Guide (Beta)</a> に詳しくまとまっています。ただし内容は英語で、サンプルもオブジェクトのデータにはアクセスしないものでした。そこで本記事では、<strong>実際に Salesforce 組織のデータを扱いながら、日本語で一連の流れを解説する</strong>ことを目的に作成しました。公式ガイドとあわせて読むことで、より理解が深まるはずです。</p>
<p>対象読者は <strong>Salesforce 開発の経験がある方</strong>です。Apex、メタデータ、VS Code + Salesforce&amp;Agentforce 関連の拡張機能、<code>sf</code> コマンドの基礎は前提とし、HXL・MCP まわりの新しい概念を重点的に解説します。</p>
<blockquote><p><strong>※注意</strong></p>
<ul>
<li>この記事は <strong>2026 年 8 月時点</strong>の情報で作成しています。HXL / MCP まわりは進化が速いため、最新の仕様は公式ドキュメントもあわせてご確認ください。</li>
<li>本番組織ではなく <strong>Sandbox / Developer Editio/ Scratch Org</strong> での検証をおすすめします。</li>
<li>本記事の内容を試すには、組織のインスタンスが <strong>Summer &#8217;26 Patch 14.4 以降</strong>である必要があります。パッチバージョンは <a href="https://status.salesforce.com/">Salesforce Status</a> で自組織のインスタンスを検索すると確認できます。</li>
<li>本手順は Developer Edition での動作を確認しています。</li>
</ul>
</blockquote>
<hr />
<h2>全体の流れ</h2>
<p>作るファイルはやや多いですが、役割ごとに整理すると次の 7 ステップになります。</p>
<ol>
<li><strong>Apex クラス</strong>で取引先の商談サマリーを取得する（データ取得ロジック）</li>
<li><strong>LightningType</strong> で戻り値の型とレンダラー（描画マッピング）を定義する</li>
<li><strong>UiWidgetBundle</strong> でウィジェットの見た目（UI）を作る</li>
<li><strong>GenAiFunction（Agent Action）</strong> で Apex を API カタログに登録する</li>
<li><strong>McpServerDefinition</strong> で MCP サーバーとして公開する</li>
<li><strong>デプロイ</strong>する</li>
<li><strong>MCP サーバーを有効化</strong>し、AI エージェントアプリから接続して動作確認する</li>
</ol>
<p>登場人物を図にすると、データの流れは次のようになります。</p>
<figure>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206880" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260824093007/06-hxl-architecture-diagram.png?w=1242" class="postimages" width="1242" height="1000" alt="" />
			  </span>
			</figure>
<p>ポイントは、<strong>「データを取得する経路（tools）」と「見た目を定義する経路（resources）」が別々</strong>で、それを McpServerDefinition が束ねているところです。ここを押さえると、以降のファイル構成がすっきり理解できます。</p>
<hr />
<h2>事前準備</h2>
<ul>
<li>Salesforce 組織（検証には Developer Edition または Sandbox を推奨）</li>
<li>Salesforce CLI（<code>sf</code>）&amp; VS Code + Salesforce &amp; Agentforce の拡張機能</li>
</ul>
<p>本記事では API バージョン <code>67.0</code> を前提にしています。</p>
<h3>HXL を有効化する</h3>
<p>まず、組織で HXL の機能を有効化しておきます。<strong>設定</strong> のクイック検索で「<strong>ヘッドレスエクスペリエンスレイヤー</strong>」を検索し、設定ページを開いて有効化スイッチをオンにします。</p>
<figure>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206878" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260824092856/01-enable-hxl.png?w=1024" class="postimages" width="1024" height="590" alt="" />
			  </span>
			</figure>
<blockquote><p><strong>※</strong> この設定が表示されない場合は、組織のインスタンスが <strong>Summer &#8217;26 Patch 14.4 以降</strong>になっているかを確認してください（パッチバージョンの確認方法は冒頭の注意を参照）。</p></blockquote>
<hr />
<blockquote><p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 本記事で作成するコード一式は、GitHub リポジトリ <a href="https://github.com/hinabasfdc/hxl-mcp-widget-sample">hinabasfdc/hxl-mcp-widget-sample</a> に公開しています。手を動かしながら読む場合は、あわせて参照してください。</p></blockquote>
<h2>ステップ 1：Apex で商談サマリーを取得する</h2>
<p>まずはデータ取得の心臓部となる Apex クラスです。取引先名を受け取り、次の 3 つを返します。</p>
<ul>
<li>商談の総数</li>
<li>フェーズ（<code>StageName</code>）別の商談件数</li>
<li>未クローズ（<code>IsClosed = false</code>）商談の合計金額</li>
</ul>
<p>MCP のツールとして呼び出せるように、<code>@InvocableMethod</code> / <code>@InvocableVariable</code> で入出力を定義するのがポイントです。クラス全体は次のとおりです。</p>
<pre><code class="language-apex">global without sharing class AccountOpportunitySummary {

    @InvocableMethod(label='取引先の商談サマリーを取得' description='取引先名から、商談件数・フェーズ別件数・未クローズ商談の合計金額を取得します。')
    global static List&lt;SummaryResponse&gt; getAccountOpportunitySummary(List&lt;SummaryRequest&gt; requests) {
        List&lt;SummaryResponse&gt; responses = new List&lt;SummaryResponse&gt;();

        for (SummaryRequest req : requests) {
            SummaryResponse res = new SummaryResponse();
            res.summary = buildSummary(req.accountName);
            responses.add(res);
        }

        return responses;
    }

    private static Summary buildSummary(String accountName) {
        Summary summary = new Summary();
        summary.accountName = accountName;
        summary.accountInitials = toInitials(accountName);
        summary.totalOpportunityCount = 0;
        summary.stageSummaryText = '';
        summary.openOpportunityAmount = 0;

        List&lt;Account&gt; accounts = [
            SELECT Id, Name
            FROM Account
            WHERE Name LIKE :('%' + accountName + '%')
            ORDER BY Name
            LIMIT 1
        ];

        if (accounts.isEmpty()) {
            return summary;
        }

        Account acc = accounts[0];
        summary.accountName = acc.Name;
        summary.accountInitials = toInitials(acc.Name);

        List&lt;String&gt; stageSummaryParts = new List&lt;String&gt;();
        for (AggregateResult ar : [
            SELECT StageName, COUNT(Id) opportunityCount
            FROM Opportunity
            WHERE AccountId = :acc.Id
            GROUP BY StageName
            ORDER BY StageName
        ]) {
            String stageName = (String) ar.get('StageName');
            Integer count = (Integer) ar.get('opportunityCount');

            summary.totalOpportunityCount += count;
            stageSummaryParts.add(stageName + ': ' + count + '件');
        }
        summary.stageSummaryText = String.join(stageSummaryParts, ' / ');

        AggregateResult openTotal = [
            SELECT SUM(Amount) totalAmount
            FROM Opportunity
            WHERE AccountId = :acc.Id AND IsClosed = false
        ];
        Decimal openAmount = (Decimal) openTotal.get('totalAmount');
        summary.openOpportunityAmount = (openAmount != null) ? openAmount : 0;

        summary.totalOpportunityCountText = summary.totalOpportunityCount + '件';
        summary.openOpportunityAmountFormatted = formatCurrency(summary.openOpportunityAmount);

        return summary;
    }

    // ウィジェット表示用に3桁区切りの「¥」表記へ整形する(Decimal.format()はロケール依存のため使わない)
    private static String formatCurrency(Decimal amount) {
        Long rounded = amount.round();
        String sign = (rounded &lt; 0) ? '-' : '';
        String digits = String.valueOf(Math.abs(rounded));

        List&lt;String&gt; groups = new List&lt;String&gt;();
        Integer len = digits.length();
        Integer head = Math.mod(len, 3);
        if (head &gt; 0) {
            groups.add(digits.substring(0, head));
        }
        for (Integer i = head; i &lt; len; i += 3) {
            groups.add(digits.substring(i, i + 3));
        }

        return sign + '¥' + String.join(groups, ',');
    }

    // アバター表示用に、法人格の接頭辞を除いた先頭1文字を取り出す
    private static String toInitials(String name) {
        if (String.isBlank(name)) {
            return '?';
        }
        String bareName = name.replace('株式会社', '').replace('有限会社', '').trim();
        return String.isBlank(bareName) ? name.substring(0, 1) : bareName.substring(0, 1);
    }

    global class SummaryRequest {
        @InvocableVariable(label='取引先名' description='検索対象の取引先名(部分一致)' required=true)
        public String accountName;
    }

    global class SummaryResponse {
        @InvocableVariable(label='商談サマリー')
        public Summary summary;
    }

    global class Summary {
        @InvocableVariable public String accountName;
        @InvocableVariable public String accountInitials;
        @InvocableVariable public Integer totalOpportunityCount;
        @InvocableVariable public String stageSummaryText;
        @InvocableVariable public Decimal openOpportunityAmount;
        @InvocableVariable public String totalOpportunityCountText;
        @InvocableVariable public String openOpportunityAmountFormatted;
    }
}</code></pre>
<hr />
<h2>ステップ 2：LightningType で型とレンダラーを定義する</h2>
<p>ここが HXL の肝です。Apex の戻り値を、そのままウィジェットに流し込めるわけではありません。<strong>LightningType</strong> が「どの Apex 型を、どのウィジェットの、どの属性にマッピングするか」を仲介します。</p>
<p>MCP のレスポンスは 3 層の入れ子（エンベロープ）になっています。</p>
<pre><code>accountOpportunitySummaryResult        ← 一番外側（actionName / isSuccess / outputValues）
  └─ accountOpportunitySummaryOutputValues  ← 中間（summary を保持）
       └─ AccountOpportunitySummary$Summary  ← Apex の Summary クラス（実データ）</code></pre>
<p>この 3 層に対応する LightningType をそれぞれ用意します。</p>
<h3>2-1. 一番内側：Apex 型のラッパー</h3>
<p><code>lightningTypes/accountOpportunitySummary/schema.json</code> は、Apex の <code>Summary</code> クラスを LightningType として参照するだけのシンプルなものです。</p>
<pre><code class="language-json">{
  "title": "Account Opportunity Summary",
  "description": "Apex から返される取引先の商談サマリー情報",
  "lightning:type": "@apexClassType/c__AccountOpportunitySummary$Summary"
}</code></pre>
<p><code>@apexClassType/c__&lt;クラス名&gt;$&lt;内部クラス名&gt;</code> という記法で、Apex の内部クラスを直接参照できます。</p>
<h3>2-2. 中間層：OutputValues</h3>
<p><code>lightningTypes/accountOpportunitySummaryOutputValues/schema.json</code> は、Apex アクションのレスポンス payload（<code>summary</code> を持つ）を表します。<code>lightning:tags</code> に <code>mcp</code> を付けるのがポイントです。</p>
<pre><code class="language-json">{
  "title": "Account Opportunity Summary Output Values",
  "type": "object",
  "lightning:type": "lightning__objectType",
  "unevaluatedProperties": false,
  "lightning:tags": [ "mcp" ],
  "properties": {
    "summary": {
      "title": "Summary",
      "lightning:type": "@apexClassType/c__AccountOpportunitySummary$Summary"
    }
  }
}</code></pre>
<h3>2-3. 一番外側：Result エンベロープ</h3>
<p><code>lightningTypes/accountOpportunitySummaryResult/schema.json</code> が MCP に公開される最上位の型です。<code>actionName</code> / <code>isSuccess</code> / <code>outputValues</code> を持ちます。</p>
<pre><code class="language-json">{
  "title": "Account Opportunity Summary Result",
  "type": "object",
  "lightning:type": "lightning__objectType",
  "unevaluatedProperties": false,
  "lightning:tags": [ "mcp" ],
  "properties": {
    "actionName": { "title": "Action Name", "lightning:type": "lightning__textType" },
    "isSuccess":  { "title": "Is Success",  "lightning:type": "lightning__booleanType" },
    "outputValues": {
      "title": "Output Values",
      "lightning:type": "c__accountOpportunitySummaryOutputValues"
    }
  }
}</code></pre>
<h3>2-4. レンダラー：属性のマッピング</h3>
<p>そして最も重要なのが <code>renderer.json</code> です。これが <strong>「入れ子になったデータのどこを、ウィジェットのどの属性に流し込むか」</strong> を定義します。Result 型のレンダラーは、3 層をたどって値を取り出します。</p>
<pre><code class="language-json">{
  "renderer": {
    "componentOverrides": {
      "$": {
        "definition": "@widget/c/accountSummaryCard",
        "attributes": {
          "accountName": "{!$attrs.outputValues.summary.accountName}",
          "accountInitials": "{!$attrs.outputValues.summary.accountInitials}",
          "totalOpportunityCountText": "{!$attrs.outputValues.summary.totalOpportunityCountText}",
          "stageSummaryText": "{!$attrs.outputValues.summary.stageSummaryText}",
          "openOpportunityAmountFormatted": "{!$attrs.outputValues.summary.openOpportunityAmountFormatted}"
        }
      }
    }
  }
}</code></pre>
<ul>
<li><code>"definition": "@widget/c/accountSummaryCard"</code> で、次のステップで作るウィジェットを指定します。</li>
<li><code>{!$attrs.outputValues.summary.xxx}</code> という式で、エンベロープの奥にある実データを取り出してウィジェットの属性に渡します。<strong>この階層（<code>outputValues.summary.</code>）を間違えると値が表示されない</strong>ので注意してください。</li>
</ul>
<hr />
<h2>ステップ 3：UiWidgetBundle でウィジェットの UI を作る</h2>
<p>いよいよ見た目です。HXL のウィジェットは <code>tile/*</code> というコンポーネントを JSON でツリー状に組み合わせて作ります。</p>
<h3>3-1. スキーマ（受け取る属性の宣言）</h3>
<p><code>uiWidgets/accountSummaryCard/schema.json</code> で、ウィジェットが受け取る属性を宣言します。</p>
<pre><code class="language-json">{
  "title": "Account Opportunity Summary Widget",
  "type": "object",
  "properties": {
    "attributes": {
      "lightning:type": "lightning__objectType",
      "properties": {
        "accountName":                     { "title": "取引先名", "lightning:type": "lightning__textType" },
        "accountInitials":                 { "title": "取引先名イニシャル", "lightning:type": "lightning__textType" },
        "stageSummaryText":                { "title": "フェーズ別商談数", "lightning:type": "lightning__textType" },
        "totalOpportunityCountText":       { "title": "商談数(表示用)", "lightning:type": "lightning__textType" },
        "openOpportunityAmountFormatted":  { "title": "未クローズ商談合計金額(表示用)", "lightning:type": "lightning__textType" }
      }
    }
  }
}</code></pre>
<blockquote><p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> フェーズ別件数は、Apex 側で <code>フェーズA: 3件 / フェーズB: 2件</code> のような <strong>1 本のテキスト（<code>stageSummaryText</code>）に整形</strong>して渡しています。</p></blockquote>
<h3>3-2. ウィジェット本体（tile ツリー）</h3>
<p><code>uiWidgets/accountSummaryCard/accountSummaryCard.json</code> がレイアウト定義です。全体は長いので、構造を先に示します。</p>
<pre><code>tile/widget
└─ tile/container
   └─ tile/column (gap: lg)
      ├─ [ヘッダー行] tile/row (justify: between)
      │   ├─ tile/row → tile/avatar（イニシャル） + tile/column（取引先名 + キャプション）
      │   └─ tile/badge（商談数）
      ├─ [統計行] tile/row (align: stretch, isWrapped)
      │   ├─ tile/container → icon(briefcase) + 「商談数」 + 大きな数値
      │   └─ tile/container → icon(dollar-sign) + 「未クローズ商談合計金額」 + 大きな金額
      ├─ tile/separator
      └─ [フェーズ別] tile/column → 見出し + stageSummaryText</code></pre>
<p>ヘッダー部分（アバター＋取引先名＋商談数バッジ）の JSON は次のようになります。</p>
<pre><code class="language-json">{
  "definition": "tile/row",
  "attributes": { "gap": "md", "align": "center", "justify": "between", "isWrapped": true },
  "children": [
    {
      "definition": "tile/row",
      "attributes": { "gap": "sm", "align": "center" },
      "children": [
        {
          "definition": "tile/avatar",
          "attributes": {
            "initials": "{!$attrs.accountInitials}",
            "alt": "{!$attrs.accountName}",
            "size": "md",
            "shape": "circle"
          }
        },
        {
          "definition": "tile/column",
          "attributes": { "gap": "xs" },
          "children": [
            { "definition": "tile/text", "attributes": { "text": "{!$attrs.accountName}", "variant": "h3", "weight": "semibold" } },
            { "definition": "tile/text", "attributes": { "text": "取引先の商談サマリー", "variant": "caption", "color": "muted" } }
          ]
        }
      ]
    },
    {
      "definition": "tile/badge",
      "attributes": { "label": "{!$attrs.totalOpportunityCountText}", "variant": "primary" }
    }
  ]
}</code></pre>
<p>未クローズ商談合計金額を強調して表示する統計カード部分はこうです。アイコンと大きな数値を組み合わせます。</p>
<pre><code class="language-json">{
  "definition": "tile/container",
  "children": [
    {
      "definition": "tile/column",
      "attributes": { "gap": "xs" },
      "children": [
        {
          "definition": "tile/row",
          "attributes": { "gap": "sm", "align": "center" },
          "children": [
            { "definition": "tile/icon", "attributes": { "name": "dollar-sign", "size": "sm", "color": "success" } },
            { "definition": "tile/text", "attributes": { "text": "未クローズ商談合計金額", "variant": "caption", "color": "muted" } }
          ]
        },
        {
          "definition": "tile/text",
          "attributes": { "text": "{!$attrs.openOpportunityAmountFormatted}", "variant": "h2", "weight": "bold", "color": "success" }
        }
      ]
    }
  ]
}</code></pre>
<blockquote><p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>現時点でのハマりどころ：<code>tile/icon</code> の <code>name</code> は実在する値のみ</strong><br />
<code>tile/icon</code> の <code>name</code> に存在しないアイコン名（例：<code>currency</code>）を指定すると、ウィジェット全体が <code>NullPointerException</code> でクラッシュします。エラーメッセージが「An unexpected error occurred」としか出ないため、原因の特定に時間がかかりました。<code>briefcase</code> や <code>dollar-sign</code> など、<strong>実在するアイコン名</strong>を使ってください。</p></blockquote>
<p><code>UiWidgetBundle</code> のメタデータ（<code>accountSummaryCard.uiwidget-meta.xml</code>）は <code>widgetType</code> を <code>JSON</code> にしておきます。</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;UiWidgetBundle xmlns="http://soap.sforce.com/2006/04/metadata"&gt;
    &lt;masterLabel&gt;Account Summary Card&lt;/masterLabel&gt;
    &lt;description&gt;取引先名、商談数、フェーズ別商談数、未クローズ商談の合計金額を表示します。&lt;/description&gt;
    &lt;widgetType&gt;JSON&lt;/widgetType&gt;
&lt;/UiWidgetBundle&gt;</code></pre>
<hr />
<h2>ステップ 4：GenAiFunction（Agent Action）で Apex を登録する</h2>
<p>ここは見落としがちですが<strong>必須</strong>です。Apex クラスを MCP サーバーから呼び出せるようにするために、Agent アクションとして登録する必要があります。</p>
<p><code>genAiFunctions/Get_Account_Opportunity_Summary/Get_Account_Opportunity_Summary.genAiFunction-meta.xml</code>:</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;GenAiFunction xmlns="http://soap.sforce.com/2006/04/metadata"&gt;
    &lt;description&gt;取引先名から、商談件数・フェーズ別件数・未クローズ商談の合計金額を取得します。&lt;/description&gt;
    &lt;developerName&gt;Get_Account_Opportunity_Summary&lt;/developerName&gt;
    &lt;invocationTarget&gt;AccountOpportunitySummary&lt;/invocationTarget&gt;
    &lt;invocationTargetType&gt;apex&lt;/invocationTargetType&gt;
    &lt;isConfirmationRequired&gt;false&lt;/isConfirmationRequired&gt;
    &lt;masterLabel&gt;取引先の商談サマリーを取得&lt;/masterLabel&gt;
&lt;/GenAiFunction&gt;</code></pre>
<p>入力スキーマ（<code>input/schema.json</code>）では、<code>accountName</code> をユーザー入力として受け取ることを宣言します。</p>
<pre><code class="language-json">{
  "required": [ "accountName" ],
  "properties": {
    "accountName": {
      "title": "取引先名",
      "lightning:type": "lightning__textType",
      "copilotAction:isUserInput": true
    }
  },
  "lightning:type": "lightning__objectType"
}</code></pre>
<p>出力スキーマ（<code>output/schema.json</code>）では、<code>summary</code> を <strong>表示可能（<code>isDisplayable</code>）</strong> として宣言します。これがウィジェット表示につながる重要なフラグです。</p>
<pre><code class="language-json">{
  "properties": {
    "summary": {
      "title": "商談サマリー",
      "lightning:type": "@apexClassType/c__AccountOpportunitySummary$Summary",
      "copilotAction:isDisplayable": true,
      "copilotAction:isUsedByPlanner": true
    }
  },
  "lightning:type": "lightning__objectType"
}</code></pre>
<hr />
<h2>ステップ 5：McpServerDefinition で MCP サーバーとして公開する</h2>
<p>最後に、これまでの部品を束ねる <code>McpServerDefinition</code> です。ここが「データ取得（<code>tools</code>）」と「見た目（<code>resources</code>）」を接続する結節点になります。</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;McpServerDefinition xmlns="http://soap.sforce.com/2006/04/metadata"&gt;
    &lt;description&gt;取引先の商談サマリー(商談数、フェーズ別件数、未クローズ商談合計金額)を検索して返します。&lt;/description&gt;
    &lt;masterLabel&gt;account-opportunity-summary-server&lt;/masterLabel&gt;
    &lt;tools&gt;
        &lt;apiDefinition&gt;
            &lt;apiIdentifier&gt;aa:apex-AccountOpportunitySummary&lt;/apiIdentifier&gt;
            &lt;apiSource&gt;API_CATALOG&lt;/apiSource&gt;
            &lt;operation&gt;AccountOpportunitySummary&lt;/operation&gt;
        &lt;/apiDefinition&gt;
        &lt;descriptionOverride&gt;取引先名から、商談件数・フェーズ別件数・未クローズ商談の合計金額を取得します。&lt;/descriptionOverride&gt;
        &lt;destructive&gt;false&lt;/destructive&gt;
        &lt;idempotent&gt;true&lt;/idempotent&gt;
        &lt;openWorld&gt;false&lt;/openWorld&gt;
        &lt;readOnly&gt;true&lt;/readOnly&gt;
        &lt;returnDirect&gt;false&lt;/returnDirect&gt;
        &lt;toolName&gt;AccountOpportunitySummaryapex_AccountOpportunitySummary&lt;/toolName&gt;
        &lt;toolTitle&gt;AccountOpportunitySummary&lt;/toolTitle&gt;
        &lt;uiResource&gt;accountSummary&lt;/uiResource&gt;
    &lt;/tools&gt;
    &lt;resources&gt;
        &lt;resourceName&gt;accountSummary&lt;/resourceName&gt;
        &lt;resourceUri&gt;ui://widget/lightningType/c__accountOpportunitySummaryResult&lt;/resourceUri&gt;
        &lt;resourceTitle&gt;Account Opportunity Summary&lt;/resourceTitle&gt;
        &lt;description&gt;取引先名、商談数、フェーズ別商談数、未クローズ商談合計金額を表示するウィジェット。&lt;/description&gt;
    &lt;/resources&gt;
&lt;/McpServerDefinition&gt;</code></pre>
<h3>読み解きのポイント</h3>
<ul>
<li><code>&lt;tools&gt;</code> は <strong>データ取得の経路</strong>です。<code>apiIdentifier</code> の <code>aa:apex-&lt;クラス名&gt;</code> と <code>apiSource</code> の <code>API_CATALOG</code> が、ステップ 4 で登録した Agent Action を指しています。</li>
<li><code>&lt;resources&gt;</code> は <strong>見た目の経路</strong>です。<code>resourceUri</code> は <code>ui://widget/lightningType/c__&lt;LightningTypeBundleのAPI名&gt;</code> という形式で、ステップ 2 で作った Result 型を指します。</li>
<li><code>&lt;uiResource&gt;</code> と <code>&lt;resourceName&gt;</code> の値（<code>accountSummary</code>）を一致させることで、ツールとリソース（＝データと見た目）が紐づきます。</li>
</ul>
<blockquote><p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 現時点の仕様: CSP（<code>connectDomains</code> など）や <code>_meta.ui.domain</code> といった MCP プロトコルの内部設定は、<strong>Salesforce プラットフォームが自動生成</strong>します。開発者がメタデータで設定する項目ではありません。</p></blockquote>
<hr />
<h2>ステップ 6：デプロイする</h2>
<p>ソースを組織にデプロイします。依存関係の順（Apex → LightningType &amp; UiWidget → GenAiFunction → McpServerDefinition）を意識するとエラーが減りますが、まとめてデプロイしても解決してくれることが多いです。</p>
<pre><code class="language-bash">sf project deploy start \
  --source-dir force-app/main/default \
  --target-org &lt;your-org-alias&gt; \
  --wait 15 --json</code></pre>
<blockquote><p>現時点の仕様: <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>UiWidgetBundle のスキーマは「プロパティ削除」で失敗する</strong><br />
一度デプロイしたウィジェットの <code>schema.json</code> から既存プロパティを削除すると、<code>Schema update contains breaking changes: ... プロパティは削除できません</code> というエラーになります。属性を作り直したいときは、<strong>古いプロパティを残したまま新しいものを追加</strong>するのが安全です。</p></blockquote>
<hr />
<h2>ステップ 7：MCP サーバーを有効化し、AI エージェントアプリから接続する</h2>
<p>デプロイが終わったら、<strong>MCP サーバーを有効化</strong>します。これは現状 <strong>設定画面からの手動操作のみ</strong>です。</p>
<figure>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206877" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260824092854/02-enable-mcp-server.png?w=1024" class="postimages" width="1024" height="590" alt="" />
			  </span>
			</figure>
<p>有効化したら、外部クライアントアプリケーションを作成し、AI エージェントアプリ側でコネクタ（プラグイン）として登録します。この OAuth まわりの手順は、別記事「<a href="https://developer.salesforce.com/jpblogs/2026/04/salesforce-hosted-mcp-server-jp">Salesforce Hosted MCP Server 使い始め</a>」で詳しく解説されているので、そちらもあわせてご覧ください。</p>
<h3>ChatGPT と Claude で試してみる</h3>
<p>公開した MCP サーバーは、MCP に対応した AI エージェントアプリから接続できます。コネクタとして登録したうえで「SalesforceDev で United Oil の商談サマリを見せて」と話しかけると、<code>AccountOpportunitySummary</code> ツールが呼び出され、次のように同じカードが表示されます。</p>
<p><strong>Claude の場合</strong></p>
<figure>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206876" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260824092852/03-claude.png?w=1024" class="postimages" width="1024" height="590" alt="" />
			  </span>
			</figure>
<p><strong>ChatGPT の場合</strong></p>
<figure>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206875" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260824092850/04-chatgpt.png?w=1024" class="postimages" width="1024" height="590" alt="" />
			  </span>
			</figure>
<p><code>SUM(Amount)</code> も <code>GROUP BY StageName</code> も、すべて組織の実データが反映されています。フィールドレベルセキュリティ（FLS）や共有ルールも尊重されるため、安心して社内データを扱えます。同じ MCP サーバーを Claude と ChatGPT のどちらから呼び出しても、<strong>同一のウィジェットが同じ見た目で表示される</strong>点にも注目してください。</p>
<hr />
<h2>要考慮事項：ウィジェットの変更が反映されない</h2>
<p>ところで、ウィジェットの定義を更新後、デプロイは成功しているのに、AI エージェントアプリ側では<strong>古いレイアウトのまま</strong>という現象に悩まされました。組織側のメタデータを取得して確認しても、ローカルと内容は一致しています。それでも新しい見た目にならないのです。</p>
<p>原因は、<strong>AI エージェントアプリ（クライアント）側が、ウィジェットのテンプレートを <code>resourceUri</code> の文字列をキーにキャッシュしている</strong>からのようです。AI エージェントアプリの内部仕様で詳細は不明ですが<code>resourceUri</code> が同じままだと、中身をいくら変えても古いテンプレートが使われ続けるように見えました。</p>
<p>実際、クライアント側のコネクタ設定を見ると、<code>ui://widget/lightningType/c__accountOpportunitySummary...</code> という <code>resourceUri</code> をキーに、ウィジェットのテンプレートに関する情報が保持されているのが確認できます。</p>
<figure>
			  <span class="postimagessection_specify alignnone size-medium wp-image-206874" >
			    <img loading="lazy" decoding="async" src="https://d259t2jj6zp7qm.cloudfront.net/images/20260824092846/05-ui-widget-url.png?w=1024" class="postimages" width="1024" height="590" alt="" />
			  </span>
			</figure>
<p>(2026/8/28更新)</p>
<p><strong>まずは、クライアント側での「ツールの更新」をお試しください。</strong>これでテンプレート情報が新しいものに更新されることが多いです。</p>
<p>それでも上手くいかない場合のひとまずの対策は <code>resourceUri</code> のバージョニングです。</p>
<ol>
<li>LightningTypeBundle を新しい API 名でコピーする（例：<code>accountOpportunitySummaryResult</code> → <code>accountOpportunitySummaryResultV2</code>）</li>
<li><code>McpServerDefinition</code> の <code>&lt;resourceUri&gt;</code> と <code>&lt;uiResource&gt;</code> を新しいものに更新する</li>
<li>デプロイ後、AI エージェントアプリ側でコネクタ／プラグインを更新（再登録）する</li>
</ol>
<pre><code class="language-xml">&lt;!-- 変更前 --&gt;
&lt;resourceUri&gt;ui://widget/lightningType/c__accountOpportunitySummaryResult&lt;/resourceUri&gt;

&lt;!-- 変更後（V2 に）--&gt;
&lt;resourceUri&gt;ui://widget/lightningType/c__accountOpportunitySummaryResultV2&lt;/resourceUri&gt;</code></pre>
<p>なお、<strong>バージョニングが必要なのは <code>resourceUri</code> が指す LightningTypeBundle と McpServerDefinition だけ</strong>です。UiWidgetBundle は同じ名前のまま上書きデプロイで問題ありません（クライアントは URI をキーにキャッシュしているため、URI が変われば中身も取り直されます）。Apex や GenAiFunction も影響を受けません。</p>
<p>&nbsp;</p>
<hr />
<h2>おわりに</h2>
<p>本記事では、Apex・LightningType・UiWidgetBundle・GenAiFunction・McpServerDefinition を組み合わせて、<strong>組織の実データ（取引先の商談サマリー）を AI エージェントアプリにリッチなカードとして表示する</strong> HXL カスタムウィジェットを作りました。</p>
<p>従来、AI エージェントアプリに返せるのはテキストや静的なサンプルが中心でした。HXL と MCP を使うことで、<strong>Salesforce の実データを、意味のある UI として、外部の AI 体験に直接埋め込める</strong>ようになります。FLS や共有ルールも尊重されるため、セキュリティ面でも安心です。</p>
<p>一度この 7 ステップの型を押さえてしまえば、Apex のクエリとウィジェットの <code>tile</code> ツリーを差し替えるだけで、ケース一覧・在庫状況・売上ダッシュボードなど、さまざまなカスタムウィジェットに応用できます。ぜひ Developer Edition で試してみてください。</p>
<h2>参考資料</h2>
<ul>
<li><a href="https://developer.salesforce.com/docs/platform/hxl/overview">Headless Experience Layer Developer Guide (Beta)</a></li>
<li><a href="https://developer.salesforce.com/jpblogs/2026/04/salesforce-hosted-mcp-server-jp">Salesforce Hosted MCP Server 使い始め</a>（OAuth 接続手順）</li>
<li><a href="https://status.salesforce.com/">Salesforce Status</a>（インスタンスのパッチバージョン確認）</li>
<li><a href="https://github.com/hinabasfdc/hxl-mcp-widget-sample">本記事のサンプルコード一式（GitHub: hinabasfdc/hxl-mcp-widget-sample）</a></li>
</ul>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/08/headless-experience-layer-custom-ui-widget-jp">Headless Experience Layer によるカスタムウィジェット：MCP 経由で取引先の商談サマリーを AI エージェントアプリに表示する</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/08/headless-experience-layer-custom-ui-widget-jp/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206871</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260824092515/headless-experience-layer-custom-ui-widget-jp.png?w=1200" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260824092515/headless-experience-layer-custom-ui-widget-jp.png?w=1200" medium="image" />
	</item>
		<item>
		<title>Data and Communication Anti-Patterns in Lightning Web Components</title>
		<link>https://developer.salesforce.com/blogs/2026/08/data-and-communication-anti-patterns-in-lightning-web-components</link>
		<comments>https://developer.salesforce.com/blogs/2026/08/data-and-communication-anti-patterns-in-lightning-web-components#respond</comments>
		<pubDate>Thu, 20 Aug 2026 15:00:51 +0000</pubDate>
		<dc:creator><![CDATA[Tim Dionne]]></dc:creator>
				<category><![CDATA[App Development]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Developer Community]]></category>
		<category><![CDATA[Lightning Web Components]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[@wire Service]]></category>
		<category><![CDATA[Component Communication]]></category>
		<category><![CDATA[Event Handling]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[lightning data service]]></category>
		<category><![CDATA[lwc]]></category>
		<category><![CDATA[LWC Anti-Patterns]]></category>
		<category><![CDATA[Salesforce Architecture]]></category>
		<category><![CDATA[Salesforce Developer]]></category>

		<guid isPermaLink="false">https://developer.salesforce.com/blogs/?p=206863</guid>
		<description><![CDATA[<p>Learn how the wire service, public properties, and events move data through Lightning web components, and how to avoid reactivity and communication mistakes that cause stale UI, lost events, and hard-to-reproduce state bugs.</p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/08/data-and-communication-anti-patterns-in-lightning-web-components">Data and Communication Anti-Patterns in Lightning Web Components</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></description>
				<content:encoded><![CDATA[<p><span style="font-weight: 400">Most Lightning web component bugs fall into one of two categories: the component fetches the wrong data, or two components can&#8217;t talk to each other correctly. These problems are frustrating because the code looks right — the types are correct, there are no console errors — but the UI shows stale values, events disappear, or changes never propagate.</span></p>
<p>This is the second post in our series on LWC anti-patterns. The <a href="https://developer.salesforce.com/blogs/2026/06/security-anti-patterns-in-lightning-web-components"><u>first post</u></a> covered security and platform compatibility — how to write components that work within Lightning Web Security. In this post, you&#8217;ll learn the most common mistakes in three areas: the <a href="https://developer.salesforce.com/docs/platform/lwc/guide/data-wire-service-about.html"><u><code>@wire</code><span> service</span></u></a><span>, </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/js-props-intro.html"><u>public properties</u></a> (<code>@api</code>), and <a href="https://developer.salesforce.com/docs/platform/lwc/guide/events.html"><u>event handling</u></a>. Fixing these patterns is one of the highest-leverage things you can do to make your components more reliable.</p>
<h2><span style="font-weight: 400">Wire service anti-patterns</span></h2>
<p><span style="font-weight: 400">The wire service is how components reactively fetch Salesforce data, but its subscription model trips up developers who treat it like a one-time fetch. The patterns below cover the mistakes that lead to stale data, infinite loops, and updates that never arrive.</span></p>
<h3><span style="font-weight: 400"><code>@wire</code> is a subscription, not a fetch</span></h3>
<p><span>Before diving into specific anti-patterns, there&#8217;s one mental model shift that explains most wire service bugs: </span><b><code><span>@wire</span></code><span> is not a fetch wrapper</span></b><span>. It&#8217;s a subscription.</span></p>
<p>When you wire a property to <code>getRecord</code> (see <a href="https://developer.salesforce.com/docs/platform/lwc/guide/reference-wire-adapters-record.html"><u>docs</u></a>), you&#8217;re not making a one-time request. You&#8217;re subscribing to a stream. The adapter can emit multiple times — once from cache, again when the network responds, and again when any component on the page modifies that record. Your wire handler should be safe to call multiple times with the most recent value.</p>
<p><span style="font-weight: 400">This distinction matters for every pattern below.</span></p>
<h3><span style="font-weight: 400">Don&#8217;t mutate </span><span style="font-weight: 400"><code>@wire</code> </span><span style="font-weight: 400">results directly</span></h3>
<p><span style="font-weight: 400">Wire data is immutable. Assigning directly to a wired property, or to a nested field on that property, violates the contract with the wire service. The result is unpredictable: you may see stale data, missed reactive updates, or a render that doesn&#8217;t reflect your change.</span></p>
<pre language="javascript">@wire(getContacts)
contacts;

handleDelete(event) {
    // This won't work reliably — wire data is immutable
    this.contacts.data = this.contacts.data.filter(c =&gt; c.Id !== event.detail.id);
}
</pre>
<p><span style="font-weight: 400">Copy the data into a local property instead, and mutate that.</span></p>
<pre language="javascript">contacts = [];

@wire(getContacts)
wiredContacts({ data, error }) {
    if (data) {
        this.contacts = [...data];
    }
}

handleDelete(event) {
    this.contacts = this.contacts.filter(c =&gt; c.Id !== event.detail.id);
}
</pre>
<h3><span style="font-weight: 400">Don&#8217;t update wire config in </span><span style="font-weight: 400"><code>renderedCallback()</code></span></h3>
<p><code>renderedCallback()</code> (see <a href="https://developer.salesforce.com/docs/platform/lwc/guide/create-lifecycle-hooks-rendered.html"><u>docs</u></a>) runs after every render. If you change a reactive wire config property inside it, that change triggers a new render, which calls <code>renderedCallback()</code> again. You&#8217;ve created an infinite loop.</p>
<pre language="javascript">renderedCallback() {
    // This causes an infinite render loop
    this.recordId = this.template.querySelector('input').value;
}
</pre>
<p><span style="font-weight: 400">Update wire config properties in response to user events, not in rendering callbacks.</span></p>
<pre language="javascript">handleIdChange(event) {
    this.recordId = event.detail.value; // wire re-evaluates automatically
}
</pre>
<h3><span style="font-weight: 400">Don&#8217;t assume wire adapters fire in order</span></h3>
<p>Multiple wire adapters on the same component are independent subscriptions. The order they deliver data is not guaranteed, and neither is the number of times each fires. Code that reads from one wire result inside another wire&#8217;s handler, or reads wire data inside <code>connectedCallback()</code>, will fail silently when the <a href="https://developer.salesforce.com/docs/platform/lwc/guide/data-ui-api.html"><u>Lightning Data Service</u></a> (LDS) cache is cold.</p>
<pre language="javascript">connectedCallback() {
    // items.data is undefined on first load — this throws
    this.total = this.items.data.reduce((sum, item) =&gt; sum + item.UnitPrice__c, 0);
}
</pre>
<p><span style="font-weight: 400">Drive derived state from inside the wire handler itself, and guard against </span><span style="font-weight: 400">undefined</span><span style="font-weight: 400"> at every access.</span></p>
<pre language="javascript">@wire(getOrderItems, { orderId: '$recordId' })
wiredItems({ data }) {
    if (data) {
        this.items = data;
        this.total = data.reduce((sum, item) =&gt; sum + item.UnitPrice__c, 0);
    }
}
</pre>
<p><span style="font-weight: 400">If derived state depends on two adapters, compute it once both have delivered.</span></p>
<pre language="javascript">updateTotal() {
    if (this.order &amp;&amp; this.items.length &gt; 0) {
        this.total = this.items.reduce((sum, item) =&gt; sum + item.UnitPrice__c, 0);
    }
}

@wire(getOrder, { recordId: '$recordId' })
wiredOrder({ data }) {
    if (data) {
        this.order = data;
        this.updateTotal();
   }
}

@wire(getOrderItems, { orderId: '$recordId' })
wiredItems({ data }) {
    if (data) {
        this.items = data;
        this.updateTotal();
    }
}
</pre>
<h3><span style="font-weight: 400">Use schema imports for object and field references</span></h3>
<p><span style="font-weight: 400">Hard-coding SObject or field names as strings bypasses Salesforce&#8217;s ability to validate references, prevent accidental deletion, and cascade renames. A renamed field silently breaks the component at runtime.</span></p>
<pre language="javascript">// String literals — not validated at deploy time
@wire(getRecord, { recordId: '$recordId', fields: ['Account.Name', 'Account.Industry'] })
account;
</pre>
<p>Import field tokens from <code>@salesforce/schema</code> instead. The platform validates these at build time.</p>
<pre language="javascript">import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import NAME_FIELD from '@salesforce/schema/Account.Name';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';

@wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD, INDUSTRY_FIELD] })
account;
</pre>
<h3><span style="font-weight: 400">Use the correct field path in</span><span style="font-weight: 400"> <code>getRecord</code></span></h3>
<p>Every field in a <code>getRecord</code> <code>fields</code> array must be reachable from the record&#8217;s own object type. Specifying a top-level field from a different object — like including <code>Account.Name</code> in a call for a Case record — produces a malformed config. The component receives incomplete data. Because Aura components share the same Lightning Data Service (LDS) cache, a malformed config can affect those Aura components too.</p>
<pre language="javascript">// Wrong — Account.Name is not a field on Case
@wire(getRecord, { recordId: '$recordId', fields: [CASE_SUBJECT, ACCOUNT_NAME] })
record;
</pre>
<p><span style="font-weight: 400">To access a related record&#8217;s field, use the full relationship path starting from the base object.</span></p>
<pre language="javascript">import CASE_ACCOUNT_NAME from '@salesforce/schema/Case.Account.Name';

// Correct — traversal expressed from Case
@wire(getRecord, { recordId: '$recordId', fields: [CASE_SUBJECT, CASE_ACCOUNT_NAME] })
record;
</pre>
<h3><span style="font-weight: 400">Don&#8217;t use </span><span style="font-weight: 400"><code>@wire</code> </span><span style="font-weight: 400">for write operations</span></h3>
<p><code>@wire</code> is for read operations only. Using it to invoke an Apex method that performs DML gives you unpredictable invocation timing and no way to handle errors in the UI.</p>
<pre language="javascript">// Don't wire a method that performs DML
@wire(saveRecord, { record: '$record' })
savedResult;
</pre>
<p><span style="font-weight: 400">Use </span><a href="https://developer.salesforce.com/docs/platform/lwc/guide/apex-call-imperative.html"><span style="font-weight: 400">imperative Apex calls</span></a><span style="font-weight: 400"> for any operation that writes data.</span></p>
<pre language="javascript">async handleSave() {
    try {
        await saveRecord({ record: this.record });
        this.dispatchEvent(new CustomEvent('saved'));
    } catch (error) {
        // show error in UI
    }
}
</pre>
<h3><span style="font-weight: 400">Don&#8217;t mix Apex and LDS for the same record</span></h3>
<p><span style="font-weight: 400">Apex and Lightning Data Service (LDS) maintain separate, independent caches. When you fetch the same record through both at the same time, the two copies can diverge — one reflecting a recent server change, the other serving a stale value. The result is inconsistent UI state that&#8217;s hard to reproduce.</span></p>
<p><span style="font-weight: 400">Pick one data source per record and use it consistently. Use LDS wire adapters when they support your use case. Use Apex only when LDS doesn&#8217;t (unsupported objects, complex queries, transactional operations).</span></p>
<h3><span style="font-weight: 400">Don&#8217;t use </span><span style="font-weight: 400"><code>refreshApex()</code> </span><span style="font-weight: 400;font-size: 16px">on LDS wire adapters</span></h3>
<p><code>refreshApex()</code>(see <a href="https://developer.salesforce.com/docs/platform/lwc/guide/apex-result-caching.html#refresh-the-cache-when-using-wire"><u>docs</u></a>) is designed for wired Apex methods only. Calling it on an LDS adapter like <code>getRecord</code> is deprecated and has no reliable effect. LDS manages its own cache automatically when records change through LDS-aware operations.</p>
<pre language="javascript">handleRefresh() {
    refreshApex(this.record); // deprecated for LDS adapters
}
</pre>
<p>To signal that a record&#8217;s cached data is stale after an out-of-band write, use <code>notifyRecordUpdateAvailable()</code> (see <a href="https://developer.salesforce.com/docs/platform/lwc/guide/reference-notify-record-update.html"><u>docs</u></a>).</p>
<pre language="javascript">import { notifyRecordUpdateAvailable } from 'lightning/uiRecordApi';

async handleRefresh() {
    await notifyRecordUpdateAvailable([{ recordId: this.recordId }]);
    // LDS now has fresh data for all subscribed adapters
}
</pre>
<p><span style="font-weight: 400">All active wire adapters subscribed to that record will automatically receive fresh data.</span></p>
<h3><span style="font-weight: 400">Call </span><span style="font-weight: 400"><code>notifyRecordUpdateAvailable()</code> </span><span style="font-weight: 400">after imperative Apex writes</span></h3>
<p><span style="font-weight: 400">When an imperative Apex method modifies a record, LDS doesn&#8217;t know about it. Every LDS wire adapter on the page — including those in sibling and parent components — continues to serve its cached, pre-mutation value. The UI shows stale data with no error.</span></p>
<pre language="javascript">async handleSave() {
    await updateAccount({ recordId: this.recordId, name: this.name });
    // LDS still has the old value — nothing updates here
}
</pre>
<p><span style="font-weight: 400">After the write completes, notify LDS that the record is stale.</span></p>
<pre language="javascript">import { notifyRecordUpdateAvailable } from 'lightning/uiRecordApi';

async handleSave() {
    await updateAccount({ recordId: this.recordId, name: this.name });
    await notifyRecordUpdateAvailable([{ recordId: this.recordId }]);
    // LDS now has fresh data
}
</pre>
<p><span style="font-weight: 400">This keeps all LDS wire adapters across the page consistent with the server state.</span></p>
<h3><span style="font-weight: 400">Use config gating for dependent data, not promise chains</span></h3>
<p>When loading data that depends on the result of another fetch — like fetching an Account after you have its ID from a Case — developers often reach for nested imperative Apex calls or <code>Promise.then()</code> (see <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"><u>docs</u></a>) chains. This is fragile, doesn&#8217;t participate in LDS caching, and doesn&#8217;t recover automatically when upstream data changes.</p>
<p>The wire service handles this natively: a wire adapter won&#8217;t evaluate until all of its required config properties are truthy. Keep a reactive config property <code>undefined</code> until upstream data arrives, and the second adapter gates automatically.</p>
<pre language="javascript">@api recordId;   // Case record ID
accountId;       // undefined until first wire delivers

@wire(getRecord, { recordId: '$recordId', fields: [CASE_ACCOUNT_ID] })
wiredCase({ data }) {
    if (data) {
        this.accountId = data.fields.AccountId.value; // triggers second wire
    }
}

// Does not evaluate until accountId is truthy
@wire(getRecord, { recordId: '$accountId', fields: [ACCOUNT_NAME] })
accountRecord;
</pre>
<p>Both records now participate in the LDS cache. If the Case&#8217;s <code>AccountId</code> changes, the second wire re-fetches the new Account automatically.</p>
<h3><span style="font-weight: 400">Don&#8217;t swap the config object to &#8220;re-fetch&#8221;</span></h3>
<p>The <code>@wire</code> reactive system tracks individual top-level class properties, not nested object contents or the config object reference. Replacing the entire config object doesn&#8217;t trigger re-evaluation.</p>
<pre language="javascript">currentConfig = { recordId: '001000000000001', fields: [NAME_FIELD] };

@wire(getRecord, '$currentConfig')
record;

handleSelectRecord(event) {
    // Swapping the object doesn't trigger re-evaluation
    this.currentConfig = { recordId: event.detail.recordId, fields: [NAME_FIELD] };
}
</pre>
<p>Declare reactive config values as individual top-level properties and reference them with <code>$</code>.</p>
<pre language="javascript">recordId;

@wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD] })
record;

handleSelectRecord(event) {
    this.recordId = event.detail.recordId; // wire re-evaluates automatically
}
</pre>
<h3><span style="font-weight: 400">Don&#8217;t add dummy </span><span style="font-weight: 400"><code>optionalField</code> </span><span style="font-weight: 400">to bust the LDS cache</span></h3>
<p>Some developers add irrelevant fields to <code>optionalFields</code> hoping to force a cache miss. This doesn&#8217;t work. <code>optionalFields</code> are fetched opportunistically when already in the cache and silently omitted when not. They don&#8217;t invalidate cache entries for required fields.</p>
<p>Use <code>notifyRecordUpdateAvailable()</code> after any out-of-band write to signal LDS that the record is stale. See the pattern above.</p>
<h3><span style="font-weight: 400">Design wire handlers to be idempotent</span></h3>
<p>Because <code>@wire</code> is a subscription stream that can emit multiple times, wire handlers must be safe to call with a fresh value at any time. A handler that performs one-time initialization side effects will leave the component in a stale state after subsequent emissions.</p>
<pre language="javascript">@wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD] })
wiredRecord({ data }) {
    if (data) {
        this.record = data;
        this.initializeChart(data); // breaks on second emission
    }
}
</pre>
<p><span style="font-weight: 400">Derive all displayed state from the most recent emission. For side effects that genuinely run once, guard them with a flag and keep them separate from the data-update logic:</span></p>
<pre language="javascript">_chartInitialized = false;

wiredRecord({ data }) {
    if (data) {
        this.record = data;
        if (!this._chartInitialized) {
            this._chartInitialized = true;
            this.initializeChart(data);
        }
        this.updateChart(data); // idempotent update path

    }
}
</pre>
<h2><span style="font-weight: 400">Public property anti-patterns</span></h2>
<p><span style="font-weight: 400">Public properties (@api) define the contract between a component and its parent. Data flows down through them, and the patterns below cover the mistakes that break that one-way flow or leak your component&#8217;s internal state to the outside.</span></p>
<h3><span style="font-weight: 400">Don&#8217;t reassign <code>@api</code></span><span style="font-weight: 400"> properties internally</span></h3>
<p><code>@api</code> properties are owned by the parent component. They flow data downward. If you reassign an <code>@api</code> property from within your own component, you break the unidirectional data flow contract, which can cause infinite update cycles and runtime warnings from the LWC engine.</p>
<pre language="javascript">@api value = '';

handleInput(event) {
    this.value = event.target.value; // reassigning an @api property
}
</pre>
<p><span style="font-weight: 400">Store the working value in a private property and expose a getter for the public interface.</span></p>
<pre language="javascript">_value = '';

@api
get value() { return this._value; }
set value(v) { this._value = v; }

handleInput(event) {
    this._value = event.target.value;
    this.dispatchEvent(new CustomEvent('change', { detail: { value: this._value } }));
}
</pre>
<h3><span style="font-weight: 400">Don&#8217;t pass objects by reference in event </span><span style="font-weight: 400"><code>detail</code></span></h3>
<p>JavaScript passes objects by reference. When you put an object directly into a <code>CustomEvent</code> <code>detail</code> (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent"><u>docs</u></a>), any listener can mutate the original. This creates hidden coupling and hard-to-debug state corruption.</p>
<pre language="javascript">handleSave() {
    this.dispatchEvent(new CustomEvent('save', { detail: this.record })); // mutable reference
}
</pre>
<p><span style="font-weight: 400">Pass a </span><a href="https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy"><span style="font-weight: 400">shallow copy</span></a><span style="font-weight: 400"> so listeners can&#8217;t affect your component&#8217;s state.</span></p>
<pre language="javascript">handleSave() {
    this.dispatchEvent(new CustomEvent('save', { detail: { ...this.record } }));
}
</pre>
<h2><span style="font-weight: 400">Event handling anti-patterns</span></h2>
<p><span style="font-weight: 400">Events carry data upward, from a component to its ancestors. The patterns below cover the mistakes that make events hard to listen for, leak mutable state between listeners, or quietly break component encapsulation.</span></p>
<h3><span style="font-weight: 400">Use</span><span style="font-weight: 400"> <code>CustomEvent</code></span><span style="font-weight: 400"></span><span style="font-weight: 400">, not</span><span style="font-weight: 400"> <code>Event</code></span></h3>
<p>Unlike <code>CustomEvent</code>, the base <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event"><u><code>Event</code><span> constructor</span></u></a><span> doesn&#8217;t support a </span><code>detail</code> property. Using it forces you to attach data through non-standard properties, which is fragile and inconsistent with platform conventions.</p>
<pre language="javascript">this.dispatchEvent(new Event('recordselected'));
</pre>
<pre language="javascript">this.dispatchEvent(new CustomEvent('recordselected', {
    detail: { recordId: this.selectedId }
}));
</pre>
<h3><span style="font-weight: 400">Don&#8217;t prefix event names with &#8220;on&#8221;</span></h3>
<p>HTML attribute syntax uses <code>on</code> as a prefix for event handlers. If you name your custom event <code>onstatuschange</code>, a parent component must listen for it as <code>ononstatuschange</code> — which is confusing and risks conflicts with reserved event names.</p>
<pre language="javascript">this.dispatchEvent(new CustomEvent('statuschange', { detail: { status } }));
</pre>
<pre language="html">&lt;c-child onstatuschange={handleStatusChange}&gt;&lt;/c-child&gt;
</pre>
<h3><span style="font-weight: 400">Don&#8217;t mutate</span><span style="font-weight: 400"> <code>event.detail</code></span></h3>
<p><code>event.detail</code> is a shared object reference. Mutating it inside your handler modifies the object that other listeners in the same bubbling chain will read.</p>
<pre language="javascript">handleStatusChange(event) {
    event.detail.status = 'processed'; // affects other listeners
    this.currentStatus = event.detail.status;
}
</pre>
<p>Read from <code>event.detail</code> and store the value locally — never write back to it.</p>
<pre language="javascript">handleStatusChange(event) {
    this.currentStatus = event.detail.status;
}
</pre>
<p><span style="font-weight: 400">If you need to enrich event data for upstream listeners, stop the original event and re-dispatch a new one with the updated payload. This makes the transformation explicit.</span></p>
<pre language="javascript">handleStatusChange(event) {
    event.stopPropagation();
    this.dispatchEvent(new CustomEvent('statuschange', {
        bubbles: true,
        detail: { ...event.detail, status: 'processed', processedAt: Date.now() }
    }));
}
</pre>
<h3><span style="font-weight: 400">Always remove event listeners you add imperatively</span></h3>
<p>Listeners added via <code>addEventListener</code> (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener"><u>docs</u></a>) to elements outside your component&#8217;s template — like <code>window</code> (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window"><u>docs</u></a>) or <code>document</code> (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document"><u>docs</u></a>) — will hold a reference to your component after it disconnects. This prevents garbage collection and can fire callbacks on a disconnected component.</p>
<pre language="javascript">connectedCallback() {
    window.addEventListener('resize', this.handleResize.bind(this)); // never removed
}
</pre>
<p>The cleanest fix is to declare the event handler as an arrow function class field, which is bound at class definition time and always has the same reference. Then, remove the event handler by calling <code>removeEventListener()</code> (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener"><u>docs</u></a>).</p>
<pre language="javascript">handleResize = () =&gt; {
    this.width = window.innerWidth;
};

connectedCallback() {
    window.addEventListener('resize', this.handleResize);
}

disconnectedCallback() {
    window.removeEventListener('resize', this.handleResize);
}
</pre>
<h3><span style="font-weight: 400">Don&#8217;t call </span><span style="font-weight: 400"><code>.bind()</code> </span><span style="font-weight: 400">inline with </span><span style="font-weight: 400"><code>addEventListener</code></span><span style="font-weight: 400"></span></h3>
<p><code>.bind()</code> (see <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind"><u>docs</u></a>) returns a <b>new function object</b> every time it&#8217;s called. If you pass <code>this.handler.bind(this)</code> to <code>addEventListener</code> and then pass <code>this.handler.bind(this)</code> again to <code>removeEventListener</code>, you&#8217;re passing two different functions. The listener is never removed.</p>
<pre language="javascript">connectedCallback() {
    window.addEventListener('scroll', this.handleScroll.bind(this));
}

disconnectedCallback() {
    window.removeEventListener('scroll', this.handleScroll.bind(this)); // different function — does nothing
}
</pre>
<p>Use an arrow function class field (same reference always) or store a single <code>.bind()</code> result in a property and reuse it.</p>
<pre language="javascript">// Arrow field — preferred
handleScroll = () =&gt; { this.scrollTop = window.scrollY; };

// Or stored bind reference
connectedCallback() { this._scrollHandler = this.handleScroll.bind(this); }
</pre>
<h3><span style="font-weight: 400">Don&#8217;t use </span><span style="font-weight: 400"><code>bubbles: true, composed: true</code> unneccessarily</span></h3>
<p>Setting both <code>bubbles </code><span>(see </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/bubbles"><u>docs</u></a><span>) </span><span>and </span><code>composed</code> (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/composed"><u>docs</u></a>) event properties to <code>true</code> causes the event to propagate through every shadow boundary all the way to the document root. This breaks encapsulation, creates implicit dependencies on ancestor components, and risks event name collisions higher in the tree. The LWC documentation is explicit: &#8220;Lightning web components don&#8217;t use this configuration.&#8221;</p>
<p>Use the default (<code>bubbles: false, composed: false</code>) for events handled by a direct parent. Use <code>bubbles: true, composed: false</code> only when a grandparent within the same shadow tree needs to hear the event. Reserve <code>composed: true</code> for rare cases where crossing a shadow boundary is genuinely necessary, and stop propagation at the first component that handles it.</p>
<h3><span style="font-weight: 400">Replace </span><span style="font-weight: 400"><code>pubsub</code> </span><span style="font-weight: 400">with Lightning Message Service</span></h3>
<p><span style="font-weight: 400">The community </span><a href="https://github.com/developerforce/pubsub"><span style="font-weight: 400">pubsub</span></a><span style="font-weight: 400"> module is deprecated and no longer actively maintained. It only works within a single page, can&#8217;t cross namespace boundaries, and requires manual unregistration — a step that&#8217;s easy to forget and causes memory leaks.</span></p>
<p>Use Lightning Message Service — <code>lightning/messageService</code> — (see <a href="https://developer.salesforce.com/docs/platform/lightning-component-reference/guide/lightning-message-service?type=Develop"><u>docs</u></a>) instead. It works across LWC, Aura, and Visualforce components, spans multiple pages and namespaces, and manages subscription lifecycle cleanly.</p>
<pre language="javascript">import { subscribe, unsubscribe, MessageContext } from 'lightning/messageService';
import RECORD_SELECTED_CHANNEL from '@salesforce/messageChannel/RecordSelected__c';

export default class RecordConsumer extends LightningElement {
    @wire(MessageContext) messageContext;
    _subscription;

    connectedCallback() {
        this._subscription = subscribe(
            this.messageContext,
            RECORD_SELECTED_CHANNEL,
            (message) =&gt; this.handleRecordSelected(message)
        );
    }

    disconnectedCallback() {
        unsubscribe(this._subscription);
        this._subscription = null;
    }
}
</pre>
<h3><span style="font-weight: 400">Don&#8217;t register capture-phase listeners</span></h3>
<p>LWC doesn&#8217;t support the <code>capture</code> (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#capture"><u>docs</u></a>) phase of DOM event propagation. Passing <code>{ capture: true }</code> to <code>addEventListener</code> has no effect — the listener won&#8217;t fire on the way down the tree. There&#8217;s no runtime error; the code just behaves as if the option wasn&#8217;t set.</p>
<p>Design event handling around the bubbling phase. If you need to intercept an event before child components handle it, restructure the component tree so the intercepting component is a direct parent, or use a declarative handler on the child element in the template.</p>
<h2><span style="font-weight: 400">Conclusion</span></h2>
<p><span style="font-weight: 400">The patterns in this post share a common root: misunderstanding how data flows through LWC. The wire service is a subscription stream — not a fetch call. </span><span style="font-weight: 400">@api</span><span style="font-weight: 400"> properties flow down from the parent — they&#8217;re not yours to reassign. Events carry immutable snapshots — don&#8217;t mutate their payloads or attach shared object references to them.</span></p>
<p><span style="font-weight: 400">Correcting your mental model on these three surfaces prevents the large majority of data and communication bugs in LWC development.</span></p>
<p><span style="font-weight: 400">In the next and final post in this series, we&#8217;ll cover </span><b>runtime behavior</b><span style="font-weight: 400"> — DOM access, component lifecycle, async patterns, performance, and base component usage.</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/lwc/guide/data-wire-service-about.html"><span style="font-weight: 400">Wire Service documentation</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.reference_wire_adapters_record"><span style="font-weight: 400">LDS — getRecord</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.reference_notify_record_update"><span style="font-weight: 400">notifyRecordUpdateAvailable()</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.use_message_channel"><span style="font-weight: 400">Lightning Message Service</span></a></li>
<li style="font-weight: 400"><a href="https://developer.salesforce.com/docs/platform/lwc/guide/events.html"><span style="font-weight: 400">LWC Events documentation</span></a></li>
<li style="font-weight: 400"><a href="https://github.com/salesforce/eslint-plugin-lwc"><span style="font-weight: 400">@lwc/eslint-plugin-lwc rules</span></a></li>
</ul>
<h2><span style="font-weight: 400">About the author</span></h2>
<p><b>Tim Dionne</b><span style="font-weight: 400"> is a Principal Member of Technical Staff (PMTS) on the Customer Centric Engineering team. He&#8217;s worked on many UI features of Salesforce over the years, starting with VisualForce, Aura Components, and Lightning Web Components with an emphasis on Lightning Web Security and Lightning Data Service.</span></p>
<p>The post <a href="https://developer.salesforce.com/blogs/2026/08/data-and-communication-anti-patterns-in-lightning-web-components">Data and Communication Anti-Patterns in Lightning Web Components</a> appeared first on <a href="https://developer.salesforce.com/blogs">Salesforce Developers Blog</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://developer.salesforce.com/blogs/2026/08/data-and-communication-anti-patterns-in-lightning-web-components/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<post-id xmlns="com-wordpress:feed-additions:1">206863</post-id><media:thumbnail url="https://d259t2jj6zp7qm.cloudfront.net/images/20260819155500/Generic-D-7.png?w=1778" />
<media:content url="https://d259t2jj6zp7qm.cloudfront.net/images/20260819155500/Generic-D-7.png?w=1778" medium="image" />
	</item>
	</channel>
</rss>
