diff --git a/packages/@n8n/nodes-langchain/nodes/llms/LmChatAwsBedrock/LmChatAwsBedrock.node.ts b/packages/@n8n/nodes-langchain/nodes/llms/LmChatAwsBedrock/LmChatAwsBedrock.node.ts
index fa8da0b03f..e8d764e0c0 100644
--- a/packages/@n8n/nodes-langchain/nodes/llms/LmChatAwsBedrock/LmChatAwsBedrock.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/llms/LmChatAwsBedrock/LmChatAwsBedrock.node.ts
@@ -60,6 +60,7 @@ export class LmChatAwsBedrock implements INodeType {
displayName: 'Model',
name: 'model',
type: 'options',
+ allowArbitraryValues: true, // Hide issues when model name is specified in the expression and does not match any of the options
description:
'The model which will generate the completion. Learn more.',
typeOptions: {
diff --git a/packages/frontend/editor-ui/src/components/ParameterInput.vue b/packages/frontend/editor-ui/src/components/ParameterInput.vue
index fb6fd57449..ad269933d3 100644
--- a/packages/frontend/editor-ui/src/components/ParameterInput.vue
+++ b/packages/frontend/editor-ui/src/components/ParameterInput.vue
@@ -417,17 +417,19 @@ const getIssues = computed(() => {
['options', 'multiOptions'].includes(props.parameter.type) &&
!remoteParameterOptionsLoading.value &&
remoteParameterOptionsLoadingIssues.value === null &&
- parameterOptions.value
+ parameterOptions.value &&
+ (!isModelValueExpression.value || props.expressionEvaluated !== null)
) {
- // Check if the value resolves to a valid option
- // Currently it only displays an error in the node itself in
+ // Check if the value resolves to a valid option.
+ // For expressions do not validate if there is no evaluated value.
+ // Currently, it only displays an error in the node itself in
// case the value is not valid. The workflow can still be executed
// and the error is not displayed on the node in the workflow
const validOptions = parameterOptions.value.map((options) => options.value);
let checkValues: string[] = [];
- if (!shouldSkipParamValidation(displayValue.value)) {
+ if (!shouldSkipParamValidation(props.parameter, displayValue.value)) {
if (Array.isArray(displayValue.value)) {
checkValues = checkValues.concat(displayValue.value);
} else {
diff --git a/packages/frontend/editor-ui/src/composables/useNodeSettingsParameters.test.ts b/packages/frontend/editor-ui/src/composables/useNodeSettingsParameters.test.ts
index b1dc4b6186..83e5d14e73 100644
--- a/packages/frontend/editor-ui/src/composables/useNodeSettingsParameters.test.ts
+++ b/packages/frontend/editor-ui/src/composables/useNodeSettingsParameters.test.ts
@@ -14,11 +14,11 @@ import { mockedStore } from '@/__tests__/utils';
import type { INodeUi } from '@/Interface';
describe('useNodeSettingsParameters', () => {
- describe('setValue', () => {
- beforeEach(() => {
- setActivePinia(createTestingPinia());
- });
+ beforeEach(() => {
+ setActivePinia(createTestingPinia());
+ });
+ describe('setValue', () => {
afterEach(() => {
vi.clearAllMocks();
});
diff --git a/packages/frontend/editor-ui/src/utils/nodeSettingsUtils.test.ts b/packages/frontend/editor-ui/src/utils/nodeSettingsUtils.test.ts
index e726644108..a0e0644c8b 100644
--- a/packages/frontend/editor-ui/src/utils/nodeSettingsUtils.test.ts
+++ b/packages/frontend/editor-ui/src/utils/nodeSettingsUtils.test.ts
@@ -6,6 +6,7 @@ import type {
IDataObject,
INodeTypeDescription,
INodePropertyOptions,
+ INodeProperties,
} from 'n8n-workflow';
import {
updateDynamicConnections,
@@ -13,8 +14,9 @@ import {
nameIsParameter,
formatAsExpression,
parseFromExpression,
+ shouldSkipParamValidation,
} from './nodeSettingsUtils';
-import { SWITCH_NODE_TYPE } from '@/constants';
+import { CUSTOM_API_CALL_KEY, SWITCH_NODE_TYPE } from '@/constants';
import type { INodeUi, IUpdateInformation } from '@/Interface';
describe('updateDynamicConnections', () => {
@@ -396,3 +398,158 @@ describe('parseFromExpression', () => {
expect(parseFromExpression({}, undefined, 'json', null, [])).toBeNull();
});
});
+
+describe('shouldSkipParamValidation', () => {
+ describe('CUSTOM_API_CALL_KEY detection', () => {
+ it('should skip validation when value is CUSTOM_API_CALL_KEY', () => {
+ const parameter: INodeProperties = {
+ name: 'testParam',
+ displayName: 'Test Parameter',
+ type: 'string',
+ default: '',
+ };
+
+ const result = shouldSkipParamValidation(parameter, CUSTOM_API_CALL_KEY);
+ expect(result).toBe(true);
+ });
+
+ it('should skip validation when value is a string containing CUSTOM_API_CALL_KEY', () => {
+ const parameter: INodeProperties = {
+ name: 'testParam',
+ displayName: 'Test Parameter',
+ type: 'string',
+ default: '',
+ };
+
+ const valueWithKey = `some prefix ${CUSTOM_API_CALL_KEY} some suffix`;
+ const result = shouldSkipParamValidation(parameter, valueWithKey);
+ expect(result).toBe(true);
+ });
+
+ it('should not skip validation when value is a string not containing CUSTOM_API_CALL_KEY', () => {
+ const parameter: INodeProperties = {
+ name: 'testParam',
+ displayName: 'Test Parameter',
+ type: 'string',
+ default: '',
+ };
+
+ const result = shouldSkipParamValidation(parameter, 'regular string value');
+ expect(result).toBe(false);
+ });
+ });
+
+ describe('options parameter type with allowArbitraryValues', () => {
+ it('should skip validation for options parameter with allowArbitraryValues=true', () => {
+ const parameter: INodeProperties = {
+ name: 'optionsParam',
+ displayName: 'Options Parameter',
+ type: 'options',
+ options: [
+ { name: 'Option 1', value: 'option1' },
+ { name: 'Option 2', value: 'option2' },
+ ],
+ allowArbitraryValues: true,
+ default: '',
+ };
+
+ const result = shouldSkipParamValidation(parameter, 'arbitrary_value');
+ expect(result).toBe(true);
+ });
+
+ it('should not skip validation for options parameter with allowArbitraryValues=false', () => {
+ const parameter: INodeProperties = {
+ name: 'optionsParam',
+ displayName: 'Options Parameter',
+ type: 'options',
+ options: [
+ { name: 'Option 1', value: 'option1' },
+ { name: 'Option 2', value: 'option2' },
+ ],
+ allowArbitraryValues: false,
+ default: '',
+ };
+
+ const result = shouldSkipParamValidation(parameter, 'arbitrary_value');
+ expect(result).toBe(false);
+ });
+
+ it('should not skip validation for options parameter with allowArbitraryValues=undefined', () => {
+ const parameter: INodeProperties = {
+ name: 'optionsParam',
+ displayName: 'Options Parameter',
+ type: 'options',
+ options: [
+ { name: 'Option 1', value: 'option1' },
+ { name: 'Option 2', value: 'option2' },
+ ],
+ default: '',
+ };
+
+ const result = shouldSkipParamValidation(parameter, 'arbitrary_value');
+ expect(result).toBe(false);
+ });
+ });
+
+ describe('multiOptions parameter type with allowArbitraryValues', () => {
+ it('should skip validation for multiOptions parameter with allowArbitraryValues=true', () => {
+ const parameter: INodeProperties = {
+ name: 'multiOptionsParam',
+ displayName: 'Multi Options Parameter',
+ type: 'multiOptions',
+ options: [
+ { name: 'Option 1', value: 'option1' },
+ { name: 'Option 2', value: 'option2' },
+ ],
+ allowArbitraryValues: true,
+ default: [],
+ };
+
+ const result = shouldSkipParamValidation(parameter, ['arbitrary_value']);
+ expect(result).toBe(true);
+ });
+
+ it('should not skip validation for multiOptions parameter with allowArbitraryValues=false', () => {
+ const parameter: INodeProperties = {
+ name: 'multiOptionsParam',
+ displayName: 'Multi Options Parameter',
+ type: 'multiOptions',
+ options: [
+ { name: 'Option 1', value: 'option1' },
+ { name: 'Option 2', value: 'option2' },
+ ],
+ allowArbitraryValues: false,
+ default: [],
+ };
+
+ const result = shouldSkipParamValidation(parameter, ['arbitrary_value']);
+ expect(result).toBe(false);
+ });
+ });
+
+ describe('non-options parameter types', () => {
+ const nonOptionsParameterTypes = [
+ 'string',
+ 'number',
+ 'boolean',
+ 'json',
+ 'dateTime',
+ 'color',
+ ] as Array;
+
+ nonOptionsParameterTypes.forEach((type) => {
+ it(`should not skip validation for ${type} parameter type regardless of allowArbitraryValues`, () => {
+ const parameter: INodeProperties = {
+ name: 'testParam',
+ displayName: 'Test Parameter',
+ type,
+ allowArbitraryValues: true,
+ default: '',
+ };
+
+ const result = shouldSkipParamValidation(parameter, 'test_value');
+ expect(result).toBe(false);
+ });
+ });
+ });
+});
diff --git a/packages/frontend/editor-ui/src/utils/nodeSettingsUtils.ts b/packages/frontend/editor-ui/src/utils/nodeSettingsUtils.ts
index a3231664a6..4e5c9ccea6 100644
--- a/packages/frontend/editor-ui/src/utils/nodeSettingsUtils.ts
+++ b/packages/frontend/editor-ui/src/utils/nodeSettingsUtils.ts
@@ -359,6 +359,13 @@ export function parseFromExpression(
return null;
}
-export function shouldSkipParamValidation(value: string | number | boolean | null) {
- return typeof value === 'string' && value.includes(CUSTOM_API_CALL_KEY);
+export function shouldSkipParamValidation(
+ parameter: INodeProperties,
+ value: NodeParameterValueType,
+) {
+ return (
+ (typeof value === 'string' && value.includes(CUSTOM_API_CALL_KEY)) ||
+ (['options', 'multiOptions'].includes(parameter.type) &&
+ Boolean(parameter.allowArbitraryValues))
+ );
}
diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts
index 4643c362d5..6e5aa76a6f 100644
--- a/packages/workflow/src/interfaces.ts
+++ b/packages/workflow/src/interfaces.ts
@@ -1466,6 +1466,8 @@ export interface INodeProperties {
// allows to skip validation during execution or set custom validation/casting logic inside node
// inline error messages would still be shown in UI
ignoreValidationDuringExecution?: boolean;
+ // for type: options | multiOptions – skip validation of the value (e.g. when value is not in the list and specified via expression)
+ allowArbitraryValues?: boolean;
}
export interface INodePropertyModeTypeOptions {