---
schema_version: "1"
id: 64ce298d86e8e25297f13a42dd5009cd10c89b78f4cd23e4e2d9ea9c35f9aed7
title: Create a Custom Agent Action
collection: admin
version: current
locale: en-us
portal_url: https://docs.salesforce.com/admin/current/en-us/xcloud/264-0-0/archive_create_a_custom_agent_action.md
canonical_url: https://help.salesforce.com/s/articleView?id=xcloud.archive_create_a_custom_agent_action.htm&release=264.0.0&type=5
source_site: help.salesforce.com
content_type: documentation
content_hash: 8e83440259be2ade7a02c018e6feb0783214fd20354d20a5ebf720e1794f5b2a
description: Create a reusable action, and then assign that action to the agent in the Archive app.
products:
  - Salesforce Platform
  - Cross Cloud
release: "264"
---
# Create a Custom Agent Action

Create a reusable action, and then assign that action to the agent in the Archive app.

Before you configure the action in the Agent Builder, create the Apex class that contains the invocable method. This class handles the logic for querying the archived records. For more information, see [Create an Apex Class](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_develop_create_apex.htm)

For example, create an Apex class named `ArchiveDataQueryActionWrapper`.

```
public with sharing class ArchiveDataQueryActionWrapper {

    public class SearchRequest {
        @InvocableVariable(label='Object Name' description='Name of the object to search in, can be implicit or explicit' required=true)
        public String objectName;

        @InvocableVariable(label='Filters' description='List of field/value pairs for filtering (JSON string). Supports two formats: 1) Legacy: [{"field":"Status","value":"New"},{"field":"Status","value":"Closed"}] 2) New: {"Status":["New","Closed"]}' required=true)
        public String filtersJson;
        
        @InvocableVariable(label='Max Results' description='Maximum number of results to return')
        public Integer maxResults = 100;
        
        @InvocableVariable(label='Date Field' description='Date field name for date range filtering (optional)')
        public String dateField;
        
        @InvocableVariable(label='Start Date' description='Start date for filtering (yyyy-MM-dd format, optional, if exist must have enddate and datefield)')
        public String startDate;
        
        @InvocableVariable(label='End Date' description='End date for filtering (yyyy-MM-dd format, optional, if exist must have startdate and datefield)')
        public String endDate;        
    }
    
    public class SearchResponse {
        @InvocableVariable(label='Success' description='Whether the search was successful')
        public Boolean isSuccess;
        
        @InvocableVariable(label='Total Count' description='Total number of matching records')
        public Integer totalResultCount;
        
        @InvocableVariable(label='Records JSON' description='JSON string of matching records')
        public String recordsJson;
        
        @InvocableVariable(label='Formatted Result' description='Formatted records in rich text with 5 key fields per record')
        public String records;
        
        @InvocableVariable(label='Error Message' description='Error message if search failed')
        public String errorMessage;
        
        @InvocableVariable(label='Message' description='Status message')
        public String message;
        
        @InvocableVariable(label='Warnings' description='Warning messages about schema validation and auto-fixes')
        public String warnings;
        
        public SearchResponse() {
            this.isSuccess = false;
            this.totalResultCount = 0;
            this.recordsJson = '[]';
            this.records = '';
            this.errorMessage = '';
            this.message = '';
            this.warnings = '';
        }
    }

    @InvocableMethod(
        label='Search Archived Records' 
        description='Search archived records and return structured results for Agentforce agents.'
        category='Data Management'
    )
    public static List<SearchResponse> searchArchivedRecords(List<SearchRequest> requests) {
        List<SearchResponse> results = new List<SearchResponse>();
        for (SearchRequest req : requests) {
            SearchResponse res = new SearchResponse();
            try {
                Http http = new Http();
                HttpRequest httpReq = new HttpRequest();
                httpReq.setMethod('POST');
                httpReq.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId());
                httpReq.setHeader('Content-Type', 'application/json');

                httpReq.setEndpoint(URL.getOrgDomainUrl().toExternalForm() +
                    '/services/data/v65.0/platform/data-resilience/archive/search/agentforce');

                Map<String, Object> body = new Map<String, Object>{
                    'objectName' => req.objectName,
                    'filtersJson' => req.filtersJson,
                    'maxResults' => req.maxResults
                };
                if (String.isNotBlank(req.dateField) && 
                    String.isNotBlank(req.startDate) && 
                    String.isNotBlank(req.endDate)) {
                    body.put('dateField', req.dateField);
                    body.put('startDate', req.startDate);
                    body.put('endDate', req.endDate);
                }

                httpReq.setBody(JSON.serialize(body));
                HttpResponse httpRes = http.send(httpReq);

                Integer statusCode = httpRes.getStatusCode();
                if (statusCode == 200 || statusCode == 201) {
                    Map<String, Object> responseMap = (Map<String, Object>) JSON.deserializeUntyped(httpRes.getBody());

                    res.isSuccess = Boolean.valueOf(responseMap.get('isSuccess'));
                    res.totalResultCount = Integer.valueOf(responseMap.get('totalResultCount'));
                    res.recordsJson = String.valueOf(responseMap.get('recordsJson'));
                    res.records = String.valueOf(responseMap.get('records'));
                    res.message = String.valueOf(responseMap.get('message'));
                    res.warnings = String.valueOf(responseMap.get('warnings'));
                    res.errorMessage = String.valueOf(responseMap.get('errorMessage'));
                } else {
                    res.errorMessage = 'HTTP ' + statusCode + ': ' + httpRes.getBody();
                }
            } catch (Exception e) {
                res.errorMessage = 'Exception: ' + e.getMessage();
            }
            results.add(res);
        }
        return results;
    }
}
```

## Configure the Custom Agent Action

When the Apex class is deployed, create the agent action in the Salesforce setup.

![Note](https://sf-zdocs-cdn-prod.zoominsoftware.com/tdta-xcloud-setup-264-0-0-production-enus/6f21df35-9ea0-4f53-ac94-45c3b5dfd5bc/images/icon_note.png)

Note We recommend using the Agentforce (Default) Agent and the Agent Topic: General CRM.

Prerequisites:

- You have Salesforce Admin permissions with access to Agent configuration.
- Turn on Agentforce in your org.

1. From Setup, in the Quick Find box, enter Agent.
2. To view all the actions in your org, select **Agent Actions**.
3. Click **New Agent Action**.
4. From Reference Action Type, select **Apex**.
5. From Reference Action Category, select **Invocable Method**.
6. From Reference Action, select **Search Archived Records**.
    
    Search Archived Records references the method in the `ArchiveDataQueryActionWrapper` Apex class that you created earlier.
7. The agent action label and API name are populated with the reference action name and API name. Review these fields and, if necessary, edit the values so that they accurately describe the action.
8. Click **Next.**
9. The instructions for the custom action and each input and output are populated with the descriptions from the reference action. Review these fields and make changes. For more information, see [Best Practices for Agent Action Instructions](https://help.salesforce.com/s/articleView?id=ai.copilot_actions_instructions.htm&type=5&language=en_US).
    
    The descriptions from the reference action are copied over to give you a head start, but agent action instructions differ from traditional descriptions. An agent uses a large language model (LLM) and the reasoning engine to determine when to start topics and actions in a conversation. The instructions for the action, the inputs, and the outputs tell an agent what your action does and when and how to use it. Effective instructions vary by action and use case. Plan to test and iterate on your instructions to make sure that your action performs as expected.
10. For each input and output, specify any applicable settings.
    
     
    | Setting | Details |
    | --- | --- |
    | Collect data from user | Require this parameter to be provided by the user. |
    | Filter from copilot action | When you create an agent action, all inputs and outputs from the reference action are added to the agent action. Activate this setting when you don't want to use a parameter from the reference action with your agent action. At least one output is required to use the agent action. |
    | Require input | Require this parameter to run the action. All other inputs are treated as optional. If an input is required by the reference action, this setting turns on by default and is read-only. |
    | Show in conversation | An agent can include this parameter in a response to a user. At least one output is required. |
    
    ![Tip](https://sf-zdocs-cdn-prod.zoominsoftware.com/tdta-xcloud-setup-264-0-0-production-enus/6f21df35-9ea0-4f53-ac94-45c3b5dfd5bc/images/icon_tip.png)
    
    Tip When an action changes a record, you can require an agent to ask the user to confirm the change before the agent runs it. On the record page for the action, activate the **Require user confirmation** setting.
    
    If you're using the Agentforce Agent, deselect **Show in Conversation** under Output.
    
    To add the action to a standard topic, edit the topic configuration from the Topic Configuration tab.
11. Click **Finish**.

After you create your action, assign the action to a topic for your agent.

To test your action and preview how the output appears in an agent conversation, open the agent in the Agent Builder and start a preview conversation. Enter phrases that you expect to trigger your action, and then adjust the agent action and topic instructions based on your results. Assign your action to an agent to test it.

## Add a Custom Agent Action to a Topic

To add an agent action to a topic:

![Note](https://sf-zdocs-cdn-prod.zoominsoftware.com/tdta-xcloud-setup-264-0-0-production-enus/6f21df35-9ea0-4f53-ac94-45c3b5dfd5bc/images/icon_note.png)

Note For best performance, we recommend assigning no more than 15 actions to a topic.

1. From the Agents Setup page, open your agent in the Agent Builder.
2. If active, turn off your agent.
3. Select **Topics**.
4. Click the name of the topic that you want to add an action to.
5. Go to This Topic's Actions.
    
    If you're using the General CRM agent topic, select it.
6. Click **New**.
7. Select any actions that you want to add to your agent from the actions library, or create a custom agent action.
    
    After you select any actions that you want to add to your agent from the actions library, activate and test your agent.
8. Click **Finish**.

The actions you've added are assigned to your agent and are visible on the Topics panel of the Agent Builder. To test your topic with your newly assigned actions, restart the preview conversation to apply your changes.

## Remove a Custom Archive Agent Action from a Topic

To remove an agent action from your topic:

1. From the Topics panel of the Agent Builder, click the name of the topic you want to remove an agent action from.
2. Go to This Topic's Actions.
3. From the dropdown list next to the action, select **Remove from Topic**.

## Missing Archive Agent Permissions

Archive permissions work similarly to Archive Component Restriction Rules. Record-level access is enforced only if the object has been added to the Restriction Rules within Archive Settings. If no objects are added to the restriction rules, users have public access to all archived records of that type.

For example, if no Case rules are set, a user who can't originally access a specific case can see it after it's archived. Permissions are based on the sharing settings at the moment of archival. Any changes to Sharing Rules after the record is archived aren't applied.

#### See Also

- [Activate or Deactivate Your Agent](https://help.salesforce.com/s/articleView?id=ai.copilot_setup_activate_deactivate.htm&type=5&language=en_US)
- [Create a Custom Topic](https://help.salesforce.com/s/articleView?id=ai.copilot_topics_add_custom.htm&type=5&language=en_US)
- [Edit a Standard Topic](https://help.salesforce.com/s/articleView?id=ai.copilot_topics_edit.htm&type=5&language=en_US)
- [Enable Agentforce](https://help.salesforce.com/s/articleView?id=ai.agent_setup_enable.htm&type=5&language=en_US)
- [Search with the Archive Agent](https://help.salesforce.com/s/articleView?id=xcloud.archive_search_with_archive_agent.htm&release=264.0.0&type=5 "Search for archived records in the Archive app by using the Archive Agent. Specify an object directly in your search phrase, add criteria for accuracy, and work within system limitations to find the records you need.")
- [Test Your Agent](https://help.salesforce.com/s/articleView?id=ai.agent_parent_test.htm&type=5&language=en_US)
- [Archive Component Restriction Rules](https://help.salesforce.com/s/articleView?id=xcloud.archive_widget_restriction_rules.htm&release=264.0.0&type=5 "Control end-user visibility in Salesforce of specific archived records according to object sharing tables in the Archive app. Applicable to Archive Single Objects and Archive Multiple Objects components.")