What requestDefaults Does in an n8n Declarative Node
requestDefaults is the shared request layer in a declarative n8n node. Put the API's base URL and headers that apply to every operation there. Put the method, path, query parameters, and operation-specific body in each operation's routing.request object.
That split gives every request one clear owner:
requestDefaultsdescribes the service-wide starting point.routing.requestdescribes what one operation changes.- The credential file describes how authentication is attached.
- Node parameters provide values such as an ID, date, or filter.
The NativeShip declarative node tutorial covers the wider node structure. This post focuses on the request configuration boundary that is easy to mix up when a node has several operations.
Here is how the layers fit together for a node with two operations:
| Layer | Example value | Applies to |
|---|---|---|
requestDefaults |
baseURL: https://api.example.com/v1, Accept: application/json |
Every operation |
Credential authenticate |
Authorization: Bearer <token> |
Every request that uses the credential |
Operation routing.request |
GET /records or POST /records |
One operation |
Parameter routing |
/records/{{$value}}, qs.created_after, body name |
One field on one operation |
If you are generating the node from a spec, the OpenAPI to n8n node guide shows how the spec's servers entry becomes the base URL and each path becomes an operation route.
What belongs in requestDefaults
n8n's standard-parameters reference defines requestDefaults as the place for basic API-call information. For a declarative node, the useful starting shape is:
requestDefaults: {
baseURL: 'https://api.example.com/v1',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
baseURL is the API base URL. Keep it at the stable service or version boundary, such as /v1, and leave the resource path to the operation that owns it. The optional headers object is a place for representation headers shared by the node. The reference also allows an optional url value, which n8n appends to baseURL; most nodes can put the operation path in routing instead.
Do not treat every header as a default. A header belongs in this object only when every operation can send the same value. Accept: application/json is often a shared representation choice. An idempotency key, upload content type, conditional request header, or operation-specific preview header belongs on the operation that needs it.
What belongs in routing.request
The operation option owns the details that change from one action to another. A small declarative node can define a list operation like this:
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'List records',
value: 'list',
action: 'List records',
description: 'Return records from the service',
routing: {
request: {
method: 'GET',
url: '/records',
},
},
},
],
default: 'list',
},
The route starts from baseURL, keeps the resource path beside the operation, and leaves the shared headers in one place. When another operation uses POST, a different path, or a different body, its own routing.request object shows that change instead of hiding it in a global string.
The current declarative-style tutorial uses the same division. Its requestDefaults sets a NASA API base URL and shared headers, while each operation's routing.request supplies the method and path.
How n8n combines the layers
At run time n8n starts from requestDefaults, merges the selected operation's routing.request on top, then merges the routing of every visible parameter that has a value. When two layers set the same key, the more specific layer wins. That is why a parameter can set url: '=/records/{{$value}}' and override the operation's /records path, and why an operation-level header can replace a shared one with the same name.
Two practical consequences follow:
- Hidden parameters do not route. A field hidden by
displayOptionsfor the current operation does not add its query value or body property, so you can reuse the samenameacross operations without leaking values. - Order matters for readability, not just behavior. Keep the base in
requestDefaults, the verb and path on the operation, and field-level mapping on the field. A reviewer can then read one place for each question.
Map a node parameter into a path
A resource ID usually belongs to the operation path, not to requestDefaults. Add a parameter with a routing request that uses $value:
{
displayName: 'Record ID',
name: 'recordId',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['record'],
operation: ['get'],
},
},
routing: {
request: {
url: '=/records/{{$value}}',
},
},
},
The leading = tells n8n to evaluate the value as an expression. The {{$value}} token represents the value entered for that parameter. Keep the field visible only for the resource and operation that use it, then check how the target API expects IDs to be encoded. A value that can contain reserved URL characters needs an API-appropriate encoding strategy before publication.
Do not place a user-supplied ID in baseURL. That makes the base configuration depend on one record and can leave later operations with a URL that is difficult to inspect.
Map query parameters without changing the base URL
Filters, dates, cursors, and limits belong in routing.request.qs. For example, a date field can add a query value while leaving the operation path readable:
{
displayName: 'Created after',
name: 'createdAfter',
type: 'dateTime',
default: '',
routing: {
request: {
qs: {
created_after: '={{ new Date($value).toISOString().slice(0, 10) }}',
},
},
},
},
This pattern keeps the query key beside the field that supplies it. It also makes the API spelling visible during review. Use the provider's documented date, cursor, and array format. Do not assume that a parameter called page means the same thing across services.
For optional fields, decide whether an empty value should be omitted or sent. A provider may treat an empty filter as a real filter. Test a missing value, a fixed value, and an expression value before publishing the operation.
Keep authentication in the credential file
requestDefaults is not a safe place for an API key or bearer token. Declare the credential on the node and let the credential definition attach the authentication. The NativeShip credentials guide explains the credential file, test request, and operation boundary.
credentials: [
{
name: 'serviceApi',
required: true,
},
],
The credential's authenticate property can add a header or query value using the stored credential fields. Its test request should call a small endpoint that proves the credentials work without changing data. How to add a credential test walks through that request step by step. Keep that test route separate from an operation that creates, updates, or deletes a record.
Do not copy an Authorization value into shared headers to make a sample look complete. A token in node source, parameters, or logs can reach workflow users and repository history. If the API uses a nonstandard authentication scheme, document the actual credential type and test response before describing it as supported.
Choose declarative or programmatic configuration deliberately
requestDefaults and routing are the declarative request model. n8n combines the shared request information with operation and parameter routing so the node can describe an API without writing an execute method for each call.
A programmatic node has a different boundary. Its execute method reads input and parameters, creates IHttpRequestOptions, and calls an HTTP helper. The programmatic execute reference describes that execution model. Use it when the request or response needs branching, custom transformation, or logic that routing cannot express clearly.
Keep each model in its own boundary. If an operation needs dynamic code, conditional requests, or a response transformation that cannot be represented by declarative routing, keep that behavior in the programmatic implementation. Do not add requestDefaults to a programmatic node and expect the execute method's HTTP helper to consume it automatically.
Map operation-specific body fields
Shared headers do not make every body the same. Map a field into the request body only for the operation that uses it:
{
displayName: 'Name',
name: 'name',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['record'],
operation: ['create'],
},
},
routing: {
send: {
type: 'body',
property: 'name',
},
},
},
The operation still owns the method and path:
routing: {
request: {
method: 'POST',
url: '/records',
},
},
Confirm the supported routing syntax for the n8n version you publish against, then inspect the resulting request body. A JSON content type in requestDefaults does not prove that the body is encoded correctly.
For an operation-specific header, add it at the request route rather than changing a shared default:
routing: {
request: {
method: 'POST',
url: '/records',
headers: {
'Idempotency-Key': '={{$parameter["requestKey"]}}',
},
},
},
Use a real provider contract for the header name and value. The example shows placement, not a tested API integration.
Avoid the common requestDefaults mistakes
| Mistake | What it causes | Better boundary |
|---|---|---|
Putting /records in baseURL and adding /records again in an operation |
Duplicated paths and hard-to-read routes | Keep the stable API prefix in baseURL; put the resource path in routing.request.url |
| Adding an API key to shared headers | Secrets in source or workflow-visible configuration | Use the credential definition and a credential test request |
Putting a record ID in baseURL |
A global setting that changes per item or operation | Map the ID field to the operation route |
Using requestDefaults for a programmatic helper |
Two request models that do not share runtime code automatically | Build helper options in execute for a programmatic node |
| Putting a cursor or page number in a global default | Pagination state can leak between operations | Keep pagination fields and termination logic with the operation that owns them |
Assuming a shared Content-Type proves the body is valid |
The server rejects a body whose shape or encoding is wrong | Test method, body mapping, header, and response together |
Test the resolved request, not only the node metadata
Before release, inspect the actual request produced by each operation with secrets removed. A useful matrix includes:
| Test | Inspect |
|---|---|
| One operation with a fixed path | Base URL, path, method, and shared headers |
| A resource ID from a fixed value | Resolved path and encoding of reserved characters |
| A query field with a fixed value | Query key, date format, and omitted empty fields |
| The same query field from an expression | Per-item value resolution and resulting URL |
| A create or update operation | Body shape, method, content type, and authentication |
| An authentication failure | Credential test route, status, and safe error message |
| Two operations in the same node | No path, header, or body state leaking from one operation to the other |
| A provider error | The response context and whether the node classifies it correctly |
Record the n8n version, package version, credential type, operation, redacted request shape, response status, and output. The NativeShip custom-node guide is the right starting point for the package structure; the debugging guide can help inspect a route when the rendered request does not match the configuration.
Run the node locally
Scaffold the package with npm create @n8n/node, then run npm run dev (which calls n8n-node dev) from the project root and open http://localhost:5678. From n8n 3.0, n8n-node dev starts n8n in a container, so Docker or Podman must be installed. If you already run your own instance, use n8n-node dev --external-n8n and start that instance with N8N_DEV_RELOAD=true. Add a Manual Trigger, your node, and a fixed test value for each field, then compare the request you expected with what the provider received.
Keep the example labeled as a pattern until it has been run against the API and the supported n8n version. That evidence is what turns a clean requestDefaults object into a publishable node operation.
Frequently asked questions
It is the shared request configuration for a declarative node, including the API base URL and headers that apply across operations.
No. requestDefaults belongs to the declarative request model. A programmatic node builds its own request options inside execute and calls this.helpers.httpRequestWithAuthentication, so set the base URL there and test that code path directly.
Put the stable API service or version prefix there, such as https://api.example.com/v1. Keep resource paths, record IDs, and oper
Put them in the operation or parameter routing request under qs. Test the provider's expected names, formats, and behavior for missing values.
No. Declare a credential and let its authentication definition add the secret. Use a credential test request to check access without changing data.
Yes. Put the operation-specific header in that operation's routing request and keep requestDefaults for values that are valid for every operation.
Run each operation against the supported n8n version and target API, inspect the redacted URL, query, headers, body, authentication result, status, and output, and record the evidence.