SDK
JavaScript SDK
Official client libraries for submitting to ShipMyForm over fetch, with typed results and idiomatic bindings for every major framework. Your forms keep working without JavaScript and upgrade to fetch when it is available.
<form action> works everywhere. Reach for the SDK when you want an inline success state, typed error handling, or a hook that fits your framework, not because submissions require it.Packages
Everything is published under the @shipmyform scope on npm. Install only the one for your stack. Each framework package pulls in @shipmyform/core for you.
| Package | Use with | API |
|---|---|---|
@shipmyform/core | Any framework / vanilla JS | createClient, enhance |
@shipmyform/react | React, Next.js | useSubmit hook |
@shipmyform/vue | Vue 3 | useSubmit composable |
@shipmyform/svelte | Svelte, SvelteKit | createSubmit, use:shipmyform |
@shipmyform/solid | Solid | createSubmit primitive |
@shipmyform/astro | Astro | <ShipMyForm> component |
Core client
The framework-agnostic client. submit accepts a plain object, a FormData, or an HTMLFormElement. Passing a File or Blob switches the request to multipart/form-data automatically.
import { createClient } from "@shipmyform/core";
const form = createClient({ formId: "frm_8Kx2mQ9pL4vN" });
const result = await form.submit({
email: "[email protected]",
message: "Hello",
});
if (result.ok) {
// stored and routed
} else {
console.error(result.error, result.message);
}React
npm i @shipmyform/react
import { useSubmit } from "@shipmyform/react";
export function Contact() {
const { submit, submitting, succeeded, error } = useSubmit({
formId: "frm_8Kx2mQ9pL4vN",
});
if (succeeded) return <p>Thanks, we'll be in touch!</p>;
return (
<form onSubmit={(e) => { e.preventDefault(); submit(e.currentTarget); }}>
<input name="email" type="email" required />
<textarea name="message" required />
<button disabled={submitting}>Send</button>
{error && <p role="alert">{error.message}</p>}
</form>
);
}Vue
npm i @shipmyform/vue
<script setup lang="ts">
import { useSubmit } from "@shipmyform/vue";
const { submit, submitting, succeeded, error } = useSubmit({
formId: "frm_8Kx2mQ9pL4vN",
});
</script>
<template>
<p v-if="succeeded">Thanks!</p>
<form v-else @submit.prevent="submit($event.target as HTMLFormElement)">
<input name="email" type="email" required />
<textarea name="message" required />
<button :disabled="submitting">Send</button>
<p v-if="error" role="alert">{{ error.message }}</p>
</form>
</template>Svelte
npm i @shipmyform/svelte: a store for full control, or the use:shipmyform action for one-line progressive enhancement.
<script lang="ts">
import { createSubmit } from "@shipmyform/svelte";
const { submit, submitting, succeeded, error } = createSubmit({
formId: "frm_8Kx2mQ9pL4vN",
});
</script>
{#if $succeeded}
<p>Thanks!</p>
{:else}
<form on:submit|preventDefault={(e) => submit(e.currentTarget)}>
<input name="email" type="email" required />
<button disabled={$submitting}>Send</button>
{#if $error}<p role="alert">{$error.message}</p>{/if}
</form>
{/if}Solid
npm i @shipmyform/solid
import { Show } from "solid-js";
import { createSubmit } from "@shipmyform/solid";
export function Contact() {
const { submit, submitting, succeeded, error } = createSubmit({
formId: "frm_8Kx2mQ9pL4vN",
});
return (
<Show when={!succeeded()} fallback={<p>Thanks!</p>}>
<form onSubmit={(e) => { e.preventDefault(); submit(e.currentTarget); }}>
<input name="email" type="email" required />
<button disabled={submitting()}>Send</button>
<Show when={error()}>{(e) => <p role="alert">{e().message}</p>}</Show>
</form>
</Show>
);
}Astro
npm i @shipmyform/astro: renders a native form that posts without JavaScript and upgrades to fetch on the client. Props: formId (required), endpoint, class.
---
import ShipMyForm from "@shipmyform/astro/ShipMyForm.astro";
---
<ShipMyForm formId="frm_8Kx2mQ9pL4vN">
<input name="email" type="email" required />
<textarea name="message" required />
<button>Send</button>
</ShipMyForm>
<script>
document.addEventListener("shipmyform:success", () => alert("Thanks!"));
document.addEventListener("shipmyform:error", (e) => console.error(e.detail));
</script>Progressive enhancement
With vanilla core, enhance keeps a native <form> working without JavaScript and upgrades it to fetch when available.
import { createClient, enhance } from "@shipmyform/core";
const el = document.querySelector("form");
const client = createClient({ formId: "frm_8Kx2mQ9pL4vN" });
enhance(el, client, {
onSuccess: () => el.replaceWith("Thanks!"),
onError: (e) => alert(e.message),
});Options
Client-level options go to createClient; per-request options go to submit.
const form = createClient({
formId: "frm_8Kx2mQ9pL4vN",
endpoint: "https://forms.acme.dev", // custom domain or self-host
timeout: 10_000, // ms before the request aborts
});
await form.submit(data, {
turnstileToken, // Cloudflare Turnstile token, when the form requires one
subject, // override the email-notification subject
redirect, // redirect target used for the no-JS fallback
signal, // an AbortSignal to cancel the request
});Result and errors
Every submit resolves to a discriminated union. It never throws for an expected failure, so you branch on result.ok.
type SubmitResult =
| { ok: true }
| { ok: false; error: SubmitErrorCode; message: string; status: number };| error | Meaning |
|---|---|
rate_limited | Too many submissions from this client. Try again shortly. |
origin | The request came from a domain that is not on the form's allowlist. |
turnstile | Cloudflare Turnstile was required and the token was missing or invalid. |
too_large | The submission (or an uploaded file) exceeded the size limit. |
empty | No fields were submitted. |
not_found | No form matches that formId. |
unavailable | The service was temporarily unavailable (5xx). |
network | The request never reached the server. |
timeout | The request exceeded the client timeout. |
unknown | An unexpected error. See message for details. |
_gotcha) empty and forwards a Cloudflare Turnstile token when you pass one; the backend screens every request with an origin allowlist and rate limiting. See how spam protection works.Source and changelog live on github.com/ShipMyForm/shipmyform-js.