@wire(getRecord, ...) requires the fields property
You used `getRecord` from `lightning/uiRecordApi` without specifying which fields to fetch. LDS doesn't auto-fetch all fields like an old-school SQL `SELECT *`; you must list each field you want.
Also seen asgetRecord requires fields·@wire getRecord without fields·Required parameter fields was not provided·LDS without fields
A new LWC compiles and deploys, but at runtime the console fills with @wire(getRecord, ...) requires the fields property or Required parameter fields was not provided. The component looks right. The decorator looks right. The platform refuses to fetch the record because you forgot to list which fields you actually want.
What the platform is checking
Lightning Data Service (LDS) is the recommended way to read Salesforce records from LWC. The getRecord wire adapter from lightning/uiRecordApi requires two parameters: a recordId and either a fields list or a layoutTypes list. Without one of these, the wire has no idea what data to fetch, so it refuses to fire.
The distinction matters because LDS isn't a "give me everything" service. It's a cache-aware, change-aware data layer. It fetches exactly the fields you ask for, caches them, and notifies you when they change. Without a fields list, none of that machinery has a contract to honor.
The broken example
import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
export default class AccountSummary extends LightningElement {
@api recordId;
@wire(getRecord, { recordId: '$recordId' })
account;
}
The wire fails to fire. The console shows the required-parameter error.
The fix: name the fields you need
Import field references and list them:
import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/Account.Name';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
import REVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue';
const FIELDS = [NAME_FIELD, INDUSTRY_FIELD, REVENUE_FIELD];
export default class AccountSummary extends LightningElement {
@api recordId;
@wire(getRecord, { recordId: '$recordId', fields: FIELDS })
account;
}
Each field reference is imported from @salesforce/schema/ObjectName.FieldName. The compiler validates the imports at deploy time: an import for a field that doesn't exist refuses to deploy. This catches misspellings at compile time, before runtime.
The fields parameter accepts either a constant (like the FIELDS array above) or a property reference ('$myFieldsList') if the field set is dynamic.
The fixed example, end to end
import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/Account.Name';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
import REVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue';
const FIELDS = [NAME_FIELD, INDUSTRY_FIELD, REVENUE_FIELD];
export default class AccountSummary extends LightningElement {
@api recordId;
account;
error;
@wire(getRecord, { recordId: '$recordId', fields: FIELDS })
wiredAccount({ data, error }) {
if (data) {
this.account = {
name: data.fields.Name.value,
industry: data.fields.Industry.value,
annualRevenue: data.fields.AnnualRevenue.value,
};
this.error = undefined;
} else if (error) {
this.error = error;
this.account = undefined;
}
}
}
<template>
<template lwc:if={account}>
<p><strong>Name:</strong> {account.name}</p>
<p><strong>Industry:</strong> {account.industry}</p>
<p><strong>Revenue:</strong> {account.annualRevenue}</p>
</template>
<template lwc:elseif={error}>
<p class="slds-text-color_error">{error.body.message}</p>
</template>
</template>
The handler extracts the field values into a flat shape that's easier for the template to consume. The error path renders a useful message.
The layoutTypes alternative
Instead of naming every field, you can fetch fields based on a page layout:
@wire(getRecord, {
recordId: '$recordId',
layoutTypes: ['Compact'],
modes: ['View']
})
account;
layoutTypes values are 'Compact', 'Full', and custom names. modes filters to ['Create'], ['Edit'], ['View']. The combination tells LDS "fetch every field that this layout displays."
The trade-offs:
fields: precise, fast, deterministic. Best for production code with known requirements.layoutTypes: flexible, adapts to layout changes. Best for components that should display whatever the admin configured.
For most LWC code, the explicit fields list is the right choice.
Why LDS doesn't just fetch every field
LDS optimizes for performance. Fetching unused fields wastes bandwidth, increases cache size, and creates more change notifications than your component cares about. The platform enforces explicit field declarations to keep the data layer efficient.
The contract is: you tell LDS what you need, LDS guarantees that data is fresh and up-to-date. The "everything" pattern would defeat the optimization.
A common mistake: stringifying the field names
A pattern that looks right but fails:
@wire(getRecord, { recordId: '$recordId', fields: ['Account.Name', 'Account.Industry'] })
account;
The platform requires field tokens imported via @salesforce/schema/, not plain string literals. The string-literal version sometimes works at runtime in older API versions but isn't guaranteed. New code should always use the imported token form.
The reason: imported tokens are validated at compile time against the org's schema. Misspellings produce deploy errors. String literals aren't validated; misspellings produce runtime errors that take longer to diagnose.
Spanning fields (relationship fields)
To fetch a field on a related record, use dot notation in the schema import:
import OWNER_NAME from '@salesforce/schema/Account.Owner.Name';
The wire's data shape mirrors the dot notation:
data.fields.Owner.value.fields.Name.value
The deep nesting is awkward but the data is there. For complex graphs, build helper getters in the LWC's JS to flatten the access.
get ownerName() {
return this.account?.data?.fields?.Owner?.value?.fields?.Name?.value;
}
The optional chaining handles each undefined level on the way down.
Refresh semantics
LDS caches every record it has fetched. When the underlying record is updated (via Apex DML, the UI, another LWC, an integration), LDS invalidates its cache and re-fires the wire with fresh data. Your component re-renders automatically.
For changes that LDS can't detect (e.g., a field that's only updated by a back-end batch every night), use refreshApex(this.wiredAccount) to force a re-fetch:
import { refreshApex } from '@salesforce/apex';
// ...
async handleRefresh() {
await refreshApex(this.wiredAccount);
}
The refresh re-runs the wire and updates the component with the latest data.
When to use getRecord vs getRecordUi vs Apex
LDS offers several wire adapters. Choose based on what you need:
getRecord: fetch field data. The most common choice.getRecordUi: fetch field data plus layout metadata (labels, picklist values, layout structure). Heavier; use when you need both.getFieldValue/getFieldDisplayValue: helper functions to extract a single field value from a wired record.- Apex
@AuraEnabled(cacheable=true): arbitrary server logic with caching. Use when you need data shape LDS can't provide.
For a simple field display, getRecord is right. For dynamic forms or layouts, getRecordUi. For computed data, Apex.
Subtle bug: forgetting the @api decorator on recordId
If recordId isn't decorated with @api, the parent (the Lightning Page builder, a record context provider) can't pass the id. The wire never sees a non-null recordId, so it stays in waiting state and your data never arrives.
The fix is to decorate recordId with @api:
@api recordId;
Without this, the property exists but can't receive the value from the page context.
Test patterns
For Jest tests, the @salesforce/sfdx-lwc-jest toolkit includes wire mocking. You can simulate a wire response and assert that your component handles it correctly:
import { createElement } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import AccountSummary from 'c/accountSummary';
const mockRecord = require('./data/account.json');
describe('account-summary', () => {
it('renders fields when wire returns data', () => {
const element = createElement('c-account-summary', { is: AccountSummary });
element.recordId = '001xx000003DGb0';
document.body.appendChild(element);
getRecord.emit(mockRecord);
// Assert against the rendered output
});
});
The mock emits a stubbed record; the component renders as if LDS returned it. Tests run in deterministic time without needing a live Salesforce session.
Common related-error misreads
The error messages from the LDS layer sometimes blur into one another. Quick distinguishing notes:
@wire(getRecord, ...) requires the fields property: missing or emptyfields/layoutTypesparameter.LWC1503: @wire decorator's parameter must be a function: import is missing or the adapter isn't recognized.The record API returned an error: the recordId is invalid or the user lacks access.getRecordreturningnulldata: the wire is still loading, not an error.
Each has a specific fix. Don't conflate them.
Reading the wire response shape
The data object from getRecord has a specific shape:
{
apiName: 'Account',
id: '001xx000003DGb0',
fields: {
Name: { value: 'Acme Corp', displayValue: null },
Industry: { value: 'Technology', displayValue: 'Technology' },
AnnualRevenue: { value: 1000000, displayValue: '$1,000,000' }
},
recordTypeInfo: { ... }
}
value is the raw value. displayValue is a formatted version (for currency, picklist labels, etc.). Use displayValue for user-facing rendering when available.
For lookup fields, the value is the referenced record's id (or the spanned record's data structure, if you imported with dot notation).
Field-level security and LDS
LDS respects field-level security. If a user lacks read access to a field, the wire returns the record but omits that field from the response. Trying to read data.fields.RestrictedField returns undefined.
Components that depend on specific fields should handle FLS gracefully:
get hasRevenueAccess() {
return this.account?.data?.fields?.AnnualRevenue !== undefined;
}
The check distinguishes "no data yet" from "no access to this field." Render the field only when both conditions are true.
Performance: don't fetch unused fields
LDS efficiency depends on requesting only what you need. A component that imports 30 fields but only displays 3 wastes 90% of the cached data. The platform's change detection still notifies on every imported field; the component re-renders even when irrelevant fields change.
The discipline: import only what the template uses. Remove unused imports when refactoring.
A common refactor pattern
When a component grows to display many record fields, refactor:
- Extract field references to a shared constants module.
- The component imports the shared FIELDS array.
- Helper getters access individual fields via the shared keys.
This centralizes the field list so adding a new field is a single edit, and the component's JS body stays clean.
A note about list views and related records
getRecord fetches one record at a time. For multi-record fetches:
getRelatedListRecords: fetches records related to a parent (e.g., contacts under an account).getListUi: fetches a list view's records.getRecords(Spring 23+): fetches multiple records by id in one call.
Each has its own required-parameter checklist. getRecords for example requires both recordIds and fields. The same kind of "missing required parameter" error fires if you omit either.
Choose the right modes for create/edit
The modes parameter on getRecord and getRecordUi controls which form of the record's data you get:
View: read-only data formatted for display.Edit: data formatted for the record-edit form.Create: data formatted for new-record creation (no record-specific data, just metadata).
For a component that toggles between view and edit modes, choose View initially and re-fetch with Edit when the user clicks Edit. Or fetch both modes and switch the rendering. Either pattern works; the choice depends on UX.
When LDS is not enough
For data that LDS can't provide (computed aggregates, cross-object joins not via Salesforce relationships, external system data), use an Apex @AuraEnabled(cacheable=true) method instead. The Apex method can compute anything and the LWC wires to it the same way as to LDS:
import getDashboard from '@salesforce/apex/DashboardService.fetchSummary';
@wire(getDashboard, { accountId: '$recordId' })
dashboard;
Apex wires don't replace LDS for record reads; LDS is faster and cache-aware. Use Apex for genuinely server-side computation only.
Migrating from older Aura code
Aura components used force:recordData for the same purpose. Migrating to LWC requires replacing the Aura tag with the LWC @wire(getRecord, ...) pattern. The migration is mostly mechanical but the field-listing requirement is new for some Aura developers; in force:recordData, the layout-based fetch was the default.
If you're porting old Aura code, make a deliberate decision per component: fields for fast, deterministic data, or layoutTypes for layout-driven flexibility. The two patterns produce different operational characteristics; the choice should be intentional.
Lightning Out and external embedding
When an LWC is embedded outside Salesforce (Experience Cloud public pages, Lightning Out apps), the LDS layer still works but the data context may not be set up the same way. The recordId might not be auto-populated; you might need to pass it explicitly.
Test specifically in the embedded context if your component will be used there. Behavior that works in a Salesforce internal app can differ in external embeddings.
Further reading from Salesforce
- LWC Reference: getRecord wire adapter
- LWC Developer Guide: Lightning Data Service
- LWC Reference: @salesforce/schema imports
- LWC Reference: Refresh Data
- Trailhead: Build Lightning Web Components
Related dictionary terms
Share this fix
Related Lightning · LWC errors
Action failed: c:myComponent$controller$myAction [<JS error message>]
Lightning · LWCAn Aura component's client-side JavaScript controller threw an exception. The error message is wrapped in `Action failed: c:component$contro…
Cannot find component: c:myComponent
Lightning · LWCThe Lightning runtime tried to instantiate a component by name and the platform doesn't have it — the bundle didn't deploy, the namespace pr…
Cannot read properties of undefined (reading 'X') — in LWC component lifecycle
Lightning · LWCPlain JavaScript TypeError, but in LWC it almost always means a `@wire` result was read in `connectedCallback` or `renderedCallback` before …
Lightning component cannot be created: c:myComponent
Lightning · LWCThe framework couldn't instantiate a Lightning component at runtime — usually a missing dependency, a permission gap, or a static-resource l…
LWC1009: <template> directives are not allowed at the top level
Lightning · LWCYou put `if:true`, `for:each`, `iterator`, or another LWC template directive on the *root* `<template>` of an LWC HTML file. Directives belo…