Tuesday, 5 August 2025

Agentforce Custom Lightning Types

Introduction

The Summer '25 release of Salesforce (or maybe prior, given Agentforce releases are on a monthly cycle) introduced Custom Lightning Types, which finally gives us a degree of fine-grained control over the user interface. At the time of writing (August 2025) this is only for actions that use Apex classes for requests and responses.

In essence, a Custom Lightning Type configures an Agentforce user interface override for an Apex class. The Apex class can either be used as the input to send information to an Agentforce action, or as the output containing the response to the user request. 

Example

I've been playing around with a Custom Lighting Type to display overview information from my Limit Tracker that I created for my session at London's Calling 2025. For this I wanted two custom types:

  • An input that captured the number of days of Limit Snapshots to include in the overview
  • An output that showed the Heap and CPU details from the snapshots
I also wanted these to be more interesting than a simple form field to capture an integer and a list of values, but that only comes into play when developing the Lightning Web Components to handle the user interface, so I'll come to those later.

Output

My custom Apex action returns the following class:

public class LimitConsumption { 
    @InvocableVariable
    public String name;
                
    @InvocableVariable
    public List<LimitSnapshot> snapshots;         
        
    public LimitConsumption(String name, List<LimitSnapshot> snapshots) {
        this.name = name;
        this.snapshots = snapshots;
    }
}

It can sometimes be tricky to pinpoint the exact class returned from an action, as they tend to be inner classes and nested inside response wrappers. An easy way to find out exactly which class Agentforce will use is to go through the New Action process and see what it picks up for the output:


If you aren't from a developer background, the class reference might look a bit odd, but it breaks down as:

  • c__ this is the namespace the class is present in. c__ means the default (or lack of) namespace. 
  • LimitConsumptionOverview is the outer, or containing class
  • The $ separator indicates this is an inner class of LimitConsumptionOverview
  • LimitConsumption is the name of the actual inner class 
Once I know which class my type with will be working with, I can create an entry in the lightningTypes source folder, in this case limitConsumptionResponse, to make it clear this is used to render the response from an Agent.
   
The schema.json file configures the type to work with my custom class:
{
  "title": "Limit Consumption",
  "description": "Overview of limit consumption by a particular piece of Apex code",
  "lightning:type": "@apexClassType/c__LimitConsumptionOverview$LimitConsumption"
}

The lightning:type entry is the key piece of wiring that couples this Custom Lightning Type with my Apex class.

Next I had to decide how I wanted to render this information. Something that couldn't easily be found on the regular detail pages was appealing, and as it is handled by a Lightning Web Component I have access to the full power of JavaScript and the Lightning Design System. I decided to return a tabbed interface containing a chart :



and a list of values:



Note that I've taken a leaf out of the Salesforce examples and my custom Apex action returns the same list of fake data regardless of how it's invoked.

You can find the Lightning Web Component at : limitConsumption - it uses the Chart.js library to create the bar chart.

Once my Lightning Web Component is in place, I need to create a file in my Custom Lightning Type folder named lightningDesktopGenAi named renderer.json:

  {
    "collection": {
      "renderer": {
        "componentOverrides": {
          "$": {
            "definition": "c/limitConsumption"
          }
        }
      }
    }
  }

The key aspect here is the componentOverrides which defines my Lightning Web Component (limitConsumption in the default namespace) as the override to render the type. 

Input

My custom Apex action relies on the following input class:

public with sharing class SliderIntegerWrapper {
    @InvocableVariable
    public Integer value;
}

Note that this is simply wrapping an Integer value - while lots of the examples I've seen are around complex classes containing multiple values/nested classes, they don't have to. In this case I just want a funkier way to enter the value, so I need an Apex class to hold it.

To capture the information I decided to go with a slider component - note that this did give Agentforce a little trouble with the rendering - once the value got to double figures it wrapped to the next line!


Similar to the output, I create a lightningType folder named sliderInteger and define the schema.json to couple the type to my class:

{
  "title": "Slider Integer",
  "description": "Slider Integer",
  "lightning:type": "@apexClassType/c__SliderIntegerWrapper"
}

as this time my class is top level, the entry just contains c__ (for the default namespace) and the name of the class.

The Lightning Web Component that provides the input slider can be found at sliderInteger

This time, once my Lightning Web Component is in place, I need to define a lightningDesktopGenAi/editor.json file to define it as the override:

{
  "editor": {
    "componentOverrides": {
      "$": {
        "definition": "c/sliderInteger"
      }
    }
  }
}

The Agent Action

Now that I have my Custom Lightning Types defined and the overrides configured, I can create the Agentforce action that makes use of them, based on my LimitConsumptionOverview invocable Apex class. When defining the inputs I choose my SliderInteger type for the daysWrapper :


and for outputting the limit consumption information, I choose the LimitConsumptionResponse type:


Executing this action brings both my types into play, as can be seen from the following short video:




Lessons Learned

Something I'd strongly recommend when working with Custom Lightning Types is to build yourself a simple test page so that you can try them out without having to go via Agentforce every time you make a change. While it doesn't sound onerous, making requests and supplying inputs gets tedious very quickly! I created a test harness Lightning Web Component (limitConsumptionHarness) that sends fake data to the output component and handles the event from the input component, and it's saved me a lot of time.

The first scratch org I tried this in, I'd already used the example from the Salesforce docs, and for some reason only that one would be picked up. After spending a couple of hours trying various things, and creating yet another set of custom types, I spun up a new scratch org and it all worked fine. No idea what went wrong, but by the same token I was learning and tweaking so it could have been anything.

Make sure to use distinct names for your types, classes and Lightning Web Components. My first attempt I used the same name for the type and the component and quickly lost track of what I was configuring where.

More Information








No comments:

Post a Comment