first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,42 @@
{
"node": "n8n-nodes-base.extractFromFile",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Core Nodes"],
"resources": {
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.extractfromfile/"
}
]
},
"alias": [
"CSV",
"Spreadsheet",
"Excel",
"xls",
"xlsx",
"ods",
"tabular",
"decode",
"decoding",
"Move Binary Data",
"Binary",
"File",
"PDF",
"JSON",
"HTML",
"ICS",
"iCal",
"txt",
"Text",
"RTF",
"XML",
"64",
"Base64",
"Convert"
],
"subcategories": {
"Core Nodes": ["Files", "Data Transformation"]
}
}
@@ -0,0 +1,138 @@
import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import * as moveTo from './actions/moveTo.operation';
import * as pdf from './actions/pdf.operation';
import * as spreadsheet from './actions/spreadsheet.operation';
export class ExtractFromFile implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-missing-subtitle
description: INodeTypeDescription = {
displayName: 'Extract from File',
name: 'extractFromFile',
icon: { light: 'file:extractFromFile.svg', dark: 'file:extractFromFile.dark.svg' },
group: ['input'],
version: [1, 1.1],
description: 'Convert binary data to JSON',
defaults: {
name: 'Extract from File',
},
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
options: [
{
name: 'Extract From CSV',
value: 'csv',
action: 'Extract from CSV',
description: 'Transform a CSV file into output items',
},
{
name: 'Extract From HTML',
value: 'html',
action: 'Extract from HTML',
description: 'Transform a table in an HTML file into output items',
},
{
name: 'Extract From ICS',
value: 'fromIcs',
action: 'Extract from ICS',
description: 'Transform a ICS file into output items',
},
{
name: 'Extract From JSON',
value: 'fromJson',
action: 'Extract from JSON',
description: 'Transform a JSON file into output items',
},
{
name: 'Extract From ODS',
value: 'ods',
action: 'Extract from ODS',
description: 'Transform an ODS file into output items',
},
{
name: 'Extract From PDF',
value: 'pdf',
action: 'Extract from PDF',
description: 'Extracts the content and metadata from a PDF file',
},
{
name: 'Extract From RTF',
value: 'rtf',
action: 'Extract from RTF',
description: 'Transform a table in an RTF file into output items',
},
{
name: 'Extract From Text File',
value: 'text',
action: 'Extract from text file',
description: 'Extracts the content of a text file',
},
{
name: 'Extract From XML',
value: 'xml',
action: 'Extract from XML',
description: 'Extracts the content of an XML file',
},
{
name: 'Extract From XLS',
value: 'xls',
action: 'Extract from XLS',
description: 'Transform an Excel file into output items',
},
{
name: 'Extract From XLSX',
value: 'xlsx',
action: 'Extract from XLSX',
description: 'Transform an Excel file into output items',
},
{
name: 'Move File to Base64 String',
value: 'binaryToPropery',
action: 'Move file to base64 string',
description: 'Convert a file into a base64-encoded string',
},
],
default: 'csv',
},
...spreadsheet.description,
...moveTo.description,
...pdf.description,
],
};
async execute(this: IExecuteFunctions) {
const version = this.getNode().typeVersion;
const items = this.getInputData();
const operation = this.getNodeParameter('operation', 0);
let returnData: INodeExecutionData[] = [];
if (spreadsheet.operations.includes(operation)) {
returnData = await spreadsheet.execute.call(this, items, 'operation', {
failOnCsvBufferError: version > 1,
});
}
if (['binaryToPropery', 'fromJson', 'text', 'fromIcs', 'xml'].includes(operation)) {
returnData = await moveTo.execute.call(this, items, operation);
}
if (operation === 'pdf') {
returnData = await pdf.execute.call(this, items);
}
return [returnData];
}
}
@@ -0,0 +1,199 @@
import iconv from 'iconv-lite';
import set from 'lodash/set';
import unset from 'lodash/unset';
import type {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeProperties,
IBinaryData,
} from 'n8n-workflow';
import {
BINARY_ENCODING,
NodeOperationError,
deepCopy,
jsonParse,
BINARY_MODE_COMBINED,
} from 'n8n-workflow';
import { icsCalendarToObject } from 'ts-ics';
import { encodeDecodeOptions } from '@utils/descriptions';
import { updateDisplayOptions } from '@utils/utilities';
export const properties: INodeProperties[] = [
{
displayName: 'Input Binary Field',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
required: true,
placeholder: 'e.g data',
hint: 'The name of the input field containing the file data to be processed',
},
{
displayName: 'Destination Output Field',
name: 'destinationKey',
type: 'string',
default: 'data',
required: true,
placeholder: 'e.g data',
description: 'The name of the output field that will contain the extracted data',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add option',
default: {},
options: [
{
displayName: 'File Encoding',
name: 'encoding',
type: 'options',
options: encodeDecodeOptions,
default: 'utf8',
description: 'Specify the encoding of the file, defaults to UTF-8',
},
{
displayName: 'Strip BOM',
name: 'stripBOM',
displayOptions: {
show: {
encoding: ['utf8', 'cesu8', 'ucs2'],
},
},
type: 'boolean',
default: true,
description:
'Whether to strip the BOM (Byte Order Mark) from the file, this could help in an environment where the presence of the BOM is causing issues or inconsistencies',
},
{
displayName: 'Keep Source',
name: 'keepSource',
type: 'options',
default: 'json',
options: [
{
name: 'JSON',
value: 'json',
description: 'Include JSON data of the input item',
},
{
name: 'Binary',
value: 'binary',
description: 'Include binary data of the input item',
},
{
name: 'Both',
value: 'both',
description: 'Include both JSON and binary data of the input item',
},
],
},
],
},
];
const displayOptions = {
show: {
operation: ['binaryToPropery', 'fromJson', 'text', 'fromIcs', 'xml'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(
this: IExecuteFunctions,
items: INodeExecutionData[],
operation: string,
) {
const returnData: INodeExecutionData[] = [];
const { binaryMode } = this.getWorkflowSettings();
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
const item = items[itemIndex];
const options = this.getNodeParameter('options', itemIndex);
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', itemIndex) as
| string
| IBinaryData;
const newItem: INodeExecutionData = {
json: {},
pairedItem: { item: itemIndex },
};
const value = this.helpers.assertBinaryData(itemIndex, binaryPropertyName);
if (!value) continue;
const buffer = await this.helpers.getBinaryDataBuffer(itemIndex, binaryPropertyName);
const encoding = (options.encoding as string) || this.helpers.detectBinaryEncoding(buffer);
if (options.keepSource && options.keepSource !== 'binary') {
newItem.json = deepCopy(item.json);
}
let convertedValue: string | IDataObject;
if (operation !== 'binaryToPropery') {
convertedValue = iconv.decode(buffer, encoding, {
stripBOM: options.stripBOM as boolean,
});
} else {
convertedValue = Buffer.from(buffer).toString(BINARY_ENCODING);
}
if (operation === 'fromJson') {
if (convertedValue === '') {
convertedValue = {};
} else {
convertedValue = jsonParse(convertedValue);
}
}
if (operation === 'fromIcs') {
convertedValue = icsCalendarToObject(convertedValue as string);
}
const destinationKey = this.getNodeParameter('destinationKey', itemIndex, '') as string;
set(newItem.json, destinationKey, convertedValue);
if (typeof binaryPropertyName === 'string' && binaryMode !== BINARY_MODE_COMBINED) {
if (options.keepSource === 'binary' || options.keepSource === 'both') {
newItem.binary = item.binary;
} else {
// this binary data would not be included, but there also might be other binary data
// which should be included, copy it over and unset current binary data
newItem.binary = deepCopy(item.binary);
unset(newItem.binary, binaryPropertyName);
}
}
returnData.push(newItem);
} catch (error) {
let errorDescription;
if (typeof error.message === 'string' && error.message.includes('Unexpected token')) {
error.message = "The file selected in 'Input Binary Field' is not in JSON format";
errorDescription =
"Try to change the operation or select a JSON file in 'Input Binary Field'";
}
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
},
pairedItem: {
item: itemIndex,
},
});
continue;
}
throw new NodeOperationError(this.getNode(), error, {
itemIndex,
description: errorDescription,
});
}
}
return returnData;
}
@@ -0,0 +1,139 @@
import unset from 'lodash/unset';
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
import { NodeOperationError, deepCopy } from 'n8n-workflow';
import { extractDataFromPDF } from '@utils/binary';
import { updateDisplayOptions } from '@utils/utilities';
export const properties: INodeProperties[] = [
{
displayName: 'Input Binary Field',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
required: true,
placeholder: 'e.g data',
hint: 'The name of the input binary field containing the file to be extracted',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add option',
default: {},
options: [
{
displayName: 'Join Pages',
name: 'joinPages',
type: 'boolean',
default: true,
description:
'Whether to join the text from all pages or return an array of text from each page',
},
{
displayName: 'Keep Source',
name: 'keepSource',
type: 'options',
default: 'json',
options: [
{
name: 'JSON',
value: 'json',
description: 'Include JSON data of the input item',
},
{
name: 'Binary',
value: 'binary',
description: 'Include binary data of the input item',
},
{
name: 'Both',
value: 'both',
description: 'Include both JSON and binary data of the input item',
},
],
},
{
displayName: 'Max Pages',
name: 'maxPages',
type: 'number',
default: 0,
description: 'Maximum number of pages to include',
},
{
displayName: 'Password',
name: 'password',
type: 'string',
typeOptions: { password: true },
default: '',
description: 'Prowide password, if the PDF is encrypted',
},
],
},
];
const displayOptions = {
show: {
operation: ['pdf'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
const returnData: INodeExecutionData[] = [];
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
const item = items[itemIndex];
const options = this.getNodeParameter('options', itemIndex);
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', itemIndex);
const json = await extractDataFromPDF.call(
this,
binaryPropertyName,
options.password as string,
options.maxPages as number,
options.joinPages as boolean,
itemIndex,
);
const newItem: INodeExecutionData = {
json: {},
pairedItem: { item: itemIndex },
};
if (options.keepSource && options.keepSource !== 'binary') {
newItem.json = { ...deepCopy(item.json), ...json };
} else {
newItem.json = json;
}
if (options.keepSource === 'binary' || options.keepSource === 'both') {
newItem.binary = item.binary;
} else {
// this binary data would not be included, but there also might be other binary data
// which should be included, copy it over and unset current binary data
newItem.binary = deepCopy(item.binary);
unset(newItem.binary, binaryPropertyName);
}
returnData.push(newItem);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
},
pairedItem: {
item: itemIndex,
},
});
continue;
}
throw new NodeOperationError(this.getNode(), error, { itemIndex });
}
}
return returnData;
}
@@ -0,0 +1,71 @@
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
import * as fromFile from '../../../SpreadsheetFile/v2/fromFile.operation';
export const operations = ['csv', 'html', 'rtf', 'ods', 'xls', 'xlsx'];
export const description: INodeProperties[] = fromFile.description
.filter((property) => property.name !== 'fileFormat')
.map((property) => {
const newProperty = { ...property };
newProperty.displayOptions = {
show: {
operation: operations,
},
};
if (newProperty.name === 'options') {
newProperty.options = (newProperty.options as INodeProperties[]).map((option) => {
let newOption = option;
if (
[
'delimiter',
'encoding',
'fromLine',
'maxRowCount',
'enableBOM',
'relaxQuotes',
'skipRecordsWithErrors',
].includes(option.name)
) {
newOption = { ...option, displayOptions: { show: { '/operation': ['csv'] } } };
}
if (option.name === 'sheetName') {
newOption = {
...option,
displayOptions: { show: { '/operation': ['ods', 'xls', 'xlsx'] } },
description: 'Name of the sheet to read from in the spreadsheet',
};
}
if (option.name === 'range') {
newOption = {
...option,
displayOptions: { show: { '/operation': ['ods', 'xls', 'xlsx'] } },
};
}
if (['includeEmptyCells', 'headerRow'].includes(option.name)) {
newOption = {
...option,
displayOptions: { show: { '/operation': ['ods', 'xls', 'xlsx', 'csv', 'html'] } },
};
}
return newOption;
});
}
return newProperty;
});
export async function execute(
this: IExecuteFunctions,
items: INodeExecutionData[],
fileFormatProperty: string,
options?: fromFile.FromFileOptions,
) {
const returnData: INodeExecutionData[] = await fromFile.execute.call(
this,
items,
fileFormatProperty,
options,
);
return returnData;
}
@@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.9375 2C0.419733 2 0 2.41973 0 2.9375V37.3221C0 37.8399 0.419733 38.2596 0.9375 38.2596H26.2154C26.7332 38.2596 27.1529 37.8399 27.1529 37.3221L27.1529 30.6719L16.7969 30.6719C14.8984 30.6719 13.3594 29.1329 13.3594 27.2344V25.3594C13.3594 23.4609 14.8984 21.9219 16.7969 21.9219H27.1529L27.1529 15.7821H14.3083C13.7906 15.7821 13.3708 15.3624 13.3708 14.8446V2H0.9375Z" fill="#6275AD"/>
<path d="M16.0271 2C15.9408 2 15.8708 2.06996 15.8708 2.15625V12.8134C15.8708 13.0723 16.0807 13.2821 16.3396 13.2821H26.9967C27.083 13.2821 27.1529 13.2122 27.1529 13.1259L27.1529 12.6171C27.1529 12.3688 27.0545 12.1307 26.8791 11.955L17.2225 2.27538C17.0466 2.09908 16.8079 2 16.5588 2H16.0271Z" fill="#6275AD"/>
<path d="M29.7642 34.6503C29.034 33.916 29.0374 32.7288 29.7716 31.9986L33.6197 28.1719L16.7969 28.1719C16.2791 28.1719 15.8594 27.7521 15.8594 27.2344V25.3594C15.8594 24.8416 16.2791 24.4219 16.7969 24.4219L33.5412 24.4219L29.7716 20.6733C29.0374 19.9431 29.034 18.7559 29.7642 18.0216C30.4944 17.2874 31.6816 17.284 32.4159 18.0142L39.4471 25.0064C39.801 25.3583 40 25.8368 40 26.3359C40 26.835 39.801 27.3135 39.4471 27.6655L32.4159 34.6576C31.6816 35.3878 30.4944 35.3845 29.7642 34.6503Z" fill="#6275AD"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.9375 2C0.419733 2 0 2.41973 0 2.9375V37.3221C0 37.8399 0.419733 38.2596 0.9375 38.2596H26.2154C26.7332 38.2596 27.1529 37.8399 27.1529 37.3221L27.1529 30.6719L16.7969 30.6719C14.8984 30.6719 13.3594 29.1329 13.3594 27.2344V25.3594C13.3594 23.4609 14.8984 21.9219 16.7969 21.9219H27.1529L27.1529 15.7821H14.3083C13.7906 15.7821 13.3708 15.3624 13.3708 14.8446V2H0.9375Z" fill="#353F6E"/>
<path d="M16.0271 2C15.9408 2 15.8708 2.06996 15.8708 2.15625V12.8134C15.8708 13.0723 16.0807 13.2821 16.3396 13.2821H26.9967C27.083 13.2821 27.1529 13.2122 27.1529 13.1259L27.1529 12.6171C27.1529 12.3688 27.0545 12.1307 26.8791 11.955L17.2225 2.27538C17.0466 2.09908 16.8079 2 16.5588 2H16.0271Z" fill="#353F6E"/>
<path d="M29.7642 34.6503C29.034 33.916 29.0374 32.7288 29.7716 31.9986L33.6197 28.1719L16.7969 28.1719C16.2791 28.1719 15.8594 27.7521 15.8594 27.2344V25.3594C15.8594 24.8416 16.2791 24.4219 16.7969 24.4219L33.5412 24.4219L29.7716 20.6733C29.0374 19.9431 29.034 18.7559 29.7642 18.0216C30.4944 17.2874 31.6816 17.284 32.4159 18.0142L39.4471 25.0064C39.801 25.3583 40 25.8368 40 26.3359C40 26.835 39.801 27.3135 39.4471 27.6655L32.4159 34.6576C31.6816 35.3878 30.4944 35.3845 29.7642 34.6503Z" fill="#353F6E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,5 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
describe('ExtractFromFile', () => {
new NodeTestHarness().setupTests();
});
@@ -0,0 +1,98 @@
{
"nodes": [
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [-200, -40],
"id": "64686c0a-64a4-4a33-9e70-038c9d23c25b",
"name": "When clicking Execute workflow"
},
{
"parameters": {
"operation": "text",
"binaryPropertyName": "myfile",
"options": {}
},
"type": "n8n-nodes-base.extractFromFile",
"typeVersion": 1,
"position": [420, -40],
"id": "aaac18d3-1e99-4c47-9de8-2dc8bf95abd7",
"name": "Extract from File"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "39b5f05f-85c5-499a-86d6-591d6440f147",
"name": "text",
"value": "Karlovy Vary město lázní Příliš žluťoučký kůň úpěl ďábelské ódy.",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-20, -40],
"id": "e74c67f1-171a-42a7-be12-b6687935988f",
"name": "Edit Fields"
},
{
"parameters": {
"operation": "toText",
"sourceProperty": "text",
"binaryPropertyName": "myfile",
"options": {
"encoding": "windows1256"
}
},
"type": "n8n-nodes-base.convertToFile",
"typeVersion": 1.1,
"position": [180, -40],
"id": "77ddd6d4-1d75-4ad2-8301-e70213d8371e",
"name": "windows1256"
}
],
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"Extract from File": {
"main": [[]]
},
"Edit Fields": {
"main": [
[
{
"node": "windows1256",
"type": "main",
"index": 0
}
]
]
},
"windows1256": {
"main": [
[
{
"node": "Extract from File",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {}
}