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,110 @@
import type { INodeProperties } from 'n8n-workflow';
export const aliasOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['alias'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create an alias',
action: 'Create an alias',
},
],
default: 'create',
},
];
export const aliasFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* alias:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Alias',
name: 'alias',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['alias'],
operation: ['create'],
},
},
default: '',
description: 'The name of the alias',
},
{
displayName: 'Distinct ID',
name: 'distinctId',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['alias'],
operation: ['create'],
},
},
default: '',
description: "The user's distinct ID",
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
resource: ['alias'],
operation: ['create'],
},
},
default: {},
options: [
{
displayName: 'Context',
name: 'contextUi',
type: 'fixedCollection',
placeholder: 'Add Property',
default: {},
typeOptions: {
multipleValues: true,
},
options: [
{
displayName: 'Context',
name: 'contextValues',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
},
],
},
],
},
{
displayName: 'Timestamp',
name: 'timestamp',
type: 'dateTime',
default: '',
description: "If not set, it'll automatically be set to the current time",
},
],
},
];
@@ -0,0 +1,110 @@
import type { INodeProperties } from 'n8n-workflow';
export const eventOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['event'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create an event',
action: 'Create an event',
},
],
default: 'create',
},
];
export const eventFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* event:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Event',
name: 'eventName',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['event'],
operation: ['create'],
},
},
default: '',
description: 'The name of the event',
},
{
displayName: 'Distinct ID',
name: 'distinctId',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['event'],
operation: ['create'],
},
},
default: '',
description: "The user's distinct ID",
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
resource: ['event'],
operation: ['create'],
},
},
default: {},
options: [
{
displayName: 'Properties',
name: 'propertiesUi',
type: 'fixedCollection',
placeholder: 'Add Property',
default: {},
typeOptions: {
multipleValues: true,
},
options: [
{
displayName: 'Property',
name: 'propertyValues',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
},
],
},
],
},
{
displayName: 'Timestamp',
name: 'timestamp',
type: 'dateTime',
default: '',
description: "If not set, it'll automatically be set to the current time",
},
],
},
];
@@ -0,0 +1,75 @@
import type {
IDataObject,
IExecuteFunctions,
IHttpRequestMethods,
ILoadOptionsFunctions,
IRequestOptions,
JsonObject,
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
export async function posthogApiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
path: string,
body: any = {},
qs: IDataObject = {},
_option = {},
): Promise<any> {
const credentials = await this.getCredentials('postHogApi');
const base = credentials.url as string;
body.api_key = credentials.apiKey as string;
const options: IRequestOptions = {
headers: {
'Content-Type': 'application/json',
},
method,
body,
qs,
url: `${base}${path}`,
json: true,
};
try {
if (Object.keys(body as IDataObject).length === 0) {
delete options.body;
}
return await this.helpers.request(options);
} catch (error) {
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}
export interface IEvent {
event: string;
properties: { [key: string]: any };
}
export interface IAlias {
type: string;
event: string;
properties: { [key: string]: any };
context: { [key: string]: any };
}
export interface ITrack {
type: string;
event: string;
name: string;
messageId?: string;
distinct_id: string;
category?: string;
properties: { [key: string]: any };
context: { [key: string]: any };
}
export interface IIdentity {
event: string;
messageId?: string;
distinct_id: string;
properties: { [key: string]: any };
}
@@ -0,0 +1,101 @@
import type { INodeProperties } from 'n8n-workflow';
export const identityOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['identity'],
},
},
options: [
{
name: 'Create',
value: 'create',
action: 'Create an identity',
},
],
default: 'create',
},
];
export const identityFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* identity:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Distinct ID',
name: 'distinctId',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['identity'],
operation: ['create'],
},
},
default: '',
description: "The identity's distinct ID",
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
resource: ['identity'],
operation: ['create'],
},
},
default: {},
options: [
{
displayName: 'Properties',
name: 'propertiesUi',
type: 'fixedCollection',
placeholder: 'Add Property',
default: {},
typeOptions: {
multipleValues: true,
},
options: [
{
displayName: 'Property',
name: 'propertyValues',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
},
],
},
],
},
{
displayName: 'Message ID',
name: 'messageId',
type: 'string',
default: '',
},
{
displayName: 'Timestamp',
name: 'timestamp',
type: 'dateTime',
default: '',
description: "If not set, it'll automatically be set to the current time",
},
],
},
];
@@ -0,0 +1,18 @@
{
"node": "n8n-nodes-base.postHog",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Analytics", "Development"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/credentials/posthog/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.posthog/"
}
]
}
}
@@ -0,0 +1,286 @@
import moment from 'moment-timezone';
import type {
IExecuteFunctions,
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import { aliasFields, aliasOperations } from './AliasDescription';
import { eventFields, eventOperations } from './EventDescription';
import type { IAlias, IEvent, IIdentity, ITrack } from './GenericFunctions';
import { posthogApiRequest } from './GenericFunctions';
import { identityFields, identityOperations } from './IdentityDescription';
import { trackFields, trackOperations } from './TrackDescription';
export class PostHog implements INodeType {
description: INodeTypeDescription = {
displayName: 'PostHog',
name: 'postHog',
icon: 'file:postHog.svg',
group: ['input'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume PostHog API',
defaults: {
name: 'PostHog',
},
usableAsTool: true,
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [
{
name: 'postHogApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Alias',
value: 'alias',
},
{
name: 'Event',
value: 'event',
},
{
name: 'Identity',
value: 'identity',
},
{
name: 'Track',
value: 'track',
},
],
default: 'event',
},
...aliasOperations,
...aliasFields,
...eventOperations,
...eventFields,
...identityOperations,
...identityFields,
...trackOperations,
...trackFields,
],
};
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);
if (resource === 'alias') {
if (operation === 'create') {
for (let i = 0; i < length; i++) {
try {
const distinctId = this.getNodeParameter('distinctId', i) as string;
const alias = this.getNodeParameter('alias', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const context =
((additionalFields.contextUi as IDataObject)?.contextValues as IDataObject[]) || [];
const event: IAlias = {
type: 'alias',
event: '$create_alias',
context: context.reduce(
(obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }),
{},
),
properties: {
distinct_id: distinctId,
alias,
},
};
Object.assign(event, additionalFields);
if (additionalFields.timestamp) {
additionalFields.timestamp = moment(
additionalFields.timestamp as string,
).toISOString();
}
responseData = await posthogApiRequest.call(this, 'POST', '/batch', event);
returnData.push(responseData as IDataObject);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.message });
continue;
}
throw error;
}
}
}
}
if (resource === 'event') {
if (operation === 'create') {
try {
const events: IEvent[] = [];
for (let i = 0; i < length; i++) {
const eventName = this.getNodeParameter('eventName', i) as string;
const distinctId = this.getNodeParameter('distinctId', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const properties =
((additionalFields.propertiesUi as IDataObject)?.propertyValues as IDataObject[]) ||
[];
const event: IEvent = {
event: eventName,
properties: properties.reduce(
(obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }),
{},
),
};
event.properties.distinct_id = distinctId;
Object.assign(event, additionalFields);
if (additionalFields.timestamp) {
additionalFields.timestamp = moment(
additionalFields.timestamp as string,
).toISOString();
}
//@ts-ignore
delete event.propertiesUi;
events.push(event);
}
responseData = await posthogApiRequest.call(this, 'POST', '/capture', { batch: events });
returnData.push(responseData as IDataObject);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.message });
} else {
throw error;
}
}
}
}
if (resource === 'identity') {
if (operation === 'create') {
for (let i = 0; i < length; i++) {
try {
const distinctId = this.getNodeParameter('distinctId', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const properties =
((additionalFields.propertiesUi as IDataObject)?.propertyValues as IDataObject[]) ||
[];
const event: IIdentity = {
event: '$identify',
properties: properties.reduce(
(obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }),
{},
),
distinct_id: distinctId,
};
Object.assign(event, additionalFields);
if (additionalFields.timestamp) {
additionalFields.timestamp = moment(
additionalFields.timestamp as string,
).toISOString();
}
//@ts-ignore
delete event.propertiesUi;
responseData = await posthogApiRequest.call(this, 'POST', '/batch', event);
returnData.push(responseData as IDataObject);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.message });
continue;
}
throw error;
}
}
}
}
if (resource === 'track') {
if (operation === 'page' || operation === 'screen') {
for (let i = 0; i < length; i++) {
try {
const distinctId = this.getNodeParameter('distinctId', i) as string;
const name = this.getNodeParameter('name', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const context =
((additionalFields.contextUi as IDataObject)?.contextValues as IDataObject[]) || [];
const properties =
((additionalFields.propertiesUi as IDataObject)?.propertyValues as IDataObject[]) ||
[];
const event: ITrack = {
name,
type: operation,
event: `$${operation}`,
context: context.reduce(
(obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }),
{},
),
distinct_id: distinctId,
properties: properties.reduce(
(obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }),
{},
),
};
Object.assign(event, additionalFields);
if (additionalFields.timestamp) {
additionalFields.timestamp = moment(
additionalFields.timestamp as string,
).toISOString();
}
//@ts-ignore
delete event.propertiesUi;
responseData = await posthogApiRequest.call(this, 'POST', '/batch', event);
returnData.push(responseData as IDataObject);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.message });
continue;
}
throw error;
}
}
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}
@@ -0,0 +1,157 @@
import type { INodeProperties } from 'n8n-workflow';
export const trackOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['track'],
},
},
options: [
{
name: 'Page',
value: 'page',
description: 'Track a page',
action: 'Track a page',
},
{
name: 'Screen',
value: 'screen',
description: 'Track a screen',
action: 'Track a screen',
},
],
default: 'page',
},
];
export const trackFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* track:page */
/* -------------------------------------------------------------------------- */
{
displayName: 'Name',
name: 'name',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['track'],
operation: ['page', 'screen'],
},
},
default: '',
},
{
displayName: 'Distinct ID',
name: 'distinctId',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['track'],
operation: ['page', 'screen'],
},
},
default: '',
description: "The user's distinct ID",
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
resource: ['track'],
operation: ['page', 'screen'],
},
},
default: {},
options: [
{
displayName: 'Category',
name: 'category',
type: 'string',
default: '',
},
{
displayName: 'Context',
name: 'contextUi',
type: 'fixedCollection',
placeholder: 'Add Property',
default: {},
typeOptions: {
multipleValues: true,
},
options: [
{
displayName: 'Context',
name: 'contextValues',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
},
],
},
],
},
{
displayName: 'Message ID',
name: 'messageId',
type: 'string',
default: '',
},
{
displayName: 'Properties',
name: 'propertiesUi',
type: 'fixedCollection',
placeholder: 'Add Property',
default: {},
typeOptions: {
multipleValues: true,
},
options: [
{
displayName: 'Property',
name: 'propertyValues',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
},
],
},
],
},
{
displayName: 'Timestamp',
name: 'timestamp',
type: 'dateTime',
default: '',
description: "If not set, it'll automatically be set to the current time",
},
],
},
];
@@ -0,0 +1,9 @@
{
"type": "object",
"properties": {
"status": {
"type": "string"
}
},
"version": 2
}
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" fill="none"><path fill="#fff" d="M0 0h300v300H0z"/><path fill="#F9BD2B" d="m33 179.379 39.004 38.951H33zm0-9.738 48.755 48.689h39.004L33 130.689zm0-48.69 97.51 97.379h39.003L33 82zm48.755 0 97.509 97.379v-38.951L81.754 82zM130.51 82v38.951l48.754 48.69v-38.952z"/><path fill="#000" d="M267.023 202.75a37.7 37.7 0 0 1-26.632-11.016l-54.231-54.158v80.754h80.863z"/><path fill="#fff" d="M209.563 202.75c4.308 0 7.8-3.488 7.8-7.791 0-4.302-3.492-7.79-7.8-7.79-4.309 0-7.801 3.488-7.801 7.79 0 4.303 3.492 7.791 7.801 7.791"/><path fill="#1D4AFF" d="M33 218.33h39.004L33 179.379zm48.755-87.641L33 82v38.951l48.755 48.69zm-48.755 0v38.952l48.755 48.689v-38.951z"/><path fill="#F54E00" d="M130.51 130.689 81.755 82v38.951l48.755 48.69zM81.755 218.33h39.004l-39.004-38.951zm0-87.641v38.952l48.755 48.689v-38.951z"/></svg>

After

Width:  |  Height:  |  Size: 878 B