Web form connected to a monday.com board via a GraphQL arrow

    Connect Forms to monday.com: Lead Form → Item via GraphQL API

    Till FreitagTill Freitag19. April 20266 min Lesezeit
    Till Freitag

    TL;DR: „monday WorkForms is fine – but if you want a custom-designed form AND monday.com data, build it in Lovable and write straight into the board via GraphQL."

    — Till Freitag

    📌 Lovable Forms Series · Part 4 of 6
    You've seen the SaaS options in Part 1, built it yourself in Part 2, and made it production-ready in Part 3. Now we wire the backend: your Lovable form lands as an item in monday.com.

    Connect Forms to monday.com: Lead Form → Item via GraphQL API

    You use monday.com as your CRM, but you don't want to embed a WorkForm – you want your own Lovable form, pixel-perfect on brand, while the lead still ends up in the right board. That's what the monday GraphQL API is for.

    In this part we show the full pipeline: from form submit, through an Edge Function, to a freshly created item in your monday board – including field mapping, auth strategy, and the classic gotchas.


    Why not just WorkForms?

    monday WorkForms (see Part 1) is the fastest option – but it has limits:

    • Design is bound to monday templates
    • Embedding changes your cookie/GDPR setup
    • Conditional logic is okay, but not as free as your own code
    • Multi-step flows with custom progress bar? Limited

    The moment your form is part of your brand experience (pricing wizard, onboarding, demo request), you'll want to build it yourself – while the lead data still lives where your sales team works: monday CRM.


    Architecture in 3 boxes

    [Lovable Form][Edge Function][monday GraphQL API][Board Item]
       (frontend)        (auth + map)       (api.monday.com)       (CRM)

    Critical: never keep your monday API token in the frontend. It belongs in an Edge Function – as a secret in Lovable Cloud or Supabase.


    Step 1: Create a monday API token

    1. In monday.com: Avatar → Developers → My Access Tokens
    2. Copy the token (Personal Token is fine for testing; for production build a proper OAuth app)
    3. Store it in Lovable Cloud under Backend → Secrets as MONDAY_API_TOKEN

    ⚠️ Personal token vs. OAuth: Personal tokens are tied to your user account – if you leave the company, every integration breaks. For production, always build a proper monday app with OAuth.


    Step 2: Define field mapping

    Every column in a monday board has a Column ID (not the column name!). Find it via API or in the UI under Column → ⋮ → Customize → Copy ID.

    Form field monday column Column ID Column type
    name Item name (item title)
    email Email email__1 email
    company Company text__1 text
    phone Phone phone__1 phone
    source Lead source status__1 status
    message Notes long_text__1 long_text

    Important: monday expects column_values as a JSON string – and every column type has its own format. text is a plain string, email is {"email":"...","text":"..."}, status is {"label":"..."}.


    Step 3: Build the Edge Function

    // supabase/functions/submit-lead-to-monday/index.ts
    import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
    import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
    
    const corsHeaders = {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
    };
    
    const LeadSchema = z.object({
      name: z.string().trim().min(2).max(100),
      email: z.string().trim().email(),
      company: z.string().trim().max(200).optional(),
      phone: z.string().trim().max(50).optional(),
      source: z.enum(["website", "ads", "referral", "event"]),
      message: z.string().trim().max(2000).optional(),
    });
    
    const BOARD_ID = 1234567890; // your board
    const MONDAY_API = "https://api.monday.com/v2";
    
    serve(async (req) => {
      if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
    
      try {
        const parsed = LeadSchema.safeParse(await req.json());
        if (!parsed.success) {
          return new Response(JSON.stringify({ error: parsed.error.flatten() }), {
            status: 400,
            headers: { ...corsHeaders, "Content-Type": "application/json" },
          });
        }
        const lead = parsed.data;
    
        const token = Deno.env.get("MONDAY_API_TOKEN");
        if (!token) throw new Error("MONDAY_API_TOKEN is not configured");
    
        const columnValues = {
          email__1: { email: lead.email, text: lead.email },
          text__1: lead.company ?? "",
          phone__1: lead.phone ? { phone: lead.phone, countryShortName: "US" } : null,
          status__1: { label: lead.source },
          long_text__1: { text: lead.message ?? "" },
        };
    
        const mutation = `
          mutation ($boardId: ID!, $itemName: String!, $columnValues: JSON!) {
            create_item(
              board_id: $boardId,
              item_name: $itemName,
              column_values: $columnValues
            ) { id }
          }
        `;
    
        const res = await fetch(MONDAY_API, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            Authorization: token,
            "API-Version": "2024-01",
          },
          body: JSON.stringify({
            query: mutation,
            variables: {
              boardId: BOARD_ID,
              itemName: lead.name,
              columnValues: JSON.stringify(columnValues),
            },
          }),
        });
    
        const data = await res.json();
        if (data.errors) {
          console.error("monday error:", data.errors);
          return new Response(JSON.stringify({ error: "monday API rejected request" }), {
            status: 502,
            headers: { ...corsHeaders, "Content-Type": "application/json" },
          });
        }
    
        return new Response(JSON.stringify({ itemId: data.data.create_item.id }), {
          status: 200,
          headers: { ...corsHeaders, "Content-Type": "application/json" },
        });
      } catch (e) {
        console.error("submit-lead-to-monday:", e);
        return new Response(JSON.stringify({ error: "Internal error" }), {
          status: 500,
          headers: { ...corsHeaders, "Content-Type": "application/json" },
        });
      }
    });

    Step 4: Call it from the frontend

    // In your form's onSubmit
    const { data, error } = await supabase.functions.invoke("submit-lead-to-monday", {
      body: values,
    });
    
    if (error || data?.error) {
      toast.error("Could not submit lead.");
      return;
    }
    toast.success("Thanks! We'll be in touch within 24h.");
    form.reset();

    No API token in your bundle, no CORS workarounds – the browser only talks to your Edge Function, which talks to monday.


    Bidirectional updates: webhooks

    Want sales to update lead status in monday and have it sync back to your CRM mirror or post to Slack? Set up a webhook in monday (Integrations → Webhooks → enter URL) that fires on change_column_value and calls a monday-webhook Edge Function. Classic flow:

    1. Sales sets status to "Qualified"
    2. monday fires webhook → Edge Function
    3. Edge Function posts in Slack channel and writes an audit log in your Lovable Cloud DB

    Pitfalls (lessons learned)

    Problem Cause Fix
    Invalid JSON for column_values column_values as JS object instead of JSON string JSON.stringify() the whole object
    Status not set Label doesn't exist in status type Create labels in the board first or use index instead of label
    Rate limit (429) monday: 5000 calls/min per account Edge Function with short backoff + retry
    Personal token expires Tied to a user Build an OAuth app for production
    Phone gets dropped Phone type needs countryShortName {phone, countryShortName: "US"} instead of plain string

    Bonus: subitems & updates

    Want to also create a subitem task for the sales owner or post an update to the item? Both are second GraphQL mutations in the same Edge Function:

    mutation ($parentId: ID!) {
      create_subitem(parent_item_id: $parentId, item_name: "Initial outreach") { id }
    }
    
    mutation ($itemId: ID!, $body: String!) {
      create_update(item_id: $itemId, body: $body) { id }
    }

    Now your sales team has it all in one item: original request, planned action, history.


    Conclusion

    With ~80 lines of Edge Function code, every Lovable form becomes a fully-fledged monday lead – with your design, your logic, and no WorkForm embed. This is the custom counterpart to Part 1 and a textbook use case for the Lovable + monday stack.

    👉 Next in the series: Part 5 – Smart Forms with AI · Part 6 – File Uploads in Lovable Forms
    👉 Back: Part 1 · Part 2 · Part 3

    Need help with the setup? Talk to us – we build monday integrations for CRM, HR and operations teams.

    TeilenLinkedInWhatsAppE-Mail

    Verwandte Artikel

    Glassmorphic form with an AI sparkle icon and auto-complete suggestions
    19. April 20265 min

    Smart Forms with AI in Lovable: Auto-Complete, AI Validation & Conversational Forms

    Part 5 of the Lovable Forms series: how to upgrade forms with the Lovable AI Gateway – auto-complete, AI-driven validati…

    Weiterlesen
    Three form tool UI cards floating with connection lines to a Lovable app
    16. April 20264 min

    Form Tools for Lovable Projects: Typeform, Tally & monday WorkForms Compared

    Part 1 of the Lovable Forms series: which SaaS form tool fits your Lovable project? Tally, Typeform & monday WorkForms c…

    Weiterlesen
    Glasmorphes Kontaktformular mit farbigen Eingabefeldern und Checkbox auf pastellfarbenem Hintergrund
    4. März 20264 min

    Contact Forms in Lovable – Best Practices for Professional Forms

    Part 3 of the Lovable Forms series: production-ready best practices for contact forms in Lovable – validation, GDPR, spa…

    Weiterlesen
    Drag-and-drop upload zone with floating file icons and cloud storage
    19. April 20265 min

    File Uploads in Lovable Forms: Drag & Drop, Supabase Storage, RLS and Signed URLs

    Part 6 of the Lovable Forms series: full setup for file uploads in Lovable forms – with react-dropzone, Supabase Storage…

    Weiterlesen
    Build Forms in Lovable: React Hook Form, zod & Lovable Cloud Step by Step
    19. März 20265 min

    Build Forms in Lovable: React Hook Form, zod & Lovable Cloud Step by Step

    Part 2 of the Lovable Forms series: how to build forms directly in Lovable – with React Hook Form, zod, shadcn/ui and Lo…

    Weiterlesen
    Setting Up Google Login in Lovable – SSO & Auth Step by Step
    19. März 20266 min

    Setting Up Google Login in Lovable – SSO & Auth Step by Step

    How to set up Google Sign-In for your Lovable project – from Google Cloud Console to a working login page. Complete guid…

    Weiterlesen
    The Best Lovable Resources – Your Ultimate Guide 2026
    18. März 20263 min

    The Best Lovable Resources – Your Ultimate Guide 2026

    All essential Lovable resources in one place: official docs, community, YouTube tutorials, pricing, and our best guides …

    Weiterlesen
    Architecture diagram of a modern Vibe Coding stack with Lovable, Supabase and Resend as core components
    16. März 20265 min

    The Vibe Coding Stack 2026: Lovable, Supabase, Resend – And What's Still Missing

    This is the tech stack we use to build full-stack apps in 2026 – without a traditional dev team. Three core tools, two f…

    Weiterlesen
    monday WorkForms Not Enough? 5 Form Alternatives Compared
    8. März 20266 min

    monday WorkForms Not Enough? 5 Form Alternatives Compared

    monday.com WorkForms hits its limits fast: no item updates, no conditional logic, no payments. We compare SuperForm, Fil…

    Weiterlesen