lwc-lds-writes
Use when an LWC must create, update, or delete records via Lightning Data Service (`lightning/uiRecordApi` — `createRecord`, `updateRecord`, `deleteRecord`) or via `lightning-record-edit-form` / `lightning-record-form`. Triggers: 'updateRecord cache not refreshing', 'createRecord returns DUPLICATES_DETECTED', 'updateRecord input shape with field references', 'lightning-record-edit-form submit not committing', 'should I use LDS write or imperative Apex DML'. NOT for read-only wires (use lwc/wire-service-patterns), NOT for refreshing wired data after a write (use lwc/lwc-wire-refresh-patterns), and NOT for Apex DML from LWC (use apex/dml-patterns).
Best use case
lwc-lds-writes is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use when an LWC must create, update, or delete records via Lightning Data Service (`lightning/uiRecordApi` — `createRecord`, `updateRecord`, `deleteRecord`) or via `lightning-record-edit-form` / `lightning-record-form`. Triggers: 'updateRecord cache not refreshing', 'createRecord returns DUPLICATES_DETECTED', 'updateRecord input shape with field references', 'lightning-record-edit-form submit not committing', 'should I use LDS write or imperative Apex DML'. NOT for read-only wires (use lwc/wire-service-patterns), NOT for refreshing wired data after a write (use lwc/lwc-wire-refresh-patterns), and NOT for Apex DML from LWC (use apex/dml-patterns).
Teams using lwc-lds-writes should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/lwc-lds-writes/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How lwc-lds-writes Compares
| Feature / Agent | lwc-lds-writes | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Use when an LWC must create, update, or delete records via Lightning Data Service (`lightning/uiRecordApi` — `createRecord`, `updateRecord`, `deleteRecord`) or via `lightning-record-edit-form` / `lightning-record-form`. Triggers: 'updateRecord cache not refreshing', 'createRecord returns DUPLICATES_DETECTED', 'updateRecord input shape with field references', 'lightning-record-edit-form submit not committing', 'should I use LDS write or imperative Apex DML'. NOT for read-only wires (use lwc/wire-service-patterns), NOT for refreshing wired data after a write (use lwc/lwc-wire-refresh-patterns), and NOT for Apex DML from LWC (use apex/dml-patterns).
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# LWC Lightning Data Service Writes
Activate when an LWC needs to **mutate** record data — create, update, or delete — and the right answer is the Lightning Data Service write API (`lightning/uiRecordApi` or the form base components) rather than imperative Apex. The skill produces a correctly shaped `recordInput`, an explicit cache-refresh strategy, and error handling for the four UI API error shapes that surface to the user. It complements `lwc/wire-service-patterns` (read path) and `lwc/lwc-wire-refresh-patterns` (refresh path); writes are deliberately separated because the input format, error shape, and cache semantics differ from reads.
---
## Before Starting
Gather this context before writing any LDS write code:
- **Operation type and cardinality** — single record from a user gesture (LDS is the right fit), or a batch/transaction (likely Apex DML). LDS writes are one record per call.
- **Record-edit form vs imperative call** — `lightning-record-edit-form` and `lightning-record-form` are LDS in disguise; they cover 80% of single-record edit UX without writing a JS handler. Imperative `updateRecord` is for when the form layout is wrong for the use case (multi-step wizard, conditional fields, custom save button labelling).
- **Whether the same component reads the record via wired `getRecord`** — if yes, LDS will refresh that wire automatically after a write to the same `recordId`. If a *different* component reads it, you may need `notifyRecordUpdateAvailable`.
- **Sharing / CRUD / FLS posture** — UI API enforces sharing, CRUD, FLS, and validation rules in the running user's context; Apex `without sharing` does not. Choosing LDS is also choosing the user's full access stack.
---
## Core Concepts
### `lightning/uiRecordApi` is the JS write surface
`createRecord({ apiName, fields })`, `updateRecord({ fields: { Id, ... } })`, and `deleteRecord(recordId)` are imported from `lightning/uiRecordApi` and return Promises. They call the Salesforce UI API (`/services/data/vXX.X/ui-api/records/...`), which enforces sharing, CRUD, FLS, validation rules, duplicate rules, and triggers exactly as if the user clicked Save in standard UI.
### The `fields` object shape uses API names, not FieldId imports
For writes, `fields` is a plain object whose keys are **string field API names** (not `@salesforce/schema/Account.Name` imports). The exception is `apiName`, which can come from a schema import:
```javascript
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import NAME_FIELD from '@salesforce/schema/Account.Name';
import { createRecord } from 'lightning/uiRecordApi';
const recordInput = {
apiName: ACCOUNT_OBJECT.objectApiName, // schema import → ".objectApiName"
fields: {
[NAME_FIELD.fieldApiName]: 'Acme', // schema import → ".fieldApiName"
Industry: 'Technology' // or just a string literal
}
};
const account = await createRecord(recordInput);
```
For `updateRecord`, the `fields` object **must include `Id`** at the top level inside `fields`:
```javascript
await updateRecord({ fields: { Id: this.recordId, Industry: 'Manufacturing' } });
```
`recordInput` for create vs update is shaped differently (`apiName` only on create), and getting this wrong throws `INVALID_FIELD_FOR_INSERT_UPDATE`.
### LDS auto-refreshes wired `getRecord` for the same recordId in the same component
After a successful `updateRecord` or `createRecord`, any `@wire(getRecord, { recordId: '$recordId', fields: [...] })` in the *same* component instance is re-evaluated automatically. You do not call `refreshApex` — that is for Apex-backed wires. Cross-component refresh requires `notifyRecordUpdateAvailable([{ recordId }])` to tell LDS that records changed outside its visibility (after Apex DML, for example).
### UI API errors come back as a structured object, not a thrown string
The Promise rejects with `{ body: { output: { errors: [...], fieldErrors: {...} } }, statusText, status }`. Field-level errors are keyed by field API name. Catch the rejection and surface field errors next to the right input, surface object-level errors as a toast, and treat `INSUFFICIENT_ACCESS_OR_READONLY` differently from `FIELD_VALIDATION_EXCEPTION`.
---
## Common Patterns
### Pattern: `lightning-record-edit-form` for the standard single-record edit
**When to use:** a single record, the layout you need is close to a page layout, and you want LDS sharing/CRUD/FLS enforcement plus inline field-level errors for free.
**How it works:** Wrap `lightning-input-field` elements inside `<lightning-record-edit-form record-id={recordId} object-api-name="Account" onsuccess={handleSuccess} onerror={handleError}>`. Submit with `<lightning-button type="submit">` or programmatically via `this.template.querySelector('lightning-record-edit-form').submit()`. No JS write call needed.
**Why not the alternative:** Reaching for `updateRecord` when a form would do means you also rebuild field rendering, validation, and CRUD/FLS toggling.
### Pattern: imperative `updateRecord` for a custom save button or conditional field set
**When to use:** the save UX is non-standard (multi-step, computed fields, conditional save validation that the form can't express), but the write is still a single record.
**How it works:**
```javascript
import { updateRecord } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
async handleSave() {
try {
await updateRecord({ fields: { Id: this.recordId, ...this.dirtyFields } });
this.dispatchEvent(new ShowToastEvent({ title: 'Saved', variant: 'success' }));
} catch (err) {
const fieldErrs = err?.body?.output?.fieldErrors ?? {};
this.fieldErrors = fieldErrs;
const objectErrs = err?.body?.output?.errors?.map(e => e.message).join('; ');
if (objectErrs) {
this.dispatchEvent(new ShowToastEvent({ title: 'Save failed', message: objectErrs, variant: 'error' }));
}
}
}
```
**Why not the alternative:** Calling Apex DML loses sharing/FLS enforcement unless the controller does it manually, and bypasses validation rules' UI behavior (record-page error placement).
### Pattern: cross-component refresh with `notifyRecordUpdateAvailable`
**When to use:** a write happens via Apex DML (or in another component) and a different LWC on the same page wires the same record via `getRecord`.
**How it works:** After the Apex call resolves, call `notifyRecordUpdateAvailable([{ recordId }])`. LDS re-fetches the record on its next provisioning cycle, and any `@wire(getRecord)` for that recordId across the page receives the new value.
**Why not the alternative:** Without it, the wired component holds the cached pre-write copy until the user navigates away.
### Pattern: `deleteRecord` with optimistic UI removal
**When to use:** a list view component where the user clicks "Remove" on a row.
**How it works:** Optimistically splice the row out of the rendered array, call `await deleteRecord(rowId)`, and on rejection re-insert the row plus toast. If the list itself is wired, call `refreshApex` on that wire after success.
---
## Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| Single-record edit, standard layout | `lightning-record-edit-form` | Free CRUD/FLS, validation rendering, no JS write |
| Single-record edit, custom UX | imperative `updateRecord` | Keeps LDS enforcement and cache benefits |
| Multi-record transaction (atomic) | Apex DML via `@AuraEnabled` | LDS writes are per-record; no transaction boundary |
| Mutation must run as `without sharing` (admin tool) | Apex DML | LDS always runs in user context |
| Need to suppress validation rules / triggers | Not possible from LDS | Use a different architecture; no LDS bypass exists |
| Update from another component, must refresh this one | `notifyRecordUpdateAvailable` after the write | LDS auto-refresh only inside the same component instance |
| Bulk insert (e.g., 50 contacts at once) | Apex DML | LDS writes are one record per call; 50 calls is a CPU/UX problem |
For "should this be Flow / Apex / LDS at all?", read `standards/decision-trees/automation-selection.md` first.
---
## Recommended Workflow
1. Confirm the use case is single-record and user-initiated. Multi-record or system writes belong in Apex.
2. Decide form vs imperative: if a `lightning-record-edit-form` covers the layout, default to it.
3. For imperative writes, build `recordInput` with `apiName` (only on create) plus a `fields` object. For updates, `Id` goes inside `fields`, not at the top level.
4. Wire the post-write UX: success toast/navigation, field error mapping, object-error fallback. Map `err.body.output.fieldErrors` to your input components.
5. Decide refresh strategy: same-component wires refresh automatically; cross-component wires need `notifyRecordUpdateAvailable`; Apex-backed wires need `refreshApex`.
6. Verify CRUD/FLS in a non-admin profile sandbox before shipping — UI API rejects writes the user lacks access to, but with a non-obvious `INSUFFICIENT_ACCESS_OR_READONLY` error rather than silent no-op.
7. Add Jest tests that mock `createRecord`/`updateRecord`/`deleteRecord` from `lightning/uiRecordApi` and assert the `recordInput` shape your code produces.
---
## Review Checklist
- [ ] `apiName` only present on `createRecord` calls, never on `updateRecord`
- [ ] `Id` for updates is inside `fields`, not at the top level of `recordInput`
- [ ] `fields` keys are string API names (or `.fieldApiName` from a schema import)
- [ ] Error handler reads `err.body.output.fieldErrors` and `err.body.output.errors`
- [ ] Cross-component refresh uses `notifyRecordUpdateAvailable`, not a custom event
- [ ] Apex-backed wired data refreshed via `refreshApex`, not LDS APIs
- [ ] Jest test mocks `lightning/uiRecordApi` and asserts the call shape
- [ ] Confirmed in a non-admin profile that CRUD/FLS denial surfaces a meaningful error to the user
---
## Salesforce-Specific Gotchas
1. **`Id` placement differs from REST API** — `updateRecord({ fields: { Id, ... } })`. Putting `Id` at the top level of `recordInput` (mirroring REST `PATCH /sobjects/Account/<id>`) throws `INVALID_FIELD_FOR_INSERT_UPDATE`.
2. **Schema imports for fields use `.fieldApiName` for writes** — `import NAME from '@salesforce/schema/Account.Name'` exposes `NAME.fieldApiName` (a string). Using the import object itself as a key emits `[object Object]` and fails at the server.
3. **LDS writes do not bulk** — there is no array form. Looping `await updateRecord(...)` for 200 records will trigger 200 UI API calls and a multi-second UX.
4. **Validation rule errors are returned at field-level when possible** — but rules with `null` `errorDisplayField` come back as object-level errors only. Code defensively for both shapes.
5. **`INSUFFICIENT_ACCESS_OR_READONLY` covers FLS denial, CRUD denial, sharing denial, and recordtype denial** — the error shape doesn't distinguish them. Don't claim "you don't own this record" in the toast unless you've separately verified.
6. **Same-record auto-refresh only happens for `getRecord` wires in the same component** — it does NOT cascade to `getRecords` (note the plural), GraphQL wires, or Apex-backed wires. Each needs its own refresh path.
7. **`deleteRecord` returns `undefined` on success, not the deleted record** — code that destructures the return throws `TypeError`.
8. **A read-only field included in `fields` causes the entire write to fail** — even if you're not changing it, including `LastModifiedDate` or a formula field in `fields` produces `INVALID_FIELD_FOR_INSERT_UPDATE`. Filter dirty-only.
---
## Output Artifacts
| Artifact | Description |
|---|---|
| LWC write handler | JS handler with shaped `recordInput`, structured error mapping, and refresh call |
| Field-error binding | Mapping from `err.body.output.fieldErrors` keys to `lightning-input-field` / form inputs |
| Cross-component refresh wiring | `notifyRecordUpdateAvailable` call site (often after an Apex DML resolves) |
| Jest spec | Mocks `lightning/uiRecordApi`; asserts `recordInput` shape and error-path UX |
---
## Related Skills
- `lwc/wire-service-patterns` — for the read path and reactive parameters; LDS writes complement, not replace, this skill
- `lwc/lwc-wire-refresh-patterns` — for the post-write refresh path (`refreshApex`, `RefreshView`, `getRecordNotifyChange`/`notifyRecordUpdateAvailable`)
- `lwc/lwc-base-component-recipes` — for `lightning-record-edit-form` and `lightning-record-form` configuration patterns
- `apex/dml-patterns` — for the Apex DML alternative when LDS writes are not the right fit (multi-record, system context, transaction boundary)
- `standards/decision-trees/automation-selection.md` — for the "Flow vs LWC vs Apex" decision before reaching for any of the aboveRelated Skills
xss-and-injection-prevention
Use when writing or reviewing Visualforce pages, Apex controllers, or LWC components that output user-supplied data, build dynamic queries, or construct HTTP responses. Triggers: 'XSS in Visualforce', 'SOQL injection vulnerability', 'how to encode output in Apex', 'JSENCODE Visualforce', 'open redirect prevention'. NOT for Apex CRUD/FLS enforcement (use soql-security or apex-crud-and-fls), NOT for Shield encryption (use shield-encryption-key-management), NOT for AppExchange security review process (use secure-coding-review-checklist).
visualforce-security-and-modernization
Use when hardening or modernizing legacy Visualforce pages — covers the platform CSRF token model and when disabling it is a security regression, view state encryption guarantees and the 170 KB ceiling, FLS/CRUD enforcement gaps on `<apex:outputField>` and on getters that return sObjects, `<apex:includeScript>` interaction with the org Content Security Policy, hosting LWC inside a VF page via `lightning:container` / `lightning-out`, and the retire-vs-harden-vs-leave-alone decision for an inventory of legacy pages. Triggers: 'should I rewrite this Visualforce page in LWC', 'CSRF protection disabled on Visualforce page is that safe', 'community user sees a field they should not on a Visualforce page', 'view state encryption is that enough for sensitive data', 'how do I host an LWC inside a Visualforce page', 'apex:dynamicComponent and apex:actionFunction safe to keep'. NOT for greenfield Visualforce architecture (use apex/visualforce-fundamentals — controller types, view state pattern selection, PDF rendering); NOT for Visualforce email template authoring (use apex/visualforce-email-templates if/when that skill is authored); NOT for general Apex security review across triggers and async (use apex/soql-security and security/secure-coding-review-checklist).
transaction-security-policies
Transaction Security policy creation and configuration: condition builder, enhanced policies, enforcement actions (block, MFA, notification, end session), real-time monitoring mode, and policy troubleshooting. NOT for Event Monitoring log analysis or Shield Event Monitoring setup (use event-monitoring). NOT for Apex testing or debug-log analysis.
sso-saml-troubleshooting
Diagnosing broken SAML SSO into Salesforce — IdP-initiated vs SP-initiated flows, signing-certificate validity / expiry, NameID format mismatches, RelayState handling, audience / entityId / issuer mismatches, clock skew, the SAML Assertion Validator in Setup, the Login History debug log, and the My Domain prerequisite for SSO. Covers the standard diagnostic loop: read the SAML response, identify which check failed, fix at the IdP or SP. NOT for OAuth / OpenID Connect SSO (see security/oauth-openid-troubleshooting), NOT for setting up SSO from scratch (see security/sso-saml-setup).
shield-kms-byok-setup
Configure Shield Platform Encryption with customer-supplied (BYOK) or customer-held (Cache-Only Key Service) tenant secrets, rotate them, and recover. NOT for Classic Encryption or field masking.
shield-event-log-retention-strategy
Use when designing Salesforce Shield Event Monitoring retention, SIEM routing, and storage-tier strategy — which event types to keep, for how long, where, and how to answer audit queries across hot/warm/cold tiers. Triggers: 'shield event log retention', 'route event monitoring to splunk', 'how long to keep login history', 'siem salesforce integration', 'event monitoring storage tier'. NOT for enabling Shield (see salesforce-shield-deployment).
session-management-and-timeout
Use this skill when configuring session timeout values, concurrent session limits, session IP locking, or logout behavior in Salesforce. Covers org-wide session settings, profile-level overrides, Connected App session policies, and Metadata API SecuritySettings deployment. NOT for OAuth token refresh flows, login IP ranges, or MFA/identity-provider configuration.
session-high-assurance-policies
Enforce step-up authentication for sensitive pages/objects using High Assurance session level and login flow policies. NOT for initial MFA enrollment UX.
service-account-credential-rotation
Use when designing credential rotation for integration users, connected apps, named credentials, and OAuth client secrets in Salesforce. Covers rotation cadence, zero-downtime handover, secret storage, and detection of stale credentials. Triggers: 'rotate integration user password', 'connected app secret rotation', 'named credential rotation', 'stale service account', 'zero downtime secret rotation'. NOT for end-user password policies.
security-incident-response
When to use: active or suspected Salesforce org compromise, unauthorized access investigation, attacker containment, forensic evidence collection from EventLogFile/LoginHistory, session revocation, OAuth token cleanup, eradication of attacker persistence, and post-incident recovery verification. Trigger keywords: org compromised, suspicious login, attacker access, session revocation, forensic investigation, breach response, event log forensics, login anomaly investigation, incident response runbook. Does NOT cover general security setup, permission set design, field-level security configuration, or proactive security hardening — those are separate skills. NOT for general security setup.
security-health-check
Use when running, interpreting, or acting on Salesforce Security Health Check results — reading the score, understanding risk categories, evaluating specific settings, creating or importing a custom baseline, querying the Tooling API programmatically, or planning remediation from findings. Triggers: 'security health check score', 'health check failing settings', 'custom baseline', 'remediate health check findings', 'fix risk'. NOT for org hardening implementation, permission model design, or broad baseline config beyond what Health Check directly measures.
secure-coding-review-checklist
Use this skill to audit Apex, Visualforce, LWC, and Aura code for Salesforce security review readiness — covering CRUD/FLS enforcement, SOQL injection, XSS, CSRF, and open redirects. NOT for network-level penetration testing, Shield Platform Encryption key management, or general org permission set design.