n8n-io/n8n · error · OperationalError

Invalid message type

Error message

Invalid message type

What it means

Thrown by getMessagesPromptTemplates in promptUtils when a message template's 'type' does not match any of SystemMessagePromptTemplate, AIMessagePromptTemplate, or HumanMessagePromptTemplate (matched via lc_name()). The message type is unrecognized, so no LangChain prompt-template class can be selected.

Source

Thrown at packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/methods/promptUtils.ts:53

	context,
	itemIndex,
	messages,
}: {
	context: IExecuteFunctions;
	itemIndex: number;
	messages: MessageTemplate[];
}): Promise<BaseMessagePromptTemplateLike[]> {
	return await Promise.all(
		messages.map(async (message) => {
			// Find the appropriate message class based on type
			const messageClass = [
				SystemMessagePromptTemplate,
				AIMessagePromptTemplate,
				HumanMessagePromptTemplate,
			].find((m) => m.lc_name() === message.type);

			if (!messageClass) {
				throw new OperationalError('Invalid message type', {
					extra: { messageType: message.type },
				});
			}

			// Handle image messages specially for human messages
			if (messageClass === HumanMessagePromptTemplate && message.messageType !== 'text') {
				return await createImageMessage({ context, itemIndex, message });
			}

			// Process text messages
			// Escape curly braces in the message to prevent LangChain from treating them as variables
			return messageClass.fromTemplate(
				(message.message || '').replace(/[{}]/g, (match) => match + match),
			);
		}),
	);
}

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Open the node's Messages config and set each message's type to one of: system, ai, or human.
  2. If hand-editing workflow JSON, ensure each message.type is exactly one of the supported lc_name() values.
  3. Delete and re-create the offending message row to clear stale fixedCollection state.
  4. Upgrade/migrate the workflow through the supported node version path rather than editing raw JSON.

Example fix

// before
const messageClass = [SystemMessagePromptTemplate, AIMessagePromptTemplate, HumanMessagePromptTemplate]
  .find((m) => m.lc_name() === message.type);
if (!messageClass) {
  throw new OperationalError('Invalid message type', { extra: { messageType: message.type } });
}

// after — enumerate accepted values in the error
const ACCEPTED = ['system', 'ai', 'human'];
if (!ACCEPTED.includes(message.type)) {
  throw new OperationalError(`Invalid message type '${message.type}'. Accepted: ${ACCEPTED.join(', ')}`, {
    extra: { messageType: message.type },
  });
}
Defensive patterns

Strategy: validation

Validate before calling

const VALID_TYPES = new Set(['system', 'ai', 'human']);
for (const m of messages) {
  if (!VALID_TYPES.has(m.type)) {
    throw new Error(`Invalid message type '${m.type}'. Must be system, ai, or human.`);
  }
}

Type guard

function isValidMessageType(t: unknown): t is 'system' | 'ai' | 'human' {
  return t === 'system' || t === 'ai' || t === 'human';
}

Prevention

When it happens

Trigger: A message row in the Basic LLM Chain 'Messages' fixedCollection has a 'type' value that is not 'system', 'ai', or 'human' (e.g. a typo, a legacy value, or an injected custom value). The .find() returns undefined and OperationalError is thrown with the bad type recorded in extra.messageType.

Common situations: Workflow exported from an older node version with a renamed message type; user hand-edited the workflow JSON and introduced an invalid type; a template was migrated partially.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/669f5bdae55fc12c. Report an issue: GitHub.