Getting started
What you can build
Ready-to-copy blueprints: a client portal with Microsoft sign-in, an approval queue, a quote that ends at Stripe checkout, an AI answer page and an order-status lookup.
Each blueprint lists the forms (paste the JSON with update_form, or build them in the editor) and what each n8n workflow does. Replace https://n8n.example.com with your n8n and keep every workflow's Webhook node set to respond with a Respond to Webhook node.
Building with an AI agent? Point it at this page: “Build the client portal blueprint from form2wf's use-cases page in my organization.” The MCP server can create the forms; you wire the n8n side.
Client portal with Microsoft sign-in#
Clients sign in with their work Microsoft account, see their projects and open requests, and file new ones. No accounts to create: access is limited to their company's email domain.
Access: both forms Verified, email domain clientco.com (see Verified). Theme: put both in one folder with a folder theme.
{ "showTitle": false, "fields": [
{ "id": "menu", "type": "nav", "navItems": [
{ "label": "Overview", "form": "portal-home" }, { "label": "New request", "form": "portal-request" } ] },
{ "id": "hello", "type": "markdown",
"markdown": "## Welcome back, {{data.name}}\n\n{{data.notice}}" },
{ "id": "projects", "type": "table", "label": "Your projects", "dataSource": "projects", "tableSearch": true, "pageSize": 10,
"columns": [ { "field": "name", "label": "Project" }, { "field": "stage", "label": "Stage" }, { "field": "due", "label": "Due" } ] }
] }{ "showTitle": false, "fields": [
{ "id": "menu", "type": "nav", "navItems": [
{ "label": "Overview", "form": "portal-home" }, { "label": "New request", "form": "portal-request" } ] },
{ "id": "subject", "type": "text", "name": "subject", "label": "Subject", "required": true },
{ "id": "details", "type": "textarea", "name": "details", "label": "What do you need?", "required": true },
{ "id": "file", "type": "file", "name": "attachment", "label": "Attachment" }
] }- portal-home workflow: read
body.endUser.email(verified by Microsoft), look up that client's projects in your project tool, reply{ "data": { "name": …, "notice": …, "projects": [ … ] } }. - portal-request workflow: create the ticket tagged with
body.endUser.email, then reply{ "message": "Thanks — we'll reply within a day.", "navigate": { "form": "portal-home" } }to land back on the overview.
Approval queue#
Managers see pending expense claims and approve or reject each with one click. The table redraws itself after every action.
Access: Verified with your own domain, or Protected with a Managers role.
{ "fields": [
{ "id": "queue", "type": "table", "label": "Waiting for approval", "dataSource": "claims",
"tableSearch": true, "pageSize": 25,
"columns": [ { "field": "employee", "label": "Employee" }, { "field": "amount", "label": "Amount" },
{ "field": "reason", "label": "Reason" }, { "field": "submitted", "label": "Submitted" } ],
"rowActions": [
{ "id": "approve", "label": "Approve", "type": "webhook", "style": "primary",
"webhookUrl": "https://n8n.example.com/webhook/claims/approve?id={{row.id}}" },
{ "id": "reject", "label": "Reject", "type": "webhook", "style": "danger", "confirmMessage": "Reject this claim?",
"webhookUrl": "https://n8n.example.com/webhook/claims/reject?id={{row.id}}" }
] }
] }- list-pending: query claims with status pending; reply
{ "data": { "claims": [ … ] } }. - approve / reject: check the
X-F2WF-User-Emailheader is a manager, update the claim by theidquery value, notify the employee, reply{ "message": "Approved" }. The table reloads its rows automatically.
Quote that ends at Stripe checkout#
A visitor picks options, sees the price, and pays — your workflow prices the order and creates the Stripe Checkout session, so prices can't be tampered with in the browser.
{ "submitText": "Continue to payment", "fields": [
{ "id": "plan", "type": "radio", "name": "plan", "label": "Package", "options": ["Starter", "Growth", "Scale"], "required": true, "defaultValue": "{{params.plan}}" },
{ "id": "seats", "type": "number", "name": "seats", "label": "Seats", "defaultValue": "5", "required": true },
{ "id": "email", "type": "email", "name": "email", "label": "Billing email", "required": true },
{ "id": "terms", "type": "markdown", "markdown": "By continuing you agree to the [terms](https://example.com/terms). You can cancel any time." }
] }- create-checkout: look up the package price on the server, create a Stripe Checkout Session (n8n's Stripe node or an HTTP Request to
/v1/checkout/sessions) withsuccess_urlpointing at a thank-you page, then reply{ "redirectUrl": "<session.url>" }. The widget sends the visitor straight to Stripe. - stripe-webhook (a second workflow, called by Stripe): on
checkout.session.completed, provision the order and email the receipt.
Link to it with a preselected package: https://your-site.com/buy?plan=Growth.
AI answer page#
A visitor pastes a contract (or a job description, a support question…) and gets a formatted AI answer on the next page. Sign-in keeps strangers from spending your model budget.
Access: Verified (any Microsoft account), or Public with a strict rate limit for a lead magnet that collects an email first.
{ "submitText": "Review it", "fields": [
{ "id": "text", "type": "textarea", "name": "contract", "label": "Paste the contract", "required": true },
{ "id": "focus", "type": "select", "name": "focus", "label": "Focus on", "options": ["Risks", "Payment terms", "Termination"] }
] }{ "fields": [
{ "id": "answer", "type": "markdown", "dataSource": "review", "maxHeight": 600 },
{ "id": "again", "type": "link", "label": "Review another", "linkAction": "navigate", "linkTargetForm": "review" }
] }- run-review: send
body.data.contractto your model (n8n's AI Agent or OpenAI/Anthropic node) asking for a markdown answer, store it with an id, reply{ "navigate": { "form": "review-result", "params": { "id": "<id>" } } }. - get-review: load the stored answer by
body.params.id(check it belongs tobody.endUser.idon a Verified form) and reply{ "data": { "review": "### Summary\n…" } }.
Model output is shown as markdown: headings, lists and tables render, while any HTML or script in the answer is shown as plain text.
Order status lookup#
Customers check an order without logging in: they enter the order number and the email it was placed with.
{ "submitText": "Find my order", "fields": [
{ "id": "order", "type": "text", "name": "order", "label": "Order number", "required": true, "defaultValue": "{{params.order}}" },
{ "id": "email", "type": "email", "name": "email", "label": "Email used for the order", "required": true }
] }{ "fields": [
{ "id": "status", "type": "markdown", "dataSource": "summary" },
{ "id": "items", "type": "table", "label": "Items", "dataSource": "items",
"columns": [ { "field": "name", "label": "Item" }, { "field": "qty", "label": "Qty" } ] }
] }- find-order: look the order up in Shopify/WooCommerce; if the email matches, store a short-lived lookup token and reply
{ "navigate": { "form": "track-result", "params": { "t": "<token>" } } }, otherwise{ "message": "We couldn't find that order." , "refresh": true }. - order-details: resolve
body.params.tto the order and reply with a markdownsummaryand theitems.
Never pass the raw order number alone to the result page — anyone could guess the next one. Use a random token (as above) or make the result page Verified.
More ideas#
- Bookkeeper intake: a Verified upload form per client; the onload webhook shows a "still missing" checklist as markdown task list (
- [x] T4,- [ ] RRSP receipt). - Event check-in: a searchable attendee table with a Check in row action.
- Tenant maintenance: Verified request form with photo upload, plus a table of open tickets for that tenant.
- Ops console: buttons for "Re-run sync", "Resend invoice" that wrap workflows a teammate shouldn't touch directly.