The Record Id is the one value an LWC can't work without. It's the unique key for a single record — an Account, a Contact, a custom object row — and it's how your component knows which record it's actually dealing with.
It behaves like the primary key of a database row: every other field hangs off it. A getRecord wire needs it to pull fields. An Apex update needs it to know what to change. Navigation needs it to know where to go. Hand your component a solid Id and the rest clicks into place.
The wrinkle is that there's no one way to get it. How the Id reaches your component depends entirely on where the component is placed — and picking the wrong route is exactly how a component that's flawless on a record page falls apart the moment it's dropped somewhere else.
The intuition
You can't navigate to “somewhere”
Imagine handing someone the best sat-nav in the world and telling them to drive — but never giving them a destination. All that capability, nowhere to point it. They idle at the kerb.
That's a component with no Record Id. It can query, update, render, navigate — but only once someone drops a pin. The pin is the Id. Everything in this guide is just a different way that pin gets dropped onto your component's map.
The Record Id is the pin on your component's map. The five routes below are just different ways it gets dropped.
The trailmap
Five routes to the same pin
Here's the lay of the land before the detail. The same component can reach its Id by five different routes, and the route is chosen by context — where the component runs — not by preference. Four are solid. One is a shortcut that tends to strand you.
Route 01 — the default
The pin comes pre-dropped
On a record page, the pin is already on the map before your component even loads. Declare a public property named exactly recordId, and the Lightning framework recognises the record and fills it in for you. No wiring, no parsing — it simply arrives. This is the right answer roughly nine times in ten.
Your component sits directly on a record page — Account, Contact, Opportunity, or any custom object.
It must be spelled recordId exactly. Rename it to accountId and nothing gets injected.
Route 02 — off a record page
Read the pin from the trip link
On an app page, the utility bar, a console, or an Experience site, there's no record context to inject — so @api recordId stays empty. Here the pin rides in the page's URL, and the safe way to read it is to wire CurrentPageReference and pull the Id from its state. You never touch the raw URL yourself, which keeps you safe across Classic, Lightning, and future changes.
The component has no record parent and the Id arrives through the page's URL state.
Custom params work the same way: a link with ?c__targetId=00Q… reads as pageRef.state.c__targetId — still through state, still safe.
Route 03 — nested components
The lead navigator shares coordinates
When a parent owns the record context and coordinates several children — a dashboard showing Contacts, Opportunities, and Cases for one Account — only the parent needs to know how it got the Id. It passes the pin down through a public property, and each child just receives it. The children stay reusable and never have to care where it came from.
A parent feeds the same Id to several child components and you want them to stay modular.
parentLwc.html
accountContacts.js — the child receives the Id
Route 04 — the tempting shortcut
Guessing the pin by counting blocks
You can read the browser URL and grab the Id by position — like navigating by “third turn after the petrol station” instead of an actual address. It works on a tidy record-page URL, where the Id sits neatly between the object name and the action. And it strands you the moment the streets change shape.
The index is a guess. On an app page, utility bar, or console, parts[6] points somewhere else. Routes 01 and 02 are guaranteed by Salesforce; the URL guarantees nothing.
Route 05 — you already have it
You know the pin — just drive there
This route flips the situation. You're not discovering an Id; you're holding one — from Apex, a wire, or a parent — and you want to take the user to that record. NavigationMixin opens the matching record page programmatically. Perfect for a button that jumps straight to an Account.
You have an Id in hand and want a button or action that navigates to its record page.
At a glance
The five routes, side by side
Same destination, different terrain. The whole skill is matching the route to where your component runs.
| Route | Best context | Reliability |
|---|---|---|
| @api recordId | Directly on a record page | Guaranteed |
| CurrentPageReference | App page · utility bar · Experience | Guaranteed |
| record-id attribute | Parent feeding child components | As solid as the parent |
| URL parsing | Last resort only | Fragile |
| NavigationMixin | You already hold the Id | Guaranteed |
Walk the route
Show an Account's contacts in five steps
The most common real build: a component that drops onto an Account page and lists that account's contacts — using the default route and nothing else.
Declare the pin
Add a public property named exactly recordId with the @api decorator.
Place it on the record page
Drop the component onto the Account record page. Salesforce now injects the account's Id automatically.
Wire the related records
Pass '$recordId' into a getRelatedListRecords wire so it refreshes when the Id resolves.
Render it
Feed the wire's result into a lightning-datatable to show names and emails.
Test on a real record
Open any Account — the contacts appear, and the same component works on every Account with no changes.
Pocket guide
Good routes & wrong turns
Good routes
- On a record page → @api recordId, done.
- Off a record page → wire CurrentPageReference.
- Have a parent → let it pass the Id down.
- Already hold an Id → NavigationMixin.
- Bind wires to '$recordId' so they refresh.
Wrong turns
- Renaming the property (must be recordId).
- Expecting @api recordId to fill off a record page.
- Slicing the raw URL by index.
- Reading the Id in connectedCallback too early.
- Forgetting the $ on a reactive wire.
Field notes
Keep these in your pack
- The injected property is always
recordId— spelling and casing matter. @api recordIdonly fills on a record page; everywhere else it's empty.- Read URL params through
pageRef.state, never by slicingwindow.location. - Make wires reactive with
'$recordId'so they re-run when the Id arrives. - A child only gets the Id if the parent actually binds
record-id={recordId}. NavigationMixinis for going to a record, not discovering the current one.
Back to camp
The whole map in a sentence
Strip away the APIs and it's one idea: a component is only as useful as the pin it's given. The Record Id is that pin, and the craft is letting it land the way Salesforce intends — by context, not by guesswork.
On a record page, the pin comes pre-dropped. Off it, read the trip link. With a parent, take the coordinates from the lead. And when you already hold the Id, just drive there. Reach for the URL only when every other route is blocked.
Right context → right route → the Id lands in recordId and everything downstream just works.

