# LinkedIn CSV preparation for n8n

Import `n8n-linkedin-csv-workflow.json` into your n8n instance. This is a file handoff and preparation workflow, not a native LinkedIn lead-extraction integration. The built-in LinkedIn node currently documents post creation; it is not used here. The workflow reads a CSV you already obtained and are authorized to process. It does not fetch profiles, verify contact information, resolve Sales Navigator identifiers or provide an outreach-permission check.

## Run the synthetic example

1. Import the JSON from your local file. It is inactive by default and requires no LinkedIn, Google or other service credentials.
2. Inspect the three nodes: **Upload CSV and select column** → **Validate CSV without losing originals** → **Download clean and review ZIP**. Keep the imported workflow setting `binaryMode: separate`. The Code node expects the file in `input.binary.csv_file`; combined mode puts binary data inside JSON instead and is unsupported by this template. If that setting is changed, the Code node stops with an instruction to restore separate mode and submit again.
3. Execute the **whole workflow**, then open its test form. Executing only the trigger does not run the preparation and download nodes.
4. Upload `n8n-linkedin-csv-synthetic.csv`. Enter profile column **2** and choose **Comma**. The first column is number 1. Column selection has no default and never falls back to the person's name or a similarly named field.
5. Download `linkedin-csv-clean-review.zip` from the completion form. Inspect `report.json` and `review.csv` before using `clean.csv`.
6. Compare against `n8n-linkedin-csv-expected-clean.csv`, `n8n-linkedin-csv-expected-review.csv` and `n8n-linkedin-csv-expected-report.json`.

All people, companies and identifiers in the fixture are synthetic and have not been checked against live profiles. The input has 12 data records. Expected results: **3 clean rows, 8 review rows and 1 folded duplicate**, accounting for all 12 input records. The fixture includes a multiline quoted field, quoted comma, escaped quotes, formula-like values, two conflicting records for one URL, an unsupported Sales Navigator identifier, a missing URL, dot segments, an extra cell, a company URL and a blank data record. Record numbers include the header as record 1; a multiline record can span several physical lines.

## Download contents

| File | Contents |
| --- | --- |
| `clean.csv` | Valid-shaped, unambiguous public profile URLs and all original cells from the retained source row. `_normalized_profile_url` is a separate value; the original URL cell remains. `_source_records` lists every folded record and its line range. |
| `review.csv` | Every record excluded from the clean file, with a reason, source record, line range, provisional canonical URL when relevant, raw record text and all original cells. |
| `input-original.csv.txt` | The exact upload bytes, including BOM and original line endings. The `.txt` suffix avoids opening unescaped input directly in a spreadsheet by default. |
| `originals.json` | Original header cells and data-cell arrays, raw record text, line ranges and disposition. Duplicate header names and extra cells are preserved by position. |
| `report.json` | Mapping, delimiter, parser/header warnings, blocking status and row accounting. Counts refer to parsed records; a syntax-damaged segment can contain more than one intended physical row. |

CSV headers use `column_1:Original Header`, `column_2:Original Header`, etc. This avoids object-key overwrites when source headers repeat. These positional labels are template output names, not production headers promised by LinkedIn, n8n, a CRM or another exporter.

## Processing rules

- Input is UTF-8, with an optional BOM; comma, semicolon and tab delimiters must be selected explicitly. Quoted delimiters, doubled quotes, multiline fields, CR/LF/CRLF, trailing blank cells and interior blank records are supported. Input size is limited by this template to 5 MiB; this is not a LinkedIn or n8n account limit.
- A public `/in/slug` URL may use HTTP or HTTPS, `linkedin.com`, `www.linkedin.com` or a two-letter locale subdomain. Tracking queries, fragments and a trailing slash are removed. Scheme becomes HTTPS and host becomes `www.linkedin.com`. Profile slug case and percent encoding remain unchanged. This is a lexical format check, not a check that the profile exists or belongs to the named person.
- Company URLs, Sales Navigator paths/IDs, unrelated hosts, credentials, explicit ports, dot segments, extra path segments, backslashes, malformed percent encodings and encoded slashes go to review. No HTTP resolution or alias inference occurs. Different slug cases or percent-encoding spellings remain distinct keys.
- A missing URL or wrong-width row goes to review. Extra cells remain visible in additional positional columns and in JSON. Duplicate or blank header names are reported; explicit positional mapping lets the user choose the intended column without overwriting either header.
- Repeated canonical URL rows are folded only when every non-profile cell is exactly equal. Every contributing source record is kept in JSON and clean provenance. A difference in any other field, including blank versus filled, routes **all** rows in that URL group to review. Names are never dedupe keys. No row is silently deleted because its identifier is blank.
- A CSV syntax error, invalid mapping or unsupported delimiter blocks all clean data rows. Parsing does not guess a repair. Invalid encoding or an unreadable delimiter can produce only a report and the original bytes because record boundaries cannot be read safely. A missing upload, multiple input items or an oversized upload stops with an explicit execution error; the original trigger input remains available in that execution.
- Both output CSVs prefix cells beginning with `=`, `+`, `-` or `@` (including after whitespace/control characters) with an apostrophe. Quoting alone is not formula protection. This intentionally changes the exported CSV string; original values remain in `originals.json` and the exact input. Review the destination's import behavior before using apostrophe-prefixed fields as CRM data. Do not open the raw original in a spreadsheet unless you have checked formula-like content.

## Data location and access

The form sends the file to **your n8n instance**. A self-hosted instance processes it on that server; n8n Cloud processes it in that hosted environment. This is not browser-only processing. No workflow node sends CSV contents to LinkedIn, Leadsforlinked, Google Sheets, an enrichment provider or another external endpoint. JavaScript uses n8n's binary-buffer helper and built-in `Buffer`/`TextDecoder`; there are no npm dependencies, HTTP Request nodes or API credentials.

The import requests that successful, failed and manual execution saving and progress saving be disabled. Check the imported settings, execution pruning, binary storage, backups and administrator policies on your instance. The Form Ending uses n8n's waiting/resume mechanism, so n8n may persist transient workflow and binary state while returning the download even when routine execution saving is disabled. These settings are not a guarantee of zero storage or automatic deletion. Use synthetic input first and delete test executions when appropriate.

Form authentication is **None**, with no production activation. Use the test form for initial runs. Before exposing the production form URL, configure access appropriate to your deployment, such as Form Trigger Basic Auth, and add the credentials that this optional authentication method requires. The workflow does not provide user authorization, a suppression policy or a CRM blocklist.

## Local verification without n8n

Node.js is needed only for this optional local harness; it is not an extra package installed inside n8n. Keep the workflow JSON and harness in the same directory.

```sh
node n8n-linkedin-csv-harness.cjs n8n-linkedin-csv-synthetic.csv 2 Comma output
```

This writes the ZIP and its five contents to `output`, and prints the report. Omitting the last argument prints the report without writing results. The harness executes the **exact embedded Code-node source** in a local JavaScript VM with an n8n binary-helper stub. It does not run a Form Trigger, import a workflow into a server, exercise an actual n8n task runner or test the browser's download interaction.

Repository maintenance tests:

```sh
node docs/strategy/tests/build-n8n-linkedin-csv-package.cjs
node --test docs/strategy/tests/n8n-linkedin-csv-workflow.test.cjs
```

The build script embeds the portable source and refreshes the synthetic fixtures. The 22 tests include fixed expected accounting, exact-byte preservation, original cells/provenance, conflicting duplicate retention, duplicate headers, malformed CSV, URL rejection, formula escaping, ZIP CRC checks and a targeted failure for combined JSON binary uploads. Local harness tests passed on 12 October 2026. **No live n8n instance or browser download was executed for this package.** Run the supplied synthetic fixture through your instance before using real data.

## Node schema and source checks

Checked live official documentation and official repository source on **12 October 2026**:

| Node | Workflow type/version | Check |
| --- | --- | --- |
| Form Trigger | `n8n-nodes-base.formTrigger`, 2.4 | Current source lists version 2.4, supports file elements, single-file input, required numeric mapping, delimiter dropdown and version 2.4 field names. |
| Code | `n8n-nodes-base.code`, 2 | Current source and tagged 2.42.7 Code source list version 2 and JavaScript Run Once for All Items. Binary data is read with `this.helpers.getBinaryDataBuffer(0, 'csv_file')`. |
| Form Ending | `n8n-nodes-base.form`, 2.3 | Current source lists version 2.3 and `operation: completion`, `respondWith: returnBinary`, `inputDataFieldName: bundle`. |
| Extract From File | Not used | Official docs support extracting CSV and text. This package uses a custom parser to retain duplicate headers, raw record text, original cells and explicit syntax failures rather than rely on an object-header conversion. |

The official workflow interfaces support `settings.binaryMode`, and the official constants define `BINARY_MODE_SEPARATE` as `separate`. Form upload handling uses that setting to keep the upload in the binary field. The JSON explicitly sets separate mode; do not switch it to combined mode. This workflow-level layout setting is distinct from whether an instance stores binary bytes in memory, a filesystem or other storage.

The official GitHub release API reported release **n8n 2.42.7**, published 12 October 2026, when checked. That is release metadata, not evidence that this workflow was run on 2.42.7. Tagged Form source retrieval was unavailable; the Form schemas were checked against the live official `master` source. We do not claim compatibility with every historical release or instance-specific binary-storage/task-runner setting. If your instance cannot load these node versions, update it through your normal deployment process or adapt and retest the nodes.

Official references:

- [LinkedIn node operations](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.linkedin/)
- [Extract From File](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.extractfromfile/)
- [Form Trigger](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.formtrigger/)
- [Form Ending: Return Binary File](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.form/)
- [Code node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/)
- [Get the binary data buffer](https://docs.n8n.io/build/code-in-n8n/cookbook/code-node/get-the-binary-data-buffer.md)
- [Workflow saving settings](https://docs.n8n.io/build/manage-workflows/configure-workflow-settings.md)
- [Official Form Trigger versions](https://github.com/n8n-io/n8n/blob/master/packages/nodes-base/nodes/Form/FormTrigger.node.ts)
- [Official Form node schema](https://github.com/n8n-io/n8n/blob/master/packages/nodes-base/nodes/Form/Form.node.ts)
- [Official Form input/binary handling](https://github.com/n8n-io/n8n/blob/master/packages/nodes-base/nodes/Form/utils/utils.ts)
- [Official binary layout constants](https://github.com/n8n-io/n8n/blob/master/packages/workflow/src/constants.ts)
- [Official workflow binary-mode setting](https://github.com/n8n-io/n8n/blob/master/packages/workflow/src/interfaces.ts)
- [Official Form download handling](https://github.com/n8n-io/n8n/blob/master/packages/nodes-base/nodes/Form/utils/formCompletionUtils.ts)
- [Tagged Code node schema](https://github.com/n8n-io/n8n/blob/n8n%402.42.7/packages/nodes-base/nodes/Code/Code.node.ts)
- [JavaScript runner globals](https://github.com/n8n-io/n8n/blob/master/packages/%40n8n/task-runner/src/js-task-runner/js-task-runner.ts)
- [Release metadata](https://api.github.com/repos/n8n-io/n8n/releases/latest)

## Optional Google Sheets destination

There is no Sheets node in the default workflow. After resolving review records and checking the destination schema, you can upload `clean.csv` to a new sheet yourself or extend a separate copy using an authenticated Google Sheets node. That extension requires your Google credentials, shares the selected data with Google, needs an explicit column map and duplicate/update policy, and has not been built or tested here. Do not describe a manual CSV upload as a native LinkedIn integration or infer that every clean row is approved to contact.
