Repository: n8n-io/n8n
Pull request #13922: feat(editor): Implement folder move functionality (no-changelog)
Author: login: MiloradFilipovic
Labels: name: n8n team
description: Authored by the n8n team
name: Released
Created: 2025-03-14T09:12:17Z | Merged: 2025-03-17T11:46:22Z

## Summary
Phase 4 of folders project: Adding functionality to move folders and workflows + telemetry

<img width="484" alt="image" src="https://github.com/user-attachments/assets/b9694702-9459-47a1-8798-94312ca067c7" />


## Related Linear tickets, Github issues, and Community forum posts
Closes ADO-3160
Closes ADO-3320

## Review / Merge checklist

- [x] PR title and summary are descriptive. ([conventions](../blob/master/.github/pull_request_title_conventions.md)) <!--
   **Remember, the title automatically goes into the changelog.
   Use `(no-changelog)` otherwise.**
-->
- [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created.
- [ ] Tests included. <!--
   A bug is not considered fixed, unless a test is added to prevent it from happening again.
   A feature is not complete without tests.
-->
- [ ] PR Labeled with `release/backport` (if the PR is an urgent fix that needs to be backported)



Diff:
diff --git a/packages/frontend/editor-ui/src/Interface.ts b/packages/frontend/editor-ui/src/Interface.ts
index 73fca7fc74618..9a1e8c0bc3098 100644
--- a/packages/frontend/editor-ui/src/Interface.ts
+++ b/packages/frontend/editor-ui/src/Interface.ts
@@ -240,11 +240,11 @@ export interface IWorkflowDataUpdate {
 	pinData?: IPinData;
 	versionId?: string;
 	meta?: WorkflowMetadata;
+	parentFolderId?: string;
 }
 
 export interface IWorkflowDataCreate extends IWorkflowDataUpdate {
 	projectId?: string;
-	parentFolderId?: string;
 }
 
 /**
@@ -326,7 +326,7 @@ export type BaseResource = {
 
 export type WorkflowListItem = Omit<
 	IWorkflowDb,
-	'nodes' | 'connections' | 'settings' | 'pinData' | 'versionId' | 'usedCredentials' | 'meta'
+	'nodes' | 'connections' | 'settings' | 'pinData' | 'usedCredentials' | 'meta'
 > & {
 	resource: 'workflow';
 	parentFolder?: { id: string; name: string };
diff --git a/packages/frontend/editor-ui/src/api/workflows.ts b/packages/frontend/editor-ui/src/api/workflows.ts
index 5e97417ccdac8..9d2bd5c0c6425 100644
--- a/packages/frontend/editor-ui/src/api/workflows.ts
+++ b/packages/frontend/editor-ui/src/api/workflows.ts
@@ -137,6 +137,48 @@ export async function renameFolder(
 export async function getProjectFolders(
 	context: IRestApiContext,
 	projectId: string,
+	options?: {
+		skip?: number;
+		take?: number;
+		sortBy?: string;
+	},
+	filter?: {
+		excludeFolderIdAndDescendants?: string;
+		name?: string;
+	},
 ): Promise<FolderListItem[]> {
-	return await makeRestApiRequest(context, 'GET', `/projects/${projectId}/folders`);
+	const res = await getFullApiResponse<FolderListItem[]>(
+		context,
+		'GET',
+		`/projects/${projectId}/folders`,
+		{
+			...(filter ? { filter } : {}),
+			...(options ? options : {}),
+		},
+	);
+	return res.data;
+}
+
+export async function moveFolder(
+	context: IRestApiContext,
+	projectId: string,
+	folderId: string,
+	parentFolderId?: string,
+): Promise<void> {
+	return await makeRestApiRequest(context, 'PATCH', `/projects/${projectId}/folders/${folderId}`, {
+		parentFolderId,
+	});
+}
+
+export async function getFolderContent(
+	context: IRestApiContext,
+	projectId: string,
+	folderId: string,
+): Promise<{ totalSubFolders: number; totalWorkflows: number }> {
+	const res = await getFullApiResponse<{ totalSubFolders: number; totalWorkflows: number }>(
+		context,
+		'GET',
+		`/projects/${projectId}/folders/${folderId}/content`,
+	);
+	return res.data;
 }
diff --git a/packages/frontend/editor-ui/src/components/Folders/DeleteFolderModal.vue b/packages/frontend/editor-ui/src/components/Folders/DeleteFolderModal.vue
index ffed4bc890502..840792fed6c58 100644
--- a/packages/frontend/editor-ui/src/components/Folders/DeleteFolderModal.vue
+++ b/packages/frontend/editor-ui/src/components/Folders/DeleteFolderModal.vue
@@ -1,5 +1,5 @@
 <script lang="ts" setup>
-import { ref, computed, onMounted } from 'vue';
+import { ref, computed } from 'vue';
 import { useToast } from '@/composables/useToast';
 import Modal from '@/components/Modal.vue';
 import { createEventBus, type EventBus } from '@n8n/utils/event-bus';
@@ -7,6 +7,7 @@ import { useI18n } from '@/composables/useI18n';
 import { useFoldersStore } from '@/stores/folders.store';
 import { useRoute } from 'vue-router';
 import type { FolderListItem } from '@/Interface';
+import { useProjectsStore } from '@/stores/projects.store';
 
 const props = defineProps<{
 	modalName: string;
@@ -26,34 +27,25 @@ const i18n = useI18n();
 const route = useRoute();
 
 const foldersStore = useFoldersStore();
+const projectsStore = useProjectsStore();
 
 const loading = ref(false);
 const operation = ref('');
 const deleteConfirmText = ref('');
-const selectedFolderId = ref<string | null>(null);
+const selectedFolder = ref<{ id: string; name: string } | null>(null);
 const projectFolders = ref<FolderListItem[]>([]);
 
 const currentFolder = computed(() => {
 	return projectFolders.value.find((folder) => folder.id === props.activeId);
 });
 
-// Available folders to transfer are all folders except the current folder, it's parent and its children
-const availableFolders = computed(() => {
-	return projectFolders.value.filter(
-		(folder) =>
-			folder.id !== props.activeId &&
-			folder.parentFolder?.id !== props.activeId &&
-			folder.id !== currentFolder.value?.parentFolder?.id,
-	);
-});
-
 const folderToDelete = computed(() => {
 	if (!props.activeId) return null;
 	return foldersStore.breadcrumbsCache[props.activeId];
 });
 
 const isPending = computed(() => {
-	return folderToDelete.value ? !folderToDelete.value.name : false;
+	return selectedFolder.value ? !selectedFolder.value.name : false;
 });
 
 const title = computed(() => {
@@ -74,12 +66,35 @@ const enabled = computed(() => {
 	) {
 		return true;
 	}
-	if (operation.value === 'transfer' && selectedFolderId.value) {
+	if (operation.value === 'transfer' && selectedFolder.value) {
 		return true;
 	}
 	return false;
 });
 
+const folderContentWarningMessage = computed(() => {
+	const folderCount = props.data.content.subFolderCount ?? 0;
+	const workflowCount = props.data.content.workflowCount ?? 0;
+
+	let folderText = '';
+	let workflowText = '';
+	if (folderCount > 0) {
+		folderText = i18n.baseText('folder.count', { interpolate: { count: folderCount } });
+	}
+	if (workflowCount > 0) {
+		workflowText = i18n.baseText('workflow.count', { interpolate: { count: workflowCount } });
+	}
+	if (folderCount > 0 && workflowCount > 0) {
+		folderText += ` ${i18n.baseText('folder.and.workflow.separator')} `;
+	}
+	return i18n.baseText('folder.delete.modal.confirmation', {
+		interpolate: {
+			folders: folderText,
+			workflows: workflowText,
+		},
+	});
+});
+
 async function onSubmit() {
 	if (!enabled.value) {
 		return;
@@ -90,16 +105,13 @@ async function onSubmit() {
 		await foldersStore.deleteFolder(
 			route.params.projectId as string,
 			props.activeId,
-			selectedFolderId.value ?? undefined,
+			selectedFolder.value?.id ?? undefined,
 		);
 
 		let message = '';
-		if (selectedFolderId.value) {
-			const selectedFolder = availableFolders.value.find(
-				(folder) => folder.id === selectedFolderId.value,
-			);
+		if (selectedFolder.value) {
 			message = i18n.baseText('folders.transfer.confirm.message', {
-				interpolate: { folderName: selectedFolder?.name ?? '' },
+				interpolate: { folderName: selectedFolder.value.name ?? '' },
 			});
 		}
 		showMessage({
@@ -107,7 +119,11 @@ async function onSubmit() {
 			title: i18n.baseText('folders.delete.success.message'),
 			message,
 		});
-		props.data.workflowListEventBus.emit('folder-deleted', { folderId: props.activeId });
+		props.data.workflowListEventBus.emit('folder-deleted', {
+			folderId: props.activeId,
+			workflowCount: props.data.content.workflowCount,
+			folderCount: props.data.content.subFolderCount,
+		});
 		modalBus.emit('close');
 	} catch (error) {
 		showError(error, i18n.baseText('folders.delete.error.message'));
@@ -116,9 +132,9 @@ async function onSubmit() {
 	}
 }
 
-onMounted(async () => {
-	projectFolders.value = await foldersStore.fetchProjectFolders(route.params.projectId as string);
-});
+const onFolderSelected = (payload: { id: string; name: string }) => {
+	selectedFolder.value = payload;
+};
 </script>
 
 <template>
@@ -139,9 +155,7 @@ onMounted(async () => {
 				</div>
 				<div v-else :class="$style.content">
 					<div>
-						<n8n-text color="text-base">{{
-							i18n.baseText('folder.delete.modal.confirmation')
-						}}</n8n-text>
+						<n8n-text color="text-base">{{ folderContentWarningMessage }}</n8n-text>
 					</div>
 					<el-radio
 						v-model="operation"
@@ -155,24 +169,13 @@ onMounted(async () => {
 						<n8n-text color="text-dark">{{
 							i18n.baseText('folders.transfer.selectFolder')
 						}}</n8n-text>
-						<N8nSelect
-							v-model="selectedFolderId"
-							option-label="name"
-							option-value="id"
-							:placeholder="i18n.baseText('folders.transfer.selectFolder')"
-						>
-							<N8nOption
-								v-for="folder in availableFolders"
-								:key="folder.id"
-								:value="folder.id"
-								:label="folder.name"
-							>
-								<div :class="$style['folder-select-item']">
-									<n8n-icon icon="folder" />
-									<span> {{ folder.name }}</span>
-								</div>
-							</N8nOption>
-						</N8nSelect>
+						<MoveToFolderDropdown
+							v-if="projectsStore.currentProject"
+							:current-folder-id="props.activeId"
+							:current-project-id="projectsStore.currentProject?.id"
+							:parent-folder-id="currentFolder?.parentFolder?.id"
+							@folder:selected="onFolderSelected"
+						/>
 					</div>
 					<el-radio
 						v-model="operation"
diff --git a/packages/frontend/editor-ui/src/components/Folders/MoveToFolderDropdown.vue b/packages/frontend/editor-ui/src/components/Folders/MoveToFolderDropdown.vue
new file mode 100644
index 0000000000000..d8d9ca7553a28
--- /dev/null
+++ b/packages/frontend/editor-ui/src/components/Folders/MoveToFolderDropdown.vue
@@ -0,0 +1,114 @@
+<script setup lang="ts">
+import { useI18n } from '@/composables/useI18n';
+import type { FolderListItem } from '@/Interface';
+import { useFoldersStore } from '@/stores/folders.store';
+import { N8nSelect } from '@n8n/design-system';
+import { ref } from 'vue';
+
+/**
+ * This component is used to select a folder to move a resource (folder or workflow) to.
+ * Based on the provided resource type, it fetches the available folders and displays them in a dropdown.
+ * For folders, it filters out current folder parent and all off it's children (done in the back-end)
+ * For workflows, it only filters out the current workflows's folder.
+ */
+
+type Props = {
+	currentProjectId: string;
+	currentFolderId?: string;
+	parentFolderId?: string;
+};
+
+const props = withDefaults(defineProps<Props>(), {
+	currentFolderId: '',
+	parentFolderId: '',
+});
+
+const emit = defineEmits<{
+	'folder:selected': [value: { id: string; name: string }];
+}>();
+
+const i18n = useI18n();
+
+const foldersStore = useFoldersStore();
+
+const moveFolderDropdown = ref<InstanceType<typeof N8nSelect>>();
+const selectedFolderId = ref<string | null>(null);
+const availableFolders = ref<FolderListItem[]>([]);
+const loading = ref(false);
+
+const fetchAvailableFolders = async (query?: string) => {
+	if (!query) {
+		availableFolders.value = [];
+		return;
+	}
+	loading.value = true;
+	const folders = await foldersStore.fetchFoldersAvailableForMove(
+		props.currentProjectId,
+		props.currentFolderId,
+		{ name: query ?? undefined },
+	);
+	if (!props.parentFolderId) {
+		availableFolders.value = folders;
+	} else {
+		availableFolders.value = folders.filter((folder) => folder.id !== props.parentFolderId);
+	}
+	loading.value = false;
+};
+
+const onFolderSelected = (folderId: string) => {
+	const selectedFolder = availableFolders.value.find((folder) => folder.id === folderId);
+	if (!selectedFolder) {
+		return;
+	}
+	emit('folder:selected', { id: folderId, name: selectedFolder.name });
+};
+</script>
+
+<template>
+	<div :class="$style['move-folder-dropdown']" data-test-id="move-to-folder-dropdown">
+		<N8nSelect
+			ref="moveFolderDropdown"
+			v-model="selectedFolderId"
+			:filterable="true"
+			:remote="true"
+			:remote-method="fetchAvailableFolders"
+			:loading="loading"
+			:placeholder="i18n.baseText('folders.move.modal.select.placeholder')"
+			option-label="name"
+			option-value="id"
+			@update:model-value="onFolderSelected"
+		>
+			<N8nOption
+				v-for="folder in availableFolders"
+				:key="folder.id"
+				:value="folder.id"
+				:label="folder.name"
+			>
+				<div :class="$style['folder-select-item']">
+					<n8n-icon :class="$style['folder-icon']" icon="folder" />
+					<span :class="$style['folder-name']"> {{ folder.name }}</span>
+				</div>
+			</N8nOption>
+		</N8nSelect>
+	</div>
+</template>
+
+<style module lang="scss">
+.move-folder-dropdown {
+	display: flex;
+}
+
+.folder-select-item {
+	display: flex;
+	gap: var(--spacing-2xs);
+	align-items: center;
+	max-width: 90%;
+	overflow: hidden;
+	white-space: nowrap;
+
+	.folder-name {
+		text-overflow: ellipsis;
+		overflow: hidden;
+	}
+}
+</style>
diff --git a/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.test.ts b/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.test.ts
new file mode 100644
index 0000000000000..def649d03bbc5
--- /dev/null
+++ b/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.test.ts
@@ -0,0 +1,97 @@
+import { createComponentRenderer } from '@/__tests__/render';
+import MoveToFolderModal from './MoveToFolderModal.vue';
+import { createTestingPinia } from '@pinia/testing';
+import { cleanupAppModals, createAppModals, mockedStore } from '@/__tests__/utils';
+import { useUIStore } from '@/stores/ui.store';
+import { MOVE_FOLDER_MODAL_KEY } from '@/constants';
+import { waitFor } from '@testing-library/vue';
+import { screen } from '@testing-library/vue';
+
+vi.mock('vue-router', () => {
+	const push = vi.fn();
+	const resolve = vi.fn().mockReturnValue({ href: '/projects/1/folders/1' });
+	return {
+		useRouter: vi.fn().mockReturnValue({
+			push,
+			resolve,
+		}),
+		useRoute: vi.fn().mockReturnValue({
+			params: {
+				projectId: '1',
+
+				folderId: '1',
+			},
+			query: {},
+		}),
+		RouterLink: vi.fn(),
+	};
+});
+
+const renderComponent = createComponentRenderer(MoveToFolderModal, {
+	props: {
+		modalName: MOVE_FOLDER_MODAL_KEY,
+	},
+});
+
+const TEST_FOLDER_RESOURCE = {
+	id: 'test-folder-id',
+	name: 'Test Folder',
+	parentFolderId: 'test-parent-folder-id',
+};
+
+const TEST_WORKFLOW_RESOURCE = {
+	id: 'test-workflow-id',
+	name: 'Test Workflow',
+	parentFolderId: 'test-parent-folder-id',
+};
+
+let uiStore: ReturnType<typeof mockedStore<typeof useUIStore>>;
+describe('MoveToFolderModal', () => {
+	beforeEach(() => {
+		createAppModals();
+		createTestingPinia();
+		uiStore = mockedStore(useUIStore);
+		uiStore.modalsById = {
+			[MOVE_FOLDER_MODAL_KEY]: {
+				open: true,
+			},
+		};
+	});
+
+	afterEach(() => {
+		cleanupAppModals();
+		vi.clearAllMocks();
+	});
+
+	it('should render for folder resource', async () => {
+		const { getByTestId } = renderComponent({
+			props: {
+				data: {
+					resource: TEST_FOLDER_RESOURCE,
+					resourceType: 'folder',
+				},
+			},
+		});
+		await waitFor(() => expect(getByTestId('moveFolder-modal')).toBeInTheDocument());
+		expect(
+			screen.getByText(`Move "${TEST_FOLDER_RESOURCE.name}" to another folder`),
+		).toBeInTheDocument();
+		expect(screen.getByTestId('move-folder-description')).toBeInTheDocument();
+	});
+
+	it('should render for workflow resource', async () => {
+		const { getByTestId } = renderComponent({
+			props: {
+				data: {
+					resource: TEST_WORKFLOW_RESOURCE,
+					resourceType: 'workflow',
+				},
+			},
+		});
+		await waitFor(() => expect(getByTestId('moveFolder-modal')).toBeInTheDocument());
+		expect(
+			screen.getByText(`Move "${TEST_WORKFLOW_RESOURCE.name}" to another folder`),
+		).toBeInTheDocument();
+		expect(screen.queryByTestId('move-folder-description')).not.toBeInTheDocument();
+	});
+});
diff --git a/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.vue b/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.vue
new file mode 100644
index 0000000000000..c5d32d0ea68f1
--- /dev/null
+++ b/packages/frontend/editor-ui/src/components/Folders/MoveToFolderModal.vue
@@ -0,0 +1,132 @@
+<script lang="ts" setup>
+import { useI18n } from '@/composables/useI18n';
+import { MOVE_FOLDER_MODAL_KEY } from '@/constants';
+import { useProjectsStore } from '@/stores/projects.store';
+import { useUIStore } from '@/stores/ui.store';
+import { type EventBus } from '@n8n/utils/event-bus';
+import { computed, ref } from 'vue';
+
+/**
+ * This modal is used to move a resource (folder or workflow) to a different folder.
+ */
+
+type Props = {
+	modalName: string;
+	data: {
+		resourceType: 'folder' | 'workflow';
+		resource: {
+			id: string;
+			name: string;
+			parentFolderId?: string;
+		};
+		workflowListEventBus: EventBus;
+	};
+};
+
+const props = defineProps<Props>();
+
+const i18n = useI18n();
+
+const projectsStore = useProjectsStore();
+const uiStore = useUIStore();
+
+const selectedFolder = ref<{ id: string; name: string } | null>(null);
+
+const title = computed(() => {
+	return i18n.baseText('folders.move.modal.title', {
+		interpolate: { folderName: props.data.resource.name },
+	});
+});
+
+const currentFolder = computed(() => {
+	if (props.data.resourceType === 'workflow') {
+		return;
+	}
+	return {
+		id: props.data.resource.id,
+		name: props.data.resource.name,
+	};
+});
+
+const onFolderSelected = (payload: { id: string; name: string }) => {
+	selectedFolder.value = payload;
+};
+
+const onSubmit = () => {
+	if (props.data.resourceType === 'folder') {
+		props.data.workflowListEventBus.emit('folder-moved', {
+			newParent: selectedFolder.value,
+			folder: { id: props.data.resource.id, name: props.data.resource.name },
+		});
+	} else {
+		props.data.workflowListEventBus.emit('workflow-moved', {
+			newParent: selectedFolder.value,
+			workflow: {
+				id: props.data.resource.id,
+				name: props.data.resource.name,
+				oldParentId: props.data.resource.parentFolderId,
+			},
+		});
+	}
+	uiStore.closeModal(MOVE_FOLDER_MODAL_KEY);
+};
+</script>
+
+<template>
+	<Modal :name="modalName" :title="title" width="500" :class="$style.container">
+		<template #content>
+			<MoveToFolderDropdown
+				v-if="projectsStore.currentProject"
+				:current-folder-id="currentFolder?.id"
+				:current-project-id="projectsStore.currentProject?.id"
+				:parent-folder-id="props.data.resource.parentFolderId"
+				:exclude-only-parent="props.data.resourceType === 'workflow'"
+				@folder:selected="onFolderSelected"
+			/>
+			<p
+				v-if="props.data.resourceType === 'folder'"
+				:class="$style.description"
+				data-test-id="move-folder-description"
+			>
+				{{ i18n.baseText('folders.move.modal.description') }}
+			</p>
+		</template>
+		<template #footer="{ close }">
+			<div :class="$style.footer">
+				<n8n-button
+					type="secondary"
+					:label="i18n.baseText('generic.cancel')"
+					float="right"
+					data-test-id="cancel-move-folder-button"
+					@click="close"
+				/>
+				<n8n-button
+					:disabled="!selectedFolder"
+					:label="i18n.baseText('folders.move.modal.confirm')"
+					float="right"
+					data-test-id="confirm-move-folder-button"
+					@click="onSubmit"
+				/>
+			</div>
+		</template>
+	</Modal>
+</template>
+
+<style module lang="scss">
+.container {
+	h1 {
+		max-width: 90%;
+	}
+}
+
+.description {
+	font-size: var(--font-size-s);
+	margin: var(--spacing-s) 0;
+}
+
+.footer {
+	display: flex;
+	gap: var(--spacing-2xs);
+	justify-content: flex-end;
+}
+</style>
diff --git a/packages/frontend/editor-ui/src/components/Modals.vue b/packages/frontend/editor-ui/src/components/Modals.vue
index b4b40ed275075..7d5e82c9a8196 100644
--- a/packages/frontend/editor-ui/src/components/Modals.vue
+++ b/packages/frontend/editor-ui/src/components/Modals.vue
@@ -34,6 +34,7 @@ import {
 	PROMPT_MFA_CODE_MODAL_KEY,
 	COMMUNITY_PLUS_ENROLLMENT_MODAL,
 	DELETE_FOLDER_MODAL_KEY,
+	MOVE_FOLDER_MODAL_KEY,
 } from '@/constants';
 
 import AboutModal from '@/components/AboutModal.vue';
@@ -287,5 +288,11 @@ import type { EventBus } from '@n8n/utils/event-bus';
 				<DeleteFolderModal :modal-name="modalName" :active-id="activeId" :data="data" />
 			</template>
 		</ModalRoot>
+
+		<ModalRoot :name="MOVE_FOLDER_MODAL_KEY">
+			<template #default="{ modalName, activeId, data }">
+				<MoveToFolderModal :modal-name="modalName" :active-id="activeId" :data="data" />
+			</template>
+		</ModalRoot>
 	</div>
 </template>
diff --git a/packages/frontend/editor-ui/src/components/Projects/ProjectHeader.vue b/packages/frontend/editor-ui/src/components/Projects/ProjectHeader.vue
index e4b8aac4adbd1..b0d053a3a5458 100644
--- a/packages/frontend/editor-ui/src/components/Projects/ProjectHeader.vue
+++ b/packages/frontend/editor-ui/src/components/Projects/ProjectHeader.vue
@@ -56,8 +56,9 @@ const showSettings = computed(
 );
 
 const homeProject = computed(() => projectsStore.currentProject ?? projectsStore.personalProject);
-const isFoldersFeatureEnabled = computed(() => settingsStore.settings.folders.enabled);
-const isProjectPage = computed(() => route.name === VIEWS.PROJECTS_WORKFLOWS);
+const showFolders = computed(() => {
+	return settingsStore.isFoldersFeatureEnabled && route.name !== VIEWS.WORKFLOWS;
+});
 
 const ACTION_TYPES = {
 	WORKFLOW: 'workflow',
@@ -85,7 +86,7 @@ const menu = computed(() => {
 				!getResourcePermissions(homeProject.value?.scopes).credential.create,
 		},
 	];
-	if (isFoldersFeatureEnabled.value && isProjectPage.value) {
+	if (showFolders.value) {
 		items.push({
 			value: ACTION_TYPES.FOLDER,
 			label: i18n.baseText('projects.header.create.folder'),
diff --git a/packages/frontend/editor-ui/src/components/WorkflowCard.vue b/packages/frontend/editor-ui/src/components/WorkflowCard.vue
index a58f987a4ecb8..fb83a70ed6656 100644
--- a/packages/frontend/editor-ui/src/components/WorkflowCard.vue
+++ b/packages/frontend/editor-ui/src/components/WorkflowCard.vue
@@ -20,7 +20,7 @@ import TimeAgo from '@/components/TimeAgo.vue';
 import { useProjectsStore } from '@/stores/projects.store';
 import ProjectCardBadge from '@/components/Projects/ProjectCardBadge.vue';
 import { useI18n } from '@/composables/useI18n';
-import { useRouter } from 'vue-router';
+import { useRoute, useRouter } from 'vue-router';
 import { useTelemetry } from '@/composables/useTelemetry';
 import { ResourceType } from '@/utils/projects.utils';
 import type { EventBus } from '@n8n/utils/event-bus';
@@ -33,6 +33,7 @@ const WORKFLOW_LIST_ITEM_ACTIONS = {
 	DUPLICATE: 'duplicate',
 	DELETE: 'delete',
 	MOVE: 'move',
+	MOVE_TO_FOLDER: 'moveToFolder',
 };
 
 const props = withDefaults(
@@ -52,12 +53,14 @@ const emit = defineEmits<{
 	'click:tag': [tagId: string, e: PointerEvent];
 	'workflow:deleted': [];
 	'workflow:active-toggle': [value: { id: string; active: boolean }];
+	'action:move-to-folder': [value: { id: string; name: string; parentFolderId?: string }];
 }>();
 
 const toast = useToast();
 const message = useMessage();
 const locale = useI18n();
 const router = useRouter();
+const route = useRoute();
 const telemetry = useTelemetry();
 
 const settingsStore = useSettingsStore();
@@ -69,6 +72,11 @@ const projectsStore = useProjectsStore();
 const resourceTypeLabel = computed(() => locale.baseText('generic.workflow').toLowerCase());
 const currentUser = computed(() => usersStore.currentUser ?? ({} as IUser));
 const workflowPermissions = computed(() => getResourcePermissions(props.data.scopes).workflow);
+
+const showFolders = computed(() => {
+	return settingsStore.isFoldersFeatureEnabled && route.name !== VIEWS.WORKFLOWS;
+});
+
 const actions = computed(() => {
 	const items = [
 		{
@@ -88,6 +96,13 @@ const actions = computed(() => {
 		});
 	}
 
+	if (workflowPermissions.value.update && !props.readOnly && showFolders.value) {
+		items.push({
+			label: locale.baseText('folders.actions.moveToFolder'),
+			value: WORKFLOW_LIST_ITEM_ACTIONS.MOVE_TO_FOLDER,
+		});
+	}
+
 	if (workflowPermissions.value.move && projectsStore.isTeamProjectFeatureEnabled) {
 		items.push({
 			label: locale.baseText('workflows.item.changeOwner'),
@@ -175,6 +190,13 @@ async function onAction(action: string) {
 		case WORKFLOW_LIST_ITEM_ACTIONS.MOVE:
 			moveResource();
 			break;
+		case WORKFLOW_LIST_ITEM_ACTIONS.MOVE_TO_FOLDER:
+			emit('action:move-to-folder', {
+				id: props.data.id,
+				name: props.data.name,
+				parentFolderId: props.data.parentFolder?.id,
+			});
+			break;
 	}
 }
 
diff --git a/packages/frontend/editor-ui/src/components/layouts/ResourcesListLayout.vue b/packages/frontend/editor-ui/src/components/layouts/ResourcesListLayout.vue
index d5ccc2ee7e867..cbb9e3ebd79bd 100644
--- a/packages/frontend/editor-ui/src/components/layouts/ResourcesListLayout.vue
+++ b/packages/frontend/editor-ui/src/components/layouts/ResourcesListLayout.vue
@@ -541,7 +541,7 @@ const loadPaginationFromQueryString = async () => {
 							<n8n-input
 								ref="search"
 								:model-value="filtersModel.search"
-								:class="[$style['search'], 'mr-2xs']"
+								:class="$style.search"
 								:placeholder="i18n.baseText(`${resourceKey}.search.placeholder` as BaseTextKey)"
 								clearable
 								data-test-id="resources-list-search"
diff --git a/packages/frontend/editor-ui/src/constants.ts b/packages/frontend/editor-ui/src/constants.ts
index 94a1316d0fc78..cfb8bdf2f7956 100644
--- a/packages/frontend/editor-ui/src/constants.ts
+++ b/packages/frontend/editor-ui/src/constants.ts
@@ -74,6 +74,7 @@ export const NEW_ASSISTANT_SESSION_MODAL = 'newAssistantSession';
 export const EXTERNAL_SECRETS_PROVIDER_MODAL_KEY = 'externalSecretsProvider';
 export const COMMUNITY_PLUS_ENROLLMENT_MODAL = 'communityPlusEnrollment';
 export const DELETE_FOLDER_MODAL_KEY = 'deleteFolder';
+export const MOVE_FOLDER_MODAL_KEY = 'moveFolder';
 
 export const COMMUNITY_PACKAGE_MANAGE_ACTIONS = {
 	UNINSTALL: 'uninstall',
@@ -429,6 +430,11 @@ export const MODAL_CANCEL = 'cancel';
 export const MODAL_CONFIRM = 'confirm';
 export const MODAL_CLOSE = 'close';
 
+/**
+ *	Invalid characters: \/:*?"<>|
+ * 	Invalid name: empty or only dots
+ */
+export const VALID_FOLDER_NAME_REGEX = /^(?!\.+$)(?!\s+$)[^\\/:*?"<>|]{1,100}$/;
 export const VALID_EMAIL_REGEX =
 	/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
 export const VALID_WORKFLOW_IMPORT_URL_REGEX = /^http[s]?:\/\/.*\.json$/i;
diff --git a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json
index 67c40a857ca0b..9a36883f26e7e 100644
--- a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json
@@ -907,7 +907,7 @@
 	"folders.delete.typeToConfirm": "delete {folderName}",
 	"folders.delete.confirm.message": "Are to sure you want to delete this folder?",
 	"folders.delete.success.message": "Folder deleted",
-	"folder.delete.modal.confirmation": "What should we do with the folders and workflows within this folder?",
+	"folder.delete.modal.confirmation": "What should we do with {folders} {workflows} in this folder?",
 	"folder.count": "the {count} folder | the {count} folders",
 	"workflow.count": "the {count} workflow | the {count} workflows",
 	"folder.and.workflow.separator": "and",
@@ -922,6 +922,18 @@
 	"folders.rename.error.title": "Problem renaming folder",
 	"folders.rename.success.message": "Folder renamed",
 	"folders.not.found.message": "Folder not found",
+	"folders.move.modal.title": "Move \"{folderName}\" to another folder",
+	"folders.move.modal.description": "Note: Moving this folder will also move all workflows and subfolders within it.",
+	"folders.move.modal.select.placeholder": "Search for a folder",
+	"folders.move.modal.confirm": "Move to folder",
+	"folders.move.success.title": "Successfully moved folder",
+	"folders.move.success.message": "<b>{folderName}</b> has been moved to <b>{newFolderName}</b>, along with all its workflows and subfolders.<br/><br/><a href=\"{link}\">View {newFolderName}</a>",
+	"folders.move.error.title": "Problem moving folder",
+	"folders.move.workflow.error.title": "Problem moving workflow",
+	"folders.move.workflow.success.title": "Successfully moved workflow",
+	"folders.move.workflow.success.message": "<b>{workflowName}</b> has been moved to <b>{newFolderName}</b>.<br/><br/><a href=\"{link}\">View {newFolderName}</a>",
+	"folders.open.error.title": "Problem opening folder",
+	"folders.create.error.title": "Problem creating folder",
 	"generic.oauth1Api": "OAuth1 API",
 	"generic.oauth2Api": "OAuth2 API",
 	"genericHelpers.loading": "Loading",
diff --git a/packages/frontend/editor-ui/src/stores/folders.store.ts b/packages/frontend/editor-ui/src/stores/folders.store.ts
index f49eed516825e..70367e5a026d9 100644
--- a/packages/frontend/editor-ui/src/stores/folders.store.ts
+++ b/packages/frontend/editor-ui/src/stores/folders.store.ts
@@ -1,6 +1,11 @@
 import { defineStore } from 'pinia';
 import { STORES } from '@/constants';
-import type { FolderCreateResponse, FolderShortInfo, FolderTreeResponseItem } from '@/Interface';
+import type {
+	FolderCreateResponse,
+	FolderListItem,
+	FolderShortInfo,
+	FolderTreeResponseItem,
+} from '@/Interface';
 import * as workflowsApi from '@/api/workflows';
 import { useRootStore } from './root.store';
 import { ref } from 'vue';
@@ -108,6 +113,48 @@ export const useFoldersStore = defineStore(STORES.FOLDERS, () => {
 		return await workflowsApi.getProjectFolders(rootStore.restApiContext, projectId);
 	}
 
+	async function fetchFoldersAvailableForMove(
+		projectId: string,
+		folderId?: string,
+		filter?: {
+			name?: string;
+		},
+	): Promise<FolderListItem[]> {
+		const folders = await workflowsApi.getProjectFolders(
+			rootStore.restApiContext,
+			projectId,
+			{
+				sortBy: 'updatedAt:desc',
+			},
+			{
+				excludeFolderIdAndDescendants: folderId,
+				name: filter?.name ? filter.name : undefined,
+			},
+		);
+		const forCache: FolderShortInfo[] = folders.map((folder) => ({
+			id: folder.id,
+			name: folder.name,
+			parentFolder: folder.parentFolder?.id,
+		}));
+		cacheFolders(forCache);
+		return folders;
+	}
+
+	async function moveFolder(
+		projectId: string,
+		folderId: string,
+		parentFolderId?: string,
+	): Promise<void> {
+		await workflowsApi.moveFolder(rootStore.restApiContext, projectId, folderId, parentFolderId);
+	}
+
+	async function fetchFolderContent(
+		projectId: string,
+		folderId: string,
+	): Promise<{ totalWorkflows: number; totalSubFolders: number }> {
+		return await workflowsApi.getFolderContent(rootStore.restApiContext, projectId, folderId);
+	}
+
 	return {
 		fetchTotalWorkflowsAndFoldersCount,
 		breadcrumbsCache,
@@ -120,5 +167,8 @@ export const useFoldersStore = defineStore(STORES.FOLDERS, () => {
 		deleteFoldersFromCache,
 		renameFolder,
 		fetchProjectFolders,
+		fetchFoldersAvailableForMove,
+		moveFolder,
+		fetchFolderContent,
 	};
 });
diff --git a/packages/frontend/editor-ui/src/stores/ui.store.ts b/packages/frontend/editor-ui/src/stores/ui.store.ts
index 24da0f0e725c9..6aafad597d7da 100644
--- a/packages/frontend/editor-ui/src/stores/ui.store.ts
+++ b/packages/frontend/editor-ui/src/stores/ui.store.ts
@@ -38,6 +38,7 @@ import {
 	COMMUNITY_PLUS_ENROLLMENT_MODAL,
 	API_KEY_CREATE_OR_EDIT_MODAL_KEY,
 	DELETE_FOLDER_MODAL_KEY,
+	MOVE_FOLDER_MODAL_KEY,
 } from '@/constants';
 import type {
 	INodeUi,
@@ -162,12 +163,20 @@ export const useUIStore = defineStore(STORES.UI, () => {
 			open: false,
 			activeId: null,
 			data: {
+				workflowListEventBus: undefined,
 				content: {
 					workflowCount: 0,
 					subFolderCount: 0,
 				},
 			},
 		},
+		[MOVE_FOLDER_MODAL_KEY]: {
+			open: false,
+			activeId: null,
+			data: {
+				workflowListEventBus: undefined,
+			},
+		},
 	});
 
 	const modalStack = ref<string[]>([]);
@@ -498,6 +507,17 @@ export const useUIStore = defineStore(STORES.UI, () => {
 		openModalWithData({ name: DELETE_FOLDER_MODAL_KEY, data: { workflowListEventBus, content } });
 	};
 
+	const openMoveToFolderModal = (
+		resourceType: 'folder' | 'workflow',
+		resource: { id: string; name: string; parentFolderId?: string },
+		workflowListEventBus: EventBus,
+	) => {
+		openModalWithData({
+			name: MOVE_FOLDER_MODAL_KEY,
+			data: { resourceType, resource, workflowListEventBus },
+		});
+	};
+
 	const addActiveAction = (action: string) => {
 		if (!activeActions.value.includes(action)) {
 			activeActions.value.push(action);
@@ -670,6 +690,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
 		resetLastInteractedWith,
 		setProcessingExecutionResults,
 		openDeleteFolderModal,
+		openMoveToFolderModal,
 	};
 });
 
diff --git a/packages/frontend/editor-ui/src/views/WorkflowsView.test.ts b/packages/frontend/editor-ui/src/views/WorkflowsView.test.ts
index f7a7f356e7ae3..fec84bcd28c5a 100644
--- a/packages/frontend/editor-ui/src/views/WorkflowsView.test.ts
+++ b/packages/frontend/editor-ui/src/views/WorkflowsView.test.ts
@@ -295,6 +295,7 @@ describe('Folders', () => {
 		createdAt: new Date().toISOString(),
 		updatedAt: new Date().toISOString(),
 		active: true,
+		versionId: '1',
 		homeProject: {
 			id: '1',
 			name: 'Project 1',
diff --git a/packages/frontend/editor-ui/src/views/WorkflowsView.vue b/packages/frontend/editor-ui/src/views/WorkflowsView.vue
index e56ae72662b2d..893cb567cc18a 100644
--- a/packages/frontend/editor-ui/src/views/WorkflowsView.vue
+++ b/packages/frontend/editor-ui/src/views/WorkflowsView.vue
@@ -16,6 +16,7 @@ import {
 	VIEWS,
 	DEFAULT_WORKFLOW_PAGE_SIZE,
 	MODAL_CONFIRM,
+	VALID_FOLDER_NAME_REGEX,
 } from '@/constants';
 import type {
 	IUser,
@@ -174,13 +175,14 @@ const mainBreadcrumbsActions = computed(() =>
 );
 
 const readOnlyEnv = computed(() => sourceControlStore.preferences.branchReadOnly);
-const foldersEnabled = computed(() => settingsStore.settings.folders.enabled);
 const isOverviewPage = computed(() => route.name === VIEWS.WORKFLOWS);
 const currentUser = computed(() => usersStore.currentUser ?? ({} as IUser));
 const isShareable = computed(
 	() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing],
 );
-const showFolders = computed(() => foldersEnabled.value && !isOverviewPage.value);
+const showFolders = computed(() => {
+	return settingsStore.isFoldersFeatureEnabled && !isOverviewPage.value;
+});
 
 const currentFolder = computed(() => {
 	return currentFolderId.value ? foldersStore.breadcrumbsCache[currentFolderId.value] : null;
@@ -320,7 +322,11 @@ sourceControlStore.$onAction(({ name, after }) => {
 	after(async () => await initialize());
 });
 
-const onFolderDeleted = async (payload: { folderId: string }) => {
+const onFolderDeleted = async (payload: {
+	folderId: string;
+	workflowCount: number;
+	folderCount: number;
+}) => {
 	const folderInfo = foldersStore.getCachedFolder(payload.folderId);
 	foldersStore.deleteFoldersFromCache([payload.folderId, folderInfo?.parentFolder ?? '']);
 	// If the deleted folder is the current folder, navigate to the parent folder
@@ -333,6 +339,11 @@ const onFolderDeleted = async (payload: { folderId: string }) => {
 	} else {
 		await fetchWorkflows();
 	}
+	telemetry.track('User deleted folder', {
+		folder_id: payload.folderId,
+		deleted_sub_folders: payload.folderCount,
+		deleted_sub_workflows: payload.workflowCount,
+	});
 };
 
 /**
@@ -346,12 +357,16 @@ onMounted(async () => {
 	workflowListEventBus.on('resource-moved', fetchWorkflows);
 	workflowListEventBus.on('workflow-duplicated', fetchWorkflows);
 	workflowListEventBus.on('folder-deleted', onFolderDeleted);
+	workflowListEventBus.on('folder-moved', moveFolder);
+	workflowListEventBus.on('workflow-moved', onWorkflowMoved);
 });
 
 onBeforeUnmount(() => {
 	workflowListEventBus.off('resource-moved', fetchWorkflows);
 	workflowListEventBus.off('workflow-duplicated', fetchWorkflows);
 	workflowListEventBus.off('folder-deleted', onFolderDeleted);
+	workflowListEventBus.off('folder-moved', moveFolder);
+	workflowListEventBus.off('workflow-moved', onWorkflowMoved);
 });
 
 /**
@@ -669,25 +684,20 @@ const getFolderListItem = (folderId: string): FolderListItem | undefined => {
 			resource.resource === 'folder' && resource.id === folderId,
 	);
 };
-// TODO: This will only count the workflows and folders in the current page
-// Check if we need to add counts to /tree endpoint or not show them in modal
-const getCurrentFolderWorkflowCount = () => {
-	const workflows = workflowsAndFolders.value.filter(
-		(resource): resource is WorkflowListItem => resource.resource === 'workflow',
-	);
-	return workflows.length;
-};
 
-const getCurrentFolderSubFolderCount = () => {
-	const folders = workflowsAndFolders.value.filter(
-		(resource): resource is FolderListItem => resource.resource === 'folder',
-	);
-	return folders.length;
-};
-
-const getFolderContent = (folderId: string) => {
+const getFolderContent = async (folderId: string) => {
 	const folderListItem = getFolderListItem(folderId);
-	if (!folderListItem) {
+	if (folderListItem) {
+		return {
+			workflowCount: folderListItem.workflowCount,
+			subFolderCount: folderListItem.subFolderCount,
+		};
+	}
+	try {
+		// Fetch the folder content from API
+		const content = await foldersStore.fetchFolderContent(currentProject.value?.id ?? '', folderId);
+		return { workflowCount: content.totalWorkflows, subFolderCount: content.totalSubFolders };
+	} catch (error) {
 		toast.showMessage({
 			title: i18n.baseText('folders.delete.error.message'),
 			message: i18n.baseText('folders.not.found.message'),
@@ -695,10 +705,6 @@ const getFolderContent = (folderId: string) => {
 		});
 		return { workflowCount: 0, subFolderCount: 0 };
 	}
-	return {
-		workflowCount: folderListItem.workflowCount,
-		subFolderCount: folderListItem.subFolderCount,
-	};
 };
 
 // Breadcrumbs methods
@@ -770,7 +776,7 @@ const onBreadcrumbItemClick = (item: PathItem) => {
 				loading.value = false;
 			})
 			.catch((error) => {
-				toast.showError(error, 'Error navigating to folder');
+				toast.showError(error, i18n.baseText('folders.open.error.title'));
 			});
 	}
 };
@@ -796,14 +802,29 @@ const onBreadCrumbsAction = async (action: string) => {
 			break;
 		case FOLDER_LIST_ITEM_ACTIONS.DELETE:
 			if (!route.params.folderId) return;
-			const subFolderCount = getCurrentFolderSubFolderCount();
-			const workflowCount = getCurrentFolderWorkflowCount();
-			await deleteFolder(route.params.folderId as string, workflowCount, subFolderCount);
+			const content = await getFolderContent(route.params.folderId as string);
+			await deleteFolder(
+				route.params.folderId as string,
+				content.workflowCount,
+				content.subFolderCount,
+			);
 			break;
 		case FOLDER_LIST_ITEM_ACTIONS.RENAME:
 			if (!route.params.folderId) return;
 			await renameFolder(route.params.folderId as string);
 			break;
+		case FOLDER_LIST_ITEM_ACTIONS.MOVE:
+			if (!currentFolder.value) return;
+			uiStore.openMoveToFolderModal(
+				'folder',
+				{
+					id: currentFolder.value?.id,
+					name: currentFolder.value?.name,
+					parentFolderId: currentFolder.value?.parentFolder,
+				},
+				workflowListEventBus,
+			);
+			break;
 		default:
 			break;
 	}
@@ -830,13 +851,24 @@ const onFolderCardAction = async (payload: { action: string; folderId: string })
 			});
 			break;
 		case FOLDER_LIST_ITEM_ACTIONS.DELETE: {
-			const content = getFolderContent(clickedFolder.id);
+			const content = await getFolderContent(clickedFolder.id);
 			await deleteFolder(clickedFolder.id, content.workflowCount, content.subFolderCount);
 			break;
 		}
 		case FOLDER_LIST_ITEM_ACTIONS.RENAME:
 			await renameFolder(clickedFolder.id);
 			break;
+		case FOLDER_LIST_ITEM_ACTIONS.MOVE:
+			uiStore.openMoveToFolderModal(
+				'folder',
+				{
+					id: clickedFolder.id,
+					name: clickedFolder.name,
+					parentFolderId: clickedFolder.parentFolder,
+				},
+				workflowListEventBus,
+			);
+			break;
 		default:
 			break;
 	}
@@ -845,18 +877,13 @@ const onFolderCardAction = async (payload: { action: string; folderId: string })
 // Reusable action handlers
 // Both action handlers ultimately call these methods once folder to apply action to is determined
 const createFolder = async (parent: { id: string; name: string; type: 'project' | 'folder' }) => {
-	// Rules for folder name:
-	// - Invalid characters: \/:*?"<>|
-	// - Invalid name: empty or only dots
-	const validFolderNameRegex = /^(?!\.+$)(?!\s+$)[^\\/:*?"<>|]{1,100}$/;
-
 	const promptResponsePromise = message.prompt(
 		i18n.baseText('folders.add.to.parent.message', { interpolate: { parent: parent.name } }),
 		{
 			confirmButtonText: i18n.baseText('generic.create'),
 			cancelButtonText: i18n.baseText('generic.cancel'),
 			inputErrorMessage: i18n.baseText('folders.invalidName.message'),
-			inputPattern: validFolderNameRegex,
+			inputPattern: VALID_FOLDER_NAME_REGEX,
 			customClass: 'add-folder-modal',
 		},
 	);
@@ -870,10 +897,10 @@ const createFolder = async (parent: { id: string; name: string; type: 'project'
 				parent.type === 'folder' ? parent.id : undefined,
 			);
 
-			let newFolderURL = `/projects/${route.params.projectId}/folders/${newFolder.id}/workflows`;
-			if (newFolder.parentFolder) {
-				newFolderURL = `/projects/${route.params.projectId}/folders/${newFolder.id}/workflows`;
-			}
+			const newFolderURL = router.resolve({
+				name: VIEWS.PROJECTS_FOLDERS,
+				params: { projectId: route.params.projectId, folderId: newFolder.id },
+			}).href;
 			toast.showToast({
 				title: i18n.baseText('folders.add.success.title'),
 				message: i18n.baseText('folders.add.success.message', {
@@ -912,8 +939,11 @@ const createFolder = async (parent: { id: string; name: string; type: 'project'
 				// Else fetch again with same filters & pagination applied
 				await fetchWorkflows();
 			}
+			telemetry.track('User created folder', {
+				folder_id: newFolder.id,
+			});
 		} catch (error) {
-			toast.showError(error, 'Error creating folder');
+			toast.showError(error, i18n.baseText('folders.create.error.title'));
 		}
 	}
 };
@@ -928,7 +958,7 @@ const renameFolder = async (folderId: string) => {
 			cancelButtonText: i18n.baseText('generic.cancel'),
 			inputErrorMessage: i18n.baseText('folders.invalidName.message'),
 			inputValue: folder.name,
-			inputPattern: /^[a-zA-Z0-9-_ ]{1,100}$/,
+			inputPattern: VALID_FOLDER_NAME_REGEX,
 			customClass: 'rename-folder-modal',
 		},
 	);
@@ -945,6 +975,9 @@ const renameFolder = async (folderId: string) => {
 				type: 'success',
 			});
 			await fetchWorkflows();
+			telemetry.track('User renamed folder', {
+				folder_id: folderId,
+			});
 		} catch (error) {
 			toast.showError(error, i18n.baseText('folders.rename.error.title'));
 		}
@@ -974,7 +1007,101 @@ const deleteFolder = async (folderId: string, workflowCount: number, subFolderCo
 			title: i18n.baseText('folders.delete.success.message'),
 			type: 'success',
 		});
-		await onFolderDeleted({ folderId });
+		await onFolderDeleted({ folderId, workflowCount, folderCount: subFolderCount });
+	}
+};
+
+const moveFolder = async (payload: {
+	folder: { id: string; name: string };
+	newParent: { id: string; name: string };
+}) => {
+	if (!route.params.projectId) return;
+	try {
+		await foldersStore.moveFolder(
+			route.params.projectId as string,
+			payload.folder.id,
+			payload.newParent.id,
+		);
+		const isCurrentFolder = currentFolderId.value === payload.folder.id;
+		const newFolderURL = router.resolve({
+			name: VIEWS.PROJECTS_FOLDERS,
+			params: { projectId: route.params.projectId, folderId: payload.newParent.id },
+		}).href;
+		if (isCurrentFolder) {
+			// If we just moved the current folder, automatically navigate to the new folder
+			void router.push(newFolderURL);
+		} else {
+			// Else show success message and update the list
+			toast.showToast({
+				title: i18n.baseText('folders.move.success.title'),
+				message: i18n.baseText('folders.move.success.message', {
+					interpolate: { folderName: payload.folder.name, newFolderName: payload.newParent.name },
+				}),
+				onClick: (event: MouseEvent | undefined) => {
+					if (event?.target instanceof HTMLAnchorElement) {
+						event.preventDefault();
+						void router.push(newFolderURL);
+					}
+				},
+				type: 'success',
+			});
+			await fetchWorkflows();
+		}
+	} catch (error) {
+		toast.showError(error, i18n.baseText('folders.move.error.title'));
+	}
+};
+
+const moveWorkflowToFolder = async (payload: {
+	id: string;
+	name: string;
+	parentFolderId?: string;
+}) => {
+	uiStore.openMoveToFolderModal(
+		'workflow',
+		{ id: payload.id, name: payload.name, parentFolderId: payload.parentFolderId },
+		workflowListEventBus,
+	);
+};
+
+const onWorkflowMoved = async (payload: {
+	workflow: { id: string; name: string; oldParentId: string };
+	newParent: { id: string; name: string };
+}) => {
+	if (!route.params.projectId) return;
+	try {
+		const newFolderURL = router.resolve({
+			name: VIEWS.PROJECTS_FOLDERS,
+			params: { projectId: route.params.projectId, folderId: payload.newParent.id },
+		}).href;
+		const workflowResource = workflowsAndFolders.value.find(
+			(resource): resource is WorkflowListItem => resource.id === payload.workflow.id,
+		);
+		await workflowsStore.updateWorkflow(payload.workflow.id, {
+			parentFolderId: payload.newParent.id,
+			versionId: workflowResource?.versionId,
+		});
+		await fetchWorkflows();
+		toast.showToast({
+			title: i18n.baseText('folders.move.workflow.success.title'),
+			message: i18n.baseText('folders.move.workflow.success.message', {
+				interpolate: { workflowName: payload.workflow.name, newFolderName: payload.newParent.name },
+			}),
+			onClick: (event: MouseEvent | undefined) => {
+				if (event?.target instanceof HTMLAnchorElement) {
+					event.preventDefault();
+					void router.push(newFolderURL);
+				}
+			},
+			type: 'success',
+		});
+		telemetry.track('User moved content', {
+			workflow_id: payload.workflow.id,
+			source_folder_id: payload.workflow.oldParentId,
+			destination_folder_id: payload.newParent.id,
+		});
+	} catch (error) {
+		toast.showError(error, i18n.baseText('folders.move.workflow.error.title'));
 	}
 };
 </script>
@@ -1094,6 +1221,7 @@ const deleteFolder = async (folderId: string, workflowCount: number, subFolderCo
 				@workflow:moved="fetchWorkflows"
 				@workflow:duplicated="fetchWorkflows"
 				@workflow:active-toggle="onWorkflowActiveToggle"
+				@action:move-to-folder="moveWorkflowToFolder"
 			/>
 		</template>
 		<template #empty>


Reviews:
user: login: mutdmour
body: Looks great. Thanks for resolving the performance issues with loading all folders in a projects. Great to see a reusable `MoveToFolderDropdown`. 
Just left a few small points to improve on.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T10:16:10Z
user: login: MiloradFilipovic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T10:29:53Z
user: login: MiloradFilipovic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T10:32:01Z
user: login: mutdmour
body: Looks great.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T10:41:53Z

Review comments:
diff_hunk: @@ -0,0 +1,119 @@
+<script setup lang="ts">
+import { useI18n } from '@/composables/useI18n';
+import type { FolderListItem } from '@/Interface';
+import { useFoldersStore } from '@/stores/folders.store';
+import { N8nSelect } from '@n8n/design-system';
+import { nextTick, onMounted, ref } from 'vue';
+
+/**
+ * This component is used to select a folder to move a resource (folder or workflow) to.
+ * Based on the provided resource type, it fetches the available folders and displays them in a dropdown.
+ * For folders, it filters out current folder parent and all off it's children (done in the back-end)
+ * For workflows, it only filters out the current workflows's folder.
+ */
+
+type Props = {
+	currentProjectId: string;
+	currentFolderId?: string;
+	parentFolderId?: string;
+};
+
+const props = withDefaults(defineProps<Props>(), {
+	currentFolderId: '',
+	parentFolderId: '',
+});
+
+const emit = defineEmits<{
+	'folder:selected': [value: { id: string; name: string }];
+}>();
+
+const i18n = useI18n();
+
+const foldersStore = useFoldersStore();
+
+const moveFolderDropdown = ref<InstanceType<typeof N8nSelect>>();
+const selectedFolderId = ref<string | null>(null);
+const availableFolders = ref<FolderListItem[]>([]);
+const loading = ref(false);
+
+const fetchAvailableFolders = async (query?: string) => {
+	if (!query) {
+		availableFolders.value = [];
+		return;
+	}
+	loading.value = true;
+	const folders = await foldersStore.fetchFoldersAvailableForMove(
+		props.currentProjectId,
+		props.currentFolderId,
+		{ name: query ?? undefined },
+	);
+	if (!props.parentFolderId) {
+		availableFolders.value = folders;
+	} else {
+		availableFolders.value = folders.filter((folder) => folder.id !== props.parentFolderId);
+	}
+	loading.value = false;
+};
+
+const onFolderSelected = (folderId: string) => {
+	const selectedFolder = availableFolders.value.find((folder) => folder.id === folderId);
+	if (!selectedFolder) {
+		return;
+	}
+	emit('folder:selected', { id: folderId, name: selectedFolder.name });
+};
+
+onMounted(async () => {
+	await nextTick();
+	await fetchAvailableFolders();
path: packages/frontend/editor-ui/src/components/Folders/MoveToFolderDropdown.vue
user: login: mutdmour
body: Seems like we don't need this anymore in the mounted hook, since there's no initial page
created_at: 2025-03-17T09:55:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -108,6 +113,49 @@ export const useFoldersStore = defineStore(STORES.FOLDERS, () => {
 		return await workflowsApi.getProjectFolders(rootStore.restApiContext, projectId);
 	}
 
+	async function fetchFoldersAvailableForMove(
+		projectId: string,
+		folderId?: string,
+		filter?: {
+			name?: string;
+		},
+	): Promise<FolderListItem[]> {
+		const folders = await workflowsApi.getProjectFolders(
+			rootStore.restApiContext,
+			projectId,
+			{
+				take: !filter?.name ? 5 : undefined,
path: packages/frontend/editor-ui/src/stores/folders.store.ts
user: login: mutdmour
body: Magic number, would be nice if we set this as a variable on top and give it a clear name.
created_at: 2025-03-17T10:06:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -974,7 +1007,95 @@ const deleteFolder = async (folderId: string, workflowCount: number, subFolderCo
 			title: i18n.baseText('folders.delete.success.message'),
 			type: 'success',
 		});
-		await onFolderDeleted({ folderId });
+		await onFolderDeleted({ folderId, workflowCount, folderCount: subFolderCount });
+	}
+};
+
+const moveFolder = async (payload: {
+	folder: { id: string; name: string };
+	newParent: { id: string; name: string };
+}) => {
+	if (!route.params.projectId) return;
+	try {
+		await foldersStore.moveFolder(
+			route.params.projectId as string,
+			payload.folder.id,
+			payload.newParent.id,
+		);
+		const isCurrentFolder = currentFolderId.value === payload.folder.id;
+		const newFolderURL = `/projects/${route.params.projectId}/folders/${payload.newParent.id}/workflows`;
path: packages/frontend/editor-ui/src/views/WorkflowsView.vue
user: login: mutdmour
body: We should use `router.resolve` here, so that if the route is updated, this is automatically updated.
created_at: 2025-03-17T10:12:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -974,7 +1007,95 @@ const deleteFolder = async (folderId: string, workflowCount: number, subFolderCo
 			title: i18n.baseText('folders.delete.success.message'),
 			type: 'success',
 		});
-		await onFolderDeleted({ folderId });
+		await onFolderDeleted({ folderId, workflowCount, folderCount: subFolderCount });
+	}
+};
+
+const moveFolder = async (payload: {
+	folder: { id: string; name: string };
+	newParent: { id: string; name: string };
+}) => {
+	if (!route.params.projectId) return;
+	try {
+		await foldersStore.moveFolder(
+			route.params.projectId as string,
+			payload.folder.id,
+			payload.newParent.id,
+		);
+		const isCurrentFolder = currentFolderId.value === payload.folder.id;
+		const newFolderURL = `/projects/${route.params.projectId}/folders/${payload.newParent.id}/workflows`;
+		if (isCurrentFolder) {
+			// If we just moved the current folder, automatically navigate to the new folder
+			void router.push(newFolderURL);
+		} else {
+			// Else show success message and update the list
+			toast.showToast({
+				title: i18n.baseText('folders.move.success.title'),
+				message: i18n.baseText('folders.move.success.message', {
+					interpolate: { folderName: payload.folder.name, newFolderName: payload.newParent.name },
+				}),
+				onClick: (event: MouseEvent | undefined) => {
+					if (event?.target instanceof HTMLAnchorElement) {
+						event.preventDefault();
+						void router.push(newFolderURL);
+					}
+				},
+				type: 'success',
+			});
+			await fetchWorkflows();
+		}
+	} catch (error) {
+		toast.showError(error, i18n.baseText('folders.move.error.title'));
+	}
+};
+
+const moveWorkflowToFolder = async (payload: {
+	id: string;
+	name: string;
+	parentFolderId?: string;
+}) => {
+	uiStore.openMoveToFolderModal(
+		'workflow',
+		{ id: payload.id, name: payload.name, parentFolderId: payload.parentFolderId },
+		workflowListEventBus,
+	);
+};
+
+const onWorkflowMoved = async (payload: {
+	workflow: { id: string; name: string; oldParentId: string };
+	newParent: { id: string; name: string };
+}) => {
+	if (!route.params.projectId) return;
+	try {
+		const newFolderURL = `/projects/${route.params.projectId}/folders/${payload.newParent.id}/workflows`;
path: packages/frontend/editor-ui/src/views/WorkflowsView.vue
user: login: mutdmour
body: Same here
created_at: 2025-03-17T10:13:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,119 @@
+<script setup lang="ts">
+import { useI18n } from '@/composables/useI18n';
+import type { FolderListItem } from '@/Interface';
+import { useFoldersStore } from '@/stores/folders.store';
+import { N8nSelect } from '@n8n/design-system';
+import { nextTick, onMounted, ref } from 'vue';
+
+/**
+ * This component is used to select a folder to move a resource (folder or workflow) to.
+ * Based on the provided resource type, it fetches the available folders and displays them in a dropdown.
+ * For folders, it filters out current folder parent and all off it's children (done in the back-end)
+ * For workflows, it only filters out the current workflows's folder.
+ */
+
+type Props = {
+	currentProjectId: string;
+	currentFolderId?: string;
+	parentFolderId?: string;
+};
+
+const props = withDefaults(defineProps<Props>(), {
+	currentFolderId: '',
+	parentFolderId: '',
+});
+
+const emit = defineEmits<{
+	'folder:selected': [value: { id: string; name: string }];
+}>();
+
+const i18n = useI18n();
+
+const foldersStore = useFoldersStore();
+
+const moveFolderDropdown = ref<InstanceType<typeof N8nSelect>>();
+const selectedFolderId = ref<string | null>(null);
+const availableFolders = ref<FolderListItem[]>([]);
+const loading = ref(false);
+
+const fetchAvailableFolders = async (query?: string) => {
+	if (!query) {
+		availableFolders.value = [];
+		return;
+	}
+	loading.value = true;
+	const folders = await foldersStore.fetchFoldersAvailableForMove(
+		props.currentProjectId,
+		props.currentFolderId,
+		{ name: query ?? undefined },
+	);
+	if (!props.parentFolderId) {
+		availableFolders.value = folders;
+	} else {
+		availableFolders.value = folders.filter((folder) => folder.id !== props.parentFolderId);
+	}
+	loading.value = false;
+};
+
+const onFolderSelected = (folderId: string) => {
+	const selectedFolder = availableFolders.value.find((folder) => folder.id === folderId);
+	if (!selectedFolder) {
+		return;
+	}
+	emit('folder:selected', { id: folderId, name: selectedFolder.name });
+};
+
+onMounted(async () => {
+	await nextTick();
+	await fetchAvailableFolders();
path: packages/frontend/editor-ui/src/components/Folders/MoveToFolderDropdown.vue
user: login: MiloradFilipovic
body: Indeed, nice catch
created_at: 2025-03-17T10:29:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -108,6 +113,49 @@ export const useFoldersStore = defineStore(STORES.FOLDERS, () => {
 		return await workflowsApi.getProjectFolders(rootStore.restApiContext, projectId);
 	}
 
+	async function fetchFoldersAvailableForMove(
+		projectId: string,
+		folderId?: string,
+		filter?: {
+			name?: string;
+		},
+	): Promise<FolderListItem[]> {
+		const folders = await workflowsApi.getProjectFolders(
+			rootStore.restApiContext,
+			projectId,
+			{
+				take: !filter?.name ? 5 : undefined,
path: packages/frontend/editor-ui/src/stores/folders.store.ts
user: login: MiloradFilipovic
body: This is also not needed anymore since we are not fetching the initial page
created_at: 2025-03-17T10:32:01Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-03-14T09:23:42Z
body: ## [Codecov](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io) Report
Attention: Patch coverage is `63.14655%` with `171 lines` in your changes missing coverage. Please review.
| [Files with missing lines](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io) | Patch % | Lines |
|---|---|---|
| [...or-ui/src/components/Folders/DeleteFolderModal.vue](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io#diff-cGFja2FnZXMvZnJvbnRlbmQvZWRpdG9yLXVpL3NyYy9jb21wb25lbnRzL0ZvbGRlcnMvRGVsZXRlRm9sZGVyTW9kYWwudnVl) | 0.00% | [46 Missing :warning: ](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io) |
| [packages/frontend/editor-ui/src/api/workflows.ts](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io#diff-cGFja2FnZXMvZnJvbnRlbmQvZWRpdG9yLXVpL3NyYy9hcGkvd29ya2Zsb3dzLnRz) | 0.00% | [34 Missing :warning: ](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io) |
| [...ges/frontend/editor-ui/src/stores/folders.store.ts](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io#diff-cGFja2FnZXMvZnJvbnRlbmQvZWRpdG9yLXVpL3NyYy9zdG9yZXMvZm9sZGVycy5zdG9yZS50cw==) | 15.00% | [34 Missing :warning: ](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io) |
| [...or-ui/src/components/Folders/MoveToFolderModal.vue](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io#diff-cGFja2FnZXMvZnJvbnRlbmQvZWRpdG9yLXVpL3NyYy9jb21wb25lbnRzL0ZvbGRlcnMvTW92ZVRvRm9sZGVyTW9kYWwudnVl) | 67.50% | [26 Missing :warning: ](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io) |
| [...frontend/editor-ui/src/components/WorkflowCard.vue](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io#diff-cGFja2FnZXMvZnJvbnRlbmQvZWRpdG9yLXVpL3NyYy9jb21wb25lbnRzL1dvcmtmbG93Q2FyZC52dWU=) | 26.31% | [14 Missing :warning: ](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io) |
| [packages/frontend/editor-ui/src/stores/ui.store.ts](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io#diff-cGFja2FnZXMvZnJvbnRlbmQvZWRpdG9yLXVpL3NyYy9zdG9yZXMvdWkuc3RvcmUudHM=) | 52.63% | [9 Missing :warning: ](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io) |
| [...kages/frontend/editor-ui/src/components/Modals.vue](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io#diff-cGFja2FnZXMvZnJvbnRlbmQvZWRpdG9yLXVpL3NyYy9jb21wb25lbnRzL01vZGFscy52dWU=) | 0.00% | [4 Missing :warning: ](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io) |
| [...ditor-ui/src/components/Projects/ProjectHeader.vue](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io#diff-cGFja2FnZXMvZnJvbnRlbmQvZWRpdG9yLXVpL3NyYy9jb21wb25lbnRzL1Byb2plY3RzL1Byb2plY3RIZWFkZXIudnVl) | 50.00% | [2 Missing :warning: ](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io) |
| [...-ui/src/components/layouts/ResourcesListLayout.vue](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io#diff-cGFja2FnZXMvZnJvbnRlbmQvZWRpdG9yLXVpL3NyYy9jb21wb25lbnRzL2xheW91dHMvUmVzb3VyY2VzTGlzdExheW91dC52dWU=) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io) |
| [...ges/frontend/editor-ui/src/views/WorkflowsView.vue](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io#diff-cGFja2FnZXMvZnJvbnRlbmQvZWRpdG9yLXVpL3NyYy92aWV3cy9Xb3JrZmxvd3NWaWV3LnZ1ZQ==) | 99.34% | [1 Missing :warning: ](https://app.codecov.io/gh/n8n-io/n8n/pull/13922?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=n8n-io) |

:loudspeaker: Thoughts on this report? [Let us know!](https://github.com/codecov/feedback/issues/255)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-17T10:54:49Z
body: :warning: Some Cypress E2E specs are failing, please fix them before merging
author_association: CONTRIBUTOR
user: login: cypress[bot]
created_at: 2025-03-17T10:55:01Z
body: 
<h3>
  <a href="https://cloud.cypress.io/projects/5hbsdn?utm_source=github&utm_medium=PASSED&utm_campaign=project">n8n</a>&nbsp;
  <img src="https://assets.cypress.io/github-pr-comment-icons/chevron.svg" alt="" height="14" width="14" />&nbsp;
  <a href="https://cloud.cypress.io/projects/5hbsdn/runs/9791/overview?utm_source=github&utm_medium=PASSED&utm_campaign=run%20number">Run #9791</a>
</h3>

<details>
  <summary>
    <strong>Run Properties:</strong>&nbsp;
    <code><img src="https://assets.cypress.io/github-pr-comment-icons/passed.svg" alt="status check passed" height="10" width="10" />&nbsp;Passed&nbsp;<a href="https://cloud.cypress.io/projects/5hbsdn/runs/9791/overview?utm_source=github&utm_medium=PASSED&utm_campaign=run%20number">#9791</a></code> &nbsp;&bull;&nbsp;
    <code><img src="https://assets.cypress.io/github-pr-comment-icons/git-commit.svg" alt="git commit" width="10" height="10" /> <a href="https://github.com/n8n-io/n8n/commit/118789a6ccda4a3eb523b1f93a03b76d43c224fc">118789a6cc</a>: 🌳  🖥️ browsers:node18.12.0-chrome107 🤖 MiloradFilipovic 🗃️ e2e/*</code>
  </summary><br />

  <table width="744">
    <tbody>
      <tr />
      <tr height="48">
        <td width="224">
          <strong>Project</strong>
        </td>
        <td width="520">
          <code><a href="https://cloud.cypress.io/projects/5hbsdn?utm_source=github&utm_medium=PASSED&utm_campaign=project">n8n</a></code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td width="224">
          <strong>Branch Review</strong>
        </th>
        <td>
          <code><a href="https://cloud.cypress.io/projects/5hbsdn/branches/ADO-3160-move-folders/review?utm_source=github&utm_medium=PASSED&utm_campaign=review%20changes&changeRequest=13922">ADO-3160-move-folders</a></code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Run status</strong></td>
        <td>
          <code><img src="https://assets.cypress.io/github-pr-comment-icons/passed.svg" alt="status check passed" height="10" width="10" />&nbsp;Passed&nbsp;<a href="https://cloud.cypress.io/projects/5hbsdn/runs/9791/overview?utm_source=github&utm_medium=PASSED&utm_campaign=run%20number">#9791</a></code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Run duration</strong></td>
        <td><code>04m 41s</code></td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Commit</strong></td>
        <td>
          <code><img src="https://assets.cypress.io/github-pr-comment-icons/git-commit.svg" alt="git commit" width="10" height="10" /> <a href="https://github.com/n8n-io/n8n/commit/118789a6ccda4a3eb523b1f93a03b76d43c224fc">118789a6cc</a>: 🌳  🖥️ browsers:node18.12.0-chrome107 🤖 MiloradFilipovic 🗃️ e2e/*</code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Committer</strong></td>
        <td><code>Milorad Filipovic</code></td>
      </tr>
    </tbody>
    <tfoot>
      <tr />
      <tr height="48">
        <td colspan="2">
          <a href="https://cloud.cypress.io/projects/5hbsdn/runs/9791/properties?utm_source=github&utm_medium=PASSED&utm_campaign=properties">View all properties for this run ↗︎</a>
        </td>
      </tr>
    </tfoot>
  </table>
  <hr />
</details>

<table width="744">
  <thead>
    <tr />
    <tr height="48">
      <th colspan="2" align="left" width="744">Test results</th>
    </tr>
  </thead>
  <tbody>
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/failed.svg" alt="Tests that failed" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/5hbsdn/runs/9791/overview?reviewViewBy=FAILED&utm_source=github&utm_medium=PASSED&utm_campaign=failed%20tests">
          <strong>Failures</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/flaky-v3.svg" alt="Tests that were flaky" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/5hbsdn/runs/9791/overview?reviewViewBy=FLAKY&utm_source=github&utm_medium=PASSED&utm_campaign=flaky%20tests">
          <strong>Flaky</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/pending.svg" alt="Tests that did not run due to a developer annotating a test with .skip" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/5hbsdn/runs/9791/test-results?utm_source=github&utm_medium=PASSED&utm_campaign=pending%20tests&statuses=%5B%7B%22value%22%3A%22PENDING%22%2C%22label%22%3A%22PENDING%22%7D%5D">
          <strong>Pending</strong>
        </a>
      </td>
      <td>
        <code><strong>5</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/skipped-v3.svg" alt="Tests that did not run due to a failure in a mocha hook" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/5hbsdn/runs/9791/test-results?utm_source=github&utm_medium=PASSED&utm_campaign=skipped%20tests&statuses=%5B%7B%22value%22%3A%22SKIPPED%22%2C%22label%22%3A%22SKIPPED%22%7D%5D">
          <strong>Skipped</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/passed.svg" alt="Tests that passed" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/5hbsdn/runs/9791/overview?utm_source=github&utm_medium=PASSED&utm_campaign=passed%20tests">
          <strong>Passing</strong>
        </a>
      </td>
      <td>
        <code><strong>455</strong></code>
      </td>
    </tr><tfoot>
    <tr height="48"><td colspan="2"><a href="https://cloud.cypress.io/projects/5hbsdn/branches/ADO-3160-move-folders/review?utm_source=github&utm_medium=PASSED&utm_campaign=review%20changes&changeRequest=13922">View all changes introduced in this branch ↗︎</a></td></tr></tfoot>
  </tbody>
</table>






author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-17T11:32:05Z
body: :warning: Some Cypress E2E specs are failing, please fix them before merging
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-17T11:44:58Z
body: :white_check_mark: All Cypress E2E specs passed
author_association: CONTRIBUTOR
user: login: janober
created_at: 2025-03-17T14:24:26Z
body: Got released with [`n8n@1.84.0`](https://github.com/n8n-io/n8n/releases/tag/n8n%401.84.0)
author_association: MEMBER