15 KiB
Creating n8n-nodes-module
[TODO: this is the most up to date method, use it]
In this guide, you'll learn to create a custom n8n-nodes-module that can be installed separately alongside your n8n instance. The n8n-nodes-module is an npm package that contains the node. Your custom node will get loaded automatically when n8n starts.
Consider creating n8n-nodes-module if any of the following conditions satisfy your needs:
- The nodes are only for yourself, your organization, or a small group of people.
- The nodes require external dependencies that are not already available in n8n.
NOTE: n8n-nodes-module can only be installed in self-hosted n8n instances. This functionality is currently not available on n8n Cloud or the desktop app. There are plans to introduce this functionality in the future.
Prerequisites
You may already be familiar with creating nodes in n8n. If you are unfamiliar with how to create n8n nodes, you can learn about it following the instructions mentioned in the Creating Your First Node tutorial.
Install the following tools:
- Git: You can find instructions on how to install Git here.
- Node.js and npm: You can find instructions how to install both using nvm (Node Version Manager) here. The current minimum version is
14.15. In case you already have Node.js and npm installed, you can check the current version with the following command:node -v npm -v
NOTE: Use node version 14.x and npm version 6.x. If using npm version 7+, you must enable legacy peer dependencies by setting: npm config set legacy-peer-deps true.
[TODO: remove lerna per alex]
-
Lerna: You can install lerna globally with the following command:
npm i -
Install n8n: Create a new folder and install n8n using the command:
npm install n8n
Create custom n8n-nodes-module
You can create multiple n8n-nodes-modules. Each individual n8n-nodes-module should get created in a separate folder since they are different npm packages. A single n8n-nodes-module can contain multiple nodes. If you're creating multiple nodes in the same module, as a best practice create each node in a separate folder.
In this tutorial, you will create an n8n-nodes-module for the OpenWeatherMap API. You will name it n8n-nodes-weather.
To quickly get started, clone the example starter using the following command:
git clone https://github.com/n8n-io/n8n-nodes-starter.git n8n-nodes-weather.
After the repo gets cloned, open the package.json file, and update the value of the name by replacing n8n-nodes-starter with n8n-nodes-weather.
NOTE: The name of the module has to start with n8n-nodes-.
Open the cloned repository in your code editor, and create a new folder called Weather, inside the nodes folder. Create Weather.node.ts file inside the Weather folder and paste the following code:
import {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
IRequestOptions,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow';
export class Weather implements INodeType {
description: INodeTypeDescription = {
displayName: 'Weather',
name: 'Weather',
icon: 'fa:sun',
group: ['input'],
version: 1,
description: 'Gets current and future weather information',
defaults: {
name: 'Weather',
color: '#554455',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'weatherApi',
required: true,
},
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
options: [
{
name: 'Current Weather',
value: 'currentWeather',
description: 'Returns the current weather data',
},
{
name: '5 day Forecast',
value: '5DayForecast',
description: 'Returns the weather data for the next 5 days',
},
],
default: 'currentWeather',
description: 'The operation to perform.',
},
{
displayName: 'Format',
name: 'format',
type: 'options',
options: [
{
name: 'Imperial',
value: 'imperial',
description: 'Fahrenheit | miles/hour',
},
{
name: 'Metric',
value: 'metric',
description: 'Celsius | meter/sec',
},
{
name: 'Scientific',
value: 'standard',
description: 'Kelvin | meter/sec',
},
],
default: 'metric',
description: 'The format in which format the data should be returned.',
},
// ----------------------------------
// Location Information
// ----------------------------------
{
displayName: 'Location Selection',
name: 'locationSelection',
type: 'options',
options: [
{
name: 'City Name',
value: 'cityName',
},
{
name: 'City ID',
value: 'cityId',
},
{
name: 'Coordinates',
value: 'coordinates',
},
{
name: 'Zip Code',
value: 'zipCode',
},
],
default: 'cityName',
description: 'How to define the location for which to return the weather.',
},
{
displayName: 'City',
name: 'cityName',
type: 'string',
default: '',
placeholder: 'berlin,de',
required: true,
displayOptions: {
show: {
locationSelection: [
'cityName',
],
},
},
description: 'The name of the city to return the weather of.',
},
{
displayName: 'City ID',
name: 'cityId',
type: 'number',
default: 160001123,
required: true,
displayOptions: {
show: {
locationSelection: [
'cityId',
],
},
},
description: 'The id of city to return the weather of. List can be downloaded here: http://bulk.openweathermap.org/sample/',
},
{
displayName: 'Latitude',
name: 'latitude',
type: 'string',
default: '',
placeholder: '13.39',
required: true,
displayOptions: {
show: {
locationSelection: [
'coordinates',
],
},
},
description: 'The latitude of the location to return the weather of.',
},
{
displayName: 'Longitude',
name: 'longitude',
type: 'string',
default: '',
placeholder: '52.52',
required: true,
displayOptions: {
show: {
locationSelection: [
'coordinates',
],
},
},
description: 'The longitude of the location to return the weather of.',
},
{
displayName: 'Zip Code',
name: 'zipCode',
type: 'string',
default: '',
placeholder: '10115,de',
required: true,
displayOptions: {
show: {
locationSelection: [
'zipCode',
],
},
},
description: 'The id of city to return the weather of. List can be downloaded here: http://bulk.openweathermap.org/sample/',
},
{
displayName: 'Language',
name: 'language',
type: 'string',
default: '',
placeholder: 'en',
required: false,
description: 'The two letter language code to get your output in (eg. en, de, ...).',
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const credentials = await this.getCredentials('openWeatherMapApi');
if (credentials === undefined) {
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
}
const operation = this.getNodeParameter('operation', 0) as string;
let endpoint = '';
let locationSelection;
let language;
let qs: IDataObject;
for (let i = 0; i < items.length; i++) {
try {
// Set base data
qs = {
APPID: credentials.accessToken,
units: this.getNodeParameter('format', i) as string,
};
// Get the location
locationSelection = this.getNodeParameter('locationSelection', i) as string;
if (locationSelection === 'cityName') {
qs.q = this.getNodeParameter('cityName', i) as string;
} else if (locationSelection === 'cityId') {
qs.id = this.getNodeParameter('cityId', i) as number;
} else if (locationSelection === 'coordinates') {
qs.lat = this.getNodeParameter('latitude', i) as string;
qs.lon = this.getNodeParameter('longitude', i) as string;
} else if (locationSelection === 'zipCode') {
qs.zip = this.getNodeParameter('zipCode', i) as string;
} else {
throw new NodeOperationError(this.getNode(), `The locationSelection "${locationSelection}" is not known!`);
}
// Get the language
language = this.getNodeParameter('language', i) as string;
if (language) {
qs.lang = language;
}
if (operation === 'currentWeather') {
// ----------------------------------
// currentWeather
// ----------------------------------
endpoint = 'weather';
} else if (operation === '5DayForecast') {
// ----------------------------------
// 5DayForecast
// ----------------------------------
endpoint = 'forecast';
} else {
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
}
const options: IRequestOptions = {
method: 'GET',
qs,
uri: `https://api.openweathermap.org/data/2.5/${endpoint}`,
json: true,
};
let responseData;
try {
responseData = await this.helpers.request(options);
} catch (error) {
throw new NodeApiError(this.getNode(), error);
}
returnData.push(responseData as IDataObject);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({json:{ error: error.message }});
continue;
}
throw error;
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}
The OpenWeatherMap API requires credentials to return results successfully. Create WeatherApi.credentials.ts file in the Credentials folder and paste the following code:
import {
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class WeatherApi implements ICredentialType {
name = 'weatherApi';
displayName = 'Weather API';
properties: INodeProperties[] = [
{
displayName: 'Access Token',
name: 'accessToken',
type: 'string',
default: '',
},
];
}
Add the newly created node and the credential to the package.json file. Add "dist/nodes/Weather/Weather.node.js" to the nodes array in the n8n object (n8n.nodes). Similarly, add "dist/credentials/WeatherApi.credentials.js" to the credentials array in the n8n object (n8n.credentials).
Develop and test the module
Once you've created the n8n-nodes-module, you need to build the code and publish the package locally to test it. Run the following commands:
# Install dependencies
npm install
# Build the code
npm run build
# "Publish" the package locally
npm link
NOTE: If you get permission errors, run the command as a root user with sudo, for example sudo npm link.
In the terminal, open the folder where you installed n8n. Run the following command to install the locally published module.
# "Install" the above locally published module
npm link n8n-nodes-weather
Start n8n with the below command
./node_modules/n8n/bin/n8n start
You will now be able to test and use your newly created n8n-nodes-module.
Publish the n8n-nodes-module
As mentioned, the n8n-nodes-module is an npm package. To make it available to others, you can publish it to the npm registry. Refer to the Publishing unscoped public packages guide to learn about publishing packages.
Following the steps mentioned above, you can create multiple nodes within a single n8n-nodes-module. You can also create nodes that require dependencies that are not present in n8n. When creating an n8n-nodes-module make sure that you follow the following guidelines:
- The name of the module should start with
n8n-nodes-. - The
package.jsonfile has to contain a keyn8nwith the paths to nodes and credentials. - The module has to be installed alongside n8n.
Use the n8n-nodes-module in production
Once you test and publish your n8n-nodes-module you would want to use it in your production environment.
If you're running n8n via Docker, you will have to create a Docker image with the node module installed in n8n. Follow the steps below to create your Docker image:
-
Create a Dockerfile and paste the code from this Dockerfile.
-
Add the following command in your Dockerfile before the font installation command.
RUN cd /usr/local/lib/node_modules/n8n && npm install n8n-nodes-weatherYour Dockerfile should look like this:
FROM node:16-alpine ARG N8N_VERSION RUN if [ -z "$N8N_VERSION" ] ; then echo "The N8N_VERSION argument is missing!" ; exit 1; fi # Update everything and install needed dependencies RUN apk add --update graphicsmagick tzdata git tini su-exec # Set a custom user to not have n8n run as root USER root # Install n8n and the packages it needs to build it correctly. RUN apk --update add --virtual build-dependencies python3 build-base ca-certificates && \ npm config set python "$(which python3)" && \ npm_config_user=root npm install -g full-icu n8n@${N8N_VERSION} && \ apk del build-dependencies \ && rm -rf /root /tmp/* /var/cache/apk/* && mkdir /root; # Install your n8n-nodes-module. Replace <n8n-nodes-module> with the name of your module RUN cd /usr/local/lib/node_modules/n8n && npm install <n8n-nodes-module> # Install fonts RUN apk --no-cache add --virtual fonts msttcorefonts-installer fontconfig && \ update-ms-fonts && \ fc-cache -f && \ apk del fonts && \ find /usr/share/fonts/truetype/msttcorefonts/ -type l -exec unlink {} \; \ && rm -rf /root /tmp/* /var/cache/apk/* && mkdir /root ENV NODE_ICU_DATA /usr/local/lib/node_modules/full-icu WORKDIR /data COPY docker-entrypoint.sh /docker-entrypoint.sh ENTRYPOINT ["tini", "--", "/docker-entrypoint.sh"] EXPOSE 5678/tcp -
Download the docker-entrypoint.sh file, and place it in the same directory as your Dockerfile.
-
Build your Docker image:
# Replace <n8n-version-number> with the n8n release version number. # For example, N8N_VERSION=0.177.0 docker build --build-arg N8N_VERSION=<n8n-version-number> --tag=customizedn8n .
You can now use your n8n-nodes-module in Docker.
If you're running either by installing it globally or using PM2, make sure that you install your n8n-nodes-module inside n8n. n8n will find the module and load it automatically.