Home / lwc / lwc-quick-actions
lwc-quick-actions
Use when building a Lightning Web Component that runs from a record page quick-action button — either a screen action that renders UI in a modal or a headless action that invokes logic with no UI. Triggers: 'lwc quick action on record page', 'headless quick action no ui', 'closeactionscreenevent not working', 'how to pass recordid into quick action lwc'. NOT for Flow screen components — use lwc/lwc-in-flow-screens. NOT for global actions without a record context — use admin/global-actions-and-quick-actions.
Open the skill folder on GitHub
LWC Quick Actions
Use this skill when a record-page button needs to launch a Lightning Web Component — either as a modal (screen action) or as silent on-click logic (headless action). The right choice depends on whether the user has to see or confirm anything, not on what is easier to code.
Before Starting
Gather this context first:
- What object does this action run on, and what fields does it read or write?
- Does the user need to see or confirm anything, or is this a one-tap operation?
- After success, should the page toast, refresh the record, navigate, or stay where it is?
- Is the called Apex
@AuraEnabled (with sharing)and does it still enforce CRUD/FLS? - Is this invoked from a record page, a list view, or a related list — the injected inputs differ.
Core Concepts
Quick-action LWCs are regular components with a specific meta.xml shape and a specific lifecycle contract. Two modes exist: screen actions (render UI in a modal) and headless actions (no UI, a single invoke() method).
Declaration In .js-meta.xml
The bundle must declare the lightning__RecordAction target and, inside <targetConfigs>, set actionType="ScreenAction" for a modal or actionType="Action" for headless. Without that target config the component will not appear in the quick-action picker. The target also controls which inputs the platform injects — lightning__RecordAction is what causes recordId to flow in.
recordId Is Auto-Injected As @api
When the target is lightning__RecordAction, Salesforce injects recordId into the component's @api recordId property before connectedCallback. If the property is not declared with @api, the value never lands. objectApiName is not auto-injected on quick actions — if you need it, pass it through the meta.xml config or look it up via getRecord.
Screen Actions Close Through An Event
A screen action renders its template inside the quick-action modal. To dismiss the modal, the component imports CloseActionScreenEvent from lightning/actions and dispatches it: this.dispatchEvent(new CloseActionScreenEvent()). This event does not save anything — it is purely a close signal, so the save must complete before the dispatch, not after.
Headless Actions Implement invoke()
A headless action has no template. Instead it exposes an @api invoke() method that Salesforce calls when the button is clicked. The platform dismisses the action when invoke() returns (or its returned promise resolves). Toasts fired via ShowToastEvent from within invoke() render on the host page — that is the supported way to give feedback from a headless action.
Post-Action UX Must Be Deliberate
Screen actions do not automatically refresh the record. For an in-page refresh use getRecordNotifyChange from lightning/uiRecordApi or refreshApex on a wire. For a hard navigation use NavigationMixin.Navigate with standard__recordPage — do not reach for window.location.reload(), which breaks SPA navigation and destroys in-flight state.
Security Still Applies
@AuraEnabled does not bypass CRUD/FLS. Use WITH USER_MODE in SOQL/DML or Security.stripInaccessible in the Apex called by the action. The quick-action surface does not change the permission model — the user runs the action as themselves.
Common Patterns
Screen Action — Confirm, Edit, Save, Close
When to use: The user needs to see current values, edit something, or read a warning before the save commits.
How it works: Declare lightning__RecordAction with actionType="ScreenAction". Render UI bound to @api recordId. On submit, call imperative Apex or a lightning-record-edit-form, await the result, show a toast, and then dispatch CloseActionScreenEvent. Optionally call getRecordNotifyChange([{ recordId }]) so the record page reflects the change.
Why not the alternative: A headless action cannot show the form. A Flow launched as a quick action is appropriate when the UI is declarative and low-code ownership is a goal, but it carries Flow-runtime overhead and a different debugging story.
Headless Action — One-Tap Status Change
When to use: A single-field update, a confirmation toast, no form needed. Examples: "Mark as Read", "Approve", "Re-send Welcome Email".
How it works: Declare actionType="Action". Omit the template. Implement async invoke() that calls the Apex method, awaits the result, fires ShowToastEvent, and returns. Do not dispatch CloseActionScreenEvent — there is no modal to close.
Why not the alternative: A screen action with an auto-submitting template flickers a modal for no reason. A Flow would be overkill for a single-field update.
Screen Action With Post-Save Navigation
When to use: The action creates a related record and the user should land on it.
How it works: After the Apex save returns the new Id, call NavigationMixin.Navigate({ type: 'standard__recordPage', attributes: { recordId: newId, objectApiName: 'Case', actionName: 'view' } }), then dispatch CloseActionScreenEvent. The modal closes as the page transitions.
Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| Simple confirm plus single-field update | Headless action with invoke() | No UI needed; platform closes on return |
| Multi-step UI, conditional branching, or form | Screen action | Modal template is the only place to render the form |
| Declarative owners, screen with validation, low code | Flow launched as a quick action | Flow Builder owns the UX without custom JS |
| Action needs to land the user on a new related record | Screen action with NavigationMixin before close | Navigation must fire before the modal is dispatched closed |
| Bulk action across a list view | List-view action wrapper (not a single-record action) | recordId is not injected on list actions — design for a list of ids |
Recommended Workflow
- Confirm the UX — modal or no modal? If no UI is needed, pick headless.
- Write
.js-meta.xmlwithlightning__RecordActionand the correctactionType. Add<targetConfig>entries only for targets you actually support. - Declare
@api recordId(and@api invokeif headless). Read only the fields you need viagetRecordor Apex. - For screen actions, do the save first, then fire the toast, then dispatch
CloseActionScreenEvent. For headless, resolve frominvoke()after the toast. - Decide the refresh strategy —
getRecordNotifyChange,refreshApex, orNavigationMixin. Never usewindow.location.reload(). - Run
python3 scripts/check_lwc_quick_actions.pyover the bundle to catch headless-with-template, missing@api recordId, pre-save close, andwindow.locationusage. - Add an Apex test for the called method and a Jest test for the JS — verify the close event fires only after the save resolves.
Review Checklist
- [ ]
.js-meta.xmldeclareslightning__RecordActionand a matching<targetConfig>with the correctactionType. - [ ]
@api recordIdis declared in the JS — the component actually receives the record context. - [ ] Screen actions save first and dispatch
CloseActionScreenEventonly after the save resolves. - [ ] Headless actions have no
<template>and the action closes by returning frominvoke(). - [ ] Post-save refresh uses
getRecordNotifyChange,refreshApex, orNavigationMixin— neverwindow.location.reload(). - [ ] The Apex called by the action enforces CRUD/FLS (
WITH USER_MODEorstripInaccessible). - [ ] Toasts use
ShowToastEventand fire before the close/return.
Salesforce-Specific Gotchas
CloseActionScreenEventdoes not save — it only dismisses the modal. Dispatching it before the Apex save resolves throws away the save because the component is destroyed mid-promise.- Headless actions render nothing — any
<template>content in anactionType="Action"bundle is ignored; if you need a confirm dialog, useLightningConfirmfrom insideinvoke()and still resolve the promise to close. recordIdis only auto-injected underlightning__RecordAction— switching the target (e.g., tolightning__AppPage) silently stops the injection andrecordIdbecomesundefined.- Modal size is not freely configurable — quick-action modals follow the platform's sizing rules; a dedicated wide UI usually belongs in
lwc-lightning-modalor a full page instead. - List-view actions do not receive
recordId— they receive a list of record ids via the action target config. Reusing a single-record component on a list surface breaks silently.
Output Artifacts
| Artifact | Description |
|---|---|
| Quick-action LWC bundle | JS, HTML (if screen), and .js-meta.xml with the correct target and actionType |
| Post-action UX plan | Notes on toast copy, refresh strategy, and navigation target |
| Checker report | Findings on template-in-headless, missing @api recordId, premature close, and window.location usage |
Related Skills
lwc/lwc-lightning-modal— use when the UI needs to be a modal invoked from outside a quick action, or when the quick-action modal sizing is insufficient.lwc/lwc-imperative-apex— use for the Apex-call patterns inside the action, including error handling and@AuraEnabled(cacheable=true)constraints.lwc/navigation-and-routing— use when deciding betweenNavigationMixin,getRecordNotifyChange, andrefreshApexafter the action completes.lwc/lwc-in-flow-screens— use when the component belongs inside a Flow screen rather than as a record-page quick action.