Introduction

The S-Docs Callable Apex feature allows you to generate merge field values in your S-Docs templates via custom Apex code. This feature is useful if your business requirements necessitate the use of advanced logic that can't be defined directly within an S-Docs template.

You can include as many callable apex classes in your template as you'd like, however we recommend writing a single class with multiple functions or arguments, as opposed to multiple classes.

Apex Class

Your Apex class must return a <string, string> map where keys are represented by field names and values are represented by field values. The following example provides a basic overview of an acceptable class, however you can use multiple functions or arguments in your class.

global class CallableApexTest implements Callable {
    public Object call(String action, Map<String,Object> args) {
        switch on action {
            when 'getMergeFieldMapExample1' {
                return this.getMergeFieldMapExample1((String)args.get('recordId'));
            }
            when else {
                throw new ExtensionMalformedCallException('Method not implemented');
            }
        }
    }
    public class ExtensionMalformedCallException extends Exception {}
    public Map<String,String> getMergeFieldMapExample1(String recordId) {
        Opportunity opp = [SELECT Id FROM Opportunity WHERE Id=:recordId]; // Base Object Record
        Map<String,String> mergeFieldMapExample = new Map<String,String>{
            'Field_1' => 'Value_1',
            'Field_2' => 'Value_2',
            'Field_3' => 'Value_3'
        };
        return mergeFieldMapExample;
    }
}

Callable Syntax

To call your Apex class in your S-Docs template, navigate to the Source editor and use syntax similar to the following example:

<!--{{!
<callable>
<class>CallableApexTest</class>
<action>getMergeFieldMapExample1</action>
<args>{ "arg1" : "val1" }</args>
</callable>
}}-->
Field_1: {{!Field_1}}<br />
Field_2: {{!Field_2}}<br />
Field_3: {{!Field_3}}<br />

Lines [1-2] open the callable Apex statement. The Apex class name is referenced in <class> tags on line [3]. The function of the Apex class is referenced in <action> tags on line [4]. Arguments can optionally be referenced within <args> tags, as shown on line [5]. Finally, the statement is closed on lines [6-7].

You can include any merge fields referenced in your <string, string> map anywhere in your S-Docs template. The three fields from the example map are included on lines [8-10].

The example above would render the following document:

Example Use Case

To further understand this feature, let's take a look at an example use case.

Let's say that the Opportunity object contains a custom multi-select picklist field with 6 different products.

We'd like our document to display a list of 6 sections that output a letter that corresponds to the product's positioning in the chosen list. In other words, if all products are chosen, the S-Docs document should render like this:

But if Products 1, 3, and 6 are chosen, the S-Docs document should render like this:

To easily accomplish this, we can write an Apex class and then call that class in our S-Docs template. The Apex class for this function looks like this (and includes comments for clarification):

global class SDocsCallable implements Callable {
    public Object call(String action, Map<String,Object> args) {
        switch on action {
            when 'getSectionMergeFieldMap' {
                return this.getSectionMergeFieldMap((String)args.get('recordId'));
            }
            when else {
                throw new ExtensionMalformedCallException('Method not implemented');
            }
        }
    }

    public class ExtensionMalformedCallException extends Exception {}
    /*

    Example input:
    Opportunity with a Products__c value of:
    Product 1;Product 3;Product6

    To be clear:

    'Product 1' // Included
    'Product 2' // NOT Included
    'Product 3' // Included
    'Product 4' // NOT Included
    'Product 5' // NOT Included
    'Product 6' // Included

    Example output:
    {
        "Section 1" : "A",
        "Section 2" : "",
        "Section 3" : "B",
        "Section 4" : "",
        "Section 5" : "",
        "Section 6" : "C"
    }
    (these are mapped to merge fields in the S-Docs Template like so: {{!Section_1}} )

    */
    public Map<String,String> getSectionMergeFieldMap(String recordId) {
        String Products = [SELECT Products__c FROM Opportunity WHERE Id=:recordId].Products__c;
        List<String> productNames = new List<String>{
            'Product 1',
            'Product 2',
            'Product 3',
            'Product 4',
            'Product 5',
            'Product 6'
        };
        Map<String,String> mergeFieldMap = new Map<String,String>();
        Integer currentChar = 65;
        for (Integer i = 0; i < productNames.size(); i++) {
            String mergeFieldValue = '';
            if (Products.contains(productNames[i])) {
                mergeFieldValue = 'Section ' + String.fromCharArray( new List<Integer> { currentChar } );
                currentChar++;
            }
            mergeFieldMap.put('Section_' + (i + 1), mergeFieldValue);
        }
        return mergeFieldMap;
    }
}

Our S-Docs template Source code for this use case looks like this:

<!--{{!
<callable>
<class>SDocsCallable</class>
<action>getSectionMergeFieldMap</action>
<args>{ "arg1" : "val1" }</args>
</callable>
}}-->Section 1: {{!Section_1}}<br />
Section 2: {{!Section_2}}<br />
Section 3: {{!Section_3}}<br />
Section 4: {{!Section_4}}<br />
Section 5: {{!Section_5}}<br />
Section 6: {{!Section_6}}<br />

When we generate the document, the correct letters correspond to the correct sections:

As you can see, the callable Apex feature allows you to perform complex functions in your documents that otherwise aren't available in the S-Docs template editor.