# 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](https://form2wf.com/docs/mcp) 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](https://form2wf.com/docs/access#verified)). **Theme:** put both in one folder with a folder theme.

form portal-home (onload: portal-home webhook):

```
{ "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" } ] }
] }
```

form portal-request (submit: portal-request webhook):

```
{ "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.

form approvals (onload: list-pending webhook):

```
{ "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-Email` header is a manager, update the claim by the `id` query 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.

form quote (submit: create-checkout webhook):

```
{ "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`) with `success_url` pointing 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.

form review (submit: run-review webhook):

```
{ "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"] }
] }
```

form review-result (onload: get-review webhook):

```
{ "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.contract` to 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 to `body.endUser.id` on 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.

form track (submit: find-order webhook):

```
{ "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 }
] }
```

form track-result (onload: order-details webhook):

```
{ "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.t` to the order and reply with a markdown `summary` and the `items`.

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.

---
Source: https://form2wf.com/docs/use-cases — Form2Workflow documentation (Getting started)
