Create Node
Learn how to create custom nodes for Deforge
This guide will help you create your own node for the Deforge system. Please follow all instructions carefully to ensure compatibility and maintainability.
Concept
A node is a modular unit of functionality that can be connected to other nodes to create complex workflows. Each node performs a specific task, such as processing data, interacting with APIs, or manipulating information. Nodes can have inputs and outputs, allowing them to receive data from other nodes and pass results along the workflow.
A node consist of the following main components:
- The
configobject that defines the node's metadata, inputs, outputs, and fields. This same object is used to determine the appearance of the node in the editor. - The
run()method that contains the logic to be executed when the node is run.
Each node is independent of any other node and must be able to function on its own. Hence, each node should have its own package.json file if it requires external dependencies.
File structure
The nodes are organized in a specific file structure within the Deforge library. Each node should be placed in its own folder under the appropriate category. The file name of the script will always be node.js. The folder name of the node should match the type field in the node configuration and it should be unique across the library.
The file structure looks like this (You can click on folders to expand/collapse them and see the files inside):
Boilerplate
Use the snippet node if you are on VSCode, and it will generate a boilerplate code for you to start with. It has all the necessary imports and structure to get you started quickly.
It generates the following code:
See boilerplate code
import BaseNode from "../../core/BaseNode/node.js";
const config = {
title: "",
category: "",
type: "<DirectoryName>",
icon: {},
desc: "",
credit: 0,
inputs: [],
outputs: [],
fields: [],
difficulty: "",
tags: []
};
class DirectoryName extends BaseNode {
constructor() {
super(config);
}
/**
* @override
* @inheritDoc
* @param {import('../../core/BaseNode/node.js').Inputs[]} inputs
* @param {import('../../core/BaseNode/node.js').Contents[]} contents
* @param {import('../../core/BaseNode/node.js').IServerData} serverData
*/
estimateUsage(inputs, contents, serverData) {
return this.getCredit();
}
/**
* @override
* @inheritDoc
* @param {import('../../core/BaseNode/node.js').Inputs[]} inputs
* @param {import('../../core/BaseNode/node.js').Contents[]} contents
* @param {import('../../core/BaseNode/node.js').IWebConsole} webconsole
* @param {import('../../core/BaseNode/node.js').IServerData} serverData
*/
async run(inputs, contents, webconsole, serverData) {
}
}
export default DirectoryNameYou can edit the boilerplate code to fit your node's requirements. Read the further sections for a detailed explanation on how to create a node.
Extend from BaseNode
Every node must extend the BaseNode class (imported from ../../core/BaseNode/node.js) and implement the run() method. This is essential for the node to function correctly within the Deforge system.
Check BaseNode/node.ts for all available methods.
import BaseNode from "../../core/BaseNode/node.js"; // Important
class my_node extends BaseNode {
constructor() {
super(config);
}
// ...
async run(inputs, contents, webconsole, serverData) {
}
}Node Configuration Format
Each node file must define a config object at the top, following the format below (see BaseNode/node.ts for reference):
const config = {
title: "Node Title",
category: "category_folder_name",
type: "node_class_name", // should match the folder name
icon: {
type: "svg/jpeg/png",
content: "base64 of the image"
},
desc: "Optional node description",
credits: 0, // Amount of deforge credits to be used by the node
inputs: [
{ name: "Name", type: "NodeType", desc: "" },
],
outputs: [
{ name: "Name", type: "NodeType", desc: "" },
],
fields: [
{ name: "fieldOnNode", type: "HTML input type", desc: "", value: "placeholder value" },
],
difficulty: "easy/medium/hard",
tags: ["tag1", "tag2"],
}This config object is pretty much the identity of the node and will be used for creating documentations, rendering the node in the editor, and validating inputs/outputs.
Accessing Inputs and Contents
Each node's run() method receives inputs and contents arrays as parameters. These represent the data passed to the node via a connection (inputs) or via the fields on the node itself (contents). To get a value, you should always prioritize inputs over contents. Here's how to access them:
// Utility method to get input or content value
getValue(inputs, contents, name, defaultValue = null) {
const input = inputs.find((i) => i.name === name);
if (input?.value !== undefined) return input.value;
const content = contents.find((c) => c.name === name);
if (content?.value !== undefined) return content.value;
return defaultValue;
}
// Example usage in run()
async run(inputs, contents, webconsole, serverData) {
const myInputValue = this.getValue(inputs, contents, "MyInputField", "default");
}Example from the google sheets node :
async run(inputs, contents, webconsole, serverData) {
const Title = this.getValue(inputs, contents, "Title", "New Sheet");
const CsvRaw = this.getValue(inputs, contents, "CSV Raw", "");
const CsvLink = this.getValue(inputs, contents, "CSV Link", "");
}Accessing other information
You can access some information passed on from the server via the serverData object. The object contains information and utility objects from the server.
The following objects are always available in the serverData parameter:
workflowId: The ID of the current workflow being executed.chatId: An id that represents a chat, its value depends on how the workflow is being executed (via chatbot, api, widget, etc).envList: A key-value pair list of env variables for the given workflow.socialList: A key-value pair list of the social accounts and access tokens for the connected accounts in the given workflow.chatId: A chat or user ID of the user executing the workflow. (Must be passed as a chatId query via the deployment url)redisUtil: An utility class that contains three methods: setKey, deleteKey and getKey that can be used to store data in redis. (Key format:deforge:subcontext:task. Example:deforge:twitter:cookies)refreshUtil: An utility to update refreshed oauth tokens in the database.s3Util: An utility class to manage files in the S3 storage used by Deforge.
The following objects are available based on certain conditions in the workflows:
tgPayload: If the workflow is triggered via Telegram Trigger, this object contains the Telegram payload data.slackPayload: If the workflow is triggered via Slack Trigger, this object contains the Slack payload data.widgetPayload: If the workflow is triggered via Widget Trigger, this object contains the Widget payload data.chatbotPayload: If the workflow is triggered via Chatbot Trigger, this object contains the Chatbot payload data.apiPayload: If the workflow is triggered via API Trigger, this object contains the API request data.email: If the workflow is triggered via Gmail Trigger, this object contains the Email address.
Details about serverData objects
Below you will find more details about a few selected serverData objects.
Returning Output
To return output from your node, you need to return an array of output objects from the run() method. Each output object should have a name (matching the output defined in the config) and a value.
return { "Output Name": outputValue };Example from the YouTube Upload node :
return { "Video Link": uploadedUrl };If your node can fail, return null.
Logging
Use the webconsole object passed to the run() method for logging. Logs created with webconsole will show up on the execution logs section of Deforge Editor. The webconsole object has three methods:
webconsole.success(...args)- Shows the logs in green.webconsole.info(...args)- Shows the logs in blue.webconsole.error(...args)- Shows the logs in red.
You can pass as many parameters as you want to these methods. Example:
webconsole.info("Starting download", url);
webconsole.success("Upload complete", resultUrl);
webconsole.error("Failed to process", error.message);Example Node Structure
import BaseNode from "../../core/BaseNode/node.js";
const config = { /* ...see above... */ };
class my_node extends BaseNode {
constructor() {
super(config);
}
async run(inputs, contents, webconsole, serverData) {
const Field = this.getValue(inputs, contents, "Field", "default");
webconsole.info("Processing field", Field);
// ... your logic ...
return { "Output": result };
}
}
export default my_node;Congrats
Now you are ready to create your very own nodes for Deforge
Conclusion
Creating custom nodes for Deforge allows you to extend the platform's capabilities and tailor it to your specific needs. By following this guide, you should be able to create nodes that integrate seamlessly with the Deforge ecosystem.
For more examples, see other nodes in the repository. Always follow this structure for compatibility with the Deforge system.
An application to test your own nodes visually will be available soon...
Last updated on