Get the FREE Ultimate OpenClaw Setup Guide →

formcarry

npx machina-cli add skill hicay/claude-code-skills/formcarry --openclaw
Files (1)
SKILL.md
10.6 KB

Formcarry — Forms Without a Backend

Formcarry is a form backend that collects submissions from HTML or JavaScript forms with minimal configuration. No server code needed. Setup takes ~3 minutes.

Endpoint format: https://formcarry.com/s/{FORM_ID}

Before Building

  1. Does the user have a Formcarry account? If not, they need one at formcarry.com
  2. What type of form? Contact, feedback, registration, payment, file upload
  3. Submission method? HTML (page redirect) or AJAX (no reload)
  4. Framework? Plain HTML, React, Vue, Next.js, Astro, or other
  5. Features needed? File uploads, spam protection, auto-responder, webhooks, payments

Quick Start — 3 Steps

Step 1: Create form in Formcarry dashboard

Dashboard → Create Form → Copy endpoint URL from Setup tab.

Step 2: Point your form to the endpoint

<form action="https://formcarry.com/s/{FORM_ID}" method="POST">
  <input type="text" name="name" required>
  <input type="email" name="email" required>
  <textarea name="message"></textarea>
  <button type="submit">Send</button>
</form>

Step 3: Submit and check dashboard

Submissions appear in Formcarry dashboard. Email notifications sent to form owner.

Critical rule: Every <input>, <textarea>, and <select> MUST have a name attribute. Without it, data won't be collected.


Submission Methods

Method 1: HTML Form (Page Redirect)

Simplest approach. Form submits, user redirects to thank-you page.

<form action="https://formcarry.com/s/{FORM_ID}" method="POST">
  <input type="text" name="name" placeholder="Name" required>
  <input type="email" name="email" placeholder="Email" required>
  <textarea name="message" placeholder="Message"></textarea>
  <button type="submit">Send</button>
</form>

Method 2: Fetch API (No Reload)

form.addEventListener("submit", async (e) => {
  e.preventDefault();

  const data = Object.fromEntries(new FormData(form));

  const response = await fetch("https://formcarry.com/s/{FORM_ID}", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Accept": "application/json",
    },
    body: JSON.stringify(data),
  });

  const result = await response.json();

  if (result.code === 200) {
    // Success
  } else {
    // Error: result.message
  }
});

Method 3: React

const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");

async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault();
  setStatus("sending");

  const data = Object.fromEntries(new FormData(e.currentTarget));

  try {
    const res = await fetch("https://formcarry.com/s/{FORM_ID}", {
      method: "POST",
      headers: { "Content-Type": "application/json", "Accept": "application/json" },
      body: JSON.stringify(data),
    });

    const result = await res.json();
    setStatus(result.code === 200 ? "sent" : "error");
  } catch {
    setStatus("error");
  }
}

For complete code examples (Vue, Axios, jQuery, XMLHttpRequest, file uploads), see references/code-examples.md.


Special Hidden Fields

Control behavior with hidden inputs:

FieldPurposeExample
_nextRedirect URL after submit<input type="hidden" name="_next" value="https://site.com/thanks">
_gotchaHoneypot spam trap<input type="hidden" name="_gotcha">

Note: Dashboard thank-you page URL overrides _next. Leave dashboard field empty to use _next.


Spam Protection

Three layers, use together for best results:

1. Built-in (automatic)

Formcarry's algorithm detects spam automatically. Spam is flagged, not deleted.

2. Honeypot field

<input type="hidden" name="_gotcha">

Bots fill hidden fields. Humans don't. Submissions with _gotcha filled are flagged as spam.

3. Google reCAPTCHA

Configure reCAPTCHA secret key in dashboard or via API (googleRecaptcha parameter).

Plan note: Free plan counts spam toward submission limit. Paid plans don't.


File Uploads

Requirement: enctype="multipart/form-data" on the form tag. Limit: 10MB per file. Paid plans only.

<form action="https://formcarry.com/s/{FORM_ID}" method="POST" enctype="multipart/form-data">
  <input type="text" name="name">
  <input type="file" name="attachment">
  <button type="submit">Send</button>
</form>

Multiple files: add multiple attribute or use multiple <input type="file"> elements.

AJAX file uploads: Use FormData object, do NOT set Content-Type header (browser sets it automatically with boundary).

const formData = new FormData(formElement);

const res = await fetch("https://formcarry.com/s/{FORM_ID}", {
  method: "POST",
  headers: { "Accept": "application/json" },
  body: formData, // NOT JSON.stringify — send FormData directly
});

Field Validations

Configure in dashboard. Six validators:

ValidatorRule
requiredField cannot be empty
numberMust be a number
emailMust be valid email
urlMust be valid URL
containsMust contain specified value
notContainsMust NOT contain specified value

Error response (HTTP 422):

{
  "code": 422,
  "status": "error",
  "title": "Validation Failed",
  "errors": {
    "email": { "message": "email is not a valid email", "rule": "email" }
  }
}

Handle validation errors by highlighting fields:

if (result.code === 422) {
  Object.keys(result.errors).forEach(field => {
    document.querySelector(`[name="${field}"]`).classList.add("error");
  });
}

Email Notifications

Self notifications (to form owner)

Enabled by default. Configure email, subject, and template in dashboard.

Auto-response (to submitter)

Enable in dashboard. Requires an email field in the form. Sends confirmation to the person who submitted.

Custom email templates

Available on Growth/Premium plans. Use Mustache-style variables:

Hi {{name}},

Thanks for reaching out about {{subject}}.

{{#phone}}
We'll also call you at {{phone}}.
{{/phone}}

{{#fallback}}company | your company{{/fallback}}

Variables: {{fieldName}} — maps to form field name attributes. Conditionals: {{#field}}...{{/field}} — renders only if field has value. Fallbacks: {{#fallback}}field | default text{{/fallback}} — default when empty. Iteration: {{#fields}}{{key}}: {{value}}{{/fields}} — loop through all fields.


Webhooks

Configure webhook URL in dashboard or via API. Formcarry POSTs submission data to your URL in real-time.

Use cases: Slack notifications, CRM updates, database sync, custom processing.


Stripe Payments

Collect payments alongside form submissions. Configure Stripe integration in dashboard.


REST API

For programmatic form management and submission retrieval.

Authentication: Pass api_key header in every request. Find key in dashboard → Integrations.

For complete API reference (create/delete forms, list/filter submissions, pagination), see references/api-reference.md.


Pricing Quick Reference

PlanPriceFormsSubmissions/moFile StorageTeam
Baby (Free)$0150Yes1
Starter$5/moUnlimited5001GB3
Basic$15/moUnlimited2,0002GB5
Premium$80/moUnlimited30,00030GB20
EnterpriseCustomUnlimitedUnlimitedUnlimitedUnlimited

Common Patterns

Contact form with spam protection

<form action="https://formcarry.com/s/{FORM_ID}" method="POST">
  <input type="text" name="name" required>
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>
  <input type="hidden" name="_gotcha">
  <input type="hidden" name="_next" value="https://site.com/thanks">
  <button type="submit">Send Message</button>
</form>

Newsletter signup (minimal)

<form action="https://formcarry.com/s/{FORM_ID}" method="POST">
  <input type="email" name="email" placeholder="your@email.com" required>
  <button type="submit">Subscribe</button>
</form>

Multi-step with JS (collect, then submit)

Collect data across steps in a JS object, then submit all at once via Fetch.

React hook pattern

function useFormcarry(formId: string) {
  const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
  const [errors, setErrors] = useState<Record<string, string>>({});

  async function submit(data: Record<string, string>) {
    setStatus("sending");
    setErrors({});

    try {
      const res = await fetch(`https://formcarry.com/s/${formId}`, {
        method: "POST",
        headers: { "Content-Type": "application/json", "Accept": "application/json" },
        body: JSON.stringify(data),
      });

      const result = await res.json();

      if (result.code === 200) {
        setStatus("sent");
      } else if (result.code === 422) {
        setErrors(Object.fromEntries(
          Object.entries(result.errors).map(([k, v]: any) => [k, v.message])
        ));
        setStatus("error");
      } else {
        setStatus("error");
      }
    } catch {
      setStatus("error");
    }
  }

  return { status, errors, submit };
}

Troubleshooting

ProblemCauseFix
Fields show as emptyMissing name attributeAdd name to every input
CORS errorMissing Accept headerAdd Accept: application/json
File upload failsMissing enctypeAdd enctype="multipart/form-data"
File upload fails (AJAX)Setting Content-Type manuallyDon't set Content-Type, send FormData directly
_next redirect not workingDashboard has custom thank-you URLClear dashboard thank-you page field
Spam not filteredNo protection configuredAdd _gotcha hidden field + enable reCAPTCHA
422 errorField validation failedCheck result.errors for specific field issues

Source

git clone https://github.com/hicay/claude-code-skills/blob/main/formcarry/SKILL.mdView on GitHub

Overview

Formcarry provides a form backend to collect submissions from HTML or JavaScript forms with minimal setup. No server code is required, and you get an endpoint like https://formcarry.com/s/{FORM_ID} plus options for file uploads, spam protection, webhooks, auto-responders, and even Stripe payments.

How This Skill Works

Create a form in the Formcarry dashboard to get your unique endpoint. Point your HTML form action or use AJAX/Fetch/React to post data to https://formcarry.com/s/{FORM_ID}. Ensure every input, textarea, and select has a name attribute so Formcarry can capture the data; submissions appear in the dashboard and notifications can be configured.

When to Use It

  • Add a working contact or lead capture form to a website without writing server-side code
  • Collect submissions for a landing page or app without building a backend
  • Use HTML forms with page redirects or AJAX submissions (Fetch/React/Vue) to Formcarry endpoint
  • Enable features like file uploads, spam protection, webhooks, and auto-responders
  • Process payments or leverage Formcarry REST API for advanced form workflows

Quick Start

  1. Step 1: Create form in Formcarry dashboard and copy the endpoint URL
  2. Step 2: Point your HTML form to https://formcarry.com/s/{FORM_ID} and include name attributes on inputs
  3. Step 3: Submit and check the Formcarry dashboard for submissions (email notifications optional)

Best Practices

  • Ensure every input, textarea, and select has a name attribute to capture data
  • Point the form action to https://formcarry.com/s/{FORM_ID} and choose the appropriate method
  • Choose between HTML form submission (redirect) or AJAX (no reload) based on UX needs
  • Use hidden fields like _next to control redirect behavior after submission
  • Leverage Formcarry features (spam protection, webhooks, auto-responders, file uploads, payments) as needed

Example Use Cases

  • Simple contact form on a website
  • Lead capture form on a marketing landing page
  • Registration form for an event or webinar
  • Feedback or survey form on a product page
  • Payment-enabled form using Stripe via Formcarry

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers