Caputchin
Integration guides

Frontend integration examples

The widget is a standard custom element, so it works in every framework with no wrapper to install. The shape is always the same: load the package once (client side integration), render <caputchin-widget sitekey="cpt_pub_..."> inside your form, and read the hidden caputchin-token field it injects when the visitor clears the challenge. For a game, swap in <caputchin-game ... game="caputchin/games/leaf-memory">.

The examples below differ only in each framework's syntax and its one custom-element quirk.

Plain HTML

The injected field rides along with a normal form POST, so there is nothing to wire up:

<script src="https://cdn.jsdelivr.net/npm/@caputchin/widget@3/dist/widget.js"></script>

<form action="/contact" method="POST">
  <input name="email" type="email" required />
  <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
  <button type="submit">Send</button>
</form>

React

Import once at startup, then read the field from FormData on submit:

import "@caputchin/widget";

export function ContactForm() {
  async function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    await fetch("/api/contact", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ email: data.get("email"), token: data.get("caputchin-token") }),
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <caputchin-widget sitekey="cpt_pub_..." />
      <button type="submit">Send</button>
    </form>
  );
}

If you use TypeScript with React, declare both elements once so JSX accepts them:

// caputchin.d.ts
import type { DetailedHTMLProps, HTMLAttributes } from "react";
declare module "react" {
  namespace JSX {
    interface IntrinsicElements {
      "caputchin-widget": DetailedHTMLProps<HTMLAttributes<HTMLElement> & { sitekey: string }, HTMLElement>;
      "caputchin-game": DetailedHTMLProps<HTMLAttributes<HTMLElement> & { sitekey: string; game?: string; games?: string }, HTMLElement>;
    }
  }
}

This declare module "react" form is specific to React's JSX. In plain TypeScript the elements are just HTMLElement and need no declaration, and the other frameworks below type custom elements their own way, so you only need this block in a React project.

Vue

Tell Vue's compiler that caputchin- tags are custom elements, so it does not try to resolve them as components:

// vite.config.js
export default {
  plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith("caputchin-") } } })],
};
<script setup>
import "@caputchin/widget";
function onSubmit(e) {
  const data = new FormData(e.target);
  // send data.get("caputchin-token") to your backend
}
</script>

<template>
  <form @submit.prevent="onSubmit">
    <input name="email" type="email" required />
    <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
    <button type="submit">Send</button>
  </form>
</template>

Svelte

Svelte renders custom elements as-is, no config needed:

<script>
  import "@caputchin/widget";
  function onSubmit(e) {
    const data = new FormData(e.currentTarget);
    // send data.get("caputchin-token") to your backend
  }
</script>

<form on:submit|preventDefault={onSubmit}>
  <input name="email" type="email" required />
  <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
  <button type="submit">Send</button>
</form>

Angular

Add CUSTOM_ELEMENTS_SCHEMA to the module or standalone component so Angular allows the unknown tag, and import the package once (in main.ts):

import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import "@caputchin/widget";

@Component({
  selector: "contact-form",
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <form (submit)="onSubmit($event)">
      <input name="email" type="email" required />
      <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
      <button type="submit">Send</button>
    </form>
  `,
})
export class ContactFormComponent {
  onSubmit(e: Event) {
    const data = new FormData(e.target as HTMLFormElement);
    // send data.get("caputchin-token") to your backend
  }
}

Next.js

The same React code works. The custom element renders as an empty tag during server rendering and upgrades on the client once the package loads, so no special handling is needed beyond importing @caputchin/widget in a client component (or loading the CDN script with next/script).

See also

On this page