mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Pre built agents experiment v2 (no-changelog) (#18842)
This commit is contained in:
@@ -14,6 +14,7 @@ export interface Props {
|
||||
title: string;
|
||||
showActionArrow?: boolean;
|
||||
isOfficial?: boolean;
|
||||
hideNodeIcon?: boolean;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
@@ -35,7 +36,7 @@ const { t } = useI18n();
|
||||
}"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<div :class="$style.nodeIcon">
|
||||
<div v-if="!hideNodeIcon" :class="$style.nodeIcon">
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -2705,6 +2705,7 @@
|
||||
"workflows.empty.learnN8n": "Learn n8n",
|
||||
"workflows.empty.button.disabled.tooltip": "Your current role in the project does not allow you to create workflows",
|
||||
"workflows.empty.easyAI": "Test a simple AI Agent example",
|
||||
"workflows.empty.preBuiltAgents": "Try a pre-built agent",
|
||||
"workflows.empty.shared-with-me": "No {resource} has been shared with you",
|
||||
"workflows.empty.shared-with-me.link": "<a href=\"#\">Back to Personal</a>",
|
||||
"workflows.list.easyAI": "Test the power of AI in n8n with this simple AI Agent Workflow",
|
||||
@@ -2755,6 +2756,8 @@
|
||||
"workflows.readyToRunWorkflows.error": "Error loading n8n collection. Please try again later.",
|
||||
"workflows.archivedOnly.hint": "Archived workflows are hidden in this view. {link}",
|
||||
"workflows.archivedOnly.hint.link": "Update filters",
|
||||
"workflows.preBuiltAgents.callout": "Get started faster with our",
|
||||
"workflows.preBuiltAgents.linkText": "pre-built agents",
|
||||
"workflowSelectorParameterInput.createNewSubworkflow.name": "My Sub-Workflow",
|
||||
"importCurlModal.title": "Import cURL command",
|
||||
"importCurlModal.input.label": "cURL Command",
|
||||
@@ -3438,5 +3441,8 @@
|
||||
"workflowDiff.deletedWorkflow.remote": "The workflow doesn't exist on remote",
|
||||
"workflowDiff.newWorkflow": "New workflow",
|
||||
"workflowDiff.newWorkflow.database": "The workflow will be created in the database",
|
||||
"workflowDiff.newWorkflow.remote": "The workflow will be created on remote"
|
||||
"workflowDiff.newWorkflow.remote": "The workflow will be created on remote",
|
||||
"preBuiltAgentTemplates.title": "Pre-built agents",
|
||||
"preBuiltAgentTemplates.tutorials": "Tutorial templates",
|
||||
"preBuiltAgentTemplates.viewAllLink": "View all templates"
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import { TemplateClickSource, trackTemplatesClick } from '@/utils/experiments';
|
||||
import { I18nT } from 'vue-i18n';
|
||||
import { usePersonalizedTemplatesV2Store } from '@/experiments/templateRecoV2/stores/templateRecoV2.store';
|
||||
import { useKeybindings } from '@/composables/useKeybindings';
|
||||
import { useCalloutHelpers } from '@/composables/useCalloutHelpers';
|
||||
|
||||
const becomeTemplateCreatorStore = useBecomeTemplateCreatorStore();
|
||||
const cloudPlanStore = useCloudPlanStore();
|
||||
@@ -58,6 +59,7 @@ const router = useRouter();
|
||||
const telemetry = useTelemetry();
|
||||
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||
const { getReportingURL } = useBugReporting();
|
||||
const calloutHelpers = useCalloutHelpers();
|
||||
|
||||
useKeybindings({
|
||||
ctrl_alt_o: () => handleSelect('about'),
|
||||
@@ -97,13 +99,28 @@ const mainMenuItems = computed<IMenuItem[]>(() => [
|
||||
icon: 'cloud',
|
||||
available: settingsStore.isCloudDeployment && hasPermission(['instanceOwner']),
|
||||
},
|
||||
{
|
||||
// Link to in-app pre-built agent templates, available experiment is enabled
|
||||
id: 'templates',
|
||||
icon: 'package-open',
|
||||
label: i18n.baseText('mainSidebar.templates'),
|
||||
position: 'bottom',
|
||||
available:
|
||||
settingsStore.isTemplatesEnabled &&
|
||||
calloutHelpers.isPreBuiltAgentsCalloutVisible.value &&
|
||||
!personalizedTemplatesV2Store.isFeatureEnabled(),
|
||||
route: { to: { name: VIEWS.PRE_BUILT_AGENT_TEMPLATES } },
|
||||
},
|
||||
{
|
||||
// Link to templateRecoV2 modal, available when experiment is enabled
|
||||
id: 'templates',
|
||||
icon: 'package-open',
|
||||
label: i18n.baseText('mainSidebar.templates'),
|
||||
position: 'bottom',
|
||||
available: settingsStore.isTemplatesEnabled && personalizedTemplatesV2Store.isFeatureEnabled(),
|
||||
available:
|
||||
settingsStore.isTemplatesEnabled &&
|
||||
!calloutHelpers.isPreBuiltAgentsCalloutVisible.value &&
|
||||
personalizedTemplatesV2Store.isFeatureEnabled(),
|
||||
},
|
||||
{
|
||||
// Link to in-app templates, available if custom templates are enabled and experiment is disabled
|
||||
@@ -113,6 +130,7 @@ const mainMenuItems = computed<IMenuItem[]>(() => [
|
||||
position: 'bottom',
|
||||
available:
|
||||
settingsStore.isTemplatesEnabled &&
|
||||
!calloutHelpers.isPreBuiltAgentsCalloutVisible.value &&
|
||||
templatesStore.hasCustomTemplatesHost &&
|
||||
!personalizedTemplatesV2Store.isFeatureEnabled(),
|
||||
route: { to: { name: VIEWS.TEMPLATES } },
|
||||
@@ -125,6 +143,7 @@ const mainMenuItems = computed<IMenuItem[]>(() => [
|
||||
position: 'bottom',
|
||||
available:
|
||||
settingsStore.isTemplatesEnabled &&
|
||||
!calloutHelpers.isPreBuiltAgentsCalloutVisible.value &&
|
||||
!templatesStore.hasCustomTemplatesHost &&
|
||||
!personalizedTemplatesV2Store.isFeatureEnabled(),
|
||||
link: {
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
TAGS_MANAGER_MODAL_KEY,
|
||||
VERSIONS_MODAL_KEY,
|
||||
WHATS_NEW_MODAL_KEY,
|
||||
PRE_BUILT_AGENTS_MODAL_KEY,
|
||||
WORKFLOW_ACTIVATION_CONFLICTING_WEBHOOK_MODAL_KEY,
|
||||
WORKFLOW_ACTIVE_MODAL_KEY,
|
||||
WORKFLOW_DIFF_MODAL_KEY,
|
||||
@@ -350,6 +351,12 @@ import NodeRecommendationModal from '@/experiments/templateRecoV2/components/Nod
|
||||
</template>
|
||||
</ModalRoot>
|
||||
|
||||
<ModalRoot :name="PRE_BUILT_AGENTS_MODAL_KEY">
|
||||
<template #default="{ modalName, data }">
|
||||
<PreBuiltAgentsModal :modal-name="modalName" :data="data" />
|
||||
</template>
|
||||
</ModalRoot>
|
||||
|
||||
<!-- Dynamic modals from modules -->
|
||||
<DynamicModalLoader />
|
||||
</div>
|
||||
|
||||
@@ -424,7 +424,12 @@ export function getActiveViewCallouts(
|
||||
results.push(getPreBuiltAgentsCalloutWithDivider());
|
||||
} else if ([AI_CATEGORY_MEMORY, AI_CATEGORY_TOOLS].includes(title)) {
|
||||
results.push(getPreBuiltAgentsCallout());
|
||||
} else if (title === 'Google Calendar' || title === 'Telegram') {
|
||||
} else if (title === 'Google Calendar') {
|
||||
const templateLink = getTemplateLink(PrebuiltAgentTemplates.CalendarAgent, templates);
|
||||
if (templateLink) {
|
||||
results.push(templateLink);
|
||||
}
|
||||
} else if (title === 'Telegram') {
|
||||
const templateLink = getTemplateLink(PrebuiltAgentTemplates.VoiceAssistantAgent, templates);
|
||||
if (templateLink) {
|
||||
results.push(templateLink);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { PRE_BUILT_AGENTS_MODAL_KEY } from '@/constants';
|
||||
import { N8nHeading } from '@n8n/design-system';
|
||||
import { createEventBus } from '@n8n/utils/event-bus';
|
||||
import { computed } from 'vue';
|
||||
import { useCalloutHelpers } from '@/composables/useCalloutHelpers';
|
||||
import type { INodeCreateElement } from '@/Interface';
|
||||
|
||||
const i18n = useI18n();
|
||||
const modalBus = createEventBus();
|
||||
|
||||
const calloutHelpers = useCalloutHelpers();
|
||||
|
||||
const preBuiltAgents = computed<INodeCreateElement[]>(() =>
|
||||
calloutHelpers.getPreBuiltAgentNodeCreatorItems(),
|
||||
);
|
||||
|
||||
function onSelected(actionCreateElement: INodeCreateElement) {
|
||||
if (actionCreateElement.type === 'openTemplate') {
|
||||
calloutHelpers.openSampleWorkflowTemplate(actionCreateElement.properties.templateId, {
|
||||
telemetry: {
|
||||
source: 'modal',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
max-width="500px"
|
||||
max-height="85vh"
|
||||
:event-bus="modalBus"
|
||||
:name="PRE_BUILT_AGENTS_MODAL_KEY"
|
||||
:center="true"
|
||||
:show-close="true"
|
||||
:class="$style.modal"
|
||||
>
|
||||
<template #header>
|
||||
<div :class="$style.header">
|
||||
<N8nHeading size="xlarge">{{ i18n.baseText('workflows.empty.preBuiltAgents') }}</N8nHeading>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div :class="$style.container">
|
||||
<ItemsRenderer :elements="preBuiltAgents" :class="$style.items" @selected="onSelected" />
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.modal {
|
||||
:global(.el-dialog__body) {
|
||||
padding: 0;
|
||||
padding-bottom: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -14,6 +14,7 @@ import { updateCurrentUserSettings } from '@n8n/rest-api-client/api/users';
|
||||
import {
|
||||
NODE_CREATOR_OPEN_SOURCES,
|
||||
PRE_BUILT_AGENTS_EXPERIMENT,
|
||||
PRE_BUILT_AGENTS_MODAL_KEY,
|
||||
REGULAR_NODE_CREATOR_VIEW,
|
||||
VIEWS,
|
||||
} from '@/constants';
|
||||
@@ -21,16 +22,21 @@ import {
|
||||
getPrebuiltAgents,
|
||||
getRagStarterWorkflowJson,
|
||||
getSampleWorkflowByTemplateId,
|
||||
getTutorialTemplates,
|
||||
isPrebuiltAgentTemplateId,
|
||||
isTutorialTemplateId,
|
||||
SampleTemplates,
|
||||
} from '@/utils/templates/workflowSamples';
|
||||
import type { INodeCreateElement, OpenTemplateElement } from '@/Interface';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
|
||||
export function useCalloutHelpers() {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const telemetry = useTelemetry();
|
||||
const postHog = usePostHog();
|
||||
const i18n = useI18n();
|
||||
|
||||
const rootStore = useRootStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const usersStore = useUsersStore();
|
||||
@@ -38,7 +44,7 @@ export function useCalloutHelpers() {
|
||||
const nodeCreatorStore = useNodeCreatorStore();
|
||||
const viewStacks = useViewStacks();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const i18n = useI18n();
|
||||
const uiStore = useUIStore();
|
||||
|
||||
const isRagStarterCalloutVisible = computed(() => {
|
||||
const template = getRagStarterWorkflowJson();
|
||||
@@ -57,6 +63,7 @@ export function useCalloutHelpers() {
|
||||
|
||||
const getPreBuiltAgentNodeCreatorItems = (): OpenTemplateElement[] => {
|
||||
const templates = getPrebuiltAgents();
|
||||
|
||||
return templates.map((template) => {
|
||||
return {
|
||||
key: template.template.meta.templateId,
|
||||
@@ -77,6 +84,40 @@ export function useCalloutHelpers() {
|
||||
});
|
||||
};
|
||||
|
||||
const getTutorialTemplatesNodeCreatorItems = (): OpenTemplateElement[] => {
|
||||
const templates = getTutorialTemplates();
|
||||
|
||||
return templates.map((template) => {
|
||||
return {
|
||||
key: template.template.meta.templateId,
|
||||
type: 'openTemplate',
|
||||
properties: {
|
||||
templateId: template.template.meta.templateId,
|
||||
title: template.name,
|
||||
description: template.description,
|
||||
nodes: template.nodes.flatMap((node) => {
|
||||
const nodeType = nodeTypesStore.getNodeType(node.name, node.version);
|
||||
if (!nodeType) {
|
||||
return [];
|
||||
}
|
||||
return nodeType;
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const openPreBuiltAgentsModal = async (source: 'workflowsEmptyState' | 'workflowsList') => {
|
||||
telemetry.track('User opened pre-built Agents collection', {
|
||||
source,
|
||||
node_type: null,
|
||||
section: null,
|
||||
});
|
||||
|
||||
await nodeTypesStore.loadNodeTypesIfNotLoaded();
|
||||
uiStore.openModal(PRE_BUILT_AGENTS_MODAL_KEY);
|
||||
};
|
||||
|
||||
const openPreBuiltAgentsCollection = async (options: {
|
||||
telemetry: {
|
||||
source: 'ndv' | 'nodeCreator';
|
||||
@@ -91,6 +132,7 @@ export function useCalloutHelpers() {
|
||||
section: options.telemetry.section ?? null,
|
||||
});
|
||||
|
||||
await nodeTypesStore.loadNodeTypesIfNotLoaded();
|
||||
const items: INodeCreateElement[] = getPreBuiltAgentNodeCreatorItems();
|
||||
|
||||
ndvStore.setActiveNodeName(null);
|
||||
@@ -124,7 +166,7 @@ export function useCalloutHelpers() {
|
||||
templateId: string,
|
||||
options: {
|
||||
telemetry: {
|
||||
source: 'ndv' | 'nodeCreator';
|
||||
source: 'ndv' | 'nodeCreator' | 'modal' | 'templates';
|
||||
nodeType?: string;
|
||||
section?: string;
|
||||
};
|
||||
@@ -141,6 +183,13 @@ export function useCalloutHelpers() {
|
||||
node_type: options.telemetry.nodeType ?? null,
|
||||
section: options.telemetry.section ?? null,
|
||||
});
|
||||
} else if (isTutorialTemplateId(templateId)) {
|
||||
telemetry.track('User inserted tutorial template', {
|
||||
template: templateId,
|
||||
source: options.telemetry.source,
|
||||
node_type: options.telemetry.nodeType ?? null,
|
||||
section: options.telemetry.section ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
const template = getSampleWorkflowByTemplateId(templateId);
|
||||
@@ -185,8 +234,10 @@ export function useCalloutHelpers() {
|
||||
|
||||
return {
|
||||
openSampleWorkflowTemplate,
|
||||
openPreBuiltAgentsModal,
|
||||
openPreBuiltAgentsCollection,
|
||||
getPreBuiltAgentNodeCreatorItems,
|
||||
getTutorialTemplatesNodeCreatorItems,
|
||||
isRagStarterCalloutVisible,
|
||||
isPreBuiltAgentsCalloutVisible,
|
||||
isCalloutDismissed,
|
||||
|
||||
@@ -14,7 +14,11 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { SampleTemplates, isPrebuiltAgentTemplateId } from '@/utils/templates/workflowSamples';
|
||||
import {
|
||||
SampleTemplates,
|
||||
isPrebuiltAgentTemplateId,
|
||||
isTutorialTemplateId,
|
||||
} from '@/utils/templates/workflowSamples';
|
||||
import {
|
||||
clearPopupWindowState,
|
||||
getExecutionErrorMessage,
|
||||
@@ -79,6 +83,11 @@ export async function executionFinished(
|
||||
template: templateId,
|
||||
status: data.status,
|
||||
});
|
||||
} else if (isTutorialTemplateId(templateId)) {
|
||||
telemetry.track('User executed tutorial template', {
|
||||
template: templateId,
|
||||
status: data.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ export const FROM_AI_PARAMETERS_MODAL_KEY = 'fromAiParameters';
|
||||
export const WORKFLOW_EXTRACTION_NAME_MODAL_KEY = 'workflowExtractionName';
|
||||
export const WHATS_NEW_MODAL_KEY = 'whatsNew';
|
||||
export const WORKFLOW_DIFF_MODAL_KEY = 'workflowDiff';
|
||||
export const PRE_BUILT_AGENTS_MODAL_KEY = 'preBuiltAgents';
|
||||
export const EXPERIMENT_TEMPLATE_RECO_V2_KEY = 'templateRecoV2';
|
||||
|
||||
export const COMMUNITY_PACKAGE_MANAGE_ACTIONS = {
|
||||
@@ -600,6 +601,7 @@ export const enum VIEWS {
|
||||
SHARED_CREDENTIALS = 'SharedCredentials',
|
||||
ENTITY_NOT_FOUND = 'EntityNotFound',
|
||||
ENTITY_UNAUTHORIZED = 'EntityUnAuthorized',
|
||||
PRE_BUILT_AGENT_TEMPLATES = 'PreBuiltAgentTemplates',
|
||||
}
|
||||
|
||||
export const EDITABLE_CANVAS_VIEWS = [VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW, VIEWS.EXECUTION_DEBUG];
|
||||
|
||||
@@ -20,6 +20,7 @@ import { tryToParseNumber } from '@/utils/typesUtils';
|
||||
import { projectsRoutes } from '@/routes/projects.routes';
|
||||
import TestRunDetailView from '@/views/Evaluations.ee/TestRunDetailView.vue';
|
||||
import { MfaRequiredError } from '@n8n/rest-api-client';
|
||||
import { useCalloutHelpers } from './composables/useCalloutHelpers';
|
||||
|
||||
const ChangePasswordView = async () => await import('./views/ChangePasswordView.vue');
|
||||
const ErrorView = async () => await import('./views/ErrorView.vue');
|
||||
@@ -65,6 +66,8 @@ const WorkflowOnboardingView = async () => await import('@/views/WorkflowOnboard
|
||||
const EvaluationsView = async () => await import('@/views/Evaluations.ee/EvaluationsView.vue');
|
||||
const EvaluationRootView = async () =>
|
||||
await import('@/views/Evaluations.ee/EvaluationsRootView.vue');
|
||||
const PrebuiltAgentTemplatesView = async () =>
|
||||
await import('@/views/PrebuiltAgentTemplatesView.vue');
|
||||
|
||||
function getTemplatesRedirect(defaultRedirect: VIEWS[keyof VIEWS]): { name: string } | false {
|
||||
const settingsStore = useSettingsStore();
|
||||
@@ -106,6 +109,29 @@ export const routes: RouteRecordRaw[] = [
|
||||
middleware: ['authenticated'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/templates/agents',
|
||||
name: VIEWS.PRE_BUILT_AGENT_TEMPLATES,
|
||||
components: {
|
||||
default: PrebuiltAgentTemplatesView,
|
||||
sidebar: MainSidebar,
|
||||
},
|
||||
meta: {
|
||||
templatesEnabled: true,
|
||||
getRedirect: getTemplatesRedirect,
|
||||
middleware: ['authenticated'],
|
||||
},
|
||||
beforeEnter: (_to, _from, next) => {
|
||||
const calloutHelpers = useCalloutHelpers();
|
||||
const templatesStore = useTemplatesStore();
|
||||
|
||||
if (!calloutHelpers.isPreBuiltAgentsCalloutVisible.value) {
|
||||
window.location.href = templatesStore.websiteTemplateRepositoryURL;
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
},
|
||||
// Following two routes are kept in-app:
|
||||
// Single workflow view, used when a custom template host is set
|
||||
// Also, reachable directly from this URL
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
LOCAL_STORAGE_THEME,
|
||||
WHATS_NEW_MODAL_KEY,
|
||||
WORKFLOW_DIFF_MODAL_KEY,
|
||||
PRE_BUILT_AGENTS_MODAL_KEY,
|
||||
EXPERIMENT_TEMPLATE_RECO_V2_KEY,
|
||||
} from '@/constants';
|
||||
import { STORES } from '@n8n/stores';
|
||||
@@ -126,7 +127,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
||||
PROJECT_MOVE_RESOURCE_MODAL,
|
||||
NEW_ASSISTANT_SESSION_MODAL,
|
||||
IMPORT_WORKFLOW_URL_MODAL_KEY,
|
||||
WHATS_NEW_MODAL_KEY,
|
||||
PRE_BUILT_AGENTS_MODAL_KEY,
|
||||
WORKFLOW_DIFF_MODAL_KEY,
|
||||
].map((modalKey) => [modalKey, { open: false }]),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"name": "Calendar agent",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": false,
|
||||
"templateId": "calendar_agent_with_gcal"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "calendar",
|
||||
"calendar": {},
|
||||
"timeMin": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Start_Time', `Start date for availability check`, 'string') }}",
|
||||
"timeMax": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('End_Time', `End date for availability check`, 'string') }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "e3eb2815-735f-4efd-a844-da9d53e45aae",
|
||||
"name": "Get availability",
|
||||
"type": "n8n-nodes-base.googleCalendarTool",
|
||||
"typeVersion": 1.3,
|
||||
"position": [-32, 224],
|
||||
"credentials": {}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"calendar": {},
|
||||
"returnAll": true,
|
||||
"timeMin": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('After', `Start date for events search`, 'string') }}",
|
||||
"timeMax": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Before', `End date for events search`, 'string') }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "d6c00690-8d0c-4942-aa17-077e61c909d1",
|
||||
"name": "Get events",
|
||||
"type": "n8n-nodes-base.googleCalendarTool",
|
||||
"typeVersion": 1.3,
|
||||
"position": [112, 224],
|
||||
"credentials": {}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "719aaeb8-e985-4235-bcff-c2bfbc6a8e5c",
|
||||
"name": "Chat message",
|
||||
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
|
||||
"typeVersion": 1.3,
|
||||
"position": [-400, -32],
|
||||
"webhookId": "d0b3a15a-8d1c-437c-8523-00e108af14a4"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4o-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"id": "d23e09d0-ff7c-4f1b-8e67-6864801dbc44",
|
||||
"name": "Model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"position": [-304, 224],
|
||||
"credentials": {}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
|
||||
"typeVersion": 1.3,
|
||||
"position": [-176, 224],
|
||||
"id": "56bd33c7-bb31-4957-9f12-fdcc2351880d",
|
||||
"name": "Simple Memory"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {
|
||||
"systemMessage": "=You are a helpful calendar assistant. You can check availability and retrieve events from Google Calendar. When users ask about dates, use the calendar tools to get the information. Always specify date ranges clearly when using the tools.\n\nTodays date is: {{ $now }}",
|
||||
"maxIterations": 10,
|
||||
"returnIntermediateSteps": false,
|
||||
"passthroughBinaryImages": true,
|
||||
"batching": {},
|
||||
"enableStreaming": true
|
||||
}
|
||||
},
|
||||
"id": "1e8be9e3-24a4-415b-9424-6f9010c5f333",
|
||||
"name": "Calendar agent",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 2.2,
|
||||
"position": [-160, -32]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "### Calendar agent\nThis agent can perform read actions on your Google Calendar with the **Get availability** and **Get events** tools.\n\n#### Set up\n- Configure credentials in the **Get availability, Get events** and **Model** nodes\n- Select your calendar in both the calendar tools.\n- Send a message to the agent , asking what events you have in your calendar today.\n\n#### Next steps\nTry adding a tool to create or modify events in your calendar.\n\n",
|
||||
"height": 432,
|
||||
"width": 304,
|
||||
"color": 5
|
||||
},
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"position": [-768, -96],
|
||||
"typeVersion": 1,
|
||||
"id": "e0db0cb3-6f04-4a59-9180-27f9f8fef302",
|
||||
"name": "Sticky Note"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Get availability": {
|
||||
"ai_tool": [
|
||||
[
|
||||
{
|
||||
"node": "Calendar agent",
|
||||
"type": "ai_tool",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get events": {
|
||||
"ai_tool": [
|
||||
[
|
||||
{
|
||||
"node": "Calendar agent",
|
||||
"type": "ai_tool",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Chat message": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Calendar agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Calendar agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Simple Memory": {
|
||||
"ai_memory": [
|
||||
[
|
||||
{
|
||||
"node": "Calendar agent",
|
||||
"type": "ai_memory",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "Voice assistant agent (with Telegram and Gcal)",
|
||||
"name": "Voice assistant agent",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": false,
|
||||
"templateId": "voice_assistant_agent_with_telegram_and_gcal"
|
||||
"templateId": "voice_assistant_agent_with_telegram"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
@@ -125,26 +125,6 @@
|
||||
"id": "2e9b0ca3-2a0f-441d-89a0-28365c5ba3ef",
|
||||
"name": "Set field"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"calendar": {
|
||||
"__rl": true,
|
||||
"value": "robert@n8n.io",
|
||||
"mode": "list",
|
||||
"cachedResultName": "robert@n8n.io"
|
||||
},
|
||||
"timeMin": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('After', ``, 'string') }}",
|
||||
"timeMax": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Before', ``, 'string') }}",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.googleCalendarTool",
|
||||
"typeVersion": 1.3,
|
||||
"position": [928, 288],
|
||||
"id": "a533f343-5bf8-48d7-bc35-b12072e32602",
|
||||
"name": "Get events",
|
||||
"credentials": {}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": {
|
||||
@@ -162,25 +142,12 @@
|
||||
"position": [624, 288],
|
||||
"credentials": {}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "### Voice assistant agent\nPersonal AI assistant in Telegram, handling both text and voice messages, and can access your Google Calendar.\n\n#### Set up\n- Configure credentials in the **Telegram**, **Model** and **Get events** nodes\n- Follow the bot creation steps in our [Telegram docs](https://docs.n8n.io/integrations/builtin/credentials/telegram/#supported-authentication-methods)\n- Add your user ID to the **Telegram Message Trigger** so only you can access it\n- Activate the workflow to recieve messages from Telegram\n- Send a message to the agent, e.g. What's in my calendar today?\n\n#### Next steps\nTry giving the agent more tools, so that it can draft emails and events for you from just a message.\n",
|
||||
"height": 496,
|
||||
"width": 304,
|
||||
"color": 5
|
||||
},
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"position": [-576, -80],
|
||||
"typeVersion": 1,
|
||||
"id": "a4a8f73c-f663-4ee6-b60e-d60722145726",
|
||||
"name": "Sticky Note"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "={{ $json.text || $json.message.text }}",
|
||||
"options": {
|
||||
"systemMessage": "=# Telegram Personal AI Assistant System Prompt\n\nYou are a helpful personal AI assistant that communicates with users through Telegram. You can handle both text and voice messages, and you have access to the user's email and calendar to provide comprehensive assistance.\n\n## Your Capabilities\n\nYou can help users with:\n- **General conversation** - Answer questions and provide information\n- **Email management** - Check and retrieve emails from their Gmail account\n- **Calendar assistance** - View upcoming events and schedule information\n- **Voice interaction** - Respond to transcribed voice messages naturally\n- **Personal productivity** - Help organize and manage daily tasks\n\n## Communication Style\n\n### Response Format\n- Keep responses conversational and friendly\n- Use appropriate length for Telegram (not too long)\n- Be helpful and informative\n- Match the user's tone and communication style\n\n### Voice Message Handling\n- When users send voice messages, respond naturally as if they spoke to you directly\n- Don't mention that their message was transcribed\n- Respond in a conversational way that flows naturally\n\n## Tool Usage Guidelines\n\n### Email Tool (Get emails)\n- Use when users ask about emails, messages, or communication\n- Can filter by date ranges when specified\n- Help users find specific emails or get summaries\n- Examples: \"Check my recent emails\", \"Any important messages today?\"\n\n### Calendar Tool (Get events)\n- Use when users ask about schedule, meetings, or appointments\n- Can check specific date ranges\n- Help with scheduling conflicts or availability\n- Examples: \"What's on my calendar today?\", \"Am I free tomorrow afternoon?\"\n\n## Context Awareness\n\n- Remember the current date and time: **Today is {{ $now.format('cccc') }} the {{ $now.format('yyyy-MM-dd HH:mm') }}**\n- Use this information to provide relevant, timely responses\n- Reference \"today\", \"tomorrow\", \"this week\" appropriately\n\n## User Interaction Examples\n\n### Email Queries\n**User**: \"Any important emails today?\"\n**You**: \"Let me check your recent emails...\" *[uses Get emails tool]*\n\n### Calendar Queries \n**User**: \"What's my schedule like tomorrow?\"\n**You**: \"I'll check your calendar for tomorrow...\" *[uses Get events tool]*\n\n### General Assistance\n**User**: \"How's the weather?\"\n**You**: \"I don't have access to weather data, but you could check your local weather app or ask me to help with your emails or calendar instead!\"\n\n## Important Guidelines\n\n- Maintain conversation context using the memory system\n- Be proactive in offering help with emails and calendar\n- Ask clarifying questions when requests are ambiguous\n- Respect privacy - only access information when relevant to the request\n- If you can't help with something, suggest alternatives or redirect to your available capabilities\n\nRemember: You're a personal assistant, so be personable, helpful, and focused on making the user's day easier and more organized.\n\nToday is {{ $now.format('cccc') }} the {{ $now.format('yyyy-MM-dd HH:mm') }}."
|
||||
"systemMessage": "=# Telegram Personal AI Assistant System Prompt\n\nYou are a helpful personal AI assistant that communicates with users through Telegram.\n\n## Your Capabilities\n\nYou can help users with:\n- **General conversation** - Answer questions and provide information\n\n## Communication Style\n\n### Response Format\n- Keep responses conversational and friendly\n- Use appropriate length for Telegram (not too long)\n- Be helpful and informative\n- Match the user's tone and communication style\n\n### Voice Message Handling\n- When users send voice messages, respond naturally as if they spoke to you directly\n- Don't mention that their message was transcribed\n- Respond in a conversational way that flows naturally\n\n\n## Context Awareness\n\n- Remember the current date and time: **Today is {{ $now.format('cccc') }} the {{ $now.format('yyyy-MM-dd HH:mm') }}**\n- Use this information to provide relevant, timely responses\n- Reference \"today\", \"tomorrow\", \"this week\" appropriately\n\n## Important Guidelines\n\n- Maintain conversation context using the memory system\n- Ask clarifying questions when requests are ambiguous\n- Respect privacy - only access information when relevant to the request\n- If you can't help with something, suggest alternatives or redirect to your available capabilities\n\nRemember: You're a personal assistant, so be personable, helpful, and focused on making the user's day easier and more organized.\n\nToday is {{ $now.format('cccc') }} the {{ $now.format('yyyy-MM-dd HH:mm') }}."
|
||||
}
|
||||
},
|
||||
"id": "f958d2f6-6602-4443-bfae-ef628af09b84",
|
||||
@@ -190,7 +157,6 @@
|
||||
"position": [704, 0]
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Telegram Message Trigger": {
|
||||
"main": [
|
||||
@@ -265,17 +231,6 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get events": {
|
||||
"ai_tool": [
|
||||
[
|
||||
{
|
||||
"node": "Assistant Agent",
|
||||
"type": "ai_tool",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
@@ -299,6 +254,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
@@ -0,0 +1,990 @@
|
||||
{
|
||||
"name": "API fundamentals",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": false,
|
||||
"templateId": "api_fundamentals"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "52e84ac4-6495-443e-b3d6-16d3291a6261",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Start Tutorial",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [320, 464],
|
||||
"parameters": {},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "5aab442c-2fd7-4b41-ae0f-eeaf9e722213",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "1. The Kitchen (GET /menu)",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1632, 464],
|
||||
"webhookId": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
|
||||
"parameters": {
|
||||
"path": "/tutorial/api/menu",
|
||||
"options": {},
|
||||
"responseMode": "lastNode"
|
||||
},
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"id": "cee20f21-eb2e-4ea6-947f-acbc7f27a437",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Prepare Menu Data",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1856, 464],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "item",
|
||||
"type": "string",
|
||||
"value": "Pizza"
|
||||
},
|
||||
{
|
||||
"id": "67890",
|
||||
"name": "price",
|
||||
"type": "number",
|
||||
"value": 12
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "4af53e2a-0d5a-4b2e-8e65-fbc19d138abb",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "1. The Customer (GET Menu Item)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1312, 464],
|
||||
"parameters": {
|
||||
"url": "={{ $json.base_url }}/tutorial/api/menu",
|
||||
"options": {}
|
||||
},
|
||||
"typeVersion": 4.1
|
||||
},
|
||||
{
|
||||
"id": "a1f76b04-daf5-41b9-b300-332b61d69cda",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-320, -96],
|
||||
"parameters": {
|
||||
"width": 800,
|
||||
"height": 768,
|
||||
"content": "## Tutorial - What is an API?\n\nWelcome! This workflow will teach you the basics of APIs (Application Programming Interfaces).\n\n**What is an API?**\nThink of it like ordering food at a restaurant.\n- **You** are the \"Client\" (the **HTTP Request** node). You want something.\n- The **Kitchen** is the \"Server\" (the **Webhook** node). It has the data/service you want.\n- The **API** is the **Waiter and the Menu**. It's the set of rules and options for making a request and getting a response.\n\n\n**What is an Endpoint?**\nAn endpoint is a specific address for a specific action. For example, `GET /menu` is one endpoint to get the menu, and `POST /review` is another to submit a review. Each webhook in this tutorial represents one endpoint.\n\n**How to use this tutorial:**\n1. **Activate** the workflow (toggle on the top right).\n2. **Configure the Base URL** (see the yellow note to the left).\n3. Click **\"Execute Workflow\"**. The workflow will run from top to bottom.\n4. Explore each \"Lesson\" by clicking on the **HTTP Request** node (the Customer) and its corresponding **Webhook** node (the Kitchen).\n\n\n**➡️ How to See the Data:**\nWhen you run this, the Webhook nodes run in the background. To see the data they received, go to the **\"Executions\"** tab of this workflow. You will see a separate execution for each API call!"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "d37a6305-25ff-412d-8763-06e4c0a6b8ae",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note1",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1248, 160],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 864,
|
||||
"height": 512,
|
||||
"content": "#### Lesson 1: The Basics (Method & URL)\n\nThis is the simplest possible request.\n\n- **URL (Uniform Resource Locator):** This is the **address of the restaurant's kitchen**. The HTTP Request node needs to know exactly where to send the order. We use an expression to get the Webhook's address automatically from our configuration.\n\n- **Method: `GET`**: This is **what you want to do**. `GET` is used to **retrieve** or **get** information. It's like asking the waiter, \"What's on the menu today?\" `GET` requests are simple and don't contain a \"body\" payload.\n\n\n**➡️ Look at the output of the HTTP Request node. It received exactly what the `Respond to Webhook` node sent back!**"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "edef5d0b-f0a4-4651-9ee4-84ecbe9e8062",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "2. The Customer (GET with Query Params)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1312, 1136],
|
||||
"parameters": {
|
||||
"url": "={{ $json.base_url }}/tutorial/api/order",
|
||||
"options": {},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "extra_cheese",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 4.1
|
||||
},
|
||||
{
|
||||
"id": "a2b5422d-fb46-4737-8182-dbbb50293fa1",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "2. The Kitchen (GET /order)",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1632, 1136],
|
||||
"webhookId": "b2c3d4e5-f6a7-8901-2345-67890abcdef1",
|
||||
"parameters": {
|
||||
"path": "/tutorial/api/order",
|
||||
"options": {},
|
||||
"responseMode": "lastNode"
|
||||
},
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"id": "91d5b241-64c9-48cf-bf79-fff121d0febf",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Prepare Cheese Pizza",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [2064, 1040],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "order",
|
||||
"type": "string",
|
||||
"value": "Pizza with extra cheese"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "bee0bce4-f854-4c1d-9ad1-b526f02e1925",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Prepare Plain Pizza",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [2064, 1232],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "order",
|
||||
"type": "string",
|
||||
"value": "Plain Pizza"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "d6862155-ea76-4357-bae7-c4329ec1a612",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note2",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1248, 704],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 1056,
|
||||
"height": 720,
|
||||
"content": "#### Lesson 2: Customizing a Request (Query Parameters)\n\nWhat if you want to customize your order? That's what Query Parameters are for.\n\n**Query Parameters:** These are simple `key=value` options added to the end of the URL after a `?`. They are used to filter, sort, or specify what you want.\n\nIt's like telling the waiter, \"I'll have the pizza... **and can you add extra cheese?**\"\n\nThe full URL sent by the HTTP Request node looks like this:\n`.../tutorial/api/order?extra_cheese=true`\n\n**⚠️ Important:** Because they are part of the URL, **all query parameter values are treated as strings!** The webhook receives `\"true\"` (a string), not `true` (a boolean). The IF node is set to a \"loose\" comparison to handle this correctly.\n\n**➡️ The Webhook node uses an IF node to check for this parameter and changes its response. Try setting the value to `false` in the HTTP Request node and run it again!**"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "c568bc8c-822d-42b9-ab9b-2d91ae4e4fed",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "3. The Customer (POST with Body)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1312, 1744],
|
||||
"parameters": {
|
||||
"url": "={{ $json.base_url }}/tutorial/api/review",
|
||||
"method": "POST",
|
||||
"options": {},
|
||||
"sendBody": true,
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "comment",
|
||||
"value": "The pizza was amazing!"
|
||||
},
|
||||
{
|
||||
"name": "rating",
|
||||
"value": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 4.1
|
||||
},
|
||||
{
|
||||
"id": "0f0dae83-3290-4a5f-a039-3b54d87bcf7d",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "3. The Kitchen (POST /review)",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1632, 1744],
|
||||
"webhookId": "c3d4e5f6-a7b8-9012-3456-7890abcdef12",
|
||||
"parameters": {
|
||||
"path": "/tutorial/api/review",
|
||||
"options": {},
|
||||
"httpMethod": "POST",
|
||||
"responseMode": "lastNode"
|
||||
},
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"id": "c5aa4f94-eca3-4321-b961-084917b93b81",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Process Review Data",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1856, 1744],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "status",
|
||||
"type": "string",
|
||||
"value": "review_received"
|
||||
},
|
||||
{
|
||||
"id": "67890",
|
||||
"name": "your_comment",
|
||||
"type": "string",
|
||||
"value": "={{ $json.body.comment }}"
|
||||
},
|
||||
{
|
||||
"id": "91011",
|
||||
"name": "your_rating",
|
||||
"type": "number",
|
||||
"value": "={{ $json.body.rating }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "4020d264-7c01-4cf2-b51e-b8b3f2bc382b",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note3",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1248, 1456],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 1056,
|
||||
"height": 476,
|
||||
"content": "#### Lesson 3: Sending Data (POST & Body)\n\nSometimes, you don't want to *get* data, you want to *send* it.\n\n- **Method: `POST`**: This method is used to **send new data** to the server to create or update a resource. It's like handing the waiter a completed customer feedback card.\n\n- **Body:** This is the **actual data you are sending**. Unlike a `GET` request, a `POST` request has a \"body\" where you can put complex data, like a JSON object. This is much more powerful than query parameters for sending information.\n\n\n**➡️ The HTTP Request sends a JSON object in its body. The Webhook receives it and includes your comment in its response. Check the \"Executions\" panel to see the full body the webhook received!**"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "0fec2702-e2c9-4090-a327-f1d723b144b6",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "4. The Customer (GET with Headers/Auth)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1312, 2608],
|
||||
"parameters": {
|
||||
"url": "={{ $json.base_url }}/tutorial/api/secret-dish",
|
||||
"options": {},
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "super-secret-key"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 4.1
|
||||
},
|
||||
{
|
||||
"id": "a35c1fc1-65a3-4d84-b29e-22939d334005",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "4. The Kitchen (GET /secret-dish)",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1632, 2608],
|
||||
"webhookId": "d4e5f6a7-b8c9-0123-4567-890abcdef123",
|
||||
"parameters": {
|
||||
"path": "/tutorial/api/secret-dish",
|
||||
"options": {},
|
||||
"responseMode": "responseNode"
|
||||
},
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"id": "8cb4d6d3-c29f-4562-a966-d13beafd8f40",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note4",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1248, 1968],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 1056,
|
||||
"height": 908,
|
||||
"content": "#### Lesson 4: Identification (Headers & Auth)\n\nHeaders contain meta-information *about* your request. They're not part of the data itself, but they provide important context. Authentication is a common use case.\n\n- **Headers:** Think of this as **showing your VIP membership card** or whispering a secret password to the waiter. It's information that proves who you are or what your request's properties are.\n\n- **Authentication (Auth):** This is the process of proving your identity. Here, we use a custom header (`x-api-key`) as a \"secret key\". In the real world, this is how most APIs control access.\n\n\n**➡️ The Webhook checks for the correct secret key in the headers. If it's wrong or missing, it denies the request with a `401 Unauthorized` status code. Try changing the key in the HTTP Request node!**"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "3ab5e611-71d2-4ff3-9462-fb69d60b48aa",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "5. The Customer (Request with Timeout)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"onError": "continueErrorOutput",
|
||||
"position": [1312, 3328],
|
||||
"parameters": {
|
||||
"url": "={{ $json.base_url }}/tutorial/api/slow-service",
|
||||
"options": {
|
||||
"timeout": 2000
|
||||
}
|
||||
},
|
||||
"typeVersion": 4.1
|
||||
},
|
||||
{
|
||||
"id": "5545e004-b8c3-4c47-91ee-31a2efa4c833",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "5. The Kitchen (GET /slow-service)",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1632, 3328],
|
||||
"webhookId": "e5f6a7b8-c9d0-1234-5678-90abcdef1234",
|
||||
"parameters": {
|
||||
"path": "/tutorial/api/slow-service",
|
||||
"options": {},
|
||||
"responseMode": "lastNode"
|
||||
},
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"id": "237f65ad-3f1d-4afe-8541-89108afd57de",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Prepare Slow Response",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [2064, 3328],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "status",
|
||||
"type": "string",
|
||||
"value": "Finally, your food is here!"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "dbe83810-b95f-46dd-bc2d-bf456fe11f94",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note5",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1248, 2912],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 1056,
|
||||
"height": 624,
|
||||
"content": "#### Lesson 5: Being Patient (Timeout & Error Handling)\n\nAn API request isn't instant. What if the kitchen is really busy?\n\n- **Timeout:** This is the **maximum amount of time (in milliseconds) you are willing to wait** for a response before you give up.\n\n- **On Error Output:** Notice the HTTP Request node has two outputs. The bottom one is the **error path**. If the request fails for any reason (like a timeout), the workflow will continue down this path instead of stopping.\n\n\nIn this example:\n- The **Kitchen (Webhook)** has a 3-second delay.\n- The **Customer (HTTP Request)** is only willing to wait for 2 seconds (2000 ms).\n\n\n**➡️ This request is designed to FAIL! The customer gives up before the kitchen can finish. The error output of the HTTP Request node will light up. This is crucial for building robust workflows that can handle API failures.**"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "4e74c301-06e2-4b19-8101-3c63816ea615",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Wait 3 seconds",
|
||||
"type": "n8n-nodes-base.wait",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1856, 3328],
|
||||
"webhookId": "86b44ff3-7d31-4027-a1cd-df67f9ab974a",
|
||||
"parameters": {
|
||||
"amount": 3
|
||||
},
|
||||
"typeVersion": 1.1
|
||||
},
|
||||
{
|
||||
"id": "42619763-955e-40d6-bbdb-23eea3c0bd85",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "IF Authorized",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1856, 2608],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"combinator": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"id": "ca861c2d-78d9-403b-8bab-28d8e7dcf39c",
|
||||
"operator": {
|
||||
"name": "filter.operator.equals",
|
||||
"type": "string",
|
||||
"operation": "equals"
|
||||
},
|
||||
"leftValue": "={{ $json.headers['x-api-key'] }}",
|
||||
"rightValue": "super-secret-key"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 2.2
|
||||
},
|
||||
{
|
||||
"id": "562d4436-df8c-47bf-b259-eea95ea88147",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "IF extra cheese",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1840, 1136],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "loose"
|
||||
},
|
||||
"combinator": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"id": "ca861c2d-78d9-403b-8bab-28d8e7dcf39c",
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true",
|
||||
"singleValue": true
|
||||
},
|
||||
"leftValue": "={{ $json.query.extra_cheese }}",
|
||||
"rightValue": "your-api-key-for-example"
|
||||
}
|
||||
]
|
||||
},
|
||||
"looseTypeValidation": true
|
||||
},
|
||||
"typeVersion": 2.2
|
||||
},
|
||||
{
|
||||
"id": "57a48d64-954c-4871-a5ff-5390c69a003a",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note12",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [656, 2336],
|
||||
"parameters": {
|
||||
"color": 4,
|
||||
"width": 540,
|
||||
"height": 1200,
|
||||
"content": "## Was this helpful? Let me know!\n[](https://api.ia2s.app/form/templates/academy)\n\nI really hope this tutorial helped you understand APIs better. Your feedback is incredibly valuable and helps me create better resources for the n8n community.\n\n### **Have Feedback, a Question, or a Project Idea?**\n\nI've streamlined the way we connect. It all starts with one simple form that takes less than 10 seconds. After that, you'll chat with my AI assistant who will gather the key details and pass them directly on to me.\n\n#### ➡️ **[Click here to start the conversation](https://api.ia2s.app/form/templates/academy)**\n\nUse this single link for anything you need:\n\n* **Give Feedback:** Share your thoughts on this template—whether you found a typo, encountered an unexpected error, have a suggestion, or just want to say thanks!\n\n* **n8n Coaching:** Get personalized, one-on-one guidance to master n8n. We can work together to get you launched with confidence or help you reach an expert level.\n\n* **n8n Consulting:** Have a complex business challenge or need a custom workflow built from scratch? Let's partner on a powerful automation solution tailored to your specific needs.\n\n---\n\nHappy Automating!\nLucas Peyrin | [n8n Academy](https://n8n.ac)"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "df8ea622-c2a5-4f6b-9a09-5d35b1dea0aa",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note6",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [512, -96],
|
||||
"parameters": {
|
||||
"color": 3,
|
||||
"width": 700,
|
||||
"height": 764,
|
||||
"content": "## ✨ **CONFIGURATION REQUIRED** ✨\n\nTo use this interactive tutorial, you need to tell the \"Customer\" nodes where to find the \"Kitchen\" nodes.\n\n### **1. Get your Webhook URL**\n\n* **Activate** this workflow using the toggle switch at the top right of the screen.\n* Open any Webhook node in this workflow (e.g., `1. The Kitchen (GET /menu)`).\n* Go to the **Production URL** field and click the copy button.\n\n### **2. Update the CONFIGURATION Node**\n\n* Open the `CONFIGURATION` node (the one this note is pointing to).\n* In the **Value** field, **paste the full URL** you just copied.\n\n\nThat's it! Now you can run the workflow, and all the HTTP Request nodes will know how to call your webhooks."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "c0c76d70-c630-4630-a22e-1909a410c527",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Base URL",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [912, 464],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "7edededc-2f40-4b8e-b8db-ab4816f1a28e",
|
||||
"name": "base_url",
|
||||
"type": "string",
|
||||
"value": "={{ $json.your_n8n_webhook_url.match(/^(https?:\\/\\/[^\\/]+)\\/(webhook-test|webhook|v1|[^\\/]+)/)[1] + '/' + $json.your_n8n_webhook_url.match(/^(https?:\\/\\/[^\\/]+)\\/(webhook-test|webhook|v1|[^\\/]+)/)[2].replace('webhook-test','webhook') }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "e5f25f10-a1f0-4d67-b0fb-c3a2b8abf786",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note8",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1264, 2288],
|
||||
"parameters": {
|
||||
"color": 3,
|
||||
"width": 304,
|
||||
"height": 496,
|
||||
"content": "### ⚠️ **Security Best Practice**\n\nFor this tutorial, we are putting the API key directly in the header. \n\n**In a real project, NEVER do this!**\n\nAlways use n8n's built-in **Credentials** system to store and manage secret keys. You would create a \"Header Auth\" credential and select it in the HTTP Request node's \"Authentication\" parameter. This keeps your secrets safe and out of your workflow JSON."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "e908261b-cced-4539-9ea0-f40fb9e009e5",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Respond with Secret",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [2064, 2496],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"respondWith": "json",
|
||||
"responseBody": "{\n \"dish\": \"The Chef's Special Truffle Pasta\"\n}"
|
||||
},
|
||||
"typeVersion": 1.4
|
||||
},
|
||||
{
|
||||
"id": "52d54052-eab2-45b8-ac9a-389b7fdf40d3",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Respond: Unauthorized (401)",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [2064, 2704],
|
||||
"parameters": {
|
||||
"options": {
|
||||
"responseCode": 401
|
||||
},
|
||||
"respondWith": "text",
|
||||
"responseBody": "You are not authorized to see the secret dish."
|
||||
},
|
||||
"typeVersion": 1.4
|
||||
},
|
||||
{
|
||||
"id": "f9d72d6c-47b8-4f51-8a69-ce4d557ae90f",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "OpenAPI Spec",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [2336, 704],
|
||||
"parameters": {
|
||||
"color": 6,
|
||||
"width": 696,
|
||||
"height": 2096,
|
||||
"content": "# What other services give you\n\nReal-world APIs have documentation that tells developers how to use them. This is what the documentation for our little tutorial API would look like. To put you in context, you would see this documentation and create http request nodes accordingly.\n\n**Remember**, AI can help you !\n\n\n# API Documentation Example\n\nA simple API to demonstrate http requests in n8n.\n\n## API Endpoints\n\n### GET /tutorial/api/menu\n\n**Summary:** Get the menu\n\n**Responses:**\n* `200 OK`: The restaurant menu.\n\n---\n\n### GET /tutorial/api/order\n\n**Summary:** Get a customized order\n\n**Parameters:**\n* `extra_cheese` (query, string, example: `true`): Whether to add extra cheese.\n\n\n**Responses:**\n* `200 OK`: Your customized pizza order.\n\n---\n\n### POST /tutorial/api/review\n\n**Summary:** Submit a review\n\n**Request Body (application/json):**\n```json\n{\n \"comment\": \"string\",\n \"rating\": 0\n}\n```\n* `comment` (string): The review comment.\n* `rating` (integer): The rating given (e.g., 1-5).\n\n\n**Responses:**\n* `200 OK`: Confirmation of review receipt.\n\n---\n\n### GET /tutorial/api/secret-dish\n\n**Summary:** Get the secret dish (Auth Required)\n\n**Authentication:** Requires API Key. See [Authentication](https://docs.n8n.io/integrations/builtin/credentials/httprequest/) section for details.\n\n**Responses:**\n* `200 OK`: The secret dish.\n* `401 Unauthorized`: Authentication is required or invalid.\n\n---\n\n### GET /tutorial/api/slow-service\n\n**Summary:** A slow endpoint to test timeouts\n\n**Responses:**\n* `200 OK`: A delayed response.\n\n---\n\n## Authentication\n\nThis API uses an API Key for authentication on certain endpoints.\n\n### API Key Authentication (`ApiKeyAuth`)\n\n* **Type:** API Key\n* **Location:** Header\n* **Header Name:** `x-api-key`\n\n\nTo authenticate, include your API key in the `x-api-key` header of your request.\n\n**Example:**\n```\nGET /tutorial/api/secret-dish\nHost: your-api-domain.com\nx-api-key: YOUR_API_KEY_HERE\n```"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "fba94b52-0c18-4f3c-a702-aa883a3df293",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note7",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [2320, 3040],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 384,
|
||||
"height": 496,
|
||||
"content": ""
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "d6b845a9-9d27-49ec-a9fe-ac48650bd260",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note9",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1760, 1040],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 256,
|
||||
"height": 272,
|
||||
"content": ""
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "c6d51163-f9ea-4d6b-af33-32107d3feb6e",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note10",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [2128, 448],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 176,
|
||||
"height": 224,
|
||||
"content": ""
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "b5a01aca-47be-42d8-8366-4f91bcf24682",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note11",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [2032, 1728],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 256,
|
||||
"content": ""
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "adacc21c-801a-49bb-a6da-630fc8b4272c",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "⚙️CONFIGURATION⚙️",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [688, 464],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "7edededc-2f40-4b8e-b8db-ab4816f1a28e",
|
||||
"name": "your_n8n_webhook_url",
|
||||
"type": "string",
|
||||
"value": "PASTE_YOUR_WEBHOOK_URL_HERE"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "9eb6ee32-0d75-4817-a3d0-3106d88b14e6",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note14",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [1600, 2272],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 400,
|
||||
"height": 256,
|
||||
"content": ""
|
||||
},
|
||||
"typeVersion": 1
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"1. The Kitchen (GET /menu)": [
|
||||
{
|
||||
"body": {},
|
||||
"query": {},
|
||||
"params": {},
|
||||
"headers": {
|
||||
"host": "your-n8n-instance.com",
|
||||
"accept": "application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, image/*;q=0.8, */*;q=0.7",
|
||||
"user-agent": "n8n",
|
||||
"accept-encoding": "gzip, deflate, br",
|
||||
"x-forwarded-for": "192.168.1.1",
|
||||
"x-forwarded-host": "your-n8n-instance.com",
|
||||
"x-forwarded-proto": "https"
|
||||
},
|
||||
"webhookUrl": "https://your-n8n-instance.com/webhook/tutorial/api/menu"
|
||||
}
|
||||
],
|
||||
"2. The Kitchen (GET /order)": [
|
||||
{
|
||||
"body": {},
|
||||
"query": {
|
||||
"extra_cheese": "true"
|
||||
},
|
||||
"params": {},
|
||||
"headers": {
|
||||
"host": "your-n8n-instance.com",
|
||||
"accept": "application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, image/*;q=0.8, */*;q=0.7",
|
||||
"user-agent": "n8n",
|
||||
"accept-encoding": "gzip, deflate, br",
|
||||
"x-forwarded-for": "192.168.1.1",
|
||||
"x-forwarded-host": "your-n8n-instance.com",
|
||||
"x-forwarded-proto": "https"
|
||||
},
|
||||
"webhookUrl": "https://your-n8n-instance.com/webhook/tutorial/api/order?extra_cheese=true"
|
||||
}
|
||||
],
|
||||
"3. The Kitchen (POST /review)": [
|
||||
{
|
||||
"body": {
|
||||
"rating": 5,
|
||||
"comment": "The pizza was amazing!"
|
||||
},
|
||||
"query": {},
|
||||
"params": {},
|
||||
"headers": {
|
||||
"host": "your-n8n-instance.com",
|
||||
"accept": "application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, image/*;q=0.8, */*;q=0.7",
|
||||
"user-agent": "n8n",
|
||||
"content-type": "application/json",
|
||||
"content-length": "49",
|
||||
"accept-encoding": "gzip, deflate, br",
|
||||
"x-forwarded-for": "192.168.1.1",
|
||||
"x-forwarded-host": "your-n8n-instance.com",
|
||||
"x-forwarded-proto": "https"
|
||||
},
|
||||
"webhookUrl": "https://your-n8n-instance.com/webhook/tutorial/api/review"
|
||||
}
|
||||
],
|
||||
"4. The Kitchen (GET /secret-dish)": [
|
||||
{
|
||||
"body": {},
|
||||
"query": {},
|
||||
"params": {},
|
||||
"headers": {
|
||||
"host": "your-n8n-instance.com",
|
||||
"accept": "application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, image/*;q=0.8, */*;q=0.7",
|
||||
"x-api-key": "super-secret-key",
|
||||
"user-agent": "n8n",
|
||||
"accept-encoding": "gzip, deflate, br",
|
||||
"x-forwarded-for": "192.168.1.1",
|
||||
"x-forwarded-host": "your-n8n-instance.com",
|
||||
"x-forwarded-proto": "https"
|
||||
},
|
||||
"webhookUrl": "https://your-n8n-instance.com/webhook/tutorial/api/secret-dish"
|
||||
}
|
||||
],
|
||||
"5. The Kitchen (GET /slow-service)": [
|
||||
{
|
||||
"body": {},
|
||||
"query": {},
|
||||
"params": {},
|
||||
"headers": {
|
||||
"host": "your-n8n-instance.com",
|
||||
"accept": "application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, image/*;q=0.8, */*;q=0.7",
|
||||
"user-agent": "n8n",
|
||||
"accept-encoding": "gzip, deflate, br",
|
||||
"x-forwarded-for": "192.168.1.1",
|
||||
"x-forwarded-host": "your-n8n-instance.com",
|
||||
"x-forwarded-proto": "https"
|
||||
},
|
||||
"webhookUrl": "https://your-n8n-instance.com/webhook/tutorial/api/slow-service"
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"Base URL": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "1. The Customer (GET Menu Item)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "2. The Customer (GET with Query Params)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "3. The Customer (POST with Body)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "4. The Customer (GET with Headers/Auth)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "5. The Customer (Request with Timeout)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"IF Authorized": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond with Secret",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Respond: Unauthorized (401)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Start Tutorial": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "⚙️CONFIGURATION⚙️",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Wait 3 seconds": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Slow Response",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"IF extra cheese": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Cheese Pizza",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Prepare Plain Pizza",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"⚙️CONFIGURATION⚙️": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Base URL",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"1. The Kitchen (GET /menu)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Menu Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"2. The Kitchen (GET /order)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "IF extra cheese",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"3. The Kitchen (POST /review)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Process Review Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"4. The Kitchen (GET /secret-dish)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "IF Authorized",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"5. The Kitchen (GET /slow-service)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Wait 3 seconds",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
{
|
||||
"name": "Build your first AI agent",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": false,
|
||||
"templateId": "build_your_first_ai_agent"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "2c408b32-7862-4411-9ad1-b6e9ff4e41f7",
|
||||
"name": "Sticky Note2",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"position": [592, -256],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 512,
|
||||
"height": 352,
|
||||
"content": "## [Video Tutorial](https://youtu.be/laHIzhsz12E)\n@[youtube](laHIzhsz12E)"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "3808de8d-ef18-47f5-9621-b08ba961ae01",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Introduction Note",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-752, -256],
|
||||
"parameters": {
|
||||
"width": 392,
|
||||
"height": 460,
|
||||
"content": "## Try It Out!\n\n**Launch your first AI agent—a chatbot that uses tools to fetch live info, send emails, and automate tasks.**\n\n### To get started:\n1. **Connect Gemini** (see red sticky note below)\n2. Click the **🗨 Open chat** button and try asking:\n * “What’s the weather in Paris?”\n * “Get me the latest tech news.”\n * “Give me ideas for n8n AI agents.”\n\n### Questions or Feedback?\nFor feedback, coaching, buit-for-you workflows or any questions, use my unified AI-powered contact form.\n\n➡️ **[Get in Touch Here](https://api.ia2s.app/form/templates/academy)**\n\n*Happy Automating! —Lucas Peyrin*"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "2b55763c-0541-4133-aa79-87c3ce9f0564",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note12",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-272, -160],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 300,
|
||||
"height": 252,
|
||||
"content": "💡 Later, activate this workflow and share the public chat URL to let others talk to your AI Agent!"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "f09c396a-dab8-41f8-a6e5-3dbd1dd70323",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note13",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [112, -256],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 396,
|
||||
"height": 348,
|
||||
"content": "Your AI agent can:\n1. **Receive** messages from the chat\n2. **Select** the right tools (e.g., weather, news, email)\n3. **Respond** with live, helpful answers\n\n\n**Open the AI agent node** and edit the **System Message** to adjust your agent’s thinking, behavior, and replies.\n\n\n\n\n\n\n\n\n\n\n"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "f7c57d33-2318-409d-9084-13990299db3d",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note15",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [112, 176],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 308,
|
||||
"height": 260,
|
||||
"content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\nThis node helps your agent remember the last few messages to stay on topic."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "07ed7ed3-91d0-432f-9327-d2a30601082c",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note16",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [512, 176],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 372,
|
||||
"height": 324,
|
||||
"content": "\n\n\n\n\n\n\n\n\n\n\n\n\nThese tools let your agent access real-world data or take actions. Add more to expand its abilities!\n\nClick the ➕ under the agent’s Tool input to add:\n- Google Calendar (Get Upcoming Events)\n- Gmail (Send an Email) (Gmail)"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "24fc1fd5-ed10-43d9-9b35-8facb7f357d5",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Get News",
|
||||
"type": "n8n-nodes-base.rssFeedReadTool",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [656, 224],
|
||||
"parameters": {
|
||||
"url": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('URL', `Use one of:\n- https://feeds.bbci.co.uk/news/world/rss.xml (BBC World – global headlines)\n- https://www.aljazeera.com/xml/rss/all.xml (Al Jazeera English – in‑depth global coverage)\n- http://rss.cnn.com/rss/edition_world.rss (CNN World – breaking news worldwide)\n- https://techcrunch.com/feed/ (TechCrunch – global tech & startup news)\n- http://news.ycombinator.com/rss (Hacker News – tech community headlines)\n- https://n8n.io/blog/rss (n8n Blog – updates & tutorials)\n- https://www.bonappetit.com/feed/recipes-rss-feed/rss (Bon Appétit – recent recipes list)\n- https://www.endsreport.com/rss/news-and-analysis (ENDS Report – environmental law & policy news)\n- https://medlineplus.gov/groupfeeds/new.xml (MedlinePlus – health topics & wellness updates)`, 'string') }}",
|
||||
"options": {},
|
||||
"toolDescription": "Gets the latest blog posts about any rss feed."
|
||||
},
|
||||
"typeVersion": 1.2
|
||||
},
|
||||
{
|
||||
"id": "ac7d0dd1-8885-41c1-abda-f3742c838992",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Get Weather",
|
||||
"type": "n8n-nodes-base.httpRequestTool",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [544, 224],
|
||||
"parameters": {
|
||||
"url": "https://api.open-meteo.com/v1/forecast",
|
||||
"options": {},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "latitude",
|
||||
"value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('parameters0_Value', `Latitude of the location, e.g. 45.75 for Lyon. Do not ask the user just infer it automatically.`, 'string') }}"
|
||||
},
|
||||
{
|
||||
"name": "longitude",
|
||||
"value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('parameters1_Value', `Longitude of the location, e.g. 4.85 for Lyon. Do not ask the user just infer it automatically.`, 'string') }}"
|
||||
},
|
||||
{
|
||||
"name": "current",
|
||||
"value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('parameters2_Value', `Comma-separated list of current weather variables (no whitespace).\n\nExample: temperature_2m,windspeed_10m,rain.\n\nOptions: temperature_2m,relative_humidity_2m,apparent_temperature,is_day,precipitation,rain,showers,snowfall,weathercode,cloudcover_total,pressure_msl,surface_pressure,windspeed_10m,winddirection_10m,windgusts_10m.`, 'string') }}"
|
||||
},
|
||||
{
|
||||
"name": "hourly",
|
||||
"value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('parameters3_Value', `Comma-separated list of hourly weather variables (no whitespace). Hourly is only useful to get one day's information. For weakly overview please use daily.\n\nExample: temperature_2m,precipitation.\n\nOptions: temperature_2m,relative_humidity_2m,dewpoint_2m,apparent_temperature,precipitation,rain,showers,snowfall,snow_depth,pressure_msl,surface_pressure,cloudcover_total,cloudcover_low,cloudcover_mid,cloudcover_high,windspeed_10m,winddirection_10m,windgusts_10m,visibility,is_day,sunshine_duration,soil_temperature,soil_moisture,PM10,PM2_5,carbon_monoxide,ozone,us_aqi,UV_index.`, 'string') }}"
|
||||
},
|
||||
{
|
||||
"name": "daily",
|
||||
"value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('parameters4_Value', `Comma-separated list of daily weather variables (no whitespace).\n\nExample: temperature_2m_max,precipitation_sum.\n\nOptions: weathercode,temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min,precipitation_sum,rain_sum,showers_sum,snowfall_sum,precipitation_hours,sunrise,sunset,daylight_duration,sunshine_duration,pressure_msl_max,pressure_msl_min,surface_pressure_max,surface_pressure_min,windgusts_10m_max,windspeed_10m_max,winddirection_10m_dominant,shortwave_radiation_sum.`, 'string') }}"
|
||||
},
|
||||
{
|
||||
"name": "start_date",
|
||||
"value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('parameters5_Value', `Start date in YYYY-MM-DD format. Example: 2025-07-15`, 'string') }}"
|
||||
},
|
||||
{
|
||||
"name": "end_date",
|
||||
"value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('parameters6_Value', `End date in YYYY-MM-DD format. Must be after start_date. Example: 2025-07-18`, 'string') }}"
|
||||
},
|
||||
{
|
||||
"name": "temperature_unit",
|
||||
"value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('parameters7_Value', `Unit for temperature. Options: celsius (default), fahrenheit.`, 'string') }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"toolDescription": "Get weather forecast anywhere, anytime. You can make requests by assuming most information, the only thing you need is the location (use the city name to infer lat and long automatically) and time period (assume today if not specified)"
|
||||
},
|
||||
"notesInFlow": true,
|
||||
"typeVersion": 4.2
|
||||
},
|
||||
{
|
||||
"id": "332af12a-45ab-4e5d-8dab-da21ba2111f9",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Your First AI Agent",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [192, -64],
|
||||
"parameters": {
|
||||
"options": {
|
||||
"systemMessage": "=<role>\nYou are the n8n Demo AI Agent, a friendly and helpful assistant designed to showcase the power of AI agents within the n8n automation platform. Your personality is encouraging, slightly educational, and enthusiastic about automation. Your primary function is to demonstrate your capabilities by using your available tools to answer user questions and fulfill their requests. You are conversational.\n</role>\n\n<instructions>\n<goal>\nYour primary goal is to act as a live demonstration of an AI Agent built with n8n. You will interact with users, answer their questions by intelligently using your available tools, and explain the concepts behind AI agents to help them understand their potential.\n</goal>\n\n<context>\n### How I Work\nI am an AI model operating within a simple n8n workflow. This workflow gives me two key things:\n1. **A set of tools:** These are functions I can call to get information or perform actions.\n2. **Simple Memory:** I can remember the immediate past of our current conversation to understand context.\n\n### My Purpose\nMy main purpose is to be a showcase. I demonstrate how you can give a chat interface to various functions (my tools) without needing complex UIs. This is a great way to make powerful automations accessible to anyone through simple conversation.\n\n### My Tools Instructions\nYou must choose one of your available tools if the user's request matches its capability. You cannot perform these actions yourself; you must call the tool.\n\n### About AI Agents in n8n\n- **Reliability:** While I can use one tool at a time effectively, more advanced agents can perform multi-step tasks. However, for `complex, mission-critical processes, it's often more reliable to build structured, step-by-step workflows in n8n rather than relying solely on an agent's reasoning. Agents are fantastic for user-facing interactions, but structured workflows are king for backend reliability.\n- **Best Practices:** A good practice is to keep an agent's toolset focused, typically under 10-15 tools, to ensure reliability and prevent confusion.\n\n### Current Date & Time\n{{ $now }}\n</context>\n\n<output_format>\n- Respond in a friendly, conversational, and helpful tone.\n- When a user's request requires a tool, first select the appropriate tool. Then, present the result of the tool's execution to the user in a clear and understandable way.\n- Be proactive. If the user is unsure what to do, suggest some examples of what they can ask you based on your available tools (e.g., Talk about your tools and what you know about yourself).\n</output_format>\n</instructions>"
|
||||
}
|
||||
},
|
||||
"typeVersion": 2.2
|
||||
},
|
||||
{
|
||||
"id": "95421925-c5ad-48bd-9638-c84ff5b5e3c6",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Example Chat",
|
||||
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-176, -64],
|
||||
"webhookId": "e5616171-e3b5-4c39-81d4-67409f9fa60a",
|
||||
"parameters": {
|
||||
"public": true,
|
||||
"options": {
|
||||
"title": "Your first AI Agent 🚀",
|
||||
"subtitle": "This is for demo purposes. Try me out !",
|
||||
"responseMode": "lastNode",
|
||||
"inputPlaceholder": "Type your message here...",
|
||||
"showWelcomeScreen": false
|
||||
},
|
||||
"initialMessages": "Hi there! 👋"
|
||||
},
|
||||
"typeVersion": 1.1
|
||||
},
|
||||
{
|
||||
"id": "56d2684a-7f83-4ffd-8501-3253d999b4c6",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Conversation Memory",
|
||||
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [224, 224],
|
||||
"parameters": {
|
||||
"contextWindowLength": 30
|
||||
},
|
||||
"typeVersion": 1.3
|
||||
},
|
||||
{
|
||||
"id": "c218a5da-bec7-4034-8d2b-f4bca34e551e",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Connect Gemini",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-176, 224],
|
||||
"parameters": {
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "199eb2cd-bc6b-4f61-bbf1-f196c7869b43",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-272, 176],
|
||||
"parameters": {
|
||||
"color": 3,
|
||||
"width": 294,
|
||||
"height": 316,
|
||||
"content": "\n\n\n\n\n\n\n\n\n\n\n\n1. [In Google AI Studio](https://aistudio.google.com/app/apikey) click **“Create API key in new project”** and copy it.\n\n2. Open the ```Connect Gemini``` node:\n * **Select Credential → Create New**\n * Paste into **API Key** and **Save**\n"
|
||||
},
|
||||
"typeVersion": 1
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Get News": {
|
||||
"ai_tool": [
|
||||
[
|
||||
{
|
||||
"node": "Your First AI Agent",
|
||||
"type": "ai_tool",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get Weather": {
|
||||
"ai_tool": [
|
||||
[
|
||||
{
|
||||
"node": "Your First AI Agent",
|
||||
"type": "ai_tool",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Example Chat": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Your First AI Agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Connect Gemini": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Your First AI Agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Conversation Memory": {
|
||||
"ai_memory": [
|
||||
[
|
||||
{
|
||||
"node": "Your First AI Agent",
|
||||
"type": "ai_memory",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,693 @@
|
||||
{
|
||||
"name": "Expressions",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": false,
|
||||
"templateId": "expressions_tutorial"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "2c20bdef-e54e-46ab-9c63-5cf521ec749e",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Start Tutorial",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-7360, 992],
|
||||
"parameters": {},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "25703548-3e48-4951-9e3b-1d7815e15af5",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-7616, 592],
|
||||
"parameters": {
|
||||
"color": 5,
|
||||
"width": 640,
|
||||
"height": 560,
|
||||
"content": "# Tutorial - Mastering n8n Expressions\n\nWelcome! You know what JSON is. Now, let's learn how to **use it**. This workflow teaches you how to pull data from one node and use it in another using n8n's powerful expressions.\n\n**What is an Expression?**\nAn expression is a small piece of code inside double curly braces `{{ }}` that gets replaced with a dynamic value when the workflow runs. It's the \"glue\" that connects your nodes.\n\n**How to use this tutorial:**\n1. The first node, **\"Source Data\"**, contains all the data we will use. Execute it once to see what's inside.\n2. Follow the path from top to bottom. Each node is a new lesson.\n3. Read the sticky note for each lesson, then look at the node's configuration and its output to understand the concept."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "b875f86c-a790-49bc-96a0-e1ccc72a5e80",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Source Data",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-6720, 992],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"value": "Alice"
|
||||
},
|
||||
{
|
||||
"id": "67890",
|
||||
"name": "age",
|
||||
"type": "number",
|
||||
"value": 30
|
||||
},
|
||||
{
|
||||
"id": "abcde",
|
||||
"name": "is_active",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"id": "fghij",
|
||||
"name": "skills",
|
||||
"type": "array",
|
||||
"value": "[\"JavaScript\",\"Python\",\"n8n\"]"
|
||||
},
|
||||
{
|
||||
"id": "klmno",
|
||||
"name": "projects",
|
||||
"type": "array",
|
||||
"value": "[{\"name\":\"Project A\",\"status\":\"Done\"},{\"name\":\"Project B\",\"status\":\"In Progress\"}]"
|
||||
},
|
||||
{
|
||||
"id": "pqrst",
|
||||
"name": "contact",
|
||||
"type": "object",
|
||||
"value": "{\"email\":\"alice@example.com\",\"phone\":null}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "108d6d9a-6e98-491d-87c1-78d3680f0c40",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note1",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-6944, 640],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 520,
|
||||
"height": 520,
|
||||
"content": "## Our Data Source\n\nThis node holds all the sample data for our tutorial. Think of it as a filing cabinet. All the other nodes will be reaching into this cabinet to pull out specific pieces of information.\n\nTake a look at its output to familiarize yourself with the structure.\nWe have:\n- Simple text (`name`)\n- A number (`age`)\n- A list of skills (`skills`)\n- A list of complex projects (`projects`)\n- A nested contact object (`contact`)"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "54723ed3-94c5-4104-b1c5-3ac70c262b87",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "1. The Basics",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-6192, 992],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "user_name",
|
||||
"type": "string",
|
||||
"value": "={{ $('Source Data').item.json.name }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "81dc9611-b9b5-41c3-a5ab-460cb0dc1ca6",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note2",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-6400, 576],
|
||||
"parameters": {
|
||||
"color": 2,
|
||||
"width": 500,
|
||||
"height": 580,
|
||||
"content": "## Lesson 1: Accessing a Simple Value\n\nThis is the most common thing you'll do in n8n.\n\n**The Goal:** Get the user's name from the \"Source Data\" node.\n\n**The Expression:** `{{ $('Source Data').item.json.name }}`\n\n**Breakdown:**\n- `{{ ... }}`: Tells n8n \"this is a dynamic expression\".\n- `$('Source Data')`: Selects the node we want data from.\n- `.item.json`: Narrows it down to the JSON data of the current item.\n- `.name`: Selects the specific **key** we want the value of.\n\n**Other Possibility:**\n`{{ $json.name }}` would also work in this case, as `$json` accesses the data from the previous node."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "b91f6099-5207-4000-9a0e-e6374718c123",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "3. Working with Arrays",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-4960, 992],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "second_skill",
|
||||
"type": "string",
|
||||
"value": "={{ $('Source Data').last().json.skills[1] }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "2074d65b-2d6b-41af-a4f9-fa9a195a1bf8",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note3",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-5168, 624],
|
||||
"parameters": {
|
||||
"color": 4,
|
||||
"width": 540,
|
||||
"height": 520,
|
||||
"content": "## Lesson 3: Accessing an Array Element\n\nWhat if the data is in a list (an array)? You need to specify *which* item you want.\n\n**The Goal:** Get the user's *second* skill.\n\n**The Expression:** `{{ $('Source Data').last().json.skills[1] }}`\n\n**Breakdown:**\n- `...skills`: Selects the array of skills.\n- `[1]`: Selects the item at a specific position.\n- **IMPORTANT:** Arrays are \"zero-indexed\", which means the first item is `[0]`, the second is `[1]`, the third is `[2]`, and so on."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "8ccc139f-0e8c-4c25-9d04-cecf2b335934",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "4. Going Deeper",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-4400, 992],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "user_email",
|
||||
"type": "string",
|
||||
"value": "={{ $('Source Data').last().json.contact.email }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "34264c0c-8282-4b57-a066-0548a31cbf1a",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note4",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-4608, 672],
|
||||
"parameters": {
|
||||
"color": 5,
|
||||
"width": 540,
|
||||
"height": 480,
|
||||
"content": "## Lesson 4: Accessing Nested Data\n\nSometimes, data is organized into objects within other objects.\n\n**The Goal:** Get the user's email address.\n\n**The Expression:** `{{ $('Source Data').last().json.contact.email }}`\n\n**Breakdown:**\n- `...contact`: First, we access the `contact` object.\n- `.email`: Then, we use another dot `.` to go one level deeper and get the value of the `email` key inside it."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "4c41fb6a-5f93-408f-9583-1e118d139dcf",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "5. The Combo Move",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-3808, 992],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "first_project_status",
|
||||
"type": "string",
|
||||
"value": "={{ $('Source Data').last().json.projects[0].status }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "2123cd02-5cb6-4dd7-89e5-7af1c6edf234",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note5",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-4048, 672],
|
||||
"parameters": {
|
||||
"color": 6,
|
||||
"width": 580,
|
||||
"height": 480,
|
||||
"content": "## Lesson 5: Accessing Data in an Array of Objects\n\nThis is the ultimate test of the previous lessons!\n\n**The Goal:** Get the *status* of the *first* project in the list.\n\n**The Expression:** `{{ $('Source Data').last().json.projects[0].status }}`\n\n**Breakdown:**\n1. `...projects`: We select the array of projects.\n2. `[0]`: We pick the first object in that array.\n3. `.status`: From that chosen object, we grab the value of the `status` key."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "f25a5a9d-518d-4a4a-b2ad-36fd0e1f5bd2",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "6. A Touch of Magic",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-3200, 992],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "name_in_caps",
|
||||
"type": "string",
|
||||
"value": "={{ $('Source Data').last().json.name.toUpperCase() }}"
|
||||
},
|
||||
{
|
||||
"id": "67890",
|
||||
"name": "age_in_dog_years",
|
||||
"type": "number",
|
||||
"value": "={{ Math.round($('Source Data').last().json.age / 7) }}"
|
||||
},
|
||||
{
|
||||
"id": "abcde",
|
||||
"name": "age_data_type",
|
||||
"type": "string",
|
||||
"value": "={{ typeof $('Source Data').last().json.age }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "0ebf5e6b-feb8-4f32-9979-9d7555b461ed",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note6",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-3440, 640],
|
||||
"parameters": {
|
||||
"color": 3,
|
||||
"width": 580,
|
||||
"height": 520,
|
||||
"content": "## Lesson 6: A Touch of Magic (JS Functions)\n\nYou can do more than just retrieve data; you can **manipulate and inspect it!**\n\n**The Expressions:**\n- **Transform Text:** `{{ $('Source Data').last().json.name.toUpperCase() }}`\n- **Do Math:** `{{ Math.round($('Source Data').last().json.age / 7) }}`\n- **Check Data Type:** `{{ typeof $('Source Data').last().json.age }}`\n\n**Breakdown:**\n- **`.toUpperCase()`**: A standard JavaScript function for strings.\n- **`Math.round(...)`**: The `Math` object gives you access to powerful math functions.\n- **`typeof`**: An operator that tells you what kind of data you're looking at (\"string\", \"number\", \"object\", etc.)."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "81d8c2d6-95cc-47e3-b0f0-9c698a120d1c",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "9. The \"All Items\" View",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-1264, 992],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "all_skills_string",
|
||||
"type": "string",
|
||||
"value": "={{ $('Split Out Skills').all().map(item => item.json.skills).join(', ') }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"executeOnce": true,
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "609e27f8-1702-493e-bc8b-cbade4561bd2",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note7",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-1696, 640],
|
||||
"parameters": {
|
||||
"color": 5,
|
||||
"width": 780,
|
||||
"height": 520,
|
||||
"content": "## Lesson 9: Working with Multiple Items (`$items` & Arrow Functions)\n\nWhat if a node outputs *multiple* items and you want to summarize them? `$items()` is your tool.\n\n**The Goal:** Get a single, comma-separated string of all the user's skills.\n\n**The Expression:** `{{ $('Split Out Skills').all().map(item `=>` item.json.skills).join(', ') }}`\n\n**What is `item => ...`?**\nThis is an **Arrow Function**, a shorthand for \"for each thing, do this\".\n- `item`: A temporary name for each item in the list as we loop over it.\n- =>: The \"arrow\" that separates the item from the action.\n- `item.json.skills`: The action to perform—in this case, get the skill value from the item."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "d6afc595-c6f0-47b2-894e-b03d6a0227ee",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Final Exam",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-688, 992],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "final_summary",
|
||||
"type": "string",
|
||||
"value": "=User {{ $('2. The n8n Selectors').last().json.user_name_from_first }} is {{ $('Source Data').last().json.age }}.\n\nTheir best skill is {{ $('3. Working with Arrays').last().json.second_skill }}.\n\nTheir first project was {{ $('Source Data').last().json.projects[0].name }}, which is now {{ $('5. The Combo Move').last().json.first_project_status }}.\n\nAll skills: {{ $('9. The \"All Items\" View').last().json.all_skills_string }}."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "99a7fb24-491c-436e-a775-6fabdc2c4004",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note8",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-896, 736],
|
||||
"parameters": {
|
||||
"color": 6,
|
||||
"width": 520,
|
||||
"height": 420,
|
||||
"content": "## 🎓 FINAL EXAM: Putting It All Together\n\nThis node uses everything we've learned to build a final summary object.\n\nLook at the expressions for each field. They pull data from different nodes and use different techniques you've just practiced.\n\n**Congratulations! You now have the foundational knowledge to link data and build powerful, dynamic workflows in n8n.**"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "c3f2fd66-784d-4675-8cb3-72dd33e6ee4e",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "2. The n8n Selectors",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-5568, 992],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "user_name_from_first",
|
||||
"type": "string",
|
||||
"value": "={{ $('Source Data').last().json.name }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "bade9e22-6e9d-4f25-a1b7-974cbf2d5c61",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note9",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-5872, 528],
|
||||
"parameters": {
|
||||
"width": 680,
|
||||
"height": 620,
|
||||
"content": "## Lesson 2: The n8n Selectors (`.first()`, `.last()`, `.all()`)\n\nIn the last lesson, we used `.item`. When there is only one output item from a node, this is equivalent to `.last()`. Using `.last()` explicitly is often safer and clearer.\n\n**The Goal:** Get the user's name using the `.last()` selector.\n\n**The Expression:** `{{ $('Source Data').last().json.name }}`\n\n**Why is this better?**\nIf a node ever returns multiple items, `.last()` guarantees you only get data from the very last one.\n\nIf you ever need to match the selected data with the input items, this is where `.item` cannot be replaced.\n\n**Other Selectors:**\n- **`.first()`**: Gets the data from the first item.\n- **`.all()`**: Gets data from ALL items, returning it as an array of objects. (This is different from `$items`!)"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "7c4b718e-fca8-4dbe-8b3b-d6aeeaa78d6d",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "7. Inspecting Objects",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-2640, 992],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "contact_keys",
|
||||
"type": "array",
|
||||
"value": "={{ Object.keys($('Source Data').last().json.contact) }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "089b02d1-e43b-4baf-8bcf-b2dd7a95df92",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note10",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-2832, 640],
|
||||
"parameters": {
|
||||
"color": 2,
|
||||
"width": 500,
|
||||
"height": 520,
|
||||
"content": "## Lesson 7: Inspecting Objects (`Object.keys()`)\n\nWhat if you have an object but you don't know what keys are inside it? `Object.keys()` comes to the rescue.\n\n**The Goal:** Get a list of all the keys inside the `contact` object.\n\n**The Expression:** `{{ Object.keys($('Source Data').last().json.contact) }}`\n\nThis is incredibly useful for dynamically processing data. It returns an **array** containing the names of the keys (e.g., `[\"email\", \"phone\"]`)."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "81a16f66-4fca-47af-841c-91e16ff9587e",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "8. Utility Functions",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-2064, 992],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "contact_as_string",
|
||||
"type": "string",
|
||||
"value": "={{ JSON.stringify($('Source Data').last().json.contact, null, 2) }}"
|
||||
},
|
||||
{
|
||||
"id": "06003b65-7482-4d5a-b2c0-1794859ab461",
|
||||
"name": "skills",
|
||||
"type": "array",
|
||||
"value": "={{ $('Source Data').last().json.skills }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "b7aea910-5fd6-420f-9814-77afb8ab9517",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note11",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-2304, 640],
|
||||
"parameters": {
|
||||
"width": 580,
|
||||
"height": 520,
|
||||
"content": "## Lesson 8: Utility Functions (`JSON.stringify()`)\n\nSometimes you need to convert a structured JSON object back into a clean, single string. This is common when sending data to another service, like in an AI prompt.\n\n**The Goal:** Turn the entire `contact` object into a formatted string.\n\n**The Expression:** `{{ JSON.stringify($('Source Data').last().json.contact, null, 2) }}`\n\n**Breakdown:**\n- **`JSON.stringify(...)`**: The function that does the conversion.\n- **`null, 2`**: These optional parameters tell it to \"pretty-print\" the string with an indentation of 2 spaces, making it readable."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "2b83f9a2-7c71-43bb-b940-d0c3204cf6a2",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Split Out Skills",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-1488, 992],
|
||||
"parameters": {
|
||||
"include": "allOtherFields",
|
||||
"options": {},
|
||||
"fieldToSplitOut": "skills"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "933a0592-2deb-4db7-b801-d52dbbed1252",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note12",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-352, -128],
|
||||
"parameters": {
|
||||
"color": 3,
|
||||
"width": 540,
|
||||
"height": 1280,
|
||||
"content": "## Was this helpful? Let me know!\n[](https://n8n.ac)\n\nI really hope this tutorial helped you understand n8n Expressions better. Your feedback is incredibly valuable and helps me create better resources for the n8n community.\n\n### **Share Your Thoughts & Ideas**\n\nWhether you have a suggestion, found a typo, or just want to say thanks, I'd love to hear from you!\nHere's a simple n8n form built for this purpose:\n\n#### ➡️ **[Click here to give feedback](https://api.ia2s.app/form/templates/feedback?template=Expressions%20Tutorial)**\n\n### **Ready to Build Something Great?**\n\nIf you're looking to take your n8n skills or business automation to the next level, I can help.\n\n**🎓 n8n Coaching:** Want to become an n8n pro? I offer one-on-one coaching sessions to help you master workflows, tackle specific problems, and build with confidence.\n#### ➡️ **[Book a Coaching Session](https://api.ia2s.app/form/templates/coaching?template=Expressions%20Tutorial)**\n\n**💼 n8n Consulting:** Have a complex project, an integration challenge, or need a custom workflow built for your business? Let's work together to create a powerful automation solution.\n#### ➡️ **[Inquire About Consulting Services](https://api.ia2s.app/form/templates/consulting?template=Expressions%20Tutorial)**\n\n---\n\nHappy Automating!\nLucas Peyrin | [n8n Academy](https://n8n.ac)"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "efc93cff-1e16-46fc-a839-7cca5ccd27f8",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note15",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-896, 304],
|
||||
"parameters": {
|
||||
"color": 6,
|
||||
"width": 512,
|
||||
"height": 408,
|
||||
"content": "## [>> Go to Eval Workflow <<](https://n8n.io/workflows/6236)\n\nVerify your skills with a complete eval workflow to put your Expression Skills to the test.\n[](https://n8n.io/workflows/6236)"
|
||||
},
|
||||
"typeVersion": 1
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Source Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "1. The Basics",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"1. The Basics": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "2. The n8n Selectors",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Start Tutorial": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Source Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"4. Going Deeper": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "5. The Combo Move",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split Out Skills": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "9. The \"All Items\" View",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"5. The Combo Move": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "6. A Touch of Magic",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"6. A Touch of Magic": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "7. Inspecting Objects",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"2. The n8n Selectors": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "3. Working with Arrays",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"8. Utility Functions": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split Out Skills",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"7. Inspecting Objects": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "8. Utility Functions",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"3. Working with Arrays": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "4. Going Deeper",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"9. The \"All Items\" View": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Final Exam",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
{
|
||||
"name": "JSON basics",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": false,
|
||||
"templateId": "json_basics"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "365fdd40-4e46-497b-8fef-9c356b2234cd",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Execute to Start",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-3456, 1056],
|
||||
"parameters": {},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "6c2fe8ca-9aa9-402a-949d-cc58177eb7e5",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "String",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-2816, 1056],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "e87952cb-878e-4feb-8261-342eaf887838",
|
||||
"name": "json_example_string",
|
||||
"type": "string",
|
||||
"value": "This is a simple string. In JSON, it's always enclosed in double quotes."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "3cffa562-bedc-42f9-ab4f-8b55cd3b5711",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Key & Value",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-3104, 1056],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "e87952cb-878e-4feb-8261-342eaf887838",
|
||||
"name": "key",
|
||||
"type": "string",
|
||||
"value": "value"
|
||||
},
|
||||
{
|
||||
"id": "b5f030f4-6650-4181-881f-de44790bb24b",
|
||||
"name": "another_key",
|
||||
"type": "string",
|
||||
"value": "another_value"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "c4742e5d-8017-45e9-ada5-a2897c87b4cc",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Number",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-2528, 1056],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "e87952cb-878e-4feb-8261-342eaf887838",
|
||||
"name": "json_example_integer",
|
||||
"type": "number",
|
||||
"value": 10
|
||||
},
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "json_example_float",
|
||||
"type": "number",
|
||||
"value": 12.5
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "60ee473c-635c-41d7-acd2-4fa6c3acb665",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Boolean",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-2240, 1056],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "e87952cb-878e-4feb-8261-342eaf887838",
|
||||
"name": "json_example_boolean",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "56683e92-19a0-4a17-99a9-b92120739c74",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Array",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-1664, 1056],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "e87952cb-878e-4feb-8261-342eaf887838",
|
||||
"name": "json_example_array",
|
||||
"type": "array",
|
||||
"value": "[\"first element\", 2, false, null]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "190c86c7-2d0b-47e1-a729-e22e9610dc8f",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Object",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-1360, 1056],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "e87952cb-878e-4feb-8261-342eaf887838",
|
||||
"name": "json_example_object",
|
||||
"type": "object",
|
||||
"value": "{\"key\":\"value\",\"array\":[1,2,3],\"boolean\":false,\"integer\":123,\"sub_object\":{\"sub_key\":\"Find me!\"}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "24b198bc-9a82-477f-921e-c7e5055d17cc",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-3680, 560],
|
||||
"parameters": {
|
||||
"width": 460,
|
||||
"height": 656,
|
||||
"content": "## Tutorial - What is JSON?\n\nWelcome! This workflow will teach you the basics of JSON, the language that apps and n8n nodes use to exchange information.\n\n**What is JSON?**\nImagine a contact card:\n- **Name:** John Doe\n- **Age:** 30\n- **Has Children:** Yes\n- **Phone Numbers:** [\"555-1234\", \"555-5678\"]\n\n\nJSON is just a way of writing this down so a computer can understand it perfectly.\n\n**How to use this tutorial:**\n1. Click **\"Execute Workflow\"** button.\n2. Click on each node, one by one, in order.\n3. Look at the node's output in the panel on the right and read the associated sticky note to understand what's happening."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "f9dfa173-b51f-41fb-8587-9c4ee2855265",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note1",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-3184, 704],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 260,
|
||||
"height": 516,
|
||||
"content": "#### The Heart of JSON: Key & Value\n\nEverything in JSON is built on this pair:\n- A **Key** (the name of the data, always in double quotes `\"`).\n- A **Value** (the data itself).\n\n\n`\"key\": \"value\"`\n\nIn this node's output, you see two key/value pairs. This is the basic building block for everything that follows."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "fc2ad88e-b5cc-4dc4-91e6-f246d1654e26",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note2",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-2896, 704],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 260,
|
||||
"height": 516,
|
||||
"content": "#### Data Type: String\n\nA string is simply **text**.\n- **Syntax:** The text is always enclosed in double quotes `\" \"`.\n\n\nLook at the output: the value of `json_example_string` is the text we defined."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "4973dad8-cce4-490c-8ad1-01410ffb7740",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note3",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-2608, 704],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 260,
|
||||
"height": 516,
|
||||
"content": "#### Data Type: Number\n\nThis is simply a number. It can be a whole number (integer) like 10, or a decimal (float) like 12.5.\n- **Syntax:** Just write the number directly, **WITHOUT quotes**.\n\n\n`\"age\": 30` (Correct)\n`\"age\": \"30\"` (Incorrect, this is a String!)\n\nThis distinction is crucial for doing math!"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "4240a747-d2de-42dc-882a-55aee236e76a",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note4",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-2320, 704],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 260,
|
||||
"height": 516,
|
||||
"content": "#### Data Type: Boolean\n\nThis is a value that can only be **TRUE** or **FALSE**.\n- **Syntax:** `true` or `false` (always lowercase and **WITHOUT quotes**).\n\n\nThink of it like a light switch: on (`true`) or off (`false`). It's very useful for conditions (If/Then logic)."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "6eb904bc-a082-43fd-85ea-e672830cdcd2",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note5",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-1744, 704],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 260,
|
||||
"height": 516,
|
||||
"content": "#### Data Type: Array\n\nAn array is an **ordered list** of items.\n- **Syntax:** Starts with `[` and ends with `]`. Items are separated by commas.\n\n\nAn array can hold anything: strings, numbers, booleans, and even other arrays or objects!"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "76fa5320-894b-451f-b372-59144fc0ade3",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note6",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-1456, 704],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 280,
|
||||
"height": 516,
|
||||
"content": "#### Data Type: Object (JSON Object)\n\nThis is the main concept! An object is a **collection of key/value pairs**.\n- **Syntax:** Starts with `{` and ends with `}`.\n\n\nThis is what allows us to structure complex data, like our contact card from the beginning. Notice how this object contains all the other data types we've seen!"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "5976d5fa-6788-46b8-b5e5-1cf6d09f5954",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Null",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-1952, 1056],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "e87952cb-878e-4feb-8261-342eaf887838",
|
||||
"name": "json_example_null",
|
||||
"type": "null",
|
||||
"value": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "7786c224-1cd6-4b05-a41b-d47cde98d2a0",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note7",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-2032, 704],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 260,
|
||||
"height": 516,
|
||||
"content": "#### Data Type: Null\n\nThis special type means \"nothing,\" \"no value,\" or \"empty.\"\n- **Syntax:** `null` (lowercase and **WITHOUT quotes**).\n\n\nIt's different from `0` (which is a number) or `\"\"` (which is an empty string). `null` is the intentional absence of a value."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "f8f6e7b6-3f48-4e5c-86d6-001ec61d1f81",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Using JSON (Expressions)",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-1024, 1056],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "e87952cb-878e-4feb-8261-342eaf887838",
|
||||
"name": "message",
|
||||
"type": "string",
|
||||
"value": "=Hello, the number from the tutorial is: {{ $('Number').item.json.json_example_integer }}"
|
||||
},
|
||||
{
|
||||
"id": "61f385f4-b8e2-4c69-b873-9ffc3ab3fe94",
|
||||
"name": "sub_key",
|
||||
"type": "string",
|
||||
"value": "={{ $json.json_example_object.sub_object.sub_key }}"
|
||||
},
|
||||
{
|
||||
"id": "bd752a0f-64bf-44b1-b39b-fca28e86aa5b",
|
||||
"name": "array_second_item",
|
||||
"type": "string",
|
||||
"value": "={{ $json.json_example_object.array[1] }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "0b3ddc47-b1ff-4016-957b-cb6f584a996f",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note8",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-1152, 704],
|
||||
"parameters": {
|
||||
"color": 5,
|
||||
"width": 340,
|
||||
"height": 516,
|
||||
"content": "#### ⭐ THE KEY STEP: Using JSON in n8n!\n\nNow for the magic. How do you use data from a previous node? With **expressions** `{{ }}`.\n\nThis node creates a custom message. Look at the value of the `message` field:\n`Hello, the number from the tutorial is: {{ $('Number').item.json.json_example_integer }}`\n\nIt dynamically pulled the number `10` from the \"Number\" node! This is how you make your nodes talk to each other."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "d1004e2e-15b6-4108-9811-6e7d980822d3",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Final Exam",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-672, 1056],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "e87952cb-878e-4feb-8261-342eaf887838",
|
||||
"name": "summary_string",
|
||||
"type": "string",
|
||||
"value": "={{ $('String').item.json.json_example_string }}"
|
||||
},
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "summary_number",
|
||||
"type": "number",
|
||||
"value": "={{ $('Number').item.json.json_example_integer }}"
|
||||
},
|
||||
{
|
||||
"id": "67890",
|
||||
"name": "summary_boolean",
|
||||
"type": "boolean",
|
||||
"value": "={{ $('Boolean').item.json.json_example_boolean }}"
|
||||
},
|
||||
{
|
||||
"id": "abcde",
|
||||
"name": "summary_null",
|
||||
"type": "null",
|
||||
"value": "={{ $('Null').item.json.json_example_null }}"
|
||||
},
|
||||
{
|
||||
"id": "fghij",
|
||||
"name": "summary_array",
|
||||
"type": "array",
|
||||
"value": "={{ $('Array').item.json.json_example_array }}"
|
||||
},
|
||||
{
|
||||
"id": "klmno",
|
||||
"name": "summary_object",
|
||||
"type": "object",
|
||||
"value": "={{ $('Object').item.json.json_example_object }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "43eb149b-ccd3-4557-b744-ef5d9dcf82d9",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note9",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-784, 704],
|
||||
"parameters": {
|
||||
"color": 6,
|
||||
"width": 340,
|
||||
"height": 516,
|
||||
"content": "#### 🎓 FINAL EXAM: Putting It All Together\n\nThis last node creates a final object by using expressions to pull data from **all the previous nodes**.\n\nClick on this node and look at the expressions in each field. It's a perfect summary of everything you've learned.\n\n**Congratulations! You now understand the basics of JSON and how to use it in n8n.**"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "b8fce06f-abe9-45cd-b365-97743a0d8dca",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note10",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-416, 16],
|
||||
"parameters": {
|
||||
"color": 3,
|
||||
"width": 540,
|
||||
"height": 1200,
|
||||
"content": "## Was this helpful? Let me know!\n[](https://api.ia2s.app/form/templates/academy)\n\nI really hope this tutorial helped you understand JSON better. Your feedback is incredibly valuable and helps me create better resources for the n8n community.\n\n### **Have Feedback, a Question, or a Project Idea?**\n\nI've streamlined the way we connect. It all starts with one simple form that takes less than 10 seconds. After that, you'll chat with my AI assistant who will gather the key details and pass them directly on to me.\n\n#### ➡️ **[Click here to start the conversation](https://api.ia2s.app/form/templates/academy)**\n\nUse this single link for anything you need:\n\n* **Give Feedback:** Share your thoughts on this template—whether you found a typo, encountered an unexpected error, have a suggestion, or just want to say thanks!\n\n* **n8n Coaching:** Get personalized, one-on-one guidance to master n8n. We can work together to get you launched with confidence or help you reach an expert level.\n\n* **n8n Consulting:** Have a complex business challenge or need a custom workflow built from scratch? Let's partner on a powerful automation solution tailored to your specific needs.\n\n---\n\nHappy Automating!\nLucas Peyrin | [n8n Academy](https://n8n.ac)"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "da4af3ac-a717-489d-bb64-3ef34167a0fc",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note11",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-3680, 240],
|
||||
"parameters": {
|
||||
"color": 2,
|
||||
"width": 460,
|
||||
"height": 300,
|
||||
"content": "## [Video Tutorial](https://youtu.be/PAmgrwYnzWs?si=yXG1oHIL3UiBcAPa)\n@[youtube](PAmgrwYnzWs)"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "b29ca9fc-7fc2-4dd7-9fca-d2d2a9bae237",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note14",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-3184, 288],
|
||||
"parameters": {
|
||||
"color": 7,
|
||||
"width": 576,
|
||||
"height": 392,
|
||||
"content": "[](https://www.youtube.com/watch?v=PAmgrwYnzWs)"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "0d2835ec-33c8-426b-87cd-74af33011bd5",
|
||||
"cid": "Ikx1Y2FzIFBleXJpbiI",
|
||||
"name": "Sticky Note15",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"notes": "© 2025 Lucas Peyrin",
|
||||
"creator": "Lucas Peyrin",
|
||||
"position": [-784, 368],
|
||||
"parameters": {
|
||||
"color": 6,
|
||||
"width": 336,
|
||||
"height": 312,
|
||||
"content": "## [>> Go to Eval Workflow <<](https://n8n.io/workflows/6232)\n\nVerify your skills with a complete eval workflow to put your JSON Skills to the test.\n[](https://n8n.io/workflows/6232)"
|
||||
},
|
||||
"typeVersion": 1
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Null": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Array",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Array": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Object",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Number": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Boolean",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Object": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Using JSON (Expressions)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"String": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Number",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Boolean": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Null",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Key & Value": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "String",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute to Start": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Key & Value",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Using JSON (Expressions)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Final Exam",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,679 @@
|
||||
{
|
||||
"name": "Workflow logic",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": false,
|
||||
"templateId": "workflow_logic"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "d6cf9b3d-66b8-4022-8c9d-698e89cd22fd",
|
||||
"name": "Start Sorting",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"position": [-880, 608],
|
||||
"parameters": {},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "b8c72a1e-0268-4d99-8141-ca14e35cbd6a",
|
||||
"name": "Sticky Note",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"position": [-1264, 192],
|
||||
"parameters": {
|
||||
"width": 624,
|
||||
"height": 596,
|
||||
"content": "### Tutorial: The Logic Trio (Merge, IF, Switch)\n\nWelcome! This workflow will teach you the three most important nodes for controlling the flow of your data.\n\n**The Analogy: A Package Sorting Center**\n- **Data Items:** Think of these as packages moving on a conveyor belt.\n- **Merge Node:** A point where multiple conveyor belts combine into one.\n- **IF Node:** A simple sorting gate with two paths (e.g., \"Fragile\" or \"Not Fragile\").\n- **Switch Node:** An advanced sorting machine with many paths (e.g., sorting by destination city).\n\n\n**How to use this tutorial:**\n1. Click **\"Execute Workflow\"**.\n2. Follow the flow from left to right, clicking on each node to see its output.\n3. Read the sticky notes to understand what each node does."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "81532695-73c0-4357-a957-3d0ef580578f",
|
||||
"name": "Sticky Note1",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"position": [-272, 192],
|
||||
"parameters": {
|
||||
"color": 4,
|
||||
"width": 384,
|
||||
"height": 604,
|
||||
"content": "### 1. The Merge Node\n\n**Analogy:** A conveyor belt where packages from different loading docks (the `Set` nodes) come together.\n\n**What it does:** The Merge node combines multiple streams of data into a single stream.\n\nHere, it's set to **Append** mode, which is the most common. It waits for all incoming data and then passes it all through together.\n\n**➡️ Look at the output. We now have both the letter and the parcel in one list, ready for the next step!**"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "dadbac54-6b31-4a0f-8d5e-b6121467e90e",
|
||||
"name": "Sticky Note2",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"position": [144, 192],
|
||||
"parameters": {
|
||||
"color": 4,
|
||||
"width": 384,
|
||||
"height": 596,
|
||||
"content": "### 2. The IF Node\n\n**Analogy:** A simple sorting gate with two paths: a \"true\" path and a \"false\" path.\n\n**What it does:** The IF node checks if a condition is met. If it's true, the data goes down the top output. If it's false, it goes down the bottom output.\n\nHere, we're asking a simple question: **\"Does this package have an `is_fragile` property?\"**\n\n**➡️ The parcel will go down the 'true' path, and the letters (which don't have that property) will go down the 'false' path.**"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "0c330031-eab5-4ee3-8b11-91aee526952a",
|
||||
"name": "Add 'Fragile' Handling",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [624, 512],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "handling_instructions",
|
||||
"type": "string",
|
||||
"value": "Handle with care!"
|
||||
}
|
||||
]
|
||||
},
|
||||
"includeOtherFields": true
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "d14c0e68-3823-4f15-98eb-58a0d4983861",
|
||||
"name": "Add 'Standard' Handling",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [624, 704],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "handling_instructions",
|
||||
"type": "string",
|
||||
"value": "Standard handling"
|
||||
}
|
||||
]
|
||||
},
|
||||
"includeOtherFields": true
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "2ebde854-aa6b-48de-83b1-33950a1486e0",
|
||||
"name": "Sticky Note3",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"position": [816, 272],
|
||||
"parameters": {
|
||||
"color": 5,
|
||||
"width": 384,
|
||||
"height": 552,
|
||||
"content": "### Merge Again?\n\n**Why do we need another Merge node here?**\n\nAfter the IF node, our data was split into two different paths. Before we can perform the *next* sorting step on all packages, we need to get them back onto the same conveyor belt.\n\nThis is a very common and important pattern in n8n: \n**Split -> Process -> Merge.**"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "66ca4ac5-aceb-426e-ab22-50c012602a85",
|
||||
"name": "Sticky Note4",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"position": [1232, 176],
|
||||
"parameters": {
|
||||
"color": 4,
|
||||
"width": 400,
|
||||
"height": 648,
|
||||
"content": "### 3. The Switch Node\n\n**Analogy:** An advanced sorting machine that can send packages to many different destinations.\n\n**What it does:** The Switch node is like an IF node with multiple doors. It checks the value of a single field (`destination` in this case) and sends the data down the path that matches the value.\n\n- If the destination is \"London\", it goes to output 0.\n- If it's \"New York\", it goes to output 1.\n- If it's something else, it goes to the **default** output.\n\n\n**➡️ This is much cleaner than using many IF nodes chained together!**"
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "10f56a78-85cd-4c8b-88e0-b6b3f0346e89",
|
||||
"name": "Send to London Bin",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [1776, 320],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "sorting_bin",
|
||||
"type": "string",
|
||||
"value": "A1 (London)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"includeOtherFields": true
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "2cba95bd-2c9b-42d1-90cb-74c2edf97ec7",
|
||||
"name": "Send to New York Bin",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [1776, 512],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "sorting_bin",
|
||||
"type": "string",
|
||||
"value": "B2 (New York)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"includeOtherFields": true
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "1531b4e3-eece-4c89-98bd-e9633fdd77f6",
|
||||
"name": "Send to Tokyo Bin",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [1776, 704],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "sorting_bin",
|
||||
"type": "string",
|
||||
"value": "C3 (Tokyo)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"includeOtherFields": true
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "d46604a8-663e-42bd-a175-a34edb8953fb",
|
||||
"name": "Default Bin",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [1776, 896],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "sorting_bin",
|
||||
"type": "string",
|
||||
"value": "Return to Sender"
|
||||
}
|
||||
]
|
||||
},
|
||||
"includeOtherFields": true
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "570040aa-3f8d-4f59-904a-ee3deb36a9df",
|
||||
"name": "Final Sorted Packages",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"position": [2160, 624],
|
||||
"parameters": {},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "756313e7-d2f3-45cc-a4f5-f91e7a8f778a",
|
||||
"name": "Sticky Note5",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"position": [2032, 272],
|
||||
"parameters": {
|
||||
"color": 6,
|
||||
"width": 368,
|
||||
"height": 560,
|
||||
"content": "### All Packages Sorted!\n\nCongratulations! You've successfully used the three logic nodes to sort your packages.\n\n**You learned how to:**\n- **Merge** data from different sources.\n- Use **IF** for simple true/false decisions.\n- Use **Switch** for complex, multi-path routing.\n\n\nMastering these three nodes is the key to building powerful and intelligent workflows in n8n."
|
||||
},
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "4466652f-cc2b-47b1-bf4f-98d89753881f",
|
||||
"name": "3. Switch Node",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"position": [1392, 576],
|
||||
"parameters": {
|
||||
"rules": {
|
||||
"values": [
|
||||
{
|
||||
"outputKey": "London",
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"combinator": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"id": "8d43cde4-027a-4ca7-a24c-6f74f12d6238",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "equals"
|
||||
},
|
||||
"leftValue": "={{ $json.destination }}",
|
||||
"rightValue": "London"
|
||||
}
|
||||
]
|
||||
},
|
||||
"renameOutput": true
|
||||
},
|
||||
{
|
||||
"outputKey": "New York",
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"combinator": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"id": "399a0fbd-6be5-48e9-9f66-04cf385cb418",
|
||||
"operator": {
|
||||
"name": "filter.operator.equals",
|
||||
"type": "string",
|
||||
"operation": "equals"
|
||||
},
|
||||
"leftValue": "={{ $json.destination }}",
|
||||
"rightValue": "New York"
|
||||
}
|
||||
]
|
||||
},
|
||||
"renameOutput": true
|
||||
},
|
||||
{
|
||||
"outputKey": "Tokyo",
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"combinator": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"id": "a69d387d-a174-42b3-bc5f-c8b46b7c2375",
|
||||
"operator": {
|
||||
"name": "filter.operator.equals",
|
||||
"type": "string",
|
||||
"operation": "equals"
|
||||
},
|
||||
"leftValue": "={{ $json.destination }}",
|
||||
"rightValue": "Tokyo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"renameOutput": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"fallbackOutput": "extra",
|
||||
"renameFallbackOutput": "Default"
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.2
|
||||
},
|
||||
{
|
||||
"id": "add68013-30a7-43db-93d4-5af691764684",
|
||||
"name": "Create Letter",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [-496, 416],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "package_id",
|
||||
"type": "string",
|
||||
"value": "L-001"
|
||||
},
|
||||
{
|
||||
"id": "67890",
|
||||
"name": "type",
|
||||
"type": "string",
|
||||
"value": "letter"
|
||||
},
|
||||
{
|
||||
"id": "abcde",
|
||||
"name": "destination",
|
||||
"type": "string",
|
||||
"value": "London"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "5af75a45-b912-41c6-b073-03188ae914ef",
|
||||
"name": "1. Merge Node",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"position": [-128, 592],
|
||||
"parameters": {
|
||||
"numberInputs": 3
|
||||
},
|
||||
"typeVersion": 3.2
|
||||
},
|
||||
{
|
||||
"id": "11627c3b-465a-4a4a-bfe9-95c08d502f2f",
|
||||
"name": "2. IF Node",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"position": [272, 608],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "loose"
|
||||
},
|
||||
"combinator": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"id": "a68aad83-1d09-4ebe-9732-aaedc407bb4b",
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true",
|
||||
"singleValue": true
|
||||
},
|
||||
"leftValue": "={{ $json.is_fragile }}",
|
||||
"rightValue": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"looseTypeValidation": true
|
||||
},
|
||||
"typeVersion": 2.2
|
||||
},
|
||||
{
|
||||
"id": "4b5b5ba4-25e1-4ef0-93d8-50670dbc1ce0",
|
||||
"name": "Re-group All Packages",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"position": [960, 608],
|
||||
"parameters": {},
|
||||
"typeVersion": 3.2
|
||||
},
|
||||
{
|
||||
"id": "21d270c4-c19c-42ed-a6e3-67ecedd1c0c9",
|
||||
"name": "Create 2nd Letter",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [-496, 608],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "package_id",
|
||||
"type": "string",
|
||||
"value": "L-002"
|
||||
},
|
||||
{
|
||||
"id": "67890",
|
||||
"name": "type",
|
||||
"type": "string",
|
||||
"value": "letter"
|
||||
},
|
||||
{
|
||||
"id": "abcde",
|
||||
"name": "destination",
|
||||
"type": "string",
|
||||
"value": "Tokyo"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "8a295323-9515-410c-9ac1-fb431d08cea2",
|
||||
"name": "Create Parcel",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"position": [-496, 800],
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "12345",
|
||||
"name": "package_id",
|
||||
"type": "string",
|
||||
"value": "P-001"
|
||||
},
|
||||
{
|
||||
"id": "67890",
|
||||
"name": "type",
|
||||
"type": "string",
|
||||
"value": "parcel"
|
||||
},
|
||||
{
|
||||
"id": "abcde",
|
||||
"name": "destination",
|
||||
"type": "string",
|
||||
"value": "New York"
|
||||
},
|
||||
{
|
||||
"id": "fghij",
|
||||
"name": "is_fragile",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"id": "c06a0f40-cc18-4710-8b07-9d396e89a83d",
|
||||
"name": "Sticky Note10",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"position": [2432, 32],
|
||||
"parameters": {
|
||||
"color": 3,
|
||||
"width": 540,
|
||||
"height": 800,
|
||||
"content": "## Was this helpful? Let me know!\n\nI really hope this template helped you understand how Logical Operation Nodes work here in n8n. Your feedback is incredibly valuable and helps me create better resources for the n8n community.\n\n### **Share Your Thoughts & Ideas**\n\nWhether you have a suggestion, found a typo, or just want to say thanks, I'd love to hear from you!\nHere's a simple n8n form built for this purpose:\n\n#### ➡️ **[Click here to give feedback](https://api.ia2s.app/form/templates/feedback?template=Merge%20If%20Switch)**\n\n### **Ready to Build Something Great?**\n\nIf you're looking to take your n8n skills or business automation to the next level, I can help.\n\n**🎓 n8n Coaching:** Want to become an n8n pro? I offer one-on-one coaching sessions to help you master workflows, tackle specific problems, and build with confidence.\n#### ➡️ **[Book a Coaching Session](https://api.ia2s.app/form/templates/coaching?template=Merge%20If%20Switch)**\n\n**💼 n8n Consulting:** Have a complex project, an integration challenge, or need a custom workflow built for your business? Let's work together to create a powerful automation solution.\n#### ➡️ **[Inquire About Consulting Services](https://api.ia2s.app/form/templates/consulting?template=Merge%20If%20Switch)**\n\n---\n\nHappy Automating!\nLucas Peyrin"
|
||||
},
|
||||
"typeVersion": 1
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"2. IF Node": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Add 'Fragile' Handling",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Add 'Standard' Handling",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Default Bin": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Final Sorted Packages",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"1. Merge Node": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "2. IF Node",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create Letter": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "1. Merge Node",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create Parcel": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "1. Merge Node",
|
||||
"type": "main",
|
||||
"index": 2
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Start Sorting": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create Parcel",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Create 2nd Letter",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Create Letter",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"3. Switch Node": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send to London Bin",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Send to New York Bin",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Send to Tokyo Bin",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Default Bin",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create 2nd Letter": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "1. Merge Node",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Send to Tokyo Bin": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Final Sorted Packages",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Send to London Bin": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Final Sorted Packages",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Send to New York Bin": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Final Sorted Packages",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Re-group All Packages": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "3. Switch Node",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Add 'Fragile' Handling": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Re-group All Packages",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Add 'Standard' Handling": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Re-group All Packages",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,13 @@ import emailTriageAgentWithGmailJson from '@/utils/templates/samples/agents/emai
|
||||
import jokeAgentWithHttpToolJson from '@/utils/templates/samples/agents/joke_agent_with_http_tool.json';
|
||||
import knowledgeStoreAgentWithGoogleDriveJson from '@/utils/templates/samples/agents/knowledge_store_agent_with_google_drive.json';
|
||||
import taskManagementAgentWithGoogleSheetsJson from '@/utils/templates/samples/agents/task_management_agent_with_google_sheets.json';
|
||||
import voiceAssistantAgentWithTelegramAndGcalJson from '@/utils/templates/samples/agents/voice_assistant_agent_with_telegram_and_gcal.json';
|
||||
import voiceAssistantAgentJson from '@/utils/templates/samples/agents/voice-agent.json';
|
||||
import calendarAgentJson from '@/utils/templates/samples/agents/calendar-agent.json';
|
||||
import buildYourFirstAiAgentJson from '@/utils/templates/samples/tutorial/build_your_first_ai_agent.json';
|
||||
import jsonBasicsJson from '@/utils/templates/samples/tutorial/json_basics.json';
|
||||
import expressionsTutorialJson from '@/utils/templates/samples/tutorial/expressions_tutorial.json';
|
||||
import workflowLogicJson from '@/utils/templates/samples/tutorial/workflow_logic.json';
|
||||
import apiFundamentalsJson from '@/utils/templates/samples/tutorial/api_fundamentals.json';
|
||||
/* eslint-enable import-x/extensions */
|
||||
|
||||
const getWorkflowJson = (json: unknown): WorkflowDataWithTemplateId => {
|
||||
@@ -34,39 +40,48 @@ export const SampleTemplates = {
|
||||
} as const;
|
||||
|
||||
export const PrebuiltAgentTemplates = {
|
||||
VoiceAssistantAgent: getWorkflowJson(voiceAssistantAgentWithTelegramAndGcalJson).meta.templateId,
|
||||
CalendarAgent: getWorkflowJson(calendarAgentJson).meta.templateId,
|
||||
EmailTriageAgent: getWorkflowJson(emailTriageAgentWithGmailJson).meta.templateId,
|
||||
KnowledgeStoreAgent: getWorkflowJson(knowledgeStoreAgentWithGoogleDriveJson).meta.templateId,
|
||||
TaskManagementAgent: getWorkflowJson(taskManagementAgentWithGoogleSheetsJson).meta.templateId,
|
||||
JokeAgent: getWorkflowJson(jokeAgentWithHttpToolJson).meta.templateId,
|
||||
VoiceAssistantAgent: getWorkflowJson(voiceAssistantAgentJson).meta.templateId,
|
||||
} as const;
|
||||
|
||||
export const TutorialTemplates = {
|
||||
BuildYourFirstAiAgent: getWorkflowJson(buildYourFirstAiAgentJson).meta.templateId,
|
||||
JsonBasics: getWorkflowJson(jsonBasicsJson).meta.templateId,
|
||||
Expressions: getWorkflowJson(expressionsTutorialJson).meta.templateId,
|
||||
WorkflowLogic: getWorkflowJson(workflowLogicJson).meta.templateId,
|
||||
ApiFundamentals: getWorkflowJson(apiFundamentalsJson).meta.templateId,
|
||||
} as const;
|
||||
|
||||
export const isPrebuiltAgentTemplateId = (value: string): boolean => {
|
||||
return Object.values(PrebuiltAgentTemplates).includes(value);
|
||||
};
|
||||
|
||||
interface PrebuiltAgentTemplate {
|
||||
export const isTutorialTemplateId = (value: string): boolean => {
|
||||
return Object.values(TutorialTemplates).includes(value);
|
||||
};
|
||||
|
||||
interface SampleTemplate {
|
||||
template: WorkflowDataWithTemplateId;
|
||||
name: string;
|
||||
description: string;
|
||||
nodes: INodeTypeNameVersion[];
|
||||
}
|
||||
|
||||
export const getPrebuiltAgents = (): PrebuiltAgentTemplate[] => {
|
||||
export const getPrebuiltAgents = (): SampleTemplate[] => {
|
||||
return [
|
||||
{
|
||||
name: 'Voice assistant agent',
|
||||
description: 'Personal AI assistant in Telegram, handling both text and voice messages.',
|
||||
template: getWorkflowJson(voiceAssistantAgentWithTelegramAndGcalJson),
|
||||
template: getWorkflowJson(voiceAssistantAgentJson),
|
||||
nodes: [
|
||||
{
|
||||
name: 'n8n-nodes-base.telegram',
|
||||
version: 1.2,
|
||||
},
|
||||
{
|
||||
name: 'n8n-nodes-base.googleCalendar',
|
||||
version: 1.3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -93,6 +108,18 @@ export const getPrebuiltAgents = (): PrebuiltAgentTemplate[] => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Calendar agent',
|
||||
description:
|
||||
'Agent that can interact with your Google calendar to get availability and a list of events.',
|
||||
template: getWorkflowJson(calendarAgentJson),
|
||||
nodes: [
|
||||
{
|
||||
name: 'n8n-nodes-base.googleCalendar',
|
||||
version: 1.3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Task management agent',
|
||||
description:
|
||||
@@ -119,6 +146,46 @@ export const getPrebuiltAgents = (): PrebuiltAgentTemplate[] => {
|
||||
];
|
||||
};
|
||||
|
||||
export const getTutorialTemplates = (): SampleTemplate[] => {
|
||||
return [
|
||||
{
|
||||
name: 'Build your first AI agent',
|
||||
description:
|
||||
'This template launches your very first AI Agent —an AI-powered chatbot that can do more than just talk— it can take action using tools.',
|
||||
template: getWorkflowJson(buildYourFirstAiAgentJson),
|
||||
nodes: [],
|
||||
},
|
||||
{
|
||||
name: 'JSON basics',
|
||||
description:
|
||||
'Designed to teach you the absolute basics of JSON (JavaScript Object Notation) and, more importantly, how to use it within n8n.',
|
||||
template: getWorkflowJson(jsonBasicsJson),
|
||||
nodes: [],
|
||||
},
|
||||
{
|
||||
name: 'Expressions',
|
||||
description:
|
||||
'Step-by-step tutorial designed to teach you the most important skill in n8n: using expressions to access and manipulate data.',
|
||||
template: getWorkflowJson(expressionsTutorialJson),
|
||||
nodes: [],
|
||||
},
|
||||
{
|
||||
name: 'Workflow logic',
|
||||
description:
|
||||
'This template is a hands-on tutorial that teaches you the three most fundamental nodes for controlling the flow of your automations: Merge, IF, and Switch.',
|
||||
template: getWorkflowJson(workflowLogicJson),
|
||||
nodes: [],
|
||||
},
|
||||
{
|
||||
name: 'API fundamentals',
|
||||
description:
|
||||
'Hands-on tutorial designed to demystify what an API is and how it works, right inside your n8n canvas.',
|
||||
template: getWorkflowJson(apiFundamentalsJson),
|
||||
nodes: [],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const getSampleWorkflowByTemplateId = (
|
||||
templateId: string,
|
||||
): WorkflowDataWithTemplateId | undefined => {
|
||||
@@ -126,6 +193,7 @@ export const getSampleWorkflowByTemplateId = (
|
||||
getEasyAiWorkflowJson(),
|
||||
getRagStarterWorkflowJson(),
|
||||
...getPrebuiltAgents().map((agent) => agent.template),
|
||||
...getTutorialTemplates().map((tutorial) => tutorial.template),
|
||||
];
|
||||
|
||||
return workflows.find((workflow) => workflow.meta.templateId === templateId);
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
<script setup lang="ts">
|
||||
import { VIEWS } from '@/constants';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useCalloutHelpers } from '@/composables/useCalloutHelpers';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import type { OpenTemplateElement } from '@/Interface';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
|
||||
const i18n = useI18n();
|
||||
const calloutHelpers = useCalloutHelpers();
|
||||
|
||||
const preBuiltAgents = computed<OpenTemplateElement[]>(() =>
|
||||
calloutHelpers.getPreBuiltAgentNodeCreatorItems(),
|
||||
);
|
||||
|
||||
const tutorials = computed<OpenTemplateElement[]>(() =>
|
||||
calloutHelpers.getTutorialTemplatesNodeCreatorItems(),
|
||||
);
|
||||
|
||||
const openTemplate = (templateId: string) => {
|
||||
calloutHelpers.openSampleWorkflowTemplate(templateId, {
|
||||
telemetry: {
|
||||
source: 'templates',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await nodeTypesStore.loadNodeTypesIfNotLoaded();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageViewLayout>
|
||||
<div :class="$style.content">
|
||||
<section :class="$style.section">
|
||||
<div :class="$style.header">
|
||||
<N8nHeading tag="h2" bold size="xlarge">
|
||||
{{ i18n.baseText('preBuiltAgentTemplates.title') }}
|
||||
</N8nHeading>
|
||||
<N8nLink :to="{ name: VIEWS.TEMPLATES }" underline bold>
|
||||
{{ i18n.baseText('preBuiltAgentTemplates.viewAllLink') }}
|
||||
</N8nLink>
|
||||
</div>
|
||||
|
||||
<div :class="$style.grid">
|
||||
<N8nCard
|
||||
v-for="template in preBuiltAgents"
|
||||
:key="template.key"
|
||||
:class="$style.card"
|
||||
@click="openTemplate(template.properties.templateId)"
|
||||
>
|
||||
<N8nNodeCreatorNode
|
||||
:class="$style.templateLink"
|
||||
:title="template.properties.title"
|
||||
:description="template.properties.description"
|
||||
:tag="template.properties.tag"
|
||||
:show-action-arrow="true"
|
||||
:is-trigger="false"
|
||||
:hide-node-icon="true"
|
||||
>
|
||||
<template v-if="template.properties.nodes" #extraDetails>
|
||||
<NodeIcon
|
||||
v-for="node in template.properties.nodes"
|
||||
:key="node.name"
|
||||
:node-type="node"
|
||||
:size="16"
|
||||
:show-tooltip="true"
|
||||
/>
|
||||
</template>
|
||||
</N8nNodeCreatorNode>
|
||||
</N8nCard>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section :class="$style.section">
|
||||
<N8nHeading tag="h2" bold size="xlarge">
|
||||
{{ i18n.baseText('preBuiltAgentTemplates.tutorials') }}
|
||||
</N8nHeading>
|
||||
<div :class="$style.tutorials">
|
||||
<N8nCard
|
||||
v-for="template in tutorials"
|
||||
:key="template.key"
|
||||
:class="$style.card"
|
||||
@click="openTemplate(template.properties.templateId)"
|
||||
>
|
||||
<N8nNodeCreatorNode
|
||||
:class="$style.templateLink"
|
||||
:title="template.properties.title"
|
||||
:description="template.properties.description"
|
||||
:tag="template.properties.tag"
|
||||
:show-action-arrow="true"
|
||||
:is-trigger="false"
|
||||
:hide-node-icon="true"
|
||||
>
|
||||
<template v-if="template.properties.nodes" #extraDetails>
|
||||
<NodeIcon
|
||||
v-for="node in template.properties.nodes"
|
||||
:key="node.name"
|
||||
:node-type="node"
|
||||
:size="16"
|
||||
:show-tooltip="true"
|
||||
/>
|
||||
</template>
|
||||
</N8nNodeCreatorNode>
|
||||
</N8nCard>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<N8nLink :to="{ name: VIEWS.TEMPLATES }" underline bold>
|
||||
{{ i18n.baseText('preBuiltAgentTemplates.viewAllLink') }}
|
||||
</N8nLink>
|
||||
</div>
|
||||
</PageViewLayout>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: var(--spacing-m);
|
||||
padding-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: var(--spacing-s);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
align-items: start;
|
||||
align-content: start;
|
||||
grid-auto-rows: auto;
|
||||
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
.tutorials {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.templateLink {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -13,6 +13,7 @@ import { useMessage } from '@/composables/useMessage';
|
||||
import { useProjectPages } from '@/composables/useProjectPages';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useCalloutHelpers } from '@/composables/useCalloutHelpers';
|
||||
import {
|
||||
COMMUNITY_PLUS_ENROLLMENT_MODAL,
|
||||
DEFAULT_WORKFLOW_PAGE_SIZE,
|
||||
@@ -106,6 +107,7 @@ const router = useRouter();
|
||||
const message = useMessage();
|
||||
const toast = useToast();
|
||||
const folderHelpers = useFolders();
|
||||
const calloutHelpers = useCalloutHelpers();
|
||||
|
||||
const sourceControlStore = useSourceControlStore();
|
||||
const usersStore = useUsersStore();
|
||||
@@ -418,6 +420,19 @@ const showAIStarterCollectionCallout = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
const showPrebuiltAgentsCallout = computed(() => {
|
||||
return (
|
||||
!loading.value &&
|
||||
calloutHelpers.isPreBuiltAgentsCalloutVisible.value &&
|
||||
!calloutHelpers.isCalloutDismissed('preBuiltAgentsModalCallout') &&
|
||||
!readOnlyEnv.value &&
|
||||
// We want to show the callout only if the user has permissions to create folders and workflows
|
||||
// but also on the overview page
|
||||
(projectPages.isOverviewSubPage ||
|
||||
(hasPermissionToCreateFolders.value && hasPermissionToCreateWorkflows.value))
|
||||
);
|
||||
});
|
||||
|
||||
const showPersonalizedTemplates = computed(
|
||||
() => !loading.value && personalizedTemplatesStore.isFeatureEnabled(),
|
||||
);
|
||||
@@ -882,6 +897,10 @@ const createAIStarterWorkflows = async (source: 'card' | 'callout') => {
|
||||
}
|
||||
};
|
||||
|
||||
const openPrebuiltAgentsModal = (source: 'workflowsEmptyState' | 'workflowsList') => {
|
||||
void calloutHelpers.openPreBuiltAgentsModal(source);
|
||||
};
|
||||
|
||||
const handleCreateReadyToRunWorkflows = async (source: 'card' | 'callout') => {
|
||||
try {
|
||||
const projectId = projectPages.isOverviewSubPage
|
||||
@@ -931,6 +950,10 @@ const dismissEasyAICallout = () => {
|
||||
easyAICalloutVisible.value = false;
|
||||
};
|
||||
|
||||
const dismissPreBuiltAgentsCallout = () => {
|
||||
void calloutHelpers.dismissCallout('preBuiltAgentsModalCallout');
|
||||
};
|
||||
|
||||
const openAIWorkflow = async (source: string) => {
|
||||
dismissEasyAICallout();
|
||||
telemetry.track('User clicked test AI workflow', {
|
||||
@@ -1769,7 +1792,39 @@ const onNameSubmit = async (name: string) => {
|
||||
</template>
|
||||
<template #callout>
|
||||
<N8nCallout
|
||||
v-if="showAIStarterCollectionCallout"
|
||||
v-if="showPrebuiltAgentsCallout"
|
||||
theme="secondary"
|
||||
icon="bot"
|
||||
icon-size="large"
|
||||
:class="$style['easy-ai-workflow-callout']"
|
||||
>
|
||||
<N8nText size="small">
|
||||
{{ i18n.baseText('workflows.preBuiltAgents.callout') }}
|
||||
{{ ' ' }}
|
||||
<N8nLink
|
||||
theme="secondary"
|
||||
size="small"
|
||||
:bold="true"
|
||||
:underline="true"
|
||||
@click="openPrebuiltAgentsModal('workflowsEmptyState')"
|
||||
>
|
||||
{{ i18n.baseText('workflows.preBuiltAgents.linkText') }}
|
||||
</N8nLink>
|
||||
</N8nText>
|
||||
<template #trailingContent>
|
||||
<div :class="$style['callout-trailing-content']">
|
||||
<N8nIcon
|
||||
size="small"
|
||||
icon="x"
|
||||
:title="i18n.baseText('generic.dismiss')"
|
||||
class="clickable"
|
||||
@click="dismissPreBuiltAgentsCallout()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</N8nCallout>
|
||||
<N8nCallout
|
||||
v-else-if="showAIStarterCollectionCallout"
|
||||
theme="secondary"
|
||||
icon="gift"
|
||||
:class="$style['easy-ai-workflow-callout']"
|
||||
@@ -2001,7 +2056,26 @@ const onNameSubmit = async (name: string) => {
|
||||
</div>
|
||||
</N8nCard>
|
||||
<N8nCard
|
||||
v-if="showAIStarterCollectionCallout"
|
||||
v-if="showPrebuiltAgentsCallout"
|
||||
:class="$style.emptyStateCard"
|
||||
hoverable
|
||||
data-test-id="prebuilt-agents-card"
|
||||
@click="openPrebuiltAgentsModal('workflowsList')"
|
||||
>
|
||||
<div :class="$style.emptyStateCardContent">
|
||||
<N8nIcon
|
||||
:class="$style.emptyStateCardIcon"
|
||||
:stroke-width="1.5"
|
||||
icon="bot"
|
||||
color="foreground-dark"
|
||||
/>
|
||||
<N8nText size="large" class="mt-xs pl-2xs pr-2xs">
|
||||
{{ i18n.baseText('workflows.empty.preBuiltAgents') }}
|
||||
</N8nText>
|
||||
</div>
|
||||
</N8nCard>
|
||||
<N8nCard
|
||||
v-else-if="showAIStarterCollectionCallout"
|
||||
:class="$style.emptyStateCard"
|
||||
hoverable
|
||||
data-test-id="easy-ai-workflow-card"
|
||||
|
||||
@@ -39,10 +39,10 @@ const preBuiltAgentsCallout: INodeProperties = {
|
||||
type: 'callout',
|
||||
typeOptions: {
|
||||
calloutAction: {
|
||||
label: 'Voice assistant agent',
|
||||
label: 'Calendar agent',
|
||||
icon: 'bot',
|
||||
type: 'openSampleWorkflowTemplate',
|
||||
templateId: 'voice_assistant_agent_with_telegram_and_gcal',
|
||||
templateId: 'calendar_agent_with_gcal',
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
|
||||
@@ -37,7 +37,7 @@ const preBuiltAgentsCallout: INodeProperties = {
|
||||
label: 'Voice assistant agent',
|
||||
icon: 'bot',
|
||||
type: 'openSampleWorkflowTemplate',
|
||||
templateId: 'voice_assistant_agent_with_telegram_and_gcal',
|
||||
templateId: 'voice_assistant_agent_with_telegram',
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
|
||||
Reference in New Issue
Block a user