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,35 @@
{
"node": "n8n-nodes-base.bannerbear",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Marketing"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/credentials/bannerbear/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.bannerbear/"
}
],
"generic": [
{
"label": "Automate Designs with Bannerbear and n8n",
"icon": "🎨",
"url": "https://n8n.io/blog/automate-designs-with-bannerbear-and-n8n/"
},
{
"label": "Automating Conference Organization Processes with n8n",
"icon": "🙋‍♀️",
"url": "https://n8n.io/blog/automating-conference-organization-processes-with-n8n/"
},
{
"label": "Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin",
"icon": "🎖",
"url": "https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/"
}
]
}
}
@@ -0,0 +1,190 @@
import type {
IExecuteFunctions,
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import { bannerbearApiRequest, keysToSnakeCase } from './GenericFunctions';
import { imageFields, imageOperations } from './ImageDescription';
import { templateFields, templateOperations } from './TemplateDescription';
export class Bannerbear implements INodeType {
description: INodeTypeDescription = {
displayName: 'Bannerbear',
name: 'bannerbear',
// eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg
icon: 'file:bannerbear.png',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Bannerbear API',
defaults: {
name: 'Bannerbear',
},
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [
{
name: 'bannerbearApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Image',
value: 'image',
},
{
name: 'Template',
value: 'template',
},
],
default: 'image',
},
// IMAGE
...imageOperations,
...imageFields,
// TEMPLATE
...templateOperations,
...templateFields,
],
};
methods = {
loadOptions: {
// Get all the available templates to display them to user so that they can
// select them easily
async getTemplates(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const templates = await bannerbearApiRequest.call(this, 'GET', '/templates');
for (const template of templates) {
const templateName = template.name;
const templateId = template.uid;
returnData.push({
name: templateName,
value: templateId,
});
}
return returnData;
},
// Get all the available modifications to display them to user so that they can
// select them easily
async getModificationNames(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const templateId = this.getCurrentNodeParameter('templateId');
const returnData: INodePropertyOptions[] = [];
const { available_modifications } = await bannerbearApiRequest.call(
this,
'GET',
`/templates/${templateId}`,
);
for (const modification of available_modifications) {
const modificationName = modification.name;
const modificationId = modification.name;
returnData.push({
name: modificationName,
value: modificationId,
});
}
return returnData;
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const length = items.length;
let responseData;
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
for (let i = 0; i < length; i++) {
if (resource === 'image') {
//https://developers.bannerbear.com/#create-an-image
if (operation === 'create') {
const templateId = this.getNodeParameter('templateId', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const modifications = (this.getNodeParameter('modificationsUi', i) as IDataObject)
.modificationsValues as IDataObject;
const body: IDataObject = {
template: templateId,
};
if (additionalFields.webhookUrl) {
body.webhook_url = additionalFields.webhookUrl as string;
}
if (additionalFields.metadata) {
body.metadata = additionalFields.metadata as string;
}
if (modifications) {
body.modifications = keysToSnakeCase(modifications);
// delete all fields set to empty
for (const modification of body.modifications as IDataObject[]) {
for (const key of Object.keys(modification)) {
if (modification[key] === '') {
delete modification[key];
}
}
}
}
responseData = await bannerbearApiRequest.call(this, 'POST', '/images', body);
if (additionalFields.waitForImage && responseData.status !== 'completed') {
let maxTries = (additionalFields.waitForImageMaxTries as number) || 3;
const promise = async (uid: string) => {
let data: IDataObject = {};
return await new Promise((resolve, reject) => {
const timeout = setInterval(async () => {
data = await bannerbearApiRequest.call(this, 'GET', `/images/${uid}`);
if (data.status === 'completed') {
clearInterval(timeout);
resolve(data);
}
if (--maxTries === 0) {
clearInterval(timeout);
reject(new Error('Image did not finish processing after multiple tries.'));
}
}, 2000);
});
};
responseData = await promise(responseData.uid as string);
}
}
//https://developers.bannerbear.com/#get-a-specific-image
if (operation === 'get') {
const imageId = this.getNodeParameter('imageId', i) as string;
responseData = await bannerbearApiRequest.call(this, 'GET', `/images/${imageId}`);
}
}
if (resource === 'template') {
//https://developers.bannerbear.com/#get-a-specific-template
if (operation === 'get') {
const templateId = this.getNodeParameter('templateId', i) as string;
responseData = await bannerbearApiRequest.call(this, 'GET', `/templates/${templateId}`);
}
//https://developers.bannerbear.com/#list-templates
if (operation === 'getAll') {
responseData = await bannerbearApiRequest.call(this, 'GET', '/templates');
}
}
if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData as IDataObject[]);
} else {
returnData.push(responseData as IDataObject);
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}
@@ -0,0 +1,64 @@
import { snakeCase } from 'change-case';
import type {
IExecuteFunctions,
ILoadOptionsFunctions,
IDataObject,
IHookFunctions,
IWebhookFunctions,
JsonObject,
IHttpRequestMethods,
IRequestOptions,
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
export async function bannerbearApiRequest(
this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
resource: string,
body: any = {},
query: IDataObject = {},
uri?: string,
headers: IDataObject = {},
): Promise<any> {
const credentials = await this.getCredentials('bannerbearApi');
const options: IRequestOptions = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${credentials.apiKey}`,
},
method,
body,
qs: query,
uri: uri || `https://api.bannerbear.com/v2${resource}`,
json: true,
};
if (!Object.keys(body as IDataObject).length) {
delete options.form;
}
if (!Object.keys(query).length) {
delete options.qs;
}
options.headers = Object.assign({}, options.headers, headers);
try {
return await this.helpers.request(options);
} catch (error) {
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}
export function keysToSnakeCase(elements: IDataObject[] | IDataObject): IDataObject[] {
if (!Array.isArray(elements)) {
elements = [elements];
}
for (const element of elements) {
for (const key of Object.keys(element)) {
if (key !== snakeCase(key)) {
element[snakeCase(key)] = element[key];
delete element[key];
}
}
}
return elements;
}
@@ -0,0 +1,188 @@
import type { INodeProperties } from 'n8n-workflow';
export const imageOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['image'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create an image',
action: 'Create an image',
},
{
name: 'Get',
value: 'get',
description: 'Get an image',
action: 'Get an image',
},
],
default: 'create',
},
];
export const imageFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* image:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Template Name or ID',
name: 'templateId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getTemplates',
},
required: true,
default: '',
displayOptions: {
show: {
resource: ['image'],
operation: ['create'],
},
},
description:
'The template ID you want to use. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
resource: ['image'],
operation: ['create'],
},
},
default: {},
options: [
{
displayName: 'Metadata',
name: 'metadata',
type: 'string',
default: '',
description: 'Metadata that you need to store e.g. ID of a record in your DB',
},
{
displayName: 'Wait for Image',
name: 'waitForImage',
type: 'boolean',
default: false,
description:
'Whether to wait for the image to be proccesed before returning. If after three tries the images is not ready, an error will be thrown. Number of tries can be increased by setting "Wait Max Tries".',
},
{
displayName: 'Wait Max Tries',
name: 'waitForImageMaxTries',
type: 'number',
typeOptions: {
minValue: 1,
maxValue: 10,
},
displayOptions: {
show: {
waitForImage: [true],
},
},
default: 3,
description: 'How often it should check if the image is available before it fails',
},
{
displayName: 'Webhook URL',
name: 'webhookUrl',
type: 'string',
default: '',
description: 'A URL to POST the Image object to upon rendering completed',
},
],
},
{
displayName: 'Modifications',
name: 'modificationsUi',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
placeholder: 'Add Modification',
displayOptions: {
show: {
resource: ['image'],
operation: ['create'],
},
},
default: {},
options: [
{
displayName: 'Modification',
name: 'modificationsValues',
values: [
{
displayName: 'Name or ID',
name: 'name',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getModificationNames',
loadOptionsDependsOn: ['templateId'],
},
default: '',
description:
'The name of the item you want to change. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
{
displayName: 'Text',
name: 'text',
type: 'string',
default: '',
description: 'Replacement text you want to use',
},
{
displayName: 'Color',
name: 'color',
type: 'color',
default: '',
description: 'Color hex of object',
},
{
displayName: 'Background',
name: 'background',
type: 'color',
default: '',
description: 'Color hex of text background',
},
{
displayName: 'Image URL',
name: 'imageUrl',
type: 'string',
default: '',
description: 'Replacement image URL you want to use (must be publicly viewable)',
},
],
},
],
},
/* -------------------------------------------------------------------------- */
/* image:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Image ID',
name: 'imageId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['image'],
operation: ['get'],
},
},
description: 'Unique identifier for the image',
},
];
@@ -0,0 +1,50 @@
import type { INodeProperties } from 'n8n-workflow';
export const templateOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['template'],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get a template',
action: 'Get a template',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many templates',
action: 'Get many templates',
},
],
default: 'get',
},
];
export const templateFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* template:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Template ID',
name: 'templateId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['template'],
operation: ['get'],
},
},
description: 'Unique identifier for the template',
},
];
@@ -0,0 +1,62 @@
{
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"height": {
"type": "integer"
},
"modifications": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}
},
"pdf_url": {
"type": "null"
},
"pdf_url_compressed": {
"type": "null"
},
"render_pdf": {
"type": "boolean"
},
"self": {
"type": "string"
},
"status": {
"type": "string"
},
"template": {
"type": "string"
},
"template_name": {
"type": "string"
},
"template_version": {
"type": "null"
},
"transparent": {
"type": "boolean"
},
"uid": {
"type": "string"
},
"webhook_response_code": {
"type": "null"
},
"webhook_url": {
"type": "null"
},
"width": {
"type": "integer"
}
},
"version": 1
}
@@ -0,0 +1,65 @@
{
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"height": {
"type": "integer"
},
"metadata": {
"type": "null"
},
"modifications": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}
},
"pdf_url": {
"type": "null"
},
"pdf_url_compressed": {
"type": "null"
},
"render_pdf": {
"type": "boolean"
},
"self": {
"type": "string"
},
"status": {
"type": "string"
},
"template": {
"type": "string"
},
"template_name": {
"type": "string"
},
"template_version": {
"type": "null"
},
"transparent": {
"type": "boolean"
},
"uid": {
"type": "string"
},
"webhook_response_code": {
"type": "null"
},
"webhook_url": {
"type": "null"
},
"width": {
"type": "integer"
}
},
"version": 1
}
@@ -0,0 +1,56 @@
{
"type": "object",
"properties": {
"available_modifications": {
"type": "array",
"items": {
"type": "object",
"properties": {
"background": {
"type": "null"
},
"color": {
"type": "null"
},
"image_url": {
"type": "null"
},
"name": {
"type": "string"
},
"text": {
"type": "null"
}
}
}
},
"created_at": {
"type": "string"
},
"height": {
"type": "integer"
},
"metadata": {
"type": "null"
},
"name": {
"type": "string"
},
"preview_url": {
"type": "string"
},
"self": {
"type": "string"
},
"uid": {
"type": "string"
},
"updated_at": {
"type": "string"
},
"width": {
"type": "integer"
}
},
"version": 1
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB