View .md

MuxForms on Remix adds embed.js via <Scripts> in root.tsx and a contact route with data-form-id. For custom UI, use an action that POSTs JSON to the submit API with process.env.MUXFORMS_INGEST_SECRET.

Docs

Remix

Remix actions are great for secrets—MuxForms embed is great when you do not want an action at all.

Remix

ApproachDetail
Embedroot.tsx script + contact route
actionServer POST with secret
EnvINGEST secret in .env
AllowlistBrowser embed origin

app/routes/contact.tsx

export default function Contact() {
  return (
    <main>
      <h1>Contact</h1>
      <div data-form-id="FORM_UUID" />
    </main>
  );
}

app/root.tsx (script once)

export function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <script src="https://www.muxforms.com/embed.js" defer />
      </body>
    </html>
  );
}

Optional action submit

import type { ActionFunctionArgs } from "@remix-run/node";

export async function action({ request }: ActionFunctionArgs) {
  const form = await request.formData();
  const res = await fetch("https://www.muxforms.com/api/v1/forms/FORM_UUID/submit", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.MUXFORMS_INGEST_SECRET}`,
    },
    body: JSON.stringify({
      email: String(form.get("email")),
      message: String(form.get("message")),
      _hp: "",
    }),
  });
  if (!res.ok) return { ok: false };
  return { ok: true };
}

Common questions

Vite Remix?
Same root script and route markup.
Progressive enhancement?
Embed handles client submit; action path for native <form> if you build it.
Fly.io deploy?
Allow https://your-app.fly.dev on the form.
React Router 7?
Mirror this pattern in the root layout.

Remix without mail plumbing.

Embed for speed, action when the design is custom.

Upgrade to Pro