Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
Lightning · LWC

@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 empty fields/layoutTypes parameter.
  • 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.
  • getRecord returning null data: 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:

  1. Extract field references to a shared constants module.
  2. The component imports the shared FIELDS array.
  3. 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

Related dictionary terms

Share this fix

Share on LinkedInShare on X

Related Lightning · LWC errors