You built a workflow worth sharing — and it works perfectly. Until someone else imports it.
The bottleneck is the API key. Use yours, and every user is billed against your account. Use theirs, and they each have to find the credential UI, paste their key, and reconnect every time. Both are friction. The cleaner option is to let the workflow ask for the key on the form, then thread it through to the HTTP nodes that need it. It's simpler than it sounds.
This post walks through the pattern with a working credential setup, an alternative for single-node simple cases, the gotchas, and a note on what this enables for custom node authors.
The screenshots below come from n8n's built-in Bearer Auth credential and from the
n8n-nodes-ldxhubpackage's own credential schema. The technique itself is generic — what's shown here works for any HTTP-node workflow and any custom node that supports expression-mode credentials.
The form asks, the credential listens
The simplest case: a Form Trigger collects an API key, then an HTTP node hits an authenticated endpoint with that key. Two nodes, one bridge between them — but the bridge isn't a direct expression. It runs through a credential.
The flow:
- Form Trigger collects
api_key(use thePasswordelement type for masking) - A Bearer Auth credential references that form input via expression
- HTTP node picks the credential
The Form Trigger is straightforward. Add one field:
Form Trigger
Form Fields:
- Label: API Key
- Element Type: Password
- Custom Field Name: api_key
- Required Field: yes
Element type matters. Use Password instead of Text and the input gets masked on screen — the key isn't readable to someone glancing at the browser.
Here's the rendered form a user sees when they open the workflow URL:
Wiring the credential to expression mode
For a Bearer token (which is what most modern APIs use), create a new credential of type Bearer Auth — a generic credential built into n8n that's purpose-built for Authorization: Bearer ... headers. It has a single field:
Bearer Token: YOUR_API_KEY
Click the small fx (or =) toggle next to the Bearer Token field to switch it into expression mode. Then replace the value with a reference to the form's input:
Bearer Token: ={{ $('On form submission').item.json.api_key }}
The = prefix tells n8n this is an expression, not a literal string. Everything inside {{ }} is JavaScript, and $('Node Name').item.json.field reaches across the workflow to grab a value from another node — the same long-form reference pattern from Implementation notes #001. The Bearer Auth credential automatically prepends Bearer to the token at send time, so you don't write the prefix yourself.
Save the credential. You'll see this:
[ERROR: No path back to node]
That's display-only. The credential edit UI has no workflow execution context, so it can't resolve the expression at that moment. When an HTTP node later invokes this credential from inside a running workflow, the expression resolves correctly.
This is worth pausing on, because many people stop here. They assume the credential is broken and abandon the technique. It isn't broken. Save it anyway. Run the workflow. The key flows through.
A note on the error text: Different credential types show slightly different display-only messages for the same situation. The Bearer Auth credential shows
[ERROR: No path back to node]; some custom-node credentials show[ERROR: Referenced node doesn't exist]. Same root cause (no workflow context at edit time), same harmlessness — just different wording depending on the credential implementation.A note on credential type choice: Bearer Auth uses the standard
Authorizationheader. If your target API expects a different header name —X-API-Key,X-Custom-Auth, etc. — use Header Auth (custom Name/Value) or Custom Auth (full JSON-shaped headers) instead. The expression-mode technique works identically for all of them.
Using the credential in an HTTP node
Configure the HTTP Request node to use the credential:
HTTP Request
Authentication: Generic Credential Type
Generic Auth Type: Bearer Auth
Credential: <your credential name>
Method: (whatever the API needs)
URL: (the endpoint)
The HTTP node now sends Authorization: Bearer ... with the value filled in from the form. No hardcoded keys. No per-user credential setup. The workflow asks, the user types, the request goes out.
You can drop in as many HTTP nodes as the workflow needs — every one of them references the same credential, so the key only has to be entered once on the form, regardless of how many endpoints get called downstream.
Alternative: skip the credential entirely
For a workflow with one HTTP node, you can short-circuit this. Set Authentication: None on the HTTP node and write the header directly:
HTTP Request
Authentication: None
Headers:
- Name: Authorization
- Value: =Bearer {{ $('On form submission').item.json.api_key }}
This works. It's faster to set up. It's worth knowing.
What it doesn't give you:
- Reuse across multiple HTTP nodes in the same workflow
- A path to custom nodes that expect credentials (more on that below)
- The mental separation between "this is auth material" and "this is request data"
Use it for one-off prototypes. For published templates with more than one authenticated call, the credential approach scales better.
Three things to watch out for
1. The display-only error has different wording per credential type
Already covered above — the credential edit UI has no execution context, so it can't resolve the expression at design time. Save it, run the workflow, the expression resolves. What's worth noting is that the message itself differs by credential type:
- Bearer Auth, Header Auth, and most generic credentials show:
[ERROR: No path back to node] - Some custom-node credentials (the LDX hub Dynamic credential below, for instance) show:
[ERROR: Referenced node doesn't exist]
Both are display-only. Both go away at runtime. Don't let the wording trick you into thinking one credential type is more broken than the other.
If you actually have a typo (wrong node name in the expression), you'll see the same error message at design time — same display, different cause. The only way to distinguish a display-only error from a real one is to run the workflow and look at the outgoing request.
2. The Password element type controls masking, not security
The Password element type on the Form Trigger masks input on screen. It doesn't encrypt anything, doesn't store the key separately, and doesn't hide the key from n8n's execution logs. Anyone with access to the workflow's execution history can see what was sent.
Treat masking as a courtesy to the user (so the key isn't readable over their shoulder), not as a security boundary. If you need stronger guarantees, the key needs to live in a properly stored credential — and at that point you're back to per-user credential setup, which defeats the form-based approach.
This pattern is convenience-oriented, not secret-management-oriented. For genuine secret handling — vault integration, audit trails, key rotation — n8n offers external vault support on its Enterprise plan. For everything else, the form-based pattern is a UX optimization, not a security upgrade.
3. Credentials are global, expressions are runtime-scoped
There's a slightly odd structural fact behind this pattern: the credential record itself isn't tied to any specific workflow — n8n stores credentials globally, and any workflow can reference one. But the expression inside the credential references a specific node by name ($('On form submission')), which can only resolve inside a workflow execution context.
The credential is global. The expression inside it is workflow-scoped. At edit time, those two worlds are disconnected. At runtime, n8n binds them together — but only if both halves match.
This is also why a credential built around one template's node names can fail in a different workflow that doesn't have those nodes. The mismatch is silent until the workflow runs.
Either standardize your trigger node names across templates (always On form submission), or create a separate credential per template. If you're publishing several templates that each ask for an API key, having one credential per template is the cleaner long-term shape — credentials are cheap, mismatches are expensive.
Everything above applies to standard HTTP-node workflows. The next section is about something broader: how custom nodes can participate in the same pattern, and what that means for n8n's community ecosystem.
When a custom node also supports expression credentials
Most discussions of dynamic credentials stop at the HTTP node pattern above. There's a second class worth knowing about: custom nodes (community-built, like n8n-nodes-asana or n8n-nodes-ldxhub) that define their own credential types. They don't use HTTP node headers — they read from the credential directly. Which means the form-to-credential pattern needs to be supported by the node itself.
A custom node that anticipates varied use cases lets the user choose between two modes for the same credential:
Static mode — traditional credential setup:
- Key stored directly in the credential
- One-time setup per user
- Best for personal/internal workflows
Dynamic mode — same credential, key field in expression mode (={{ $('On form submission').item.json.api_key }}):
- Key resolved from form input at runtime
- No per-user setup
- Best for distributable templates
Same credential type. Two usage patterns. The user picks based on what they're doing.
Note that only the API Key field is in expression mode here — the Base URL is left as a static
https://gw.ldxhub.io. You could put the Base URL in expression mode too (and theall-services-demotemplate in the LDX hub package does exactly that, asking the user for a host on the form). For this article's example we keep it simple: one moving part, one expression.
For node authors, the takeaway is: design your credential schema to support expression-mode values from the start. Don't hardcode a regex validation that rejects {{ }} syntax. Don't fail on empty initial values (the expression resolves at runtime, not at credential save time). Don't insist on a particular key format if the value will be computed from somewhere else.
A node that works in both modes is useful in twice as many contexts — personal automation and template publishing — for the cost of letting the credential field be an expression. That's a low investment with broad payoff.
Why this matters
The default n8n workflow is a one-account thing. You build it to call APIs against your own credentials, save it, and it runs for you. Anyone who imports it has to either match your account exactly or rewrite the auth.
Dynamic credentials change this. The same workflow now adapts to whoever runs it — their API key, their account, their bill. The template becomes a small application that anyone can pick up.
Together with dynamic dropdowns (Implementation notes #001), this is the second piece you need to ship a workflow that's actually distributable. The form asks for a key. The credential threads it through. The HTTP nodes — or compatible custom nodes — make calls. No setup ceremony, no account juggling, no copy-paste of credentials from configuration page to configuration page.
The complete reference workflow
The all-services-demo example in the n8n-nodes-ldxhub package ships with this pattern. The Form Trigger asks for an API key and a host. Dynamic credentials thread both into five different service paths, each using the LDX hub custom node with expression-mode credentials. Inspect examples/all-services-demo.json to see the credential setup and the node calls that consume it.
For a free LDX hub key to run the demo, head to gw.portal.ldxhub.io — 25,000 credits per month, no card. Or take the credential pattern and apply it to any API you want.
Closing
The credential-with-expression pattern is one of those n8n techniques that's both completely sanctioned by the design and never quite documented as "this is how you ship templates." It just sits there, in the small fx toggle next to every credential field, waiting for someone to flip it.
For node authors, designing for expression-mode support from day one costs almost nothing. For workflow authors, it's the difference between "here's my template, please configure four things to use it" and "here's the form, paste your key."
The boring part is that there's almost nothing exotic happening. The mechanism was already there. The missing piece was realizing that credentials didn't have to belong to the workflow author.




Top comments (0)