AutomationFlowsAI & RAG › Ingest Nist Csf 2.0

Ingest Nist Csf 2.0

01 — Ingest NIST CSF 2.0. Uses readWriteFile, vectorStoreSupabase, embeddingsOpenAi, documentDefaultDataLoader. Webhook trigger; 9 nodes.

Webhook trigger★★★★☆ complexityAI-powered9 nodesRead Write FileSupabase Vector StoreOpenAI EmbeddingsDocument Default Data LoaderText Splitter Recursive Character Text SplitterSupabase
AI & RAG Trigger: Webhook Nodes: 9 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Documentdefaultdataloader → OpenAI Embeddings recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "01 \u2014 Ingest NIST CSF 2.0",
  "settings": {
    "executionOrder": "v1"
  },
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "ingest-nist-csf",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "a1000000-0000-4000-8000-000000000001",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -640,
        0
      ],
      "notes": "A webhook rather than a manual trigger so the run is reproducible from a script."
    },
    {
      "parameters": {
        "fileSelector": "/data/docs/NIST.CSWP.29.pdf",
        "options": {}
      },
      "id": "a1000000-0000-4000-8000-000000000002",
      "name": "Read PDF",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1,
      "position": [
        -420,
        0
      ],
      "notes": "Mounted read-only. Only this PDF \u2014 the other two stay unprocessed to control cost."
    },
    {
      "parameters": {
        "operation": "pdf",
        "options": {}
      },
      "id": "a1000000-0000-4000-8000-000000000003",
      "name": "Extract PDF Text",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [
        -200,
        0
      ]
    },
    {
      "parameters": {
        "mode": "insert",
        "tableName": "documents",
        "options": {
          "queryName": "match_documents"
        }
      },
      "id": "a1000000-0000-4000-8000-000000000004",
      "name": "Supabase Vector Store",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreSupabase",
      "typeVersion": 1.1,
      "position": [
        20,
        0
      ],
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "notes": "Table and query name must match sql/001_documents_and_match.sql."
    },
    {
      "parameters": {
        "model": "text-embedding-3-small",
        "options": {}
      },
      "id": "a1000000-0000-4000-8000-000000000005",
      "name": "Embeddings OpenAI",
      "type": "@n8n/n8n-nodes-langchain.embeddingsOpenAi",
      "typeVersion": 1.2,
      "position": [
        -60,
        220
      ],
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "notes": "1536 dimensions \u2014 pinned by the documents.embedding column width."
    },
    {
      "parameters": {
        "dataType": "json",
        "jsonMode": "expressionData",
        "jsonData": "={{ $json.text }}",
        "textSplittingMode": "custom",
        "options": {
          "metadata": {
            "metadataValues": [
              {
                "name": "source",
                "value": "NIST.CSWP.29.pdf"
              },
              {
                "name": "title",
                "value": "NIST Cybersecurity Framework 2.0"
              }
            ]
          }
        }
      },
      "id": "a1000000-0000-4000-8000-000000000006",
      "name": "Default Data Loader",
      "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
      "typeVersion": 1,
      "position": [
        120,
        220
      ],
      "notes": "dataType only accepts json or binary \u2014 there is no text mode. Feeding the extracted text through jsonData is what routes the PDF body to the custom splitter; the default (allInputData) hands the loader the whole item and it splits per line, producing ~99-character fragments."
    },
    {
      "parameters": {
        "chunkSize": 1000,
        "chunkOverlap": 150,
        "options": {}
      },
      "id": "a1000000-0000-4000-8000-000000000007",
      "name": "Recursive Character Text Splitter",
      "type": "@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter",
      "typeVersion": 1,
      "position": [
        120,
        420
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "documents",
        "returnAll": true,
        "orderBy": "id.asc",
        "filters": {}
      },
      "id": "a1000000-0000-4000-8000-000000000008",
      "name": "Read Back Rows",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        280,
        0
      ],
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "notes": "executeOnce matters: without it this runs once per inserted chunk, and 187 full-table reads with 1536 floats per row is what tripped the task runner.",
      "executeOnce": true
    },
    {
      "parameters": {
        "jsCode": "// Row count is exact here: executeOnce plus returnAll means one full read.\n// max(id) would be wrong after a reset \u2014 DELETE does not rewind the bigserial.\nconst rows = $input.all().map((r) => r.json);\nconst lengths = rows.map((r) => (r.content ?? '').length);\nconst first = rows[0] ?? {};\nconst embedding = typeof first.embedding === 'string' ? JSON.parse(first.embedding) : first.embedding;\nreturn [{ json: {\n  rowCount: rows.length,\n  embeddingDimensions: Array.isArray(embedding) ? embedding.length : null,\n  chunkChars: { min: Math.min(...lengths), max: Math.max(...lengths), mean: Math.round(lengths.reduce((a,b)=>a+b,0)/lengths.length), total: lengths.reduce((a,b)=>a+b,0) },\n  sampleChunk: (first.content ?? '').slice(0, 600),\n  sampleMetadata: first.metadata ?? null,\n} }];"
      },
      "id": "a1000000-0000-4000-8000-000000000009",
      "name": "Summarise",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        500,
        0
      ],
      "notes": "No executeOnce here: it would clamp the node to the first item and report a row count of 1."
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Read PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read PDF": {
      "main": [
        [
          {
            "node": "Extract PDF Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract PDF Text": {
      "main": [
        [
          {
            "node": "Supabase Vector Store",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Supabase Vector Store": {
      "main": [
        [
          {
            "node": "Read Back Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Back Rows": {
      "main": [
        [
          {
            "node": "Summarise",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings OpenAI": {
      "ai_embedding": [
        [
          {
            "node": "Supabase Vector Store",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Default Data Loader": {
      "ai_document": [
        [
          {
            "node": "Supabase Vector Store",
            "type": "ai_document",
            "index": 0
          }
        ]
      ]
    },
    "Recursive Character Text Splitter": {
      "ai_textSplitter": [
        [
          {
            "node": "Default Data Loader",
            "type": "ai_textSplitter",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

01 — Ingest NIST CSF 2.0. Uses readWriteFile, vectorStoreSupabase, embeddingsOpenAi, documentDefaultDataLoader. Webhook trigger; 9 nodes.

Source: https://github.com/BhrayanM/rag-engine/blob/main/workflows/01-ingest-nist-csf.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Bread-Meat-Delivery. Uses lmChatOpenAi, agent, httpRequest, redis. Webhook trigger; 91 nodes.

OpenAI Chat, Agent, HTTP Request +14
AI & RAG

Hi! I’m Amanda, a creator of intelligent automations using n8n and Make. I’ve been building AI-powered workflows for over 2 years, always focused on usability and innovation. This one here is very spe

OpenAI Chat, Redis, OpenAI +11
AI & RAG

YouTube Agent. Uses supabase, agent, lmChatAnthropic, outputParserStructured. Webhook trigger; 56 nodes.

Supabase, Agent, Anthropic Chat +10
AI & RAG

InsightsLM - Upsert to Vector Store. Uses vectorStoreSupabase, documentDefaultDataLoader, textSplitterRecursiveCharacterTextSplitter, embeddingsOpenAi. Webhook trigger; 17 nodes.

Supabase Vector Store, Document Default Data Loader, Text Splitter Recursive Character Text Splitter +7
AI & RAG

• Create a Google Drive folder to watch. • Connect your Google Drive account in n8n and authorize access. • Point the Google Drive Trigger node to this folder (new/modified files trigger the flow).

Agent, Chat Trigger, Memory Buffer Window +14