I have an LWC where I run a query to clean up/delete records. The LWC works fine for showing 2000 rows at a time in a datagrid (although 2000 is a bit of a big grid so I set 2000 as the max but default to 200). The user can select the rows to be deleted - anything > 200 rows is sent to a batch job to do the delete, so that's all working fine.

Added to this is a an Export button, for which I'm using SheetJS - all working nicely. The problem is when there are a lot more records than 2000. One object has 110k rows to delete (most objects have way less).

We have daily backups of all data, but I need to create an export of everything that's about to be deleted prior to deletion, so would like a mechanism to export them. The user doing the delete has no access to the backups either and although I can extract it for them, it will be after the delete event, so no good for checking prior to deletion.

This cleanup process is being done by an ops admin person, not a dev user, so fiddling about with dataloader or other dev tools and getting all the filters for what to delete correct is not an option, especially since the process has to be repeatable for different clients. Plus it takes a long time and is more prone to error. Hence having created the LWC to select the client for whom to delete, and components for each object and displaying that client's data to delete only. All the user has to do is click the Export button and then click the Delete Selected or Delete All button, once they're confident the correct data will be deleted.

  1. Delete All is fine for most of the objects since they're less than 50k - it runs the delete in batches of the default 200. What would I need to do for the batch to complete the 110k rows? Is it possible in one button click? I know it can be done by deleting the max 50k and waiting for the batch job to complete, then doing the next 50k etc, but it can take a while to complete as triggers run on the objects that complicate things, so it would be nice to set off a job for all and just wait to get an email to say it's completed (an email is currently sent in the finish).

  2. How can I achieve the Export All, whether it's 2000 or 110k? Is this possible from the LWC using SheetJS? Or does this need a batch process - and if so, what do I use (since SheetJS is probably not the way to go then)?

1 Answer 1


The delete all mode can just use whatever query the LWC used originally to get all the data, up to 50,000,000 rows.

public Iterable<sObject> start(Database.BatchableContext context) {
  return Database.getQueryLocator(queryString);

For the delete selected mode, you have a maximum payload of 4 MB of data from the client to the server, so you'll be limited to something like 160,000 selections without compression of some kind. You'll just have to call the server multiple times. Each full list of ID values can be thrown directly into a batchable class.

public class DeleteSelectedRecords implements Database.Batchable<Id> {
  Id[] recordIds;
  public DeleteSelectedRecords(Id[] recordIds) {
    this.recordIds = recordIds;
  public Id[] start(Database.BatchableContext context) {
    return recordIds;
  public void execute(Database.BatchableContext context, Id[] scope) {
    Set<String> objectTypes = new Set<String>();
    for(Id recordId: scope) {
    for(String objectType: objectTypes) {
      delete Database.query('select id from '+objectType+' where id in :scope');
  public void finish(Database.BatchableContext context) {
    // send an email here

For the export all mode, you can use client-side pagination to build a list of records.

public static sObject[] getRecords(
  Id latestRecordId,
  String queryString
) {
  // Make 50000 smaller if you're hitting limits
  return Database.query(
    queryString + ' WHERE Id > :latestRecordId WITH SECURITY_ENFORCED '+
    'ORDER BY Id ASC LIMIT 50000'
async exportAll() {
  let latestRecordId;
  let queryString = 'SELECT Name FROM Account';
  let records = [];
  let result;
  do {
    result = await getRecords({ latestRecordId, queryString });
    records = records.concat(result);
  } while(result.length);
  // build export file here

The theoretical limit to how many records you can download will be constrained only by the device's memory limits.

As far as using SheetJS, that may be overkill. It'd probably be more efficient to just emit a CSV directly. Remember: a script that runs too long without relinquishing control will cause the browser to say the page is unresponsive and ask the user if it should close the page. To avoid this, make sure you're using asynchronous breaks (e.g. the exportAll example above is taking a break with await).

Also, service/web workers, which some frameworks use, are not compatible with LWC, because they are a security concern. This means that the maximum size of the resulting file with SheetJS may be constrained by processing time.

You must log in to answer this question.

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