Why This Is Useful
You want a button on a list view that runs some custom logic against the selected records. Mass update, mass delete, kick off a process, whatever.
This isn’t hard exactly, but Salesforce gives you no obvious path and there is genuinely no good documentation on it. When I first worked this out it took a lot of experimentation, so let’s save you that.
Code’s on GitHub.
Your Three Options, Two Of Which Are Bad
Go to Object Manager → Buttons, Links, and Actions → New Button or Link and pick List Button. Now you get three content sources:
1. OnClick JavaScript – does not work in Lightning Experience at all. Dead end.
2. Visualforce Page – works, and I’d rather never write Visualforce again if I can avoid it.
3. URL – this is the one, and it’s what saves the day.
So we’re going with URL, pointed at an Aura wrapper that hosts our Lightning Web Component. Yes, an Aura wrapper. Stay with me.
Why You Need Aura In The Middle
Because you cannot navigate directly to a Lightning Web Component by URL. LWCs have no addressable URL of their own.
Aura components do. So you build a thin Aura component that does nothing except host your LWC and hand it the selected record Ids.
It’s about fifteen lines of Aura you write once and never think about again. Annoying, but genuinely the cleanest route.
Step 1: The Aura Wrapper
<aura:component implements="force:lightningQuickActionWithoutHeader,force:hasRecordId,lightning:isUrlAddressable">
<aura:attribute name="recordIds" type="String" />
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<c:listViewHandler record-ids="{!v.recordIds}" />
</aura:component>({
doInit : function(component, event, helper) {
var pageRef = component.get("v.pageReference");
if (pageRef && pageRef.state && pageRef.state.c__recordIds) {
component.set("v.recordIds", pageRef.state.c__recordIds);
}
}
})lightning:isUrlAddressable is the critical bit. That’s what makes the component reachable by URL and gives you access to pageReference so you can read the parameters.
Note the c__ prefix on the state parameter. Custom URL parameters need it, and forgetting it is a classic hour-of-your-life mistake.
Step 2: The Lightning Web Component
import { LightningElement, api } from 'lwc';
import processRecords from '@salesforce/apex/ListViewController.processRecords';
export default class ListViewHandler extends LightningElement {
@api recordIds;
get idList() {
return this.recordIds ? this.recordIds.split(',') : [];
}
async handleProcess() {
try {
await processRecords({ recordIds: this.idList });
} catch (error) {
console.error('Processing failed ::: ', error);
}
}
}The Ids arrive as a comma separated string, not an array, so you split them. That catches everybody once.
Step 3: The Button URL
Back in your List Button, set Content Source to URL and use:
/lightning/cmp/c__listViewWrapper?c__recordIds={!GETRECORDIDS($ObjectType.Contact)}GETRECORDIDS() is the merge field that hands you whatever the user ticked. Swap Contact for your object.
Then add the button to your list view: Object Manager → Search Layouts for Lightning Experience → List View → Edit, and move it into Selected Buttons.
That last step is the one people forget. The button exists, it’s configured perfectly, and it doesn’t appear anywhere because it was never added to the layout.
The Apex Side
public with sharing class ListViewController
{
@AuraEnabled
public static void processRecords(List<Id> recordIds)
{
if (recordIds == null || recordIds.isEmpty())
{
throw new AuraHandledException('No records were selected');
}
List<Contact> contacts = [SELECT Id, Description
FROM Contact
WHERE Id IN :recordIds];
for (Contact cont : contacts)
{
cont.Description = 'Processed from list view';
}
update contacts;
}
}Standard bulkified Apex, exactly as covered in episode 30. One query, one DML.
Use AuraHandledException for anything you want the user to see. A raw exception surfaces as an unhelpful generic error.
Watch The Volume
List views let users select up to 200 records at a time, which is fine synchronously. But a URL has a length limit, and 200 Ids of 18 characters each plus commas gets close enough to be worth knowing about.
If you’re processing anything substantial, take the Ids and hand them straight to a Queueable job rather than doing the work in the button click. Users get an instant response and you get double the governor limits.
Get Coding With The Force Merch!!
We now have a redbubble store setup so you can buy cool Coding With The Force merchandise! Please check it out! Every purchase goes to supporting the blog and YouTube channel.
Get Shirts Here!
Get Cups, Artwork, Coffee Cups, Bags, Masks and more here!
Check Out More Coding With The Force Stuff!
If you liked this post make sure to follow us on all our social media outlets to stay as up to date as possible with everything!
Youtube
Patreon
Github
Facebook
Twitter
Instagram
Salesforce Development Books I Recommend
Advanced Apex Programming
Salesforce Lightning Platform Enterprise Architecture
Mastering Salesforce DevOps
Good Non-SF Specific Development Books:
Clean Code
Clean Architecture