## Summary Replaces the single-screen partner-application modal with a **4-step wizard** on the public form, and points the route's upstream at the new `submit-partner-application` HTTP logic function in the twenty-partners SDK app. After design review, the Expertise step landed on the validated **Category + Skills** model: a small set of *stable* macro categories the partner operates in, plus a *free, semi-structured* Skills field for the concrete things that differentiate them (React, SAP, Shopify, …). Companion PR (partners-app side): #21040 ## ⚠️ Deployment notes Before this can ship to prod, the website worker needs a new env var: - **Add `PARTNER_APPLICATION_SECRET`** to the deploy config at https://github.com/twentyhq/twenty-infra/tree/main/cloudflare/website. Without it the route returns `503` ("Partner application endpoint is not configured."). - The value must **match** the `PARTNER_APPLICATION_SECRET` workspace variable set in the partners workspace UI (Settings → Apps → Twenty Partners → Variables) — that's how the handler authenticates the incoming `X-Application-Secret` header. - `PARTNER_APPLICATION_WEBHOOK_URL` also needs repointing from the TFT webhook to the logic-function URL (`https://partner.twenty.com/s/partner-applications` or equivalent) at the same time. ## Wizard - 4 steps inside `Modal.Root`: **Identity → Profile → Expertise → Commercials**. Step-dot indicator, per-step required-field gating, reset on close. The big serif hero shows **only on step 1**; later steps use the compact `STEP n OF 4 · NAME` strip to reclaim vertical space. - **Profile** captures Type of team (Solo/Agency), LinkedIn, City, Country, Languages. Country uses the searchable Select (placeholder-only label). - **Expertise = Category + Skills + Notes:** - **Category** — multi-select cards over 5 macro categories (`ADVISORY`, `SOLUTIONING`, `DEVELOPMENT`, `HOSTING`, `SUPPORT`), each with a one-line description + examples. (Replaces the old draft `partnerScope` enum; the backend keeps the field name — see #21040.) - **Skills** — free tag input with a clickable suggestion row + keyboard autocomplete (↑/↓/Enter/Esc) and "add your own". Empty by default. - **Notes** — one free textarea (merges the former `workspaceUrl` + `customerReferences`), reviewed manually. - `deploymentExpertise` removed from the form (covered by the Hosting category). - **In-modal success view** on submit ("Thanks, / we'll be in touch!") with a Close button — replaces the old silent close. - Removes the partners-page "Which partner program is right for you?" three-cards section. ## Design-system primitives - **`Form.Select`** — searchable popup whose dropdown is **portaled to `<body>`** (fixed, anchored to the trigger, flips up, height-capped) so the modal's `overflow`/`transform` can't clip it; pointer events are stopped so clicking inside it doesn't dismiss the dialog. - **`Form.TagInput`** — optional `suggestions` prop adds the suggestion row + autocomplete menu (used by Skills); behaviour unchanged when no suggestions are passed. - **`CategoryCardSelect`** — compact multi-select cards. - `Form.MultiSelect`, `Form.Currency`. ## Validation & payload - **Single validation source:** client and server share Zod field schemas (`partner-application-field-schemas.ts`). The reducer validates via those instead of hand-rolled regexes, so client and server agree by construction (e.g. both reject non-TLD URLs). - **Typed request body:** `buildPartnerApplicationRequestBody(state)` returns a typed `PartnerApplicationRequest` (unit-tested); `handleSubmit` just serializes it. - Payload is camelCase matching the logic-function input; `applicationNotes` replaces `workspaceUrl`/`customerReferences`. - Auth: the upstream call carries an `X-Application-Secret` header backed by `PARTNER_APPLICATION_SECRET` (handler-enforced — the SDK's `isAuthRequired` only accepts user-session JWTs, not workspace API keys). The webhook-URL env uses `z.url()` (not `z.httpUrl()`) so `http://localhost:2020/...` dev destinations parse. ## Demo 📹 _Screen recording of the wizard end-to-end (open → walk steps → submit → Partner record lands):_ https://github.com/user-attachments/assets/7458dd86-e3ff-47b5-9878-0eb134ff38e3 ## Tests - **62 passing** across reducer, Zod schema, route, the new payload-builder suite, and Form helper suites. `npx tsc` clean, `nx lint:diff-with-main` clean, Lingui catalogs regenerated (French slots are a follow-up). ## Test plan - [ ] `/partners` → "Become a partner" → wizard opens on Step 1 (full hero) - [ ] Identity: name / work email / company → Next - [ ] Profile: pick **Type of team**; search country ("fra" → France); pick languages → Next (compact header from here on) - [ ] Expertise: select 1+ **Category** cards; add **Skills** (click a suggestion, type one + Enter, drive the ↑/↓ autocomplete); optionally fill **Notes** - [ ] Country dropdown opens without being clipped by the modal, and clicking inside it does **not** close the wizard - [ ] Commercials → Submit → **in-modal "Thanks, we'll be in touch!"**; Network shows POST `/api/partner-application` `200` - [ ] Partner record lands with the chosen categories in `partnerScope`, plus `skills`, `applicationNotes`, `slug` from company, `reviewed: false`, `partnerTier: 'NEW'` - [ ] Re-submit same email + different city → Partner updates; `validationStage`/`reviewed`/`partnerTier` preserved - [ ] Back/Next preserves entered values; Reset on close; mobile single-column / chips wrap --------- Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
The #1 Open-Source CRM
Website ·
Documentation ·
Roadmap ·
Discord ·
Figma
Why Twenty
Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.
Learn more about why we built Twenty
Installation
Cloud
The fastest way to get started. Sign up at twenty.com and spin up a workspace in under a minute, with no infrastructure to manage and always up to date.
Build an app
Scaffold a new app with the Twenty CLI:
npx create-twenty-app my-app
Define objects, fields, and views as code:
import { defineObject, FieldType } from 'twenty-sdk/define';
export default defineObject({
nameSingular: 'deal',
namePlural: 'deals',
labelSingular: 'Deal',
labelPlural: 'Deals',
fields: [
{ name: 'name', label: 'Name', type: FieldType.TEXT },
{ name: 'amount', label: 'Amount', type: FieldType.CURRENCY },
{ name: 'closeDate', label: 'Close Date', type: FieldType.DATE_TIME },
],
});
Then ship it to your workspace:
npx twenty app:publish --private
See the app development guide for objects, views, agents, and logic functions.
Self-hosting
Run Twenty on your own infrastructure with Docker Compose, or contribute locally via the local setup guide.
Everything you need
Twenty gives you the building blocks of a modern CRM (objects, views, workflows, and agents) and lets you extend them as code. Here's a tour of what's in the box.
Want to go deeper? Read the User Guide for product walkthroughs, or the
Documentation for developer reference.
|
|
|
|
|
|
Stack
TypeScript
Nx
NestJS, with BullMQ,
PostgreSQL,
Redis
React, with Jotai, Linaria and Lingui
Thanks
Thanks to these amazing services that we use and recommend for UI testing (Chromatic), code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
Join the Community
Star the repo ·
Discord ·
Feature requests ·
Releases ·
X ·
LinkedIn ·
Crowdin ·
Contribute





