Archive SDK Data Query

The Archive SDK Data Query enables programmatic access to archived Salesforce data in the Archive managed package. These data queries automate business processes, support third-party system integrations, create custom views and reports, and can cross-reference archived data with active records.

Note
Note This content relates to Archive. For Salesforce Archive, see Store Data Externally with Salesforce Archive.

While tools like the Archive Widget and Archive Search provide user-friendly options for manual data retrieval, the SDK offers advanced capabilities for direct data access via code. This article covers the usage of the Archive SDK, including query parameters, filters, pagination, and best practices.

Key Features

  • Global Search: Search archived records using specified criteria.
  • Fuzzy Search: Use partial matching with an asterisk (*).
  • Pagination: Retrieve large datasets in batches (up to 1,000 records per page).
  • Filters: Apply up to six filters to refine your results.
  • Deleted Object Support: Query archived records even if the object has been deleted from your production org.

Excluded Objects

These Salesforce objects are filtered out and not searchable via the SDK.

  • Feed
  • History
  • Relation
  • Share

To maintain optimal performance, and make sure that only relevant archived data is returned, these objects are excluded from searches.

Note
Note Files and Attachments can't be retrieved through the Archive SDK.

To access archived files, use the Archive Widget or Archive Search.

Note
Note

The Archive SDK enforces these request limits.

Interval Request Limit
Per Minute 1,000
Per Hour 30,000
Per Day 100,000

If you exceed this limit, you receive HTTP status code 429.

Archive SDK Data Query Methods

To define how you query archived data, set the method provided by the Archive SDK.

  • Accessing Archive Data: The methods within ArchiverAccessor are designed to interact with archived records. Then build queries and retrieve data stored in the Archive managed package.
  • Defining Query Operations: These signatures tell you what kinds of queries you can perform using the SDK, such as global searches and filtered queries.
  • Guiding Usage: By examining method signatures, developers can understand the inputs and outputs required for their implementations.

performArchiverGlobalSearch Method

Performs a global search on archived data using specified filters and parameters.

Query Parameters Overview

The Data Query SDK uses several parameters to refine your search. Some parameters are required, while others are optional. Understanding how to configure each parameter helps you get the most accurate and relevant results.

  • Object Name (Mandatory)

    Specifies the type of Salesforce object to query (e.g., Account, Contact, Case).

    Example: string sObjectName = 'Account'

    This example specifies that the query targets Account records.

    Note
    Note Each query targets a single object. Use separate queries to retrieve data from multiple objects.
  • Search Filters (Mandatory)

    Filters narrow down the search results based on field conditions.

    • Field Name: The field you want to filter by (e.g., "Name," "CreatedDate").
    • Value or List of Values: The specific value (or values) you want to match.

      Single Value Filter Example

      OB_Archiver.SearchFilter filter = new
      OB_Archiver.SearchFilter('Name', 'test'); 

      This query returns records where the Name field matches 'test.'

      Multiple Value Filter Example:
      OB_Archiver.SearchFilter filter = new
      OB_Archiver.SearchFilter('Status', new List<String>{'Open', 'Closed'}); 

      This query returns records where the Status field matches "Open" or "Closed."

      Handling Empty/Null Values:

      To search for records where a field is empty or null, add an empty string ("") to the List of Values.

      Example:

      List<SearchFilter> filters = new List<SearchFilter>();
      filters.add(new SearchFilter('Subject', '')); 

      This filter returns Case records where the Subject field is either empty or null.

      Limits:

      • Minimum Filters: 1 per query.
      • Maximum Filters: 6 per query (combined with AND logic).
      • Logic: Filters are combined using AND logic by default. OR logic is not currently supported.
      • Maximum Filter Value List Size: A filter's value list can't exceed 1,000 items.
  • Date Filters (Optional)

    If you want to search based on dates, use the DateRange parameter and the mandatory filters. This is useful for filtering records by fields such as CreatedDate, ModifiedDate, or Archive Date.

    Example: Date Filter for Specific Date Query

    Date todayDate = Date.today();
    Date jan1st2024 = Date.newInstance(2024, 1, 1);
    DateRange dateRange = new DateRange('archive_date', jan1st2024, todayDate);

    This query returns records archived between January 1, 2024, and today.

    Example: Date Filter for Multiple Date Range

    List<SF_Archive.SearchFilter> filters = new List<SF_Archive.SearchFilter>();
    SF_Archive.SearchFilter filter = new SF_Archive.SearchFilter('Name', 'test');
    filters.add(filter);
    string sObjectName = 'Account';
    
    Date myDate = Date.newInstance(2024, 1, 11); // January 11, 2024
    Date myDate2 = Date.newInstance(2023, 12, 25); // December 25, 2023
    SF_Archive.dateRange range = new SF_Archive.dateRange('CreatedDate', myDate, myDate);
    SF_Archive.dateRange range2 = new SF_Archive.dateRange('CreatedDate', myDate2, myDate2);
    list<SF_Archive.dateRange> ranges = new list<SF_Archive.dateRange> {range, range2};
    SF_Archive.ArchiverAccessorResponse res = SF_Archive.ArchiverAccessor.performArchiverGlobalSearch(sObjectName, filters, ranges);

    This query returns records archived between December 12, 2023 to January 11, 2024.

    To search based on the Archive Date add date field: archive_date

    Date Format: Use the Apex date format: MM/DD/YYYY.

  • Returned Fields (Optional)

    Specify fields to include in the results. If not specified, all fields are returned.

    Example: Returning Specific Fields

    List<string> fields = new List<string>{'Name', 'CreatedDate'};

    This query returns only the Name and CreatedDate fields for each record.

  • Page Size (Optional)

    Sets the number of records per query. The default is 25, and the maximum is 1,000.

    Example: Custom Page Size

    OB_Archiver.archiverAccessorResponse res = 
    OB_Archiver.archiverAccessor.performArchiverGlobalSearch('Account', 
    filters, 100, fields);

    This query returns 100 records per page.

    Note
    Note Exceeding 1,000 records triggers a runtime error.
  • Sorting Direction (Optional)

    Sort results by a field, such as CreatedDate.

    • Ascending ('asc'): Oldest to newest.
    • Descending ('desc'): Newest to oldest.
    • Example: Sorting Query
      SortingDirection sortingDirection = new SortingDirection('desc');

      This query returns records sorted in descending order based on their CreatedDate

      Return Type: List<ArchiveResult>

    • Specifies that the method returns a list of ArchiveResult objects representing the search results.
    • Modifiers:
      • public: The method is accessible outside the class.
      • static: The method can be called on the class itself without needing an instance of the class.

getNextPage Method

Retrieves the next page of results using the scrollId from the previous response. Use this method to paginate through large result sets that exceed the page size limit.

ScrollId (Mandatory)

Use pagination to retrieve large datasets by fetching results in pages of up to 1,000 records.

To set up the pagination workflow, perform the initial query, use the scrollId from the response to fetch subsequent pages, and continue until scrollId = '-1'..

Example: Fetching Results with Pagination

// Perform initial search query
OB_Archiver.archiverAccessorResponse res = OB_Archiver.archiverAccessor.performArchiverGlobalSearch('Account', 
filters, 100, fields);
                    
// Get the search response
OB_Archiver.archiverAccessorSearchResponse searchResponse = res.getSearchResponseMap();
                    
// Process first page of results
system.debug('Total result count: ' + searchResponse.totalResultCount);
system.debug('Records: ' + searchResponse.records);
                    
// Fetch next page using the scrollId
if (searchResponse.scrollId != '-1') {
OB_Archiver.archiverAccessorResponse nextPageRes = OB_Archiver.archiverAccessor.getNextPage(searchResponse.scrollId);
OB_Archiver.archiverAccessorSearchResponse nextPageResponse = nextPageRes.getSearchResponseMap();
system.debug('Next Page Records: ' + nextPageResponse.records);
}

Access the Methods

To find the method signatures, go to Setup, then Apex Classes. Open the ArchiverAccessor class. Locate all methods listed under the performArchiverGlobalSearch namespace.

Sample Query

This example demonstrates searching for Account records with these conditions.

  • Fields: Name, CreatedDate.
  • Limit: 100 records per page.
  • Name: Contains "test".
  • Sorting: Descending by CreatedDate.
List<OB_Archiver.SearchFilter> filters = new List<OB_Archiver.SearchFilter>();
OB_Archiver.SearchFilter filter = new OB_Archiver.SearchFilter('Name', 'test');
filters.add(filter);
                
List<string> fields = new List<string>{'Name', 'CreatedDate'};
                
SortingDirection sortingDirection = new SortingDirection('desc');
                
OB_Archiver.archiverAccessorResponse res = OB_Archiver.archiverAccessor.performArchiverGlobalSearch('Account', 
filters, 100, fields, sortingDirection);
            

Troubleshooting Common Errors

Error Code Description Resolution
INTERNAL_ERROR An unexpected failure occurred. Contact support with the error details.
RESOURCE_LIMIT_ERROR Exceeded pagination or query size limits. Reduce the page size or refine search filters.
VALIDATION_ERROR Missing or invalid search criteria. Make sure that all filters and fields are valid.

Additional Tips

  • API Limits: Monitor your API usage to avoid hitting limits.
  • Casting Response Structure: To cast the response to an sObject, serialize it into JSON and then deserialize it back into an sObject.
  • Field Accessibility: Make sure that fields are enabled and accessible.
  • Request Structure: Double-check all query parameters.

For unresolved issues, contact Archive SDK Support with detailed logs.

For testing the SDK, see Archive SDK Testing.

Archive SDK Data Query—Sample Use Case 1

In this code example, a filter, date range, and create an archive search request are validated through the status code in the Archive managed package.

Note
Note This content relates to Archive. For Salesforce Archive, see Store Data Externally with Salesforce Archive.

A getNextPage call following (scrollId != -1) is included because the size of the results is limited in the performArchiverGlobalSearch call.

string objectName = 'Account';
                
list<OB_Archiver.SearchFilter>request = new list<OB_Archiver.SearchFilter>();

OB_Archiver.SearchFilter filter1 = new OB_Archiver.SearchFilter('Name', 'test');

request.add(filter1);

OB_Archiver.DateRange range = new OB_Archiver.dateRange('CreatedDate',

system.today()-150,system.today());

OB_Archiver.ArchiverAccessorResponse res =

OB_Archiver.ArchiverAccessor.performArchiverGlobalSearch(objectName, request, range);

if(res.getStatusCode() == 200){

system.debug('code: ' + res.getStatuscode());

OB_Archiver.ArchiverAccessorSearchResponse searchResponse = res.getSearchResponseMap();

system.debug('count: '+ searchResponse.totalResultCount);

system.debug('scrollid: '+ searchResponse.scrollId);

system.debug('list size: '+ searchResponse.records.size());

string scrollId = searchResponse.scrollId;

integer page = 1;

while(scrollId != '-1'){

page++;

OB_Archiver.ArchiverAccessorResponse secondPage =

OB_Archiver.ArchiverAccessor.getNextPage(searchResponse.scrollId);

if(secondPage.getStatusCode() == 200){

OB_Archiver.ArchiverAccessorSearchResponse searchResponse2 =

secondPage.getSearchResponseMap();

scrollId = searchResponse2.scrollId;

system.debug('count: '+ searchResponse2.totalResultCount);

system.debug('scrollid: '+ searchResponse2.scrollId);

system.debug('list size: '+ searchResponse2.records.size());

system.debug('page number: '+ page);

} else{

system.debug('response is != 200: '+ secondPage.getStatusCode());

system.debug('body: '+ secondPage.getBody());

break;

} }}else{

system.debug('status code erorr|: '+ res.getStatusCode());

system.debug('status code message|: '+ res.getErrorMessage()); }

Archive SDK Data Query—Sample Use Case 2

Example use case that shows how to build and customize a widget that displays High Priority Accounts in the Archive managed package.

Note
Note This content relates to Archive. For Salesforce Archive, see Store Data Externally with Salesforce Archive.

Use Case

Your business requires that the Tier 3 Support Team have access to view archived Cases with a high-priority status.

This example shows how to:

  • Create a customized Lightning Web Component (LWC).
  • Pull archived data using the data query SDK.
  • Apply the filter (High Priority).
    Note
    Note

    Other filters can be customized by referring to the code here.

    This example is limited to 25 records. The SDK allows you to set the results per page at 1,000, the default is 25, with the option of pagination through the results.

Widget Displaying High Priority Cases

Sample SdkUseCaseWidget.js

In this code example, the parameter is AccountRecordId.

import { LightningElement, api, track } from 'lwc';
    import getCases from '@salesforce/apex/ArchiveSdk.getCases';
    export default class SampleSdkUseCaseWidget extends LightningElement {
    @api recordId;
    
    columns = [
    { label: 'Case Number', fieldName: 'caseNumber', type: 'text', cellAttributes: {alignment: 'center'}},
    { label: 'Subject', fieldName: 'subject', type: 'text' , cellAttributes: {alignment: 'center'}, },
    { label: 'Priority', fieldName: 'priority', type: 'text', cellAttributes: {alignment: 'center'}, },
    { label: 'Status', fieldName: 'status', type: 'text', cellAttributes: {alignment: 'center'}, }
    ];
    @track records;
    @track data
    @track _loading;
    
    async connectedCallback() {
    this._loading = true;
    try {
    const result = await getCases({parentId: this.recordId})
    this.records = result;
    this._loading = false;
    } catch (error) {
    this._loading = false;
    }}
}            

Sample SdkUseCaseWidget.html

The Apex controller that the LWC uses calls the Data Query SDK.

OB_Archiver.archiverAccessor.performArchiverGlobalSearch(objectName, request, fields, range);

This is the call for the Archive SDK. It includes these parameters.

  • ObjectName—Depicts the Search

  • Filters—Used to retrieve the data.

  • Fields—The projected fields to return from data.

  • Range—The date range used to query the data.

<template>
    <template if:true={_loading}>
        <lightning-spinner></lightning-spinner>
    </template>
    <lightning-datatable
        key-field="caseNumber"
        data={records}
        columns={columns}
        hide-checkbox-column>
    </lightning-datatable>
</template>

ArchiveSdk.cls

This code block is the Apex controller.

public with sharing class ArchiveSDK {
                
    @AuraEnabled
    public static List<CaseTableWrapperExample.CaseRecord> getCases(String parentId) {
    // Create an empty list to hold maps with String keys and Object values
    List<Map<String, Object>> totalRecords = new List<Map<String, Object>>();
    
    // Create a list to store search filter objects
    List<SF_Archive.SearchFilter> request = new List<SF_Archive.SearchFilter>();
    
    // Create a search filter for the 'AccountId' field with a given 'parentId' value
    SF_Archive.SearchFilter filter1 = new SF_Archive.SearchFilter('AccountId', parentId);
    
    // Create a search filter for the 'Priority' field with a given 'High' value
    SF_Archive.SearchFilter filter2 = new SF_Archive.SearchFilter('Priority', 'High');
    
    // Add the filters to the request list
    request.add(filter1);
    request.add(filter2);
    
    // Define the object name as 'Case'
    String sObjectName = 'Case';
    
    // Create a list of Strings representing field names to be retrieved from Archive DB
    List<String> fields = new List<String>{'CaseNumber', 'Subject', 'Priority', 'Status'};
    
    // Create the DateRange based on the created date
    SF_Archive.DateRange range = new SF_Archive.DateRange('CreatedDate', system.today() - 150, system.today());
    
    // Perform an initial global search and store the response
    SF_Archive.ArchiverAccessorResponse res = SF_Archive.ArchiverAccessor.performArchiverGlobalSearch(sObjectName, request, range);
    
    // Check if the HTTP response status code is 200 (success)
    if (res.getStatusCode() == 200) {
        system.debug('code: ' + res.getStatuscode());
        
        // Retrieve the initial search response map
        SF_Archive.ArchiverAccessorSearchResponse searchResponse = res.getSearchResponseMap();
        
        system.debug('count: ' + searchResponse.totalResultCount);
        system.debug('scrollid: ' + searchResponse.scrollId);
        system.debug('list size: ' + searchResponse.records.size());
        
        // Store the scroll ID for pagination
        String scrollId = searchResponse.scrollId;
        
        // Initialize the page number
        Integer page = 1;
        
        // Add the records from the initial search to the totalRecords list
        totalRecords.addAll(searchResponse.records);
        
        // Continue to fetch additional pages while the scroll ID is not '-1'
        while (scrollId != '-1') {
            page++;
            
            // Get the next page of search results using the UPDATED scrollId 
            SF_Archive.ArchiverAccessorResponse nextPageResponse = SF_Archive.ArchiverAccessor.getNextPage(scrollId);
            
            // Check if the HTTP response status code is 200 (success)
            if (nextPageResponse.getStatusCode() == 200) {
            
                // Retrieve the search response for the current page
                SF_Archive.ArchiverAccessorSearchResponse searchResponse2 = nextPageResponse.getSearchResponseMap();
                
                // CRITICAL FIX: Update the scrollId with the value from the new response 
                scrollId = searchResponse2.scrollId;
                
                system.debug('count: ' + searchResponse2.totalResultCount);
                system.debug('scrollid: ' + searchResponse2.scrollId);
                system.debug('list size: ' + searchResponse2.records.size());
                system.debug('page number: ' + page); 
                
                // Add the records from the current page to the totalRecords list
                totalRecords.addAll(searchResponse2.records);
            } else {
                // Output an error message if the HTTP response status code is not 200
                system.debug('response is != 200: ' + nextPageResponse.getStatusCode());
                system.debug('body: ' + nextPageResponse.getBody());
                
                // Break out of the loop if there is an error
                break;
            }
        } // End of while loop
    } else {
        // Handle error from initial search
        system.debug('status code error: ' + res.getStatusCode());
        system.debug('status code message: ' + res.getErrorMessage());
    }
    
    // Return the list of total records
    List<CaseTableWrapperExample.CaseRecord> result = new List<CaseTableWrapperExample.CaseRecord>();
    return CaseTableWrapperExample.getCasesTable(totalRecords);
    }
}

CaseTableWrapperExample.cls

Helper class to formulate class from Apex to LWC

public class CaseTableWrapperExample{
    public static List<caseRecord> getCasesTable(list<map<string,object>> cases){
    List<caseRecord> caseReturnList = new List<caseRecord>();
    for(Map<string,object> caseRecord: cases){
    caseRecord tempCase = new caseRecord();
    tempCase.caseNumber = String.valueOf(caseRecord.get('CaseNumber'));
    tempCase.id = String.valueOf(caseRecord.get('Id'));
    tempCase.origin = String.valueOf(caseRecord.get('Origin'));
    tempCase.status = String.valueOf(caseRecord.get('Status'));
    tempCase.subject = String.valueOf(caseRecord.get('Subject'));
    tempCase.priority = String.valueOf(caseRecord.get('Priority'));
        
    caseReturnList.add(tempCase);
    }
    return caseReturnList;
    }
    
    public class caseRecord {
    @AuraEnabled public string caseNumber;
    @AuraEnabled public string id;
    @AuraEnabled public string origin;
    @AuraEnabled public string status;
    @AuraEnabled public string priority;
    @AuraEnabled public string subject;
}}

The above code describes a sample scenario whereby you can add LWC as a widget to the Lightning page.

To view the actual Lightning data table, add this code to the sampleSdkUseCaseWidget.js-meta.xml file. Then add the Archive Widget to the Lightning Page.

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>55.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
    <target>lightning__RecordPage</target>
    <target>lightning__AppPage</target>
    </targets>
    <targetConfigs>
    <targetConfig targets="lightning__RecordPage">
    <supportedFormFactors>
    <supportedFormFactor type="Small" />
    <supportedFormFactor type="Large" />
    </supportedFormFactors>
    </targetConfig>
    <targetConfig targets="lightning__AppPage">
    <supportedFormFactors>
    <supportedFormFactor type="Small" />
    <supportedFormFactor type="Large" />
    </supportedFormFactors>
    </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Archive SDK Data Query—Sample Use Case 3

Example use case that shows how Salesforce's queueable mechanism handles tasks without exceeding governor limits, such as CPU limits or Timeout callouts in the Archive managed package.

Note
Note This content relates to Archive. For Salesforce Archive, see Store Data Externally with Salesforce Archive.

For more information, see Salesforce documentation on Apex Governor Limits

Use Case

Your business requires that the Tier 3 Support Team has access to view archived Cases with a high-priority status.

This example shows how to work around an error when the number of archived records retrieved via the SDK is too large. The error reads: OB_Archiver.ArchiverAccessor.ArchiverAccessorException: Exceeded time allotted for callout, please contact support.

This use case is created using code from these Apex classes.

ArchiveSecondPageJob

This class processes paginated data from Archive using a scroll ID, making callouts as needed. It runs asynchronously via the Queueable interface, tracking the total execution time to avoid exceeding limits and enqueuing itself for continued processing if necessary. The class handles responses, updates the scroll ID, and logs details about each retrieved data page.

public class ArchiveSecondPageJob implements Queueable,Database.AllowsCallouts {
                          
       private String scrollId;
       private long totalTime;
       private integer pageNumber;

       public archiveSecondPageJob(String prevScrollId, integer pageNumber) {
           scrollId = prevScrollId;
           this.pageNumber = pageNumber;
       }

       public void execute(QueueableContext context) {
           system.debug('executing');
           doExecute();
       }
       private void doExecute() {
               OB_Archiver.ArchiverAccessorSearchResponse searchResponse = null;
               integer page = 1;
               long totalTime = 0;
  
                       while(scrollId != '-1')
                       {       
                           //avoid 12000 ms limit
                           if(totalTime > 90000){
                               system.debug('Creating new task');
      archiveSecondPageJob q = new archiveSecondPageJob(scrollId,this.pageNumber);
                               System.enqueueJob(q);
                               return;
                           }
                           //Get the next page using the scrollId     
                           Long startTime = DateTime.now().getTime(); 
                           OB_Archiver.ArchiverAccessorResponse nextPage = OB_Archiver.ArchiverAccessor.getNextPage(scrollId); 
                           Long stopTime = DateTime.now().getTime();
                           totalTime += stopTime - startTime;
                           if(nextPage.getStatusCode() == 200){     
                               this.pageNumber++;
                               OB_Archiver.ArchiverAccessorSearchResponse searchResponse2 =nextPage.getSearchResponseMap();      
                               scrollId = searchResponse2.scrollId;
                               system.debug('count: '+ searchResponse2.totalResultCount);    
                               system.debug('scrollid: '+ searchResponse2.scrollId); 
                               system.debug('list size: '+ searchResponse2.records.size());  
                               system.debug('body: '+ searchResponse2.records);  
                               system.debug('page: '+ pageNumber);  
                               system.debug('time: '+ totalTime); 
                           }       
                           else{           
                               system.debug('response is != 200: '+ nextPage.getStatusCode());           
                               system.debug('body: '+ nextPage.getBody());
                               return;
                           }           
                       }
       }
}
            

The ArchiveSearch class performs a search on the Case object using specific filters, retrieves the results, and logs details such as the total count and scroll ID. If the search returns a valid scroll ID (indicating more pages of results), it enqueues the archiveSecondPageJob to process the next page asynchronously. This class essentially initiates the search and hands off further processing to the previously defined archiveSecondPageJob.

The format for the initial search call.

public class ArchiveSearch {
                    
    public void doSearch(){
        
                string objectName = 'Case';
        list<OB_Archiver.SearchFilter> request = new list<OB_Archiver.SearchFilter>();
    
        OB_Archiver.SearchFilter filter1 = new OB_Archiver.SearchFilter('Priority', 'High');
        request.add(filter1);
        
        list<string> fields = new list<string>{'CaseNumber', 'Id'};
        
        OB_Archiver.ArchiverAccessorResponse res = OB_Archiver.ArchiverAccessor.performArchiverGlobalSearch(objectName, request,fields);   
        if(res.getStatusCode() == 200){
           system.debug('code: ' + res.getStatuscode());
           OB_Archiver.ArchiverAccessorSearchResponse searchResponse = res.getSearchResponseMap();
           system.debug('count: '+ searchResponse.totalResultCount);
           system.debug('scrollid: '+ searchResponse.scrollId);
           system.debug('list size: '+ searchResponse.records.size());
           string scrollId = searchResponse.scrollId;
           integer page = 1;   
           if(scrollId != '-1'){
               system.debug('calling next page');
               integer pageNumber = 2;
               archiveSecondPageJob q = new archiveSecondPageJob(scrollId, pageNumber);
               System.enqueueJob(q);
   }}}}

For more information, see Salesforce Apex Developer Guide.