I've been attempting to do a purely Apex based sorting of Opportunity Line Items based on a solution I found in the Salesforce developer boards. That solution uses a combination of Javascript and Apex WebServices. The Javascript is executed from a custom button and injects a form into the page that mimics the form on the /oppitm/lineitemsort.jsp page and then submits the form.

I'm attempting to do the same but instead of using Javascript I'm trying to do a HTTP POST request from Salesforce-to-Salesforce. Creating a POST request to mimic the form on /oppitm/lineitemsort.jsp then send request. I created a Remote Site Setting to allow for callout to /oppitm/lineitemsort.jsp.

Here's what I have:

public class OppLineSortPost {
    public class CustomException extends Exception {}

    public static void sortLines(Id oppId) {
        HttpRequest req = new HttpRequest();
        req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
        req.setHeader('Cookie', cookie());           

        String url = '/' + oppId;
        req.setBody(makeBody(new Map<String, String> {
            'id' => oppId,
            'duel0' => sortOrder(oppId),
            'retURL' => url,
            'cancelURL' => url,
            'save' => ' Save ',
            '_CONFIRMATIONTOKEN' => confirmationToken(oppId)

        Http http = new Http();       
        HttpResponse resp = http.send(req);

        String respString = '\nResponse: ' + resp.toString() + '\n    Body: ' + resp.getBody();


        if (resp.getStatusCode() != 200) {
            throw new CustomException(respString);

    static String cookie() {
        // When making HTTP Requests we send this Cookie with our session information
        return String.format('oid={0}; sid={1}', new String[] { UserInfo.getOrganizationId(), sessionId() });

    static String endpoint() {
        return String.format(
            new String[] { System.Url.getSalesforceBaseUrl().getHost() }

    static String sessionId() {
        String sessionId = UserInfo.getSessionId();

        if (sessionId == null) {
            throw new CustomException('No SessionID available');

        return sessionId;

    // Lines will be ordered by Product2.SortOrder__c then by Product2.Name
    static String sortOrder(Id oppId) {
        Id[] sortedIds = new Id[] {};

        for (OpportunityLineItem line : [
            SELECT id
            FROM OpportunityLineItem
            WHERE opportunityId = :oppId
            ORDER BY PricebookEntry.Product2.SortOrder__c , PricebookEntry.Product2.Name
        ]) {

        System.debug('ORDER ITEMS: ' + sortedIds);

        return String.join(sortedIds, ',');

    static String confirmationToken(Id oppId) {
        // The Sort form contains a field _CONFIRMATIONTOKEN with a generated code,
        // we make a GET request for form first and scrap the _CONFIRMATIONTOKEN code
        // so we can use it in our subsequent POST request
        HttpRequest req = new HttpRequest();
        req.setHeader('Cookie', cookie());
        req.setEndpoint(String.format('{0}?id={1}&retURL=%2F{2}', new String[] { endpoint(), oppId, oppId }));
        Http http = new Http();
        HttpResponse resp = http.send(req);

        String body = Pattern.compile('[\\s]').matcher(resp.getBody()).replaceAll(' ');
        Matcher m = Pattern.compile('.*?id="_CONFIRMATIONTOKEN" value="(.*?)".*').matcher(body);
        if (!m.matches()) {
            throw new CustomException('Could not find Confirmation Token');

        return m.group(1);

    static String makeBody(Map<String, String> params) {
        if (params.isEmpty()) {
            return '';

        String[] encoded = new String[] {};
        for (String k : params.keySet()) {
            String v = EncodingUtil.urlEncode(params.get(k), 'UTF-8');

            encoded.add(String.format('{0}={1}', new String[] {k, v}));

        return String.join(encoded, '&');

So to sort my Opportunity Line Items I would call


This makes a GET request to to /oppitm/lineitemsort.jsp form first and scraps the _CONFIRMATIONTOKEN code. Then we create a POST request with cookie, _CONFIRMATIONTOKEN and sorted list of Opportunity Line Item ID's. The response I get back is 200 OK but the Opportunity Line Items are not sorted. Seem like the request was just ignored and it wants the redirect the client to the Opportunity page based on the content of the response body.

Here is the Response Body:

if (window.location.replace){ 
} else {;
window.location.href ='/006e0000002i3HOAAY';

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

Any one have some ideas on how to get this working? Am I missing some more Headers, Cookies, Form Data?

  • Where are you trying to sort them, in the standard opportunity page?
    – Matt Lacey
    Commented Mar 6, 2013 at 22:46
  • I'm not really following... the page that you linked to shows a solution in which the line items are sorted in Apex using SOQL. This is always the approach I would use. Why are you posting out to the line item sort JSP page from Apex?
    – jkraybill
    Commented Mar 7, 2013 at 2:32
  • @LaceySnr One possibility, if I could get this to work, would be to have a custom button on the standard Opportunity page to replace the standard "Sort" button displayed on the Opportunity Products related list. When pressed the sorting would be done without the user having to go to the JSP page and manully define the order of each item in the list.
    – Alan Morey
    Commented Mar 7, 2013 at 17:17
  • @jkraybill The SortOrder field on OpportunityLineItem object is not a writable field. There is an idea to make this creatable/updateable but that idea has been there for years. When the standard Opportunity page displays the Opportunity Products related list it uses the SortOrder or the Product Name if the SortOrder is null. The SortOrder is null until the user presses the "Sort" button and manually defines the order of each line item. That JSP page is the only way to get write a value into the SortOrder field.
    – Alan Morey
    Commented Mar 7, 2013 at 17:26
  • Aha got ya, I didn't get your intent from the question description. Given that, I do like your idea about the custom button. Honestly even totally replacing the OLI table with a custom VF page sounds like a better long-term solution than a page that posts through to a Salesforce JSP.
    – jkraybill
    Commented Mar 8, 2013 at 1:59

2 Answers 2


I gave your code a try and I think I have some more information (no solution yet)

when Salesforce makes a request through a callout it adds proxy headers to the request. I think that those headers are causing the server to respond with a redirect. I have seen the same thing happen when running a reverse proxy with header pass through.

 [HTTP_ACCEPT_LANGUAGE] => en-US,en;q=0.8
 [HTTP_COOKIE] => oid=; sid=; web_core_geoCountry=us; ApexCSIPageCookie=true; rememberUn=true; login=; com.salesforce.LocaleInfo=us; autocomplete=1;  clientSrc=; inst=APPQ;
 -------> [HTTP_SFDC_STACK_DEPTH] => 1         
 [HTTP_ACCEPT_ENCODING] => gzip,deflate,sdch
 [HTTP_USER_AGENT] => Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36
 [HTTP_ACCEPT] => text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 [CONTENT_TYPE] => application/x-www-form-urlencoded
 [HTTP_HOST] => 
 -------> [HTTP_VIA] => 1.1 proxy-sjl.net.salesforce.com:8080 (squid)
 -------> [HTTP_X_FORWARDED_FOR] =>,
 [HTTP_CACHE_CONTROL] => max-age=0
 [HTTP_CONNECTION] => keep-alive
 [PATH] => /usr/local/bin:/usr/bin:/bin

since we cant strip those out of the request, a callout is likely a no go. It works from javascript because its being generated from the browser without any of those extra proxy tags.

UPDATE: As I think about it, if you route the request through your own proxy server (ngnix) and strip the tags you might have luck. In this case ugly hack begets uglier hack.


Somebody figured this out. Please see this video [1.20] https://www.youtube.com/watch?v=BcAvvzf0EnM

From VF page it is redirected to "lineitemsort.jsp" and then back to opporutnity with line items sorted. Any ideas on how they have done it?

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .