{
  "name": "Bulk import PDF and TXT files from a folder into Open Notebook",
  "nodes": [
    {
      "id": "8cedb6ba-8e60-48cf-9aaf-e7065f94d0e0",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        144
      ],
      "parameters": {
        "color": 1,
        "width": 480,
        "height": 900,
        "content": "## Bulk import PDF and TXT files from a folder into Open Notebook\n\n**Self-hosted n8n only.** This template uses the *Execute Command* node (shell + local filesystem access), which is not available on n8n Cloud.\n\n### How it works\nRuns on demand to bulk import local PDF and TXT files into a chosen Open Notebook notebook. It reads the folder, notebook name and API base URL from one config step, resolves the target notebook ID through the Open Notebook API, discovers matching files, then loops through them one at a time. PDFs are uploaded as files; text files are sent as JSON content.\n\n### How to set up\n1. In Open Notebook, create the notebook you want to import into.\n2. Open **Set Folder and Notebook Config** and fill in the three values:\n   - `folder_in` - the folder to scan\n   - `notebook_name` - must match the notebook name exactly\n   - `api_base` - your Open Notebook API URL (default `http://localhost:5055/api`)\n3. Click **Trigger on Demand** to execute.\n\n### Requirements\n- Self-hosted n8n with the Execute Command node enabled\n- `curl` and `python3` (with the `requests` package) on the n8n host\n- A running Open Notebook instance reachable from n8n\n- Read access to the folder holding the `.pdf` and `.txt` files\n\n### Customize\nSwap **Trigger on Demand** for a Schedule Trigger to import automatically, edit the `find` command to include more file types, or change the batch size on the loop node."
      },
      "typeVersion": 1
    },
    {
      "id": "6c25fc55-9e1c-4fc6-9b68-4cc5bdbd4901",
      "name": "Note - Step 1 Configure",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 320,
        "content": "## Manual run configuration\n\nStarts the workflow manually and defines the folder, notebook name, and Open Notebook API base URL used by the rest of the workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "a34b9ff5-63ee-4498-9bd6-348a0c609e0a",
      "name": "Note - Step 2 Resolve notebook",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1120,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 320,
        "content": "## Resolve target notebook\n\nFetches the available notebooks from Open Notebook and uses custom code to identify the ID for the configured notebook name."
      },
      "typeVersion": 1
    },
    {
      "id": "f4b32d8c-1587-4089-9593-710c5cfa8bf4",
      "name": "Note - Step 3 Collect files",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1712,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 320,
        "content": "## Discover import files\n\nSearches the configured folder for TXT and PDF files, then converts the command output into individual file items for processing."
      },
      "typeVersion": 1
    },
    {
      "id": "b8cb7fe5-4617-4058-83e3-c348d9df3c77",
      "name": "Note - Step 4 Upload loop",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2320,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 320,
        "content": "## Loop and upload files\n\nIterates over each prepared file item and uploads it to the resolved Open Notebook notebook, looping back until all files are processed."
      },
      "typeVersion": 1
    },
    {
      "id": "5c270987-211a-43a8-9b5e-9cfb91d40659",
      "name": "Trigger on Demand",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        560,
        304
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "06abd68a-77e2-4e6f-81d7-c66e77c8257a",
      "name": "Set Folder and Notebook Config",
      "type": "n8n-nodes-base.set",
      "position": [
        864,
        304
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-folder",
              "name": "folder_in",
              "type": "string",
              "value": "/path/to/your/folder"
            },
            {
              "id": "cfg-notebook",
              "name": "notebook_name",
              "type": "string",
              "value": "My Notebook"
            },
            {
              "id": "cfg-api",
              "name": "api_base",
              "type": "string",
              "value": "http://localhost:5055/api"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "cfd2415a-29d4-4820-83d1-7e27694ea8cf",
      "name": "Fetch Notebooks List",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1168,
        304
      ],
      "parameters": {
        "url": "={{ $json.api_base }}/notebooks",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "df6048a2-8492-4a2c-b16c-c4d7fe6c7732",
      "name": "Resolve Target Notebook ID",
      "type": "n8n-nodes-base.code",
      "position": [
        1472,
        304
      ],
      "parameters": {
        "jsCode": "const notebooks = $input.all().map(i => i.json);\nconst cfg = $('Set Folder and Notebook Config').first().json;\nconst match = notebooks.find(n => n.name === cfg.notebook_name);\n\nif (!match) {\n  throw new Error(`Notebook \"${cfg.notebook_name}\" was not found. Please create it in the Open Notebook UI first, then run again.`);\n}\n\nreturn [{\n  json: {\n    notebook_id: match.id,\n    folder_in: cfg.folder_in,\n    api_base: cfg.api_base\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "324761f0-9a77-484b-a8bf-c65105d21836",
      "name": "Locate TXT and PDF Files",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        1760,
        304
      ],
      "parameters": {
        "command": "=find \"{{ $json.folder_in }}\" -type f \\( -iname \"*.txt\" -o -iname \"*.pdf\" \\)"
      },
      "typeVersion": 1
    },
    {
      "id": "1c9cf207-d0d0-4c88-8e9c-148ffacf575a",
      "name": "Prepare File Items",
      "type": "n8n-nodes-base.code",
      "position": [
        2064,
        304
      ],
      "parameters": {
        "jsCode": "const stdout = $input.first().json.stdout || '';\nconst files = stdout.split('\\n').map(s => s.trim()).filter(s => s.length > 0);\nconst prev = $('Resolve Target Notebook ID').first().json;\n\n// Wraps a value in single quotes so the shell treats it literally.\n// Any embedded single quote is closed, escaped and reopened: ' -> '\\''\nconst shellQuote = (v) => \"'\" + String(v).replace(/'/g, \"'\\\\''\") + \"'\";\n\nreturn files.map(filePath => {\n  const parts = filePath.split('/');\n  const fileName = parts.pop();\n  const dotIndex = fileName.lastIndexOf('.');\n  const title = dotIndex > -1 ? fileName.substring(0, dotIndex) : fileName;\n  const ext = dotIndex > -1\n    ? fileName.substring(dotIndex + 1).toLowerCase().replace(/[^a-z0-9]/g, '')\n    : '';\n  return {\n    json: {\n      file_path: filePath,\n      title,\n      ext,\n      notebook_id: prev.notebook_id,\n      api_base: prev.api_base,\n      // Shell-safe variants consumed by the Execute Command node.\n      // Keeps file names containing \" ` $ \\ or ' from breaking the command.\n      file_path_sh: shellQuote(filePath),\n      title_sh: shellQuote(title)\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "1993af8d-5d32-4e6b-9e36-ac77dcade0ea",
      "name": "Batch File Processing",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        2368,
        304
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "12f57f37-e101-4b57-a254-ca41e897e323",
      "name": "Upload Files to Notebook",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        2672,
        304
      ],
      "parameters": {
        "command": "=ext=\"{{ $json.ext }}\"\nfile_path={{ $json.file_path_sh }}\ntitle={{ $json.title_sh }}\nnotebook_id=\"{{ $json.notebook_id }}\"\napi_base=\"{{ $json.api_base }}\"\n\nif [ \"$ext\" = \"pdf\" ]; then\n  curl -s -X POST \"$api_base/sources\" \\\n    -F \"type=upload\" \\\n    -F \"title=$title\" \\\n    -F \"notebooks=[\\\"$notebook_id\\\"]\" \\\n    -F \"async_processing=true\" \\\n    -F \"file=@$file_path\"\nelse\n  python3 -c \"\nimport json, sys, requests\nwith open(sys.argv[1], encoding='utf-8', errors='ignore') as f:\n    content = f.read()\npayload = {\n    'type': 'text',\n    'content': content,\n    'title': sys.argv[2],\n    'notebooks': [sys.argv[3]],\n    'async_processing': True,\n}\nr = requests.post(sys.argv[4] + '/sources/json', json=payload)\nprint(r.status_code, r.text[:300])\n\" \"$file_path\" \"$title\" \"$notebook_id\" \"$api_base\"\nfi"
      },
      "executeOnce": false,
      "typeVersion": 1,
      "alwaysOutputData": false
    }
  ],
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "connections": {
    "Trigger on Demand": {
      "main": [
        [
          {
            "node": "Set Folder and Notebook Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare File Items": {
      "main": [
        [
          {
            "node": "Batch File Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Notebooks List": {
      "main": [
        [
          {
            "node": "Resolve Target Notebook ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Batch File Processing": {
      "main": [
        [],
        [
          {
            "node": "Upload Files to Notebook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Locate TXT and PDF Files": {
      "main": [
        [
          {
            "node": "Prepare File Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Files to Notebook": {
      "main": [
        [
          {
            "node": "Batch File Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resolve Target Notebook ID": {
      "main": [
        [
          {
            "node": "Locate TXT and PDF Files",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Folder and Notebook Config": {
      "main": [
        [
          {
            "node": "Fetch Notebooks List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}