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

LWC1503: @wire decorator's parameter must be a function

The compiler caught an `@wire` usage where the first argument isn't an Apex method, a Lightning Data Service module, or a wire adapter — usually because of a missing import, a wrong import path, or a stray comma.

Also seen asLWC1503·@wire decorator's parameter·LWC1503: @wire decorator·wire decorator must be a function

You add a new @wire to an LWC and the compiler fails with LWC1503: @wire decorator's parameter must be a function. The decorator looks correct; you copied the syntax from a working component. The compiler is right anyway: the first argument to @wire isn't a function it recognizes. Usually because of a missing import.

What the compiler is checking

The @wire decorator's first argument must be a wire adapter: an importable function that follows the wire-adapter contract. The compiler validates the first argument at deploy time. If the argument is undefined (not imported), a plain object (a function call result), or anything other than a registered adapter, the compiler refuses.

Wire adapters come from a few sources:

  • Built-in Salesforce adapters like getRecord, getRecordUi, getPicklistValues, all imported from lightning/uiRecordApi or lightning/uiObjectInfoApi.
  • Apex methods with @AuraEnabled(cacheable=true), imported via @salesforce/apex/ClassName.methodName.
  • Custom wire adapters (rare, but supported via the LDS Apex layer).

The compiler validates the import at build time. A wrong import path, a missing import, or a typo in the name all produce LWC1503.

The broken example

A component that tries to wire an Apex method without importing it:

import { LightningElement, wire } from 'lwc';
// missing: import fetchAccount from '@salesforce/apex/AccountService.fetchAccount';

export default class AccountSummary extends LightningElement {
    @wire(fetchAccount, { id: '$recordId' })   // fetchAccount is undefined
    account;
}

The deploy fails with LWC1503. The Apex method exists; the import is missing.

The fix: import the wire adapter

import { LightningElement, wire, api } from 'lwc';
import fetchAccount from '@salesforce/apex/AccountService.fetchAccount';

export default class AccountSummary extends LightningElement {
    @api recordId;

    @wire(fetchAccount, { id: '$recordId' })
    account;
}

The import resolves the symbol at deploy time. The compiler validates that AccountService.fetchAccount exists and is annotated @AuraEnabled(cacheable=true). Without the cacheable flag, the import fails with a different error.

The fixed example, end to end

A complete component that uses a custom Apex wire adapter cleanly:

// accountSummary.js
import { LightningElement, wire, api } from 'lwc';
import fetchAccountSummary from '@salesforce/apex/AccountService.fetchAccountSummary';

export default class AccountSummary extends LightningElement {
    @api recordId;
    summary;
    error;
    isLoading = true;

    @wire(fetchAccountSummary, { accountId: '$recordId' })
    wiredSummary({ data, error }) {
        this.isLoading = false;
        if (data) {
            this.summary = data;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.summary = undefined;
        }
    }
}
public class AccountService {
    @AuraEnabled(cacheable=true)
    public static AccountSummaryDto fetchAccountSummary(Id accountId) {
        if (accountId == null) {
            throw new AuraHandledException('Account id is required');
        }
        Account a = [SELECT Id, Name, Industry, AnnualRevenue FROM Account WHERE Id = :accountId LIMIT 1];
        return new AccountSummaryDto(a);
    }

    public class AccountSummaryDto {
        @AuraEnabled public Id id;
        @AuraEnabled public String name;
        @AuraEnabled public String industry;
        @AuraEnabled public Decimal annualRevenue;

        public AccountSummaryDto(Account a) {
            this.id = a.Id;
            this.name = a.Name;
            this.industry = a.Industry;
            this.annualRevenue = a.AnnualRevenue;
        }
    }
}

The Apex method is cacheable=true (required for wire), returns a typed DTO, and validates input. The LWC imports the method and wires it. The component handles loading, success, and error states explicitly.

Cacheable requirement

The cacheable=true flag is a hard requirement for any Apex method consumed via @wire. Without it, the method can only be called imperatively (via apexMethodName() returning a Promise).

The flag implies semantic guarantees:

  • The method has no side effects.
  • The result depends only on the input parameters.
  • Caching the result is safe.

If your method does DML or otherwise has side effects, you can't make it cacheable. Use imperative invocation instead:

import fetchAccount from '@salesforce/apex/AccountService.fetchAccount';

async loadAccount() {
    const account = await fetchAccount({ id: this.recordId });
    this.account = account;
}

The imperative pattern doesn't require cacheable, but loses the auto-refresh behavior of @wire.

Common import-path mistakes

LWC1503 frequently traces back to one of these:

Wrong namespace. @salesforce/apex/AccountService.fetchAccount references a class without a namespace. For a managed-package class, use @salesforce/apex/namespace.AccountService.fetchAccount.

Misspelled class or method. The compiler validates exactness. A capitalization difference or missing character fails the resolve. Use IDE autocomplete to avoid this.

Method not annotated. The method exists, but lacks @AuraEnabled or cacheable=true. The import resolves syntactically but the method isn't exposed to wires.

Wrong adapter module. Built-in adapters live in specific modules (lightning/uiRecordApi, lightning/uiListApi, etc.). Importing from the wrong module returns undefined.

Wire parameters bound with $

The wire's parameter object uses $propertyName to bind to a component property. The wire re-fires when the property changes:

@wire(fetchAccount, { id: '$recordId' })
account;

Without the $, the parameter is a literal string. The wire fires once with id = 'recordId' (the string), not the property's value. The compile passes but runtime behavior is wrong.

A subtle case: forgetting the $ produces no compile error and no runtime exception; it just queries with the literal string and returns "no results" because no record has that id. The bug manifests as an empty component with no error to chase.

The apexAdapter pattern

For Apex methods that need to be wire-able but don't fit the simple cacheable pattern, the modern approach is to write a thin Apex method that's cacheable and have it call into the more complex non-cacheable logic. The wire consumes the thin wrapper; the wrapper's purity makes it safe to cache.

@AuraEnabled(cacheable=true)
public static AccountSummaryDto fetchAccountSummary(Id accountId) {
    return AccountService.computeSummary(accountId);   // The real logic
}

The wrapper is cacheable; the inner method can be anything. This pattern keeps the wire surface clean.

Multi-parameter wires

Wires can take multiple parameters:

@wire(fetchByCriteria, { name: '$searchName', country: '$searchCountry', limit: 10 })
results;

Each $property binds to a component property. The non-$ values are static. The wire fires when any bound property changes. Static parameters never change after the first fire.

For complex filter criteria, consider passing a single object parameter instead of many flat parameters. The Apex side receives the object as a parameter, decomposes it, and runs the query.

Imperative fallback for non-cacheable Apex

When you need wire-like behavior but the method has side effects, manually trigger refreshes:

import doStuff from '@salesforce/apex/StuffService.doStuff';

async handleClick() {
    try {
        await doStuff({ id: this.recordId });
        this.result = 'Done';
    } catch (e) {
        this.error = e;
    }
}

The imperative call is awaited; you write the try/catch and the result handling explicitly. The pattern is more verbose than @wire but supports any Apex method, regardless of side effects.

Diagnostic tips

When LWC1503 fires:

  1. Read the line number from the error message.
  2. Open the source at that line.
  3. Look at the first argument of @wire(...).
  4. Confirm the symbol is imported (search for the symbol name in the imports).
  5. If imported, confirm the import path is correct (compare against working components).
  6. If imported correctly, confirm the imported method/adapter is valid (Apex methods need cacheable; module imports need the right module).

The five-step check resolves most LWC1503 incidents in under a minute.

A real-world bug

A team's component started failing LWC1503 after a deploy that should have been unrelated. The cause: a refactor renamed an Apex class from AccountService to AccountServiceV2, but the LWC import still pointed at AccountService.fetchAccount. The deploy went through (the LWC didn't change), then failed at runtime when the Apex class no longer existed at the import path.

The fix: update the import path. The prevention: when renaming Apex classes, grep for the old name in force-app/main/default/lwc/ and update every reference.

Test patterns

Jest tests for wire-using components work the same regardless of which adapter is consumed:

import { createElement } from 'lwc';
import fetchAccountSummary from '@salesforce/apex/AccountService.fetchAccountSummary';
import AccountSummary from 'c/accountSummary';

jest.mock('@salesforce/apex/AccountService.fetchAccountSummary', () => ({ default: jest.fn() }), { virtual: true });

describe('account-summary', () => {
    it('renders summary when wire returns data', () => {
        const element = createElement('c-account-summary', { is: AccountSummary });
        element.recordId = '001xx000003DGb0';
        document.body.appendChild(element);
        // Use @salesforce/sfdx-lwc-jest helpers to emit wire data
    });
});

The jest.mock with virtual: true lets you stub the Apex import without needing a real Apex class.

When the adapter exists but the call still fails

A subtle case distinct from LWC1503: the import resolves, the wire fires, but the Apex method throws. The runtime error appears in the wire handler's error parameter, not as LWC1503. Diagnostic:

  • LWC1503 fires at compile time (deploy refuses).
  • Runtime errors appear in the wire response (component renders with error set, never with data set).

These are different bug classes. Read the error message carefully to distinguish.

Module path conventions

Salesforce's module imports follow a specific naming pattern:

  • lightning/X for Salesforce-provided LDS adapters.
  • @salesforce/apex/ClassName.methodName for Apex methods.
  • @salesforce/schema/Object.Field for field references.
  • @salesforce/label/c.LabelName for custom labels.
  • @salesforce/resourceUrl/ResourceName for static resources.

Each prefix corresponds to a different platform service. Mismatched prefixes (using @salesforce/apex/ for an LDS adapter, for example) produce LWC1503 or related errors.

A common refactor surprise

When you rename a method in Apex, every LWC that imports it needs the new name. The Apex deploy succeeds; the LWC deploy fails LWC1503 because the imported name no longer exists.

The discipline: when renaming Apex methods, do the rename + LWC update in the same PR. The CI deploy catches mismatches before merging.

A subtle compile-time check the platform does

The LWC compiler validates that every imported wire adapter exists in the org's metadata. This means:

  • The Apex class referenced in @salesforce/apex/... must already be deployed.
  • The field referenced in @salesforce/schema/... must exist on the object.
  • The label referenced in @salesforce/label/... must be defined.

When deploying a fresh org from scratch, the order of metadata matters. The LWC deploy fails if its imports reference metadata that hasn't been deployed yet. The fix is to deploy the dependencies first (Apex classes, fields, labels), then the LWC.

For full-org deploys, the CLI usually handles ordering automatically by analyzing the dependency graph. For partial deploys (deploying just one LWC), you might need to explicitly include the dependencies in the manifest.

Cross-namespace wire adapters

For Apex methods exposed by a managed package, the import path includes the package's namespace:

import calculateTax from '@salesforce/apex/taxpkg.TaxCalculator.calculate';

The taxpkg is the package's namespace prefix. Without it, the import resolves to the unmanaged c namespace and fails.

For consumers of multiple managed packages, keeping the namespace prefixes straight is part of the integration's design. Document which packages each LWC depends on in a comment at the top of the file.

A wire-adapter design pattern

For complex data fetching, design a single Apex wire adapter that returns a structured DTO containing everything the component needs. The component wires once and gets all the data in one round-trip:

@AuraEnabled(cacheable=true)
public static AccountPageDataDto fetchAll(Id accountId) {
    AccountPageDataDto dto = new AccountPageDataDto();
    dto.account = [SELECT ... FROM Account WHERE Id = :accountId LIMIT 1];
    dto.contacts = [SELECT ... FROM Contact WHERE AccountId = :accountId];
    dto.opportunities = [SELECT ... FROM Opportunity WHERE AccountId = :accountId];
    return dto;
}

The LWC wires once. The Apex method does multiple queries server-side. The pattern reduces round-trips and centralizes the data shape.

A final tip: enable strict mode

Modern LWC supports strict-mode JavaScript. Enable it via // @ts-check comments in .js files (for IDE-level checking) or by adopting TypeScript directly (for compile-level checking). Strict checks catch undefined imports at edit time, before deploy.

The IDE-level checking is free; the compile-level checking via TypeScript requires a small project setup but pays off across the codebase. Either way, the LWC1503 error class becomes a near-impossibility for code that passes the strict checks.

Further reading from Salesforce

Related dictionary terms

Share this fix

Share on LinkedInShare on X

Related Lightning · LWC errors