MongoDB query returns 0 items on first execution but succeeds on second execution

I’m seeing a very strange behavior in an n8n workflow using MongoDB.
The workflow is:

  • Webhook receives LeadUserID and property_id
  • Set node formats the data
  • MongoDB node finds the Campaign document by property_id
  • A second MongoDB node searches the Campaign_Members collection using:
    • Customer_ID from the webhook
    • Campaign_ID (_id) from the previous MongoDB node
      The problem is that the second MongoDB node behaves inconsistently:
  • Automatic workflow execution → returns 0 items
  • First manual execution in the editor → returns 0 items
  • Second manual execution (without changing anything) → successfully returns the matching document
    The query is:
={
  "Customer_ID": "{{ $('Edit Fields').first().json.body.args.LeadUserID }}",
  "Campaign_ID": "{{ $json._id }}"
}

The data already exists in the database, so this is not a race condition with a recent insert.
What confuses me is that nothing changes between the first and second execution, yet the second execution consistently succeeds.
Has anyone encountered this kind of “first execution fails, second execution works” behavior with MongoDB nodes? Could it be related to data types (such as ObjectId vs string), expression evaluation timing, execution state, or another n8n-specific quirk?

Information on your n8n setup

  • n8n version: latest version
  • Database (default: MongoDB):
  • n8n EXECUTIONS_PROCESS setting (default: own, main):
  • Running n8n via : n8n cloud
  • Operating system:

Hi @alee_Ostovar

You need to ensure the Campaign_ID is passed as an ObjectId.

The most reliable way to handle ObjectId in n8n is to create the query object in a Code node. This prevents n8n from accidentally casting your IDs to strings.

  1. Add a Code Node before your second MongoDB node.
  2. Use this code:
const leadUserId = $('Edit Fields').first().json.body.args.LeadUserID;
const campaignId = $json._id;

return {
  query: {
    Customer_ID: leadUserId,
    Campaign_ID: { "$oid": campaignId } // This tells MongoDB to treat it as an ObjectId
  }
};
  1. In your MongoDB node, instead of writing the JSON manually, reference the output of the Code node: {{ $json.query }}

@alee_Ostovar
I had a very similar issue with Postgres. From what I was able to discern, it was to do with pinned data at the trigger, particularly if the trigger is a webhook or a “When executed by another workflow” trigger. Although it’s not supposed to happen, the pinned data from the trigger was being fed downstream during production executions and sometimes that data is wrong / just a null value from manual testing. And then when you manually open the execution that failed - the pinned data of the trigger gets overwritten and it magically works.

I was able to solve it by making sure I always Unpin the data of the trigger before my workflow goes live. Hasn’t happened since, and it used to occur maybe 50% of the time previously. Hope this helps.

Thanks for the suggestion. In my case, Campaign_ID is not stored as a MongoDB ObjectId. It’s stored as a string in the Campaign_Members collection (it’s just a reference value, not an actual ObjectId field).

Because of that, wrapping it as { "$oid": campaignId } would make the query search for an ObjectId, which won’t match the stored string value.

That is why

This is due to data type mismatch

Thanks, I actually tried that already. I made sure the Webhook trigger wasn’t pinned, but unfortunately the behavior didn’t change.

One workaround I found is replacing the Edit Fields (Set) node with a Code node that builds the same output. With the Code node, the workflow behaves correctly every time.

I also noticed another issue that seems related to the Edit Fields node: when passing MongoDB documents through it, the MongoDB _id is sometimes converted into a Buffer instead of remaining in its original form. That appears to be a separate problem with the Set/Edit Fields node itself.

So at the moment, using a Code node works around both issues, but it feels more like avoiding the bug than fixing it. I’m trying to understand why the native Set/Edit Fields node behaves this way.

I don’t think it’s a data type mismatch, since both variables are stored as strings, and the query is also using string values. If it were a type mismatch, I’d expect it to fail consistently rather than only on the first execution.

I was able to solve the issue by replacing the Edit Fields (Set) node with a Code node. That works, but I’m trying to understand why the native Set node exhibits this behavior in the first place.

In MongoDB, the _id field is not a string; it is a special BSON type called an ObjectId.

In your query:

{
  "Customer_ID": "{{ $('Edit Fields').first().json.body.args.LeadUserID }}",
  "Campaign_ID": "{{ $json._id }}"
}

By wrapping {{ $json._id }} in double quotes, you are explicitly telling n8n to treat the Campaign_ID as a string​. When MongoDB receives a string for a field that is stored as an ObjectId, it returns zero results because a string is not equal to an ObjectId, even if the characters are identical.

During the first manual execution, the node may fail to resolve the expression exactly as expected or fails the DB query. However, during the second execution, the editor often uses the cached output from the previous node’s execution state.

Does this apply to your case?

This is exactly the confusing part, and it’s actually the second issue I’m running into.

The Edit Fields node sometimes passes _id as [object Object], so I suspected it was a data type issue. To verify, I logged the value using a Code node immediately after a MongoDB node(before an edit fields). Here’s the output:

[
  {
    "value": "6a579e9014256aa1f68ca592",
    "typeof": "string",
    "constructor": "String",
    "isObject": false,
    "proto": "Object"
  }
]

On the surface, it appears to be a primitive string. However, given the behavior I’m seeing, I suspect there may still be an underlying BSON wrapper or some internal type/proxy involved that isn’t reflected in the Code node’s output. That would explain why the MongoDB node later treats the value as an object instead of a string.

To force it to be a primitive string every single time (automatic or manual), you should wrap your expression in a JavaScript string constructor.

Change your query from this:

{
  "Customer_ID": "{{ $('Edit Fields').first().json.body.args.LeadUserID }}",
  "Campaign_ID": "{{ $json._id }}"
}

To this:

{
  "Customer_ID": "{{ String($('Edit Fields').first().json.body.args.LeadUserID) }}",
  "Campaign_ID": "{{ String($json._id) }}"
}

By wrapping the expression in String(), you force the n8n expression engine to execute a JavaScript cast before the value is passed to the MongoDB node. This strips away any BSON wrappers, proxies, or object metadata, ensuring that MongoDB receives a literal string every time.