LIVE DEMO

THIS WAITLIST IS A .swirls FILE.

The form below is a real .swirls file powering a live workflow. Fill it out to join the waitlist. Your submission flows through the same pipeline you see in the editor.

waitlist.swirls/file to power the form
form waitlist {
  label: "Waitlist"
  enabled: true
  schema: @json {
    {
      "type": "object",
      "required": ["name", "email"],
      "properties": {
        "name": { "type": "string", "title": "Name" },
        "email": { "type": "string", "title": "Email", "format": "email" }
      },
      "additionalProperties": false
    }
  }
}

graph process_waitlist {
  label: "Process Waitlist"
  description: "Normalize input, submit Google Form, classify lead, store with metadata"

  root {
    type: code
    label: "Normalize"
    inputSchema: @json {
      {
        "type": "object",
        "required": ["name", "email"],
        "properties": {
          "name": { "type": "string" },
          "email": { "type": "string" }
        },
        "additionalProperties": false
      }
    }
    outputSchema: @json {
      {
        "type": "object",
        "required": ["name", "email", "domain"],
        "properties": {
          "name": { "type": "string" },
          "email": { "type": "string" },
          "domain": { "type": "string" }
        },
        "additionalProperties": false
      }
    }
    code: @ts {
      const { name, email } = context.nodes.root.input

      return {
        name: name.trim(),
        email: email.toLowerCase().trim(),
        domain: email.split("@")[1] ?? "unknown",
      }
    }
  }

  node submit_google_form {
    label: "Submit Google Form"
    type: http
    method: POST
    url: @ts {
      return "https://docs.google.com/forms/d/e/1FAIpQLSeRN7yt5Jh5GYdl2j2IHVxB6_onVNbYTcHjgjSWPMV9NU0vmg/formResponse"
    }
    headers: @ts {
      return {
        "Content-Type": "application/x-www-form-urlencoded",
      }
    }
    body: @ts {
      const { name, email } = context.nodes.root.output

      const params = new URLSearchParams({
        'entry.1965937490': name,
        'entry.686474847': email,
      });

      return params.toString()
    }
  }

  node classify {
    type: ai
    kind: object
    label: "Classify Lead"
    schema: @json {
      {
        "type": "object",
        "required": ["segment"],
        "properties": {
          "segment": { "type": "string" }
        }
      }
    }
    model: "google/gemini-2.5-flash"
    prompt: @ts {
      const { domain } = context.nodes.root.output
      return `Classify this lead into one of: startup, enterprise, agency, personal. Email domain: ${domain}`
    }
  }

  node store {
    type: code
    label: "Store"
    schema: @json {
      {
        "type": "object",
        "required": ["name", "email", "domain", "segment", "joinedAt"],
        "properties": {
          "name": { "type": "string" },
          "email": { "type": "string" },
          "domain": { "type": "string" },
          "segment": { "type": "string" },
          "joinedAt": { "type": "string" }
        }
      }
    }
    code: @ts {
      const { name, email, domain } = context.nodes.root.output
      const { segment } = context.nodes.classify.output

      return {
        name,
        email,
        domain,
        segment,
        joinedAt: new Date().toISOString(),
      }
    }
  }

  flow {
    root -> submit_google_form
    submit_google_form -> classify
    classify -> store
  }
}

trigger on_waitlist {
  form:waitlist -> process_waitlist
  enabled: true
}

Join the waitlist

Get early access when we launch.

HOW IT WORKS

1. Define

Write your form schema, processing graph, and triggers in a single .swirls file. Version it in Git like any other code.

2. Submit

Users fill out the form. Their input is validated against your JSON Schema and sent to the Swirls runtime.

3. Execute

The trigger fires your graph: normalize, classify with an LLM, and store. Each node runs in sequence, automatically.