{"title":"Add agent actions","slug":"add-agent-actions","url":"https://support.storeconnect.com/articles/add-agent-actions","url_markdown":"https://support.storeconnect.com/articles/add-agent-actions.md","subtitle":null,"summary":"Create an Apex-based Agentforce action that retrieves purchasable products from your StoreConnect store and returns direct-to-cart links, then attach it to an agent topic.","type":"Help_Documentation","video_url":"","keywords":"agent actions, agentforce, add agent action, apex class, invocable method, ai agent setup, direct-to-cart, purchasable products, agent topic, salesforce agentforce, storeconnect ai agent","last_modified":"2026-08-21T07:12:35+0000","body_markdown":":::note\nThis topic relates to using AI agents to service your customers, not how to [use agents for building on StoreConnect](use-ai-agents-with-storeconnect).\n:::\n\nActions translate an end user’s request into real agent action that updates your Salesforce CRM. For example, we can configure an action called **available\\_products** that authorizes the agent to get the latest information about available products from Salesforce, and display them to the user.\n\nThere are multiple ways to add agent actions: from the Agentforce Toolkit, using Flow, or via Apex code.\n\n## Add agent actions using Apex\n\nIn the procedure below, we will add an action using Apex. \n\n### Add an Apex class\n\n1.  Create a new Apex class called AFPurchasableProducts. \n\n2.  Use your own code or copy and paste the sample code below.  \n\n\n### Sample code\n\n```\npublic class AFPurchasableProducts {\n\n    public AFPurchasableProducts() {}\n\n    // ==========\n    // Input Variables\n    // ==========\n    public class Input {\n        @InvocableVariable(\n            label='Store Id'\n            description='s_c__Store__c record Id to evaluate products for'\n            required=true\n        )\n        public Id storeId;\n\n        @InvocableVariable(\n            label='Action Type'\n            description='Action to perform: add (add to cart) or buy (go to checkout). Default is buy.'\n        )\n        public String type;\n\n        @InvocableVariable(\n            label='Preserve Cart'\n            description='If true, preserves the existing cart contents when adding/buying.'\n        )\n        public Boolean preserve;\n\n        @InvocableVariable(\n            label='Return Behavior'\n            description='Redirect behavior after action: true (referrer), false (stay on site), or a callback URL.'\n        )\n        public String ret;\n\n        @InvocableVariable(\n            label='Promotion Code'\n            description='Promotion code (case-sensitive) applied to the action.'\n        )\n        public String code;\n    }\n\n    // ==========\n    // Rich Link Item\n    // ==========\n    public class ProductRichLink {\n        public String linkURL;\n        public String linkTitle;\n        public String linkImageURL;\n        public String linkImageMimeType;\n        public String linkDescriptionText;\n    }\n\n    // ==========\n    // Rich Link Response Wrapper\n    // ==========\n    public class ProductRichLinkResponse {\n\n        @InvocableVariable(\n            label='Purchasable Product Details'\n            required=true\n        )\n        public List productDetails;\n\n        public ProductRichLinkResponse() {\n            this.productDetails = new List();\n        }\n    }\n\n    // ==========\n    // Invocable Method\n    // ==========\n    @InvocableMethod(\n        label='Quick: Get Purchasable Products as Rich Links'\n        description='Given a store Id, returns, for each input, rich link details (URL, title, image URL, MIME type, description) for purchasable products.'\n    )\n    public static List execute(List inputs) {\n        List responses = new List();\n\n        if (inputs == null || inputs.isEmpty()) {\n            return responses;\n        }\n\n        Set storeIds = new Set();\n        for (Input i : inputs) {\n            if (i != null \u0026\u0026 i.storeId != null) {\n                storeIds.add(i.storeId);\n            }\n        }\n\n        Map storesById = new Map(\n            [\n                SELECT Id,\n                       s_c__Link__c,\n                       s_c__Logo_Id__r.s_c__Url__c\n                FROM s_c__Store__c\n                WHERE Id IN :storeIds\n            ]\n        );\n\n        // ---- Load all active Products ----\n        List activeProducts = [\n            SELECT Id,\n                   Name,\n                   ProductCode,\n                   IsActive,\n                   s_c__Features_Markdown__c,\n                   s_c__Subscription_Term__c,\n                   s_c__Subscription_Term_Count__c,\n                   s_c__Subscription_Term_Unit__c,\n                   s_c__Subscription_Type__c\n            FROM Product2\n            WHERE IsActive = true\n        ];\n\n        Set activeProductIds = new Set();\n        for (Product2 p : activeProducts) {\n            activeProductIds.add(p.Id);\n        }\n        Map productById = new Map(activeProducts);\n\n        // ---- Product Media: first s_c__Product_Media__c per product (by position) ----\n        Map mediaUrlByProductId = new Map();\n\n        if (!activeProductIds.isEmpty()) {\n            for (s_c__Product_Media__c pm : [\n                SELECT s_c__Product_Id__c,\n                       s_c__Media_Id__r.s_c__Url__c,\n                       s_c__Position__c\n                FROM s_c__Product_Media__c\n                WHERE s_c__Product_Id__c IN :activeProductIds\n                ORDER BY s_c__Product_Id__c, s_c__Position__c ASC\n            ]) {\n                Id pid = pm.s_c__Product_Id__c;\n                // First row per product (lowest position) wins\n                if (!mediaUrlByProductId.containsKey(pid)) {\n                    mediaUrlByProductId.put(pid, pm.s_c__Media_Id__r.s_c__Url__c);\n                }\n            }\n        }\n\n        for (Input i : inputs) {\n            ProductRichLinkResponse response = new ProductRichLinkResponse();\n            responses.add(response);\n\n            if (i == null || i.storeId == null) {\n                continue;\n            }\n\n            s_c__Store__c store = storesById.get(i.storeId);\n            if (store == null) {\n                continue;\n            }\n\n            String baseLink = store.s_c__Link__c;\n            String storeFallbackImageUrl = (store.s_c__Logo_Id__r != null)\n                ? store.s_c__Logo_Id__r.s_c__Url__c\n                : null;\n\n            List reqs =\n                new List();\n\n            s_c.AFGetPurchasableProductIdsInvocable.Request req =\n                new s_c.AFGetPurchasableProductIdsInvocable.Request();\n\n            req.productIds = new List(activeProductIds);\n            req.storeId = i.storeId;\n            reqs.add(req);\n\n            List purchResults;\n\n            try {\n                purchResults = s_c.AFGetPurchasableProductIdsInvocable.getPurchasableProductIds(reqs);\n            } catch (Exception e) {\n                continue;\n            }\n\n            if (purchResults == null || purchResults.isEmpty()) {\n                continue;\n            }\n\n            s_c.AFGetPurchasableProductIdsInvocable.Result first = purchResults[0];\n            if (first.error != null) {\n                continue;\n            }\n\n            Set purchIds = new Set();\n            if (first.productIds != null) {\n                purchIds.addAll(first.productIds);\n            }\n\n            for (Id pid : purchIds) {\n                Product2 p = productById.get(pid);\n                if (p == null) {\n                    continue;\n                }\n\n                ProductRichLink link = new ProductRichLink();\n\n                link.linkURL = buildDirectToCartUrl(\n                    baseLink,\n                    p.ProductCode,\n                    i.type,\n                    i.preserve,\n                    i.ret,\n                    i.code\n                );\n\n                link.linkTitle = p.Name;\n\n                String imageUrl = mediaUrlByProductId.get(p.Id);\n                if (String.isBlank(imageUrl)) {\n                    imageUrl = storeFallbackImageUrl;\n                }\n                link.linkImageURL = imageUrl;\n\n                if (!String.isBlank(imageUrl)) {\n                    link.linkImageMimeType = 'image/png';\n                }\n\n                if (!String.isBlank(p.s_c__Features_Markdown__c)) {\n                    link.linkDescriptionText = p.s_c__Features_Markdown__c;\n                } else {\n                    link.linkDescriptionText = p.Name;\n                }\n\n                response.productDetails.add(link);\n            }\n\n        }\n\n        return responses;\n    }\n\n    // ==========\n    // Helper: build cart URL\n    // ==========\n    @TestVisible\n    private static String buildDirectToCartUrl(\n        String baseLink,\n        String productCode,\n        String typeParam,\n        Boolean preserveParam,\n        String returnParam,\n        String codeParam\n    ) {\n        if (String.isBlank(baseLink) || String.isBlank(productCode)) {\n            return null;\n        }\n\n        String normalizedBase = baseLink.endsWith('/')\n            ? baseLink.removeEnd('/')\n            : baseLink;\n\n        String path = normalizedBase + '/cart/' +\n            EncodingUtil.urlEncode(productCode, 'UTF-8');\n\n        List params = new List();\n\n        String finalType = String.isBlank(typeParam) ? 'buy' : typeParam.toLowerCase();\n        if (finalType != 'buy' \u0026\u0026 finalType != 'add') {\n            finalType = 'buy';\n        }\n        params.add('type=' + EncodingUtil.urlEncode(finalType, 'UTF-8'));\n\n        if (preserveParam != null) {\n            params.add('preserve=' + String.valueOf(preserveParam));\n        }\n\n        if (!String.isBlank(returnParam)) {\n            // Allowed: \"true\", \"false\", or callback URL\n            params.add('return=' + EncodingUtil.urlEncode(returnParam, 'UTF-8'));\n        }\n\n        if (!String.isBlank(codeParam)) {\n            params.add('code=' + EncodingUtil.urlEncode(codeParam, 'UTF-8'));\n        }\n\n        return path + (params.isEmpty() ? '' : '?' + String.join(params, '\u0026'));\n    }\n}\n```\n\n\n### Add an action to a topic\n\n1.  Open a topic, for example, the **Sales Representative** topic from the previous procedure.\n\n2.  Go to the **This Topic’s Actions** tab.\n\n3.  Select **New** and then **+Create New Action**.\n\n    ![Add action to topic](https://res.cloudinary.com/hzkr6fi81/image/upload/v1765236274/knowledge/AgentforceAI/Set_up_agent_actions_1-create-new-action_yk3nuw.png)\n\n4.  Select the **Action Reference Type** as **Apex**. \n\n5.  Set the **Reference Action Category** to **Invocable Method** and then select your reference action. If you used our example, this would be **Quick: Get Purchasable Products for Store**.\n\n6.  Change the Agent API name from the auto generated value to available\\_products. \n\n7.  Select **Next**.\n\n\n### Configure the action\n\n1.  On the **Configure your action for Agent** screen, instruct the agent on how to use the action. Provide instructions and validations on each input and output.\n\n    ![Actions details screen](https://res.cloudinary.com/hzkr6fi81/image/upload/v1765236276/knowledge/AgentforceAI/Set_up_agent_actions_2-configure-action-for-agent_d9gxlm.png)\n\n2.  For inputs:\n\n    1.  To require customer input, select the **Require input** option. \n\n    2.  Check the **Collect data from the user** option. \n\n    3.  In the **Loading Text** field, add a placeholder message for when the agent is thinking, for example **Searching Catalog…**\n\n3.  For outputs:\n\n    1.  Where you want the agent to ignore an output, select the **Filter from agent action** option. \n\n    2.  If you want to expose the result to the user, select the **Show in Conversation** option.\n\n    3.  Enter instructions, such as **The list of purchasable products found for this store**. \n\n    4.  Select **Next**.\n\n\n### Add a storeid as a custom variable\n\n1.  In the action record, go to the **StoreId input** variable. \n\n2.  Select **Assign a Variable**.\n\n3.  Set the storeId custom variable and save it.\n\n4.  Refresh your page to go back to the Agentforce Builder home."}