@vwork-digital/n8n-nodes-fillout
Version:
N8N nodes for Fillout forms - includes both trigger and action nodes.
780 lines • 34.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Fillout = void 0;
const n8n_workflow_1 = require("n8n-workflow");
class Fillout {
constructor() {
this.description = {
displayName: 'Fillout',
name: 'fillout',
icon: 'file:fillout.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Interact with Fillout API',
defaults: {
name: 'Fillout',
},
inputs: ["main"],
outputs: ["main"],
credentials: [
{
name: 'filloutApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Form',
value: 'form',
},
{
name: 'Submission',
value: 'submission',
},
],
default: 'form',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
'form',
],
},
},
options: [
{
name: 'Get All',
value: 'getAll',
description: 'Get many forms',
action: 'Get many forms',
},
{
name: 'Get Metadata',
value: 'getMetadata',
description: 'Get form metadata and questions',
action: 'Get form metadata',
},
],
default: 'getAll',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
'submission',
],
},
},
options: [
{
name: 'Get All',
value: 'getAll',
description: 'Get many submissions',
action: 'Get many submissions',
},
{
name: 'Get',
value: 'get',
description: 'Get a submission (set to always include edit link)',
action: 'Get a submission',
},
{
name: 'Create',
value: 'create',
description: 'Create a submission',
action: 'Create a submission',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a submission',
action: 'Delete a submission',
},
],
default: 'getAll',
},
{
displayName: 'Form',
name: 'formId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getForms',
},
required: true,
default: '',
displayOptions: {
show: {
resource: [
'form',
],
operation: [
'getMetadata',
],
},
},
description: 'The form to get metadata for',
},
{
displayName: 'Form',
name: 'formId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getForms',
},
required: true,
default: '',
displayOptions: {
show: {
resource: [
'submission',
],
operation: [
'getAll',
'get',
'create',
'delete',
],
},
},
description: 'The form to work with',
},
{
displayName: 'Submission ID',
name: 'submissionId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'submission',
],
operation: [
'get',
'delete',
],
},
},
description: 'The submission to retrieve (set to always include edit link)',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 50,
description: 'Max number of results to return',
typeOptions: {
minValue: 1
},
displayOptions: {
show: {
resource: [
'submission',
],
operation: [
'getAll',
],
},
},
},
{
displayName: 'Additional Options',
name: 'additionalOptions',
type: 'collection',
placeholder: 'Add Option',
default: {},
displayOptions: {
show: {
resource: [
'submission',
],
operation: [
'getAll',
],
},
},
options: [
{
displayName: 'After Date',
name: 'afterDate',
type: 'dateTime',
default: '',
description: 'Filter submissions submitted after this date',
},
{
displayName: 'Before Date',
name: 'beforeDate',
type: 'dateTime',
default: '',
description: 'Filter submissions submitted before this date',
},
{
displayName: 'Include Edit Link',
name: 'includeEditLink',
type: 'boolean',
default: false,
description: 'Whether to include a link to edit the submission',
},
{
displayName: 'Search',
name: 'search',
type: 'string',
default: '',
description: 'Search text to filter submissions',
},
{
displayName: 'Sort',
name: 'sort',
type: 'options',
options: [
{
name: 'Ascending',
value: 'asc',
},
{
name: 'Descending',
value: 'desc',
},
],
default: 'asc',
description: 'Sort order of submissions',
},
{
displayName: 'Status',
name: 'status',
type: 'options',
options: [
{
name: 'Finished',
value: 'finished',
},
{
name: 'In Progress',
value: 'in_progress',
},
],
default: 'finished',
description: 'Status of the submissions to retrieve (etching in progress submissions available on business plan or higher).',
},
],
},
{
displayName: 'Questions',
name: 'questions',
placeholder: 'Add Question',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
displayOptions: {
show: {
resource: [
'submission',
],
operation: [
'create',
],
},
},
default: {},
options: [
{
name: 'questionValues',
displayName: 'Question',
values: [
{
displayName: 'Question ID',
name: 'id',
type: 'string',
default: '',
description: 'ID of the question',
required: true,
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Value of the answer',
required: true,
},
],
},
],
},
{
displayName: 'Submission Time',
name: 'submissionTime',
type: 'dateTime',
default: '',
displayOptions: {
show: {
resource: [
'submission',
],
operation: [
'create',
],
},
},
description: 'The time when the submission was created',
},
{
displayName: 'Last Updated At',
name: 'lastUpdatedAt',
type: 'dateTime',
default: '',
displayOptions: {
show: {
resource: [
'submission',
],
operation: [
'create',
],
},
},
description: 'The time when the submission was last updated',
},
{
displayName: 'URL Parameters',
name: 'urlParameters',
placeholder: 'Add URL Parameter',
type: 'json',
displayOptions: {
show: {
resource: [
'submission',
],
operation: [
'create',
],
},
},
default: '',
description: 'URL parameters in JSON format. Must include id, name, and value fields. Example: [{"id":"email","name":"email","value":"example@example.com"}]',
},
{
displayName: 'Scheduling',
name: 'scheduling',
placeholder: 'Add Scheduling Details',
type: 'json',
displayOptions: {
show: {
resource: [
'submission',
],
operation: [
'create',
],
},
},
default: '',
description: 'Scheduling data in JSON format. Must include id and value fields. Example: [{"id":"nLJtxBJgPA","value":{"fullName":"John Smith","email":"john@smith.com","eventStartTime":"2024-05-20T09:00:00.000Z"}}]',
},
{
displayName: 'Payments',
name: 'payments',
placeholder: 'Add Payment Details',
type: 'json',
displayOptions: {
show: {
resource: [
'submission',
],
operation: [
'create',
],
},
},
default: '',
description: 'Payment data in JSON format. Must include id and value fields. Example: [{"id":"cLJtxCKgdL","value":{"paymentId":"pi_3PRF2cFMP2ckdpfG0s0ZdJqf"}}]',
},
{
displayName: 'Login',
name: 'login',
placeholder: 'Add Login Details',
type: 'json',
displayOptions: {
show: {
resource: [
'submission',
],
operation: [
'create',
],
},
},
default: '',
description: 'Login data in JSON format. Must contain email field. Example: {"email":"verified@email.com"}',
},
],
};
this.methods = {
loadOptions: {
async getForms() {
const credentials = await this.getCredentials('filloutApi');
try {
const response = await this.helpers.request({
method: 'GET',
url: `${credentials.apiUrl}/v1/api/forms`,
headers: {
Authorization: `Bearer ${credentials.apiKey}`,
},
json: true,
});
const forms = response;
return forms.map(form => ({
name: form.name,
value: form.formId,
}));
}
catch (error) {
console.error('[Fillout] Error loading forms:', error);
throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
}
},
async getSubmissions() {
const credentials = await this.getCredentials('filloutApi');
const formId = this.getCurrentNodeParameter('formId');
if (!formId) {
return [{ name: 'Please select a form first', value: '' }];
}
try {
const response = await this.helpers.request({
method: 'GET',
url: `${credentials.apiUrl}/v1/api/forms/${formId}/submissions`,
qs: {
sort: 'desc',
limit: 50,
},
headers: {
Authorization: `Bearer ${credentials.apiKey}`,
},
json: true,
});
const data = response;
if (!data.responses || !data.responses.length) {
return [{ name: 'No submissions found', value: '' }];
}
return data.responses.map(submission => ({
name: `Submission from ${new Date(submission.submissionTime).toLocaleString()}`,
value: submission.submissionId,
}));
}
catch (error) {
console.error('[Fillout] Error loading submissions:', error);
throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
}
},
},
};
}
async execute() {
const returnData = [];
const credentials = await this.getCredentials('filloutApi');
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
if (resource === 'form') {
if (operation === 'getAll') {
try {
const response = await this.helpers.request({
method: 'GET',
url: `${credentials.apiUrl}/v1/api/forms`,
headers: {
Authorization: `Bearer ${credentials.apiKey}`,
},
json: true,
});
returnData.push({ json: { forms: response } });
}
catch (error) {
console.error('[Fillout] Error getting forms:', error);
throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
}
}
else if (operation === 'getMetadata') {
const formId = this.getNodeParameter('formId', 0);
try {
const response = await this.helpers.request({
method: 'GET',
url: `${credentials.apiUrl}/v1/api/forms/${formId}`,
headers: {
Authorization: `Bearer ${credentials.apiKey}`,
},
json: true,
});
returnData.push({ json: response });
}
catch (error) {
console.error('[Fillout] Error getting form metadata:', error);
throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
}
}
}
else if (resource === 'submission') {
if (operation === 'getAll') {
const formId = this.getNodeParameter('formId', 0);
const limit = this.getNodeParameter('limit', 0);
const additionalOptions = this.getNodeParameter('additionalOptions', 0, {});
try {
const qs = { limit };
if (additionalOptions.afterDate) {
qs.afterDate = additionalOptions.afterDate;
}
if (additionalOptions.beforeDate) {
qs.beforeDate = additionalOptions.beforeDate;
}
if (additionalOptions.status) {
qs.status = additionalOptions.status;
}
if (additionalOptions.includeEditLink) {
qs.includeEditLink = additionalOptions.includeEditLink;
}
if (additionalOptions.sort) {
qs.sort = additionalOptions.sort;
}
if (additionalOptions.search) {
qs.search = additionalOptions.search;
}
const response = await this.helpers.request({
method: 'GET',
url: `${credentials.apiUrl}/v1/api/forms/${formId}/submissions`,
qs,
headers: {
Authorization: `Bearer ${credentials.apiKey}`,
},
json: true,
});
returnData.push({ json: response });
}
catch (error) {
console.error('[Fillout] Error getting submissions:', error);
throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
}
}
else if (operation === 'get') {
const formId = this.getNodeParameter('formId', 0);
const submissionId = this.getNodeParameter('submissionId', 0);
try {
const response = await this.helpers.request({
method: 'GET',
url: `${credentials.apiUrl}/v1/api/forms/${formId}/submissions/${submissionId}?includeEditLink=true`,
headers: {
Authorization: `Bearer ${credentials.apiKey}`,
},
json: true,
});
returnData.push({ json: response });
}
catch (error) {
console.error('[Fillout] Error getting submission:', error);
throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
}
}
else if (operation === 'create') {
const formId = this.getNodeParameter('formId', 0);
const questionValues = this.getNodeParameter('questions.questionValues', 0, []);
const submissionTime = this.getNodeParameter('submissionTime', 0, '');
const lastUpdatedAt = this.getNodeParameter('lastUpdatedAt', 0, '');
let urlParameters = [];
let scheduling = [];
let payments = [];
let login = null;
try {
const urlParametersJson = this.getNodeParameter('urlParameters', 0, '');
if (urlParametersJson) {
urlParameters = JSON.parse(urlParametersJson);
console.log('[Fillout] URL Parameters:', urlParameters);
if (!Array.isArray(urlParameters)) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: 'URL Parameters must be an array',
});
}
for (const param of urlParameters) {
if (!param.id || !param.name || !param.value) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: 'Each URL Parameter must have id, name, and value fields',
});
}
}
}
}
catch (error) {
console.error('[Fillout] Error parsing URL Parameters JSON:', error);
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: 'Invalid URL Parameters JSON format',
description: 'Please provide a valid JSON array with each item containing id, name, and value fields. Example: [{"id":"email","name":"email","value":"example@example.com"}]',
});
}
try {
const schedulingJson = this.getNodeParameter('scheduling', 0, '');
if (schedulingJson) {
scheduling = JSON.parse(schedulingJson);
console.log('[Fillout] Scheduling:', scheduling);
if (!Array.isArray(scheduling)) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: 'Scheduling must be an array',
});
}
for (const item of scheduling) {
if (!item.id || !item.value || typeof item.value !== 'object') {
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: 'Each scheduling item must have id and value (object) fields',
});
}
}
}
}
catch (error) {
console.error('[Fillout] Error parsing Scheduling JSON:', error);
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: 'Invalid Scheduling JSON format',
description: 'Please provide a valid JSON array with each item containing id and value fields. Example: [{"id":"nLJtxBJgPA","value":{"fullName":"John Smith","email":"john@smith.com"}}]',
});
}
try {
const paymentsJson = this.getNodeParameter('payments', 0, '');
if (paymentsJson) {
payments = JSON.parse(paymentsJson);
console.log('[Fillout] Payments:', payments);
if (!Array.isArray(payments)) {
throw new Error('Payments must be an array');
}
for (const item of payments) {
if (!item.id || !item.value || typeof item.value !== 'object') {
throw new Error('Each payment item must have id and value (object) fields');
}
if (!item.value.paymentId) {
throw new Error('Payment value must contain paymentId field');
}
}
}
}
catch (error) {
console.error('[Fillout] Error parsing Payments JSON:', error);
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: 'Invalid Payments JSON format',
description: 'Please provide a valid JSON array with each item containing id and value fields. The value object must contain paymentId. Example: [{"id":"cLJtxCKgdL","value":{"paymentId":"pi_3PRF2cFMP2ckdpfG0s0ZdJqf"}}]',
});
}
try {
const loginJson = this.getNodeParameter('login', 0, '');
if (loginJson) {
login = JSON.parse(loginJson);
console.log('[Fillout] Login:', login);
if (typeof login !== 'object' || login === null) {
throw new Error('Login must be an object');
}
if (!login.email || typeof login.email !== 'string') {
throw new Error('Login object must contain email field as a string');
}
}
}
catch (error) {
console.error('[Fillout] Error parsing Login JSON:', error);
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: 'Invalid Login JSON format',
description: 'Please provide a valid JSON object with an email field. Example: {"email":"verified@email.com"}',
});
}
try {
const questions = questionValues.map(q => ({
id: q.id,
value: q.value,
}));
const submissionObj = {
questions,
};
if (submissionTime) {
submissionObj.submissionTime = submissionTime;
}
else {
submissionObj.submissionTime = new Date().toISOString();
}
if (lastUpdatedAt) {
submissionObj.lastUpdatedAt = lastUpdatedAt;
}
if (urlParameters.length > 0) {
submissionObj.urlParameters = urlParameters;
}
if (scheduling.length > 0) {
submissionObj.scheduling = scheduling;
}
if (payments.length > 0) {
submissionObj.payments = payments;
}
if (login) {
submissionObj.login = login;
}
const body = {
submissions: [submissionObj],
};
console.log('[Fillout] Creating submission with body:', JSON.stringify(body, null, 2));
const response = await this.helpers.request({
method: 'POST',
url: `${credentials.apiUrl}/v1/api/forms/${formId}/submissions`,
body,
headers: {
Authorization: `Bearer ${credentials.apiKey}`,
'Content-Type': 'application/json',
},
json: true,
});
returnData.push({ json: response });
}
catch (error) {
console.error('[Fillout] Error creating submission:', error);
if (error.response) {
console.error('[Fillout] Error response data:', JSON.stringify(error.response.data));
console.error('[Fillout] Error response status:', error.response.status);
console.error('[Fillout] Error response headers:', JSON.stringify(error.response.headers));
}
throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
}
}
else if (operation === 'delete') {
const formId = this.getNodeParameter('formId', 0);
const submissionId = this.getNodeParameter('submissionId', 0);
try {
await this.helpers.request({
method: 'DELETE',
url: `${credentials.apiUrl}/v1/api/forms/${formId}/submissions/${submissionId}`,
headers: {
Authorization: `Bearer ${credentials.apiKey}`,
},
});
returnData.push({
json: {
success: true,
message: `Submission ${submissionId} deleted successfully`,
},
});
}
catch (error) {
console.error('[Fillout] Error deleting submission:', error);
throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
}
}
}
return [returnData];
}
}
exports.Fillout = Fillout;
//# sourceMappingURL=Fillout.node.js.map