We wrote a custom deduplication and merge tool allowing us to overcome the Salesforce limitation only to get the deduplication UI only for a few objects. Our tool needs to work on ANY SObject (custom + standard). Except for some Salesforce Bugs new in Winter '19, it just works fine most of the time.

We also handle the re-assignment of child-objects as part of the merge process.

Now we are showing the child relationships dynamically but use only the SObject Name (or Label to be more precisely). If there is only ONE child relationship of one SObject type, this might be sufficient, but on some custom Objects (e.g. elfBCProject__c) we have many lookups to Account and if we want to Dedupe-Merge two given Accounts now, we see many lines (in the screenshot it is Project and Order Form)

enter image description here

What we really would want to see instead is the Related List Label (including it's translated version done at translation workbench).

In the setup it is defined for ANY lookup or master-detail field:

enter image description here

But it looks like salesforce is not exposing this Related List Label. I found an 5 years old post here: Access the Related List Label for a Lookup relationship

And an idea here: https://success.salesforce.com/ideaView?id=08730000000JGzuAAG

This indicates, that it is likely still not possible to get the Related List Label via APEX. I just want to confirm here in this community, if we really still can't get that Label or if there might be a hack or a trick to get it now.

1 Answer 1


Oh, this is a nasty hack, but I'm using something similar right now to get listview info.

You can use the metadata api to get the field describes. There's a relationshipLabel value in the describe results. The problem, of course, is that the user needs API access to make this work at runtime...and that just isn't good.

/services/metadata/v43.0/components/objects/Account/fields/ownerid (Bad example since the OwnerId field doesn't have a label, but you get the picture.)

To get around the access issue, we're using a batch job (which runs as the system user) to grab all the describe info we need, and writing it to custom object records that we reference like a dictionary table in our code. Probably would have used Custom Settings if I had it to do over again, but for now, it works well-enough.

Like I said -- nasty, but problem solved.


Code sample, as requested ;)

public with sharing class ListViewMetadataRetriever extends ListviewMetadataBaseClass {
    * @description This method generates metadata for a specific listview
    * @param       listviewid - Id of ListView whose metadata is needed.
    * @return      ListViewMetaData - class containing info about the listview
    public static ListViewDescribe getListViewDescribe(String sObjectName, String listviewid){

        String restAPIURL = getDescribePath(sObjectName, listviewId);

        HttpRequest httpRequest = getHttpRequest(restAPIURL);

        String response = '';
        ListViewDescribe descr;

        try {
            Http http = new Http();
            HttpResponse httpResponse = http.send(httpRequest);

            if (httpResponse.getStatusCode() == 200 ) {
                descr = (ListViewDescribe)JSON.deserialize(httpResponse.getBody(),ListViewDescribe.class) ;

            } else {
                throw new CalloutException( httpResponse.getBody() );


        } catch(System.Exception e) {
            System.debug('ERROR: '+ e);
            throw e;
        return descr;

    private static HttpRequest getHttpRequest(String restApiUrl){
        HttpRequest httpRequest = new HttpRequest();
        httpRequest.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
        httpRequest.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionID());

        return httpRequest;

    private static String getDescribePath(String sObjectName, String listviewId){
        String sfdcURL = URL.getSalesforceBaseUrl().toExternalForm(); 

        String pathStr = '';
        pathStr += sfdcUrl + '/services/data/v';
        pathStr += ListViewMetadataBaseClass.SFDC_API_VERSION + '.0';  //e.g. '44.0'
        pathStr += '/sobjects/' + sObjectName;
        pathStr += '/listviews/' + listviewId + '/describe';

        return pathStr;

    private static String getInfoPath(String sObjectName){
        String sfdcURL = URL.getSalesforceBaseUrl().toExternalForm();

        String pathStr = '';
        pathStr += sfdcUrl + '/services/data/v';
        pathStr += ListViewMetadataBaseClass.SFDC_API_VERSION + '.0';  //e.g. '44.0'
        pathStr += '/sobjects/' + sObjectName;
        pathStr += '/listviews/';

        return pathStr;


Base class:

public abstract class ListviewMetadataBaseClass {
    public static final String SFDC_API_VERSION = '42';
    public class ListViewDescribe{
        public list<Column> columns;
        public String query;

  • Cool hack! I like the metadata api!! Could you please add the one or two lines of code of how you have invoked the api callout? Did you get the result synchronously or was it an asynchronios callout?
    – Uwe Heim
    Nov 1, 2018 at 7:39
  • I can do a little better than that. ;) Nov 1, 2018 at 14:28
  • (Answer updated.) Nov 1, 2018 at 14:30
  • thaks a lot. I will keep this question open for a while to see if anyone else might come with a different approach. But your answer is most likely the best possible way to do it. Salesforce should really expose more API-features to Apex to spare us from such hacks and waste of time and energy to do simple things.
    – Uwe Heim
    Nov 1, 2018 at 14:41
  • No doubt! I'm looking forward to being able to do schema describes to get to this stuff. I'll be very happy to get rid of this mess! Nov 1, 2018 at 14:43

You must log in to answer this question.

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