Skip to content
Blog

How to Validate Parameters Before an n8n Node Calls the API

A programmatic n8n node should validate a value in two places. The node UI should prevent common mistakes while a workflow is being configured. The execute method should validate the resolved value again for every input item before it builds a request.

That second check matters because a field can contain an expression, an imported workflow can contain old values, and each incoming item can have different data. If the value is wrong, throw a clear NodeOperationError before the request leaves the node. The NativeShip custom-node guide covers the surrounding package structure; this article focuses on the parameter contract inside the node.

The order is always the same: the editor checks the field shape while the workflow is built, execute checks the resolved value for each item, and only a value that passes both reaches the API.

Step Where Example of what it catches
1. Field contract Node properties Empty required field, page size of 500 when the max is 100
2. Runtime check execute, per item Expression resolved to "", "abc" in a number field, bad URL
3. Operation rule execute, per operation Update with no fields to change
4. API call HTTP helper Only valid requests leave the node

Validate at the editor and runtime boundaries

The two checks answer different questions:

Boundary Question Good tools Failure should do
Editor Can the user enter a value in the shape the field expects? required, defaults, displayOptions, number limits, resource-locator validation, hints Keep the workflow configuration clear and show the field-level problem
Runtime Does the resolved value work for this operation and this input item? getNodeParameter, type checks, URL parsing, cross-field rules, NodeOperationError Stop before the request and identify the field and item to fix

UI metadata gives users useful feedback while they build a workflow. It does not replace runtime checks. Expressions are resolved when the workflow runs, and incoming data can be empty, typed differently, or missing a key.

Make the field contract visible in the node UI

Start by describing the value you expect in the node's properties array. n8n's node UI elements reference documents the field types and their options. A required string with a useful description gives the editor enough information to show the basic contract:

{
  displayName: 'Resource ID',
  name: 'resourceId',
  type: 'string',
  required: true,
  default: '',
  placeholder: 'record_123',
  description: 'The ID of the record to update',
  displayOptions: {
    show: {
      resource: ['record'],
      operation: ['update'],
    },
  },
}

Use displayOptions to keep an operation's fields beside the operation that needs them. If resourceId is only used for an update, hiding it for list and create operations reduces the chance of a user filling a field that the node ignores.

The n8n node UI design guidance recommends making required fields clear, adding validation rules where possible, and keeping the main error title short. Put the useful explanation in the field description, hint, or error Details area.

Set defaults that match the request contract

A default is part of the behavior users get when they add the node. Use one only when the API has a safe, predictable default. Explain it in the field description so an imported workflow does not hide a meaningful choice.

For numbers, add editor limits when they describe the API contract:

{
  displayName: 'Page Size',
  name: 'pageSize',
  type: 'number',
  required: true,
  default: 25,
  typeOptions: {
    minValue: 1,
    maxValue: 100,
    numberPrecision: 0,
  },
  description: 'Number of records to request per page. Defaults to 25.',
  displayOptions: {
    show: {
      operation: ['list'],
    },
  },
}

The UI limit improves configuration feedback. The runtime still needs to check the resolved value because an expression can produce a string, a decimal, zero, or a value outside the API's range.

Use format validation for predictable inputs

Some formats can be checked before the node runs. A resource locator can offer an ID mode and a URL mode, with a regex validation rule for each mode:

{
  displayName: 'Record',
  name: 'record',
  type: 'resourceLocator',
  default: '',
  modes: [
    {
      displayName: 'ID',
      name: 'id',
      type: 'string',
      hint: 'Enter a record ID',
      validation: [
        {
          type: 'regex',
          properties: {
            regex: '^record_[A-Za-z0-9_-]+$',
            errorMessage: 'Enter a record ID such as record_123',
          },
        },
      ],
    },
    {
      displayName: 'URL',
      name: 'url',
      type: 'string',
      hint: 'Enter the record URL',
      validation: [
        {
          type: 'regex',
          properties: {
            regex: '^https://',
            errorMessage: 'Enter an HTTPS record URL',
          },
        },
      ],
    },
  ],
}

The regex is an editor filter, not proof that the record exists or that the URL belongs to the configured account. Keep the full check in execute. If you add a List mode, implement and test its search method as well; a list selector is a separate UI and API task from local format validation. The loadOptions and resource locator guide covers the List mode and its search method.

At runtime, read a resource locator with extractValue: true so you get the ID from whichever mode the user chose, instead of parsing the URL yourself:

const recordId = this.getNodeParameter('record', itemIndex, undefined, {
  extractValue: true,
}) as string;

For that to work on URL mode, add an extractValue rule (a regex with one capture group) to the URL mode definition.

Read each parameter with the current item index

n8n runs a programmatic node over an array of input items. Pass the loop index to getNodeParameter so an expression resolves against the item being processed:

const items = this.getInputData();
const returnData = [];

for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
  const resourceId = this.getNodeParameter('resourceId', itemIndex);
  const pageSize = this.getNodeParameter('pageSize', itemIndex);

  // Validate resourceId and pageSize here, before making the request.
  // Build and send the request for this item only after those checks pass.
}

The programmatic-node tutorial shows the same execution shape: get input data, read parameters, loop over the items, and return the node's output. Reading every parameter with index 0 can make the first item's expression value appear on later requests.

Keep runtime validators small and explicit

Runtime validation should state what the API call requires. Avoid broad coercion that turns an empty string into zero or accepts a malformed URL because a value happens to be truthy.

function requiredString(value: unknown, fieldName: string): string {
  if (typeof value !== 'string' || value.trim() === '') {
    throw new Error(`${fieldName} must be a non-empty string`);
  }
  return value.trim();
}

function positiveInteger(value: unknown, fieldName: string): number {
  const numberValue = typeof value === 'number' ? value : Number(value);
  if (!Number.isInteger(numberValue) || numberValue < 1) {
    throw new Error(`${fieldName} must be a positive integer`);
  }
  return numberValue;
}

function httpUrl(value: unknown, fieldName: string): string {
  const raw = requiredString(value, fieldName);
  let parsed: URL;

  try {
    parsed = new URL(raw);
  } catch {
    throw new Error(`${fieldName} must be a valid URL`);
  }

  if (!['http:', 'https:'].includes(parsed.protocol)) {
    throw new Error(`${fieldName} must use http or https`);
  }

  return parsed.toString();
}

These helpers return normalized values that the request builder can use. They do not check service-specific rules such as whether an ID exists, whether a URL belongs to the account, or whether a field combination is allowed. Those rules belong in the operation-specific validation below.

Attach the error to the item that failed

Wrap a validation error in NodeOperationError and include itemIndex. n8n documents this error class for validation failures, missing required parameters, and workflow logic errors. The item index lets the editor point to the input that produced the bad value.

import { NodeOperationError } from 'n8n-workflow';

for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
  try {
    const resourceId = requiredString(
      this.getNodeParameter('resourceId', itemIndex),
      'Resource ID',
    );
    const pageSize = positiveInteger(
      this.getNodeParameter('pageSize', itemIndex),
      'Page size',
    );

    // Use resourceId and pageSize to build the request.
  } catch (error) {
    throw new NodeOperationError(this.getNode(), error as Error, {
      description: `Fix the parameters on input item ${itemIndex + 1} and run the node again.`,
      itemIndex,
    });
  }
}

Keep secret values out of the error. A validation message should name the field, the expected shape, and the next fix. It should not print an access token, a full credential object, or an entire request body.

Validate combinations for the selected operation

Individual fields can be valid while the operation is still impossible. Check the combination after reading the resource and operation:

Operation Required values Check before the request
List pageSize, optional cursor Page size is an integer in the API range; cursor is either empty or a valid cursor string
Get resourceId ID is present and uses the service's accepted format
Create Request body Required body fields exist and values match the API schema
Update resourceId, at least one changed field ID is present; the update body is not empty
Delete resourceId, explicit confirmation if required ID is present and the operation has not been silently mapped to another action

Example:

const operation = this.getNodeParameter('operation', itemIndex);
const resourceId = this.getNodeParameter('resourceId', itemIndex);
const updateFields = this.getNodeParameter('updateFields', itemIndex) as Record<string, unknown>;

if (operation === 'update') {
  const id = requiredString(resourceId, 'Resource ID');
  if (Object.keys(updateFields).length === 0) {
    throw new NodeOperationError(
      this.getNode(),
      'Update fields are required',
      {
        description: `Add at least one field to update record ${id}.`,
        itemIndex,
      },
    );
  }
}

Do not run a request and hope the API explains a missing local rule. A local check can name the node field and the exact fix, while a remote 400 response may only say that the request is invalid.

Validate expression results and incoming JSON

n8n fields can use fixed values or expressions. getNodeParameter returns the resolved value for the current item, so validate that result rather than assuming the editor's stored type will survive expression evaluation.

Incoming JSON has the same issue. If a node reads a customer ID from the incoming item, validate the property before adding it to a path or request body:

const inputItem = items[itemIndex];
const customerId = requiredString(
  inputItem.json.customerId,
  'Input customerId',
);

const endpoint = httpUrl(
  this.getNodeParameter('endpoint', itemIndex),
  'Endpoint',
);

Do not build a URL by concatenating an unvalidated value into a path. Encode an ID with encodeURIComponent after checking its type and accepted format. If the service uses a URL as an identifier, parse it and extract the expected part with a documented rule.

Make the request only after local checks pass

Place the credential helper and network call after validation for the current item. The NativeShip credentials guide explains the credential boundary; this check keeps bad field values from becoming remote requests:

const operation = this.getNodeParameter('operation', itemIndex);
const resourceId = requiredString(
  this.getNodeParameter('resourceId', itemIndex),
  'Resource ID',
);
const endpoint = httpUrl(
  this.getNodeParameter('endpoint', itemIndex),
  'Endpoint',
);

const requestOptions = {
  method: operation === 'get' ? 'GET' : 'PATCH',
  url: `${endpoint}/records/${encodeURIComponent(resourceId)}`,
  json: true,
} as const;

const response = await this.helpers.httpRequestWithAuthentication.call(
  this,
  'serviceApi',
  requestOptions,
);

This example assumes the API contract uses those paths and methods. Adapt the request to the service's current documentation and keep API response failures in the API-error path. NodeOperationError is for local validation and configuration; NodeApiError is for a response or failure from the external service. The n8n error-handling reference documents that boundary, and the custom node errors guide shows what each error looks like to the workflow user.

Common validation mistakes

Mistake What goes wrong Fix
Reading parameters with index 0 Every item uses the first item's expression result Pass the loop index to getNodeParameter
Number(value) || 25 An empty string or 0 silently becomes the default Check the type and range, then fail with a named error
Throwing a plain Error The editor can't tie the message to the node or item Wrap it in NodeOperationError with itemIndex
Validating only in the UI Expressions and imported workflows bypass it Repeat the check in execute
Reading process.env for defaults Fails n8n's verification rules and behaves differently per instance Pass every value through node parameters or credentials
Printing the request body in the error Secrets and personal data end up in execution logs Name the field and the fix only

The process.env point matters more after n8n 3.0. n8n's verification guidelines forbid environment-variable and file-system access, and unverified nodes no longer load by default, so a shortcut that breaks verification also keeps most users from installing the node.

Decide how Continue On Fail should behave

If the node supports Continue On Fail, keep the failed item connected to its input. n8n's documented pattern returns an error object with pairedItem for the failed index. That allows downstream expressions to identify the source item.

try {
  const value = requiredString(
    this.getNodeParameter('resourceId', itemIndex),
    'Resource ID',
  );

  returnData.push({
    json: { resourceId: value },
    pairedItem: { item: itemIndex },
  });
} catch (error) {
  if (this.continueOnFail()) {
    returnData.push({
      json: {
        error: error instanceof Error ? error.message : String(error),
      },
      pairedItem: { item: itemIndex },
    });
    continue;
  }

  throw new NodeOperationError(this.getNode(), error as Error, {
    itemIndex,
  });
}

Choose this behavior deliberately. A node that silently returns an error item can make a workflow look successful if the next node ignores the error field. A node that always throws may be correct for a required operation. Document the choice in the node description and test both workflow settings.

Test the parameter contract before publishing

Run the same checks against the n8n version and package version you plan to support. The following matrix catches errors that a fixed value in the editor will not reveal:

Test Expected result
Required fixed field is empty The editor flags it or runtime validation returns a named field error
Expression returns an empty string The node stops before the request and points to the current item
Number expression returns "25" The validator accepts it only if the API contract permits numeric strings and normalizes it intentionally
Page size is zero, negative, decimal, or above the API limit The node returns a clear range error before the request
URL has a typo or unsupported protocol URL validation identifies the field and accepted protocols
Incoming item misses a required JSON key The failed item index is included in the error
Two items resolve different IDs Each request uses its own item index and value
Update has no changed fields The node reports the operation rule without a remote call
Continue On Fail is enabled Failed output keeps pairedItem for the source item
Imported workflow contains an old field value Runtime validation applies the current contract

Run the matrix with npm run dev. From n8n 3.0, n8n-node dev runs n8n in a container, so Docker or Podman must be installed, or use n8n-node dev --external-n8n against an instance started with N8N_DEV_RELOAD=true. Use the NativeShip debugging guide to inspect the resolved parameters and item index while stepping through a node. Record the n8n and package versions with the result. If a validation rule changes the request contract, record that change in the package release notes and the node versioning guide.

The examples here are implementation patterns to run and review. They do not prove that a generated package or a particular API accepts the shown fields. Confirm the target API schema, run the matrix, and keep unsupported assumptions out of the node's public description.

Frequently asked questions

Required metadata helps the editor, but expressions, imported workflows, and incoming item values are resolved at runtime. The execute method must check the value that will enter the request.

Use NodeOperationError for local validation, missing parameters, configuration problems, and workflow logic errors. Use NodeApiError for a failure returned by the external service.

The index tells n8n which input item should resolve an expression. Using index zero for every loop iteration can reuse the first item's value for later requests.

No. Check the expected type first. Normalize a value only when the API contract permits that conversion, and reject empty strings, invalid numbers, and unsupported formats.

No. A regex can check a local shape such as a prefix and allowed characters. The service still decides whether the ID exists and whether the credential can access it.

Return an error item with the source item's pairedItem when that behavior is intentional. Otherwise throw NodeOperationError with itemIndex so the workflow stops and n8n can identify the failed item.

Test fixed values, expressions, missing incoming fields, invalid types, boundary numbers, malformed URLs, operation-specific combinations, multiple items, imported workflows, and both Continue On Fail settings on the supported n8n version.

Ready to launch your n8n, Make, Zapier or Activepieces integration?

Give your customers another way to use your product.

Get started