n8n-nodes-formbase 0.5.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # n8n-nodes-formbase
2
2
 
3
- Community node for [n8n](https://n8n.io) that resumes workflows when a customer completes a [formbase](https://formbase.so) request or submits a form. formbase collects and verifies information from customers for workflows and AI agents: a workflow creates a request, the customer completes a branded form without an account, and the verified answers arrive in n8n keyed by stable field keys.
3
+ Community nodes for [n8n](https://n8n.io) that create [formbase](https://formbase.so) requests, pause a workflow until a customer completes one, and start workflows when a request is completed, expires or is canceled, or when a form is submitted. formbase collects and verifies information from customers for workflows and AI agents: a workflow creates a request, the recipient completes a branded form without an account, and the verified answers arrive in n8n keyed by stable field keys.
4
4
 
5
5
  ## Features
6
6
 
7
- - Trigger on completed submissions, including submissions that answer a request. `data.request` carries the request ID and the caller's `externalId` and `metadata`, so the workflow that created the request can resume.
7
+ - **formbase** node: create a request for a form and a recipient, with prefilled and read-only fields, context, reminders, expiry and delivery by email; get, cancel, remind, list requests; replay a request's callback.
8
+ - Pause a workflow until the recipient answers: **Wait for the Outcome** points the request's callback at n8n's Wait node, so the workflow resumes with the completed, expired or canceled request as its input.
9
+ - **formbase Trigger** node: start a workflow when a request is completed, expires or is canceled, or when a respondent submits a form through its public link. `data.request` carries the request ID and the caller's `externalId` and `metadata`, so the workflow that created the request can pick up where it left off.
8
10
  - Trigger on abandoned submissions after a selected 12-hour, 1-day, 3-day, or 1-week idle window.
9
11
  - Load forms dynamically from the workspace the credential is scoped to, across every page.
10
12
  - Register and remove formbase webhook subscriptions with the n8n workflow lifecycle.
@@ -34,10 +36,50 @@ n8n registers its exact callback URL with formbase automatically through OAuth D
34
36
 
35
37
  Self-hosted n8n must use configured HTTPS public URL for OAuth callback. Loopback HTTP is supported for local development.
36
38
 
37
- ## Use trigger
39
+ ## Use the formbase node
40
+
41
+ Add **formbase** to a workflow, select the **Request** resource and an operation. Every operation returns the formbase response as one item per request, with `pairedItem` set, and honours **Continue on Fail**.
42
+
43
+ | Operation | formbase method | What it does |
44
+ | ------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
45
+ | **Create** | `requests.create` | Creates a request for a published form. Returns the request summary, including the request link in `url`. |
46
+ | **Get** | `requests.get` | Reads one request: status, outcome, recipient, `answers` and `display` once it is completed. |
47
+ | **Get Many** | `requests.list` | Lists the requests of a form or of the whole workspace, newest first, with status, outcome, external ID and test filters. **Return All** follows the cursor across every page; otherwise **Limit** caps it. |
48
+ | **Cancel** | `requests.cancel` | Cancels a pending request, with an optional reason the recipient sees. |
49
+ | **Remind** | `requests.remind` | Sends the recipient a reminder email now. |
50
+ | **Replay Callback** | `requests.replayCallback` | Delivers the callback of a completed, expired or canceled request again, for example after n8n was down. |
51
+
52
+ ### Create a request
53
+
54
+ 1. Pick the **Form**. The picker lists the published and unpublished forms of the credential's workspace; a request needs a published form.
55
+ 2. Enter the **Recipient Email** when formbase should email the link or send reminders; leave it empty for a request you hand out yourself.
56
+ 3. Under **Prefill**, add one entry per field key and value. The key picker loads the form's prefillable fields; turn on **Parse as JSON** for a value that is not plain text, such as a repeating group's rows or a number.
57
+ 4. Under **Context**, add values for the form's context fields: they are stored with the request and returned with the answers, but the recipient never sees them.
58
+ 5. Under **Read-Only Fields**, pick the prefilled fields the recipient may see but not change.
59
+ 6. In **Additional Fields**, set **Delivery** to `Email` to have formbase send the link, **Reminders** such as `2d, 5d` (leave the field empty to send none; remove it to inherit the form's schedule), **Expires At**, **Language**, **Recipient Name**, **Metadata** (a JSON object handed back with every event), **Test Mode**, or a **Callback URL** of your own.
60
+ 7. Set **External ID** to your own identifier, for example `{{ $execution.id }}`. The node also sends it as the request's `idempotencyKey`, so a retried execution gets the same request back (`deduplicated: true`) instead of creating a second one.
61
+
62
+ Every request also carries `formId`, `status`, `url`, `externalId`, `isTest`, `deliveryStatus`, `hasCallback`, `remindersSent`, `expiresAt` and `createdAt` in its summary.
63
+
64
+ ### Wait for the outcome
65
+
66
+ A request is answered minutes or days later. To pause the workflow until then:
67
+
68
+ 1. On the **Create** operation, turn on **Wait for the Outcome**. The node sets the request's callback URL to `{{ $execution.resumeUrl }}`, the URL n8n's Wait node listens on for this execution. Do not set **Callback URL** at the same time; the node refuses the combination.
69
+ 2. Add a **Wait** node right after it, with **Resume** set to **On Webhook Call** and the HTTP method left at `POST`. The execution pauses here.
70
+ 3. When the request is completed, expires or is canceled, formbase posts the event envelope to that URL and the execution resumes. The Wait node emits the delivery as one item with the envelope under `$json.body`.
71
+ 4. Branch on `{{ $json.body.type }}` (`request.completed`, `request.expired` or `request.canceled`) and read the answers from `{{ $json.body.data.answers.<field_key> }}`, the outcome from `{{ $json.body.data.request.outcome }}`, and the request ID from `{{ $json.body.data.request.id }}`.
72
+
73
+ [`examples/formbase-request-wait.json`](examples/formbase-request-wait.json) shows the whole pattern: formbase **Create** with **Wait for the Outcome** → **Wait** → **Switch** on the event type → **Set** reading the answers.
74
+
75
+ A resume URL only exists once the execution runs, so the test run of a Create node in the editor waits for a real answer just like a production run. Give the request an **Expires At** or a **Reminders** schedule so a forgotten request does not hold the execution open forever; an expired request resumes the workflow with `request.expired`. If n8n was unreachable when the callback fired, run **Replay Callback** for the request, or read it with **Get**: the resume URL of a finished execution is gone, so a replay only helps while the execution is still waiting.
76
+
77
+ The Wait node cannot check the `X-formbase-Signature` header that the callback carries. The resume URL is unguessable, which is what n8n relies on for every Wait node; if that is not enough for a workflow, use the trigger node instead, which verifies every delivery.
78
+
79
+ ## Use the trigger
38
80
 
39
81
  1. Add **formbase Trigger** to a workflow.
40
- 2. Select form and event. For an abandoned-submission event, select how long the response must remain unchanged.
82
+ 2. Select form and event: a request that is completed, expires or is canceled, or a public-link submission that is created, updated or abandoned. For an abandoned-submission event, select how long the response must remain unchanged.
41
83
  3. For a test execution, select **Listen for Test Event**, then submit the selected form.
42
84
  4. Activate the workflow. n8n registers its production webhook with formbase and removes it when the workflow is deactivated or deleted.
43
85
 
@@ -47,9 +89,14 @@ n8n generates a separate 256-bit signing secret for each registration. Incoming
47
89
 
48
90
  Abandoned-submission timing is enforced by formbase, not n8n. formbase checks incomplete responses hourly and calls the registered n8n webhook after the selected idle window, so delivery can occur up to about one hour after the threshold.
49
91
 
50
- ## Example workflow
92
+ One channel, one event: **Public Link Submission Created** runs for public-link submissions only, and a completed request runs **Request Completed** alone, never Public Link Submission Created. A workflow that wants every answer, whichever channel produced it, uses one trigger node on each event.
93
+
94
+ **Public Link Submission Created** runs only when a respondent first submits (`submission.completed`). An edit after submit runs **Public Link Submission Updated** (`submission.updated`) instead, and needs the form to allow editing after submit. Requests never produce an update event.
95
+
96
+ ## Example workflows
51
97
 
52
- Import [`examples/formbase-submission.json`](examples/formbase-submission.json), connect formbase credential, select form, then activate workflow. Example maps event ID, event type, submission ID, respondent email, and form name into stable output fields.
98
+ - [`examples/formbase-request-wait.json`](examples/formbase-request-wait.json): a formbase **Create** node with **Wait for the Outcome**, a **Wait** node, a **Switch** on `request.completed` / `request.expired` / `request.canceled`, and a **Set** node that reads the request ID, outcome and an answer. Connect the credential, pick a form with a `company_name` field (or change the prefill and the Set node), and run it.
99
+ - [`examples/formbase-submission.json`](examples/formbase-submission.json): a **formbase Trigger** that maps event ID, event type, submission ID, respondent email, and form name into stable output fields. Connect the credential, select a form, then activate the workflow.
53
100
 
54
101
  ## Output
55
102
 
@@ -81,17 +128,55 @@ Read a value with `{{ $json.data.answers.recommend }}`; the field keys come from
81
128
 
82
129
  The node passes the envelope through unchanged — it does not flatten answers into the top level of the item. Nothing is dropped, every key stays where the formbase contract puts it, and a field key can never collide with an envelope key such as `type` or `test`. Map the handful of values a workflow needs with a Set node, as the example workflow does.
83
130
 
84
- A submission that answered a request also carries `data.request` (`id`, and the caller's `externalId` and `metadata` when they were supplied). A response saved to PDF carries `data.submission.pdfUrl`; it is `null` when no PDF is kept. `test` is `true` for a test delivery, so a workflow can branch on it.
131
+ A submission event never carries `data.request`: a submission that answered a request arrives as a `request.completed` event instead. A response saved to PDF carries `data.submission.pdfUrl`; it is `null` when no PDF is kept. `test` is `true` for a test delivery, so a workflow can branch on it.
132
+
133
+ A request event carries the request itself in `data.request`: `id`, `status`, `outcome` (`approve`, `changes` or `decline` when the form has a decision), `externalId`, `metadata`, `context`, `recipient`, `language`, `createdAt` and `completedAt`, `expiredAt` or `canceledAt` (with `cancelReason`). A `request.completed` event also carries `form`, `submission`, `answers` and `display` exactly like a submission event; an expired or canceled request has no answers.
134
+
135
+ ```json
136
+ {
137
+ "id": "evt_req123",
138
+ "type": "request.completed",
139
+ "createdAt": "2026-09-22T12:34:56.000Z",
140
+ "apiVersion": "2026-09-22",
141
+ "test": false,
142
+ "data": {
143
+ "request": {
144
+ "id": "req_abc123",
145
+ "status": "completed",
146
+ "outcome": "approve",
147
+ "externalId": "run-42",
148
+ "metadata": { "runId": "run-42" },
149
+ "context": { "case_id": "CASE-9" },
150
+ "recipient": { "email": "ada@acme.com", "name": "Ada" },
151
+ "completedAt": "2026-09-22T12:34:56.000Z"
152
+ },
153
+ "form": { "id": "frm_abc123", "name": "Vendor onboarding", "snapshotId": "snp_..." },
154
+ "submission": { "id": "sub_xyz789", "respondentEmail": "ada@acme.com", "submittedAt": "2026-09-22T12:34:56.000Z", "pdfUrl": null, "language": "en" },
155
+ "answers": { "company_name": "Acme" },
156
+ "display": { "company_name": "Acme" }
157
+ }
158
+ }
159
+ ```
85
160
 
86
161
  Delivered events use these `type` values:
87
162
 
88
- | `type` | Meaning |
89
- | ----------------------- | ---------------------------------------------------------- |
90
- | `submission.completed` | A respondent completed a new response. |
91
- | `submission.updated` | An existing completed response changed. |
92
- | `submission.abandoned` | An incomplete response reached the configured idle window. |
163
+ | `type` | Meaning |
164
+ | ---------------------- | ------------------------------------------------------------------------ |
165
+ | `request.completed` | The recipient completed the request. `data.answers` holds the answers. |
166
+ | `request.expired` | The request reached its expiry before it was completed. |
167
+ | `request.canceled` | The caller canceled the request. |
168
+ | `submission.completed` | A respondent submitted the form through its public link. |
169
+ | `submission.updated` | A respondent edited a public-link submission they already sent. |
170
+ | `submission.abandoned` | An incomplete public-link response reached the configured idle window. |
171
+
172
+ Webhook registration events select which deliveries trigger the workflow, and each one delivers a single event `type`:
173
+
174
+ - `submission_created` (**Public Link Submission Created**) delivers `submission.completed`.
175
+ - `submission_updated` (**Public Link Submission Updated**) delivers `submission.updated`.
176
+ - `submission_abandoned` (**Public Link Submission Abandoned**) delivers `submission.abandoned`, after the selected idle window.
177
+ - `request_completed`, `request_expired` and `request_canceled` deliver `request.completed`, `request.expired` and `request.canceled`.
93
178
 
94
- Webhook registration events (`submission_created` and `submission_abandoned`) select which deliveries trigger the workflow. The event `type` identifies what happened to the response. Use `id` to deduplicate retries; the same values arrive as `X-formbase-Event-Id` and `X-formbase-Event-Type` headers.
179
+ Use `id` to deduplicate retries; the same values arrive as `X-formbase-Event-Id` and `X-formbase-Event-Type` headers.
95
180
 
96
181
  Full contracts: [formbase API methods](https://docs.formbase.so/developers/rest-api) and [webhook reference](https://docs.formbase.so/developers/webhooks-reference).
97
182
 
@@ -104,7 +189,7 @@ npm run build
104
189
  npm run lint
105
190
  ```
106
191
 
107
- `npm test` runs unit tests plus a lifecycle test that drives the node against an in-process formbase API over real HTTP (`test/fakeFormbase.mts`). `npm run dev` starts n8n with the node loaded and rebuilds on changes. Compiled package files are written to `dist/`. Run `npm pack --dry-run` before publishing to inspect package contents.
192
+ `npm test` runs unit tests plus lifecycle tests that drive both nodes against an in-process formbase API over real HTTP (`test/fakeFormbase.mts`). `npm run dev` starts n8n with the node loaded and rebuilds on changes. Compiled package files are written to `dist/`. Run `npm pack --dry-run` before publishing to inspect package contents.
108
193
 
109
194
  ## License
110
195
 
@@ -0,0 +1,12 @@
1
+ import type { IExecuteFunctions, ILoadOptionsFunctions, INodeExecutionData, INodePropertyOptions, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class Formbase implements INodeType {
3
+ description: INodeTypeDescription;
4
+ methods: {
5
+ loadOptions: {
6
+ getForms(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
7
+ getPrefillKeys(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
8
+ getContextKeys(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
9
+ };
10
+ };
11
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
12
+ }