Fix "Paired Item Data … Is Unavailable" in n8n (pairedItem for Custom Nodes)
In this video
Your workflow runs, and a node after your custom node turns red:
Paired item data for item from node 'Acme CRM' is unavailable. Ensure 'Acme CRM' is providing the required output.
The red node is where it broke. The node named in the message is where it started. If you built that node, the fix is usually one line in its execute() method.
What n8n is looking for
Every item a node returns can carry a note saying which input item produced it. n8n calls it pairedItem:
{ json: { email: 'asha@example.com' }, pairedItem: { item: 2 } }
That note is what makes .item work in expressions. When a later node evaluates $('Acme CRM').item, n8n doesn't return the first item. It follows the links back from the current item to the Acme CRM item it came from. If Acme CRM never set pairedItem, there is no link to follow, and you get this error.
A node without pairedItem works fine on its own, which is why the problem often ships unnoticed. It only appears when someone uses .item on a node after it.
Read the whole message
- The node in quotes is the culprit. Fix that node, not the one showing the error.
- Check for the pinned-data version. If the culprit's output is pinned, n8n says: Using the item method doesn't work with pinned data in this scenario. Please unpin 'Acme CRM' and try again. Unpin, run again, and see whether the error remains.
The fix in a programmatic node
In execute(), every returned item says where it came from. There are three shapes.
One in, one out. One contact in, one updated contact out:
for (let i = 0; i < items.length; i++) {
const contact = await updateContact(i);
returnData.push({ json: contact, pairedItem: { item: i } });
}
One in, many out. A search for one input item returns many contacts. n8n has a helper that links every result to that input:
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(contacts),
{ itemData: { item: i } },
);
returnData.push(...executionData);
Many in, one out. A summary of all the input items points at every one of them:
returnData.push({
json: summary,
pairedItem: items.map((_, index) => ({ item: index })),
});
Error items need it too. When continueOnFail() turns a failure into an output item, give that item its pairedItem:
returnData.push({ json: { error: error.message }, pairedItem: { item: i } });
The lint errors guide covers the rest of that catch block.
Lint catches most of it
n8n's community-node lint now has a missing-paired-item rule, and it is an error, not a warning:
Missing pairedItem on INodeExecutionData object. Add
pairedItem: { item: index }to preserve item linking.
It checks item objects you write out in place inside execute(). It can't see inside variables:
returnData.push({ json: contact }); // reported
const out = { json: contact };
returnData.push(out); // not reported
So test the way users will: run the node with three or four input items, add a node after it, and reference $('Your Node').item in one of its fields. If every item resolves, the links are there.
Declarative nodes don't need this
In a declarative node, n8n makes the HTTP requests and builds the output items itself, so it keeps the item links for you. n8n's docs say you don't need to consider item linking when building a declarative-style node. This is one more reason to start declarative and move to execute() only when the API needs it (Part 5 of the series covers when).
NativeShip, the tool we build, generates n8n nodes from an OpenAPI spec. Our test build came out declarative and passed all five checks, so it got item linking from n8n without any execute() code.
If it's someone else's node
If you only use the node, you can work around it until the author fixes it:
- The node returns one item:
{{ $('Acme CRM').first().json.email }} - The order is guaranteed to match:
{{ $('Acme CRM').all()[$itemIndex].json.email }}. Only safe if the node returns its items in the same order it received them. - The data is already in the incoming item:
{{ $json.email }}. Often the simplest; no need to reach back at all.
Then open an issue on the node's repository with the full error message. The fix on their side is usually the one line above.
Recap
- The node named in the message is the one to fix.
- Unpin its data and retry first.
- Set
pairedItemon every returned item: the index for one-to-one,constructExecutionMetaDatafor one-to-many, a list for many-to-one, and on error items too. - Lint catches literals, not variables, so test with several items and
.item. - Declarative nodes get item linking from n8n.
Frequently asked questions
An expression such as $('Acme CRM').item asked for the item that the current item came from, and the named node didn't record that link. The node named in the message is the one to fix, even though the error shows on a later node.
In execute(), give every returned item a pairedItem pointing at its input item: { json, pairedItem: { item: i } }. When one input produces several outputs, wrap them with this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(results), { itemData: { item: i } }).
No. In a declarative node n8n makes the requests and builds the output items itself, so it keeps the item links. Only programmatic nodes with an execute() method need to set pairedItem.
Unpin the node's data and run again first. If it persists, use $('Node').first() when the node returns one item, $('Node').all()[$itemIndex] when the order is guaranteed to match, or $json when the data is in the incoming item. Then report it to the node's author.