Remediate

getting started

remediate is a feedback widget for react. one component on the client, one helper on the server. screenshots, screen recordings, voice notes, element pins. straight to your endpoint as a structured payload.

no accounts, no saas, no storage.

install

npm install remediate
or try,,or

or set up with an agent

install the skill once, then run /remediate in any react project.

npx skills add fvckprth/remediate

then run /remediate. it'll detect your framework, pick a backend, install the package, scaffold the server route, and wire the component into your layout.

works for any agent that reads agent skills — claude code, codex, cursor, opencode, and the rest. the installer auto-detects which agents you have and drops the skill in the right place. add -g for a global install across all your projects.

after setup, the skill also handles the full lifecycle — just ask your agent:

  • add integration — "add slack" or "also send to discord" adds or replaces a backend, with fan-out support for multiple destinations in one route
  • wire auth — "pass user info" detects clerk, nextauth, or supabase and wires metadata + headers props
  • test endpoint — "test the feedback route" sends a synthetic submission via curl to verify the route works
  • upgrade — "upgrade remediate" checks npm for the latest version and handles breaking changes
  • scaffold dashboard — "build an admin page" generates a page to view submitted feedback, matched to your backend
  • capture gating — "only annotations and text notes" configures captureTypes, with conditional support for role-based or environment-based gating
  • remove — "remove remediate" cleanly uninstalls the widget, route, and package while preserving captured data

add the server route

the widget POSTs FormData. parse it with parseFeedback.

// app/api/feedback/route.ts (next.js app router)
import { parseFeedback } from "remediate/server";

export async function POST(req: Request) {
  const { submission, files } = await parseFeedback(req);

  // submission.items is the actual content — not submission.body
  const body = submission.items
    .map((item) => {
      if (item.type === "textNote") return item.text;
      if (item.type === "annotation") return item.note;
      return item.additionalText;
    })
    .filter(Boolean)
    .join("\n");

  // files is a Map<string, ParsedFile>, not an array
  for (const [, file] of files) {
    // file.blob   — raw Blob for your storage layer
    // file.type   — mime type, e.g. "image/png"
    // file.category — "screenshot" | "recording" | "voice"
    console.log(file.filename, file.category, file.blob.size);
  }

  return Response.json({ ok: true, id: submission.id });
}

submission is the structured json. the content lives in submission.items — a mixed array of text notes, annotations, photos, videos, and voice notes. files is a Map<string, ParsedFile> keyed by field name (e.g. screenshot-itm_abc123). see payload for the full shape.

works anywhere you have a web Request: app router, remix, hono, bun, deno, cloudflare workers.

add the component

import { Remediate } from "remediate";

export default function App() {
  return (
    <>
      <YourApp />
      <Remediate endpoint="/api/feedback" />
    </>
  );
}

next.js app router: Remediate is a client component. if you add it to a server component (like layout.tsx), wrap it in a file with "use client" at the top, or place it in a client component that already has one.

a floating button appears in the corner. click it, capture, submit. styles inject themselves.

passing user context

use metadata to attach user identity, session info, or anything else to every submission. it arrives verbatim as submission.metadata on the server.

<Remediate
  endpoint="/api/feedback"
  metadata={{ userId: user.id, email: user.email }}
/>
// on the server
const { submission } = await parseFeedback(req);
console.log(submission.metadata.userId); // whatever you passed

try it without a backend

skip the route. log to the console.

<Remediate onSubmit={(payload) => console.log(payload)} />

open devtools, capture something, watch the payload. when you're ready, add endpoint. both can coexist. the POST runs first, then onSubmit on success.

props

all props are optional.

prop
what is it
endpoint
url to POST feedback as FormData. the widget auto-submits to this endpoint.
onSubmit
called when the user submits. receives the full payload including captured blobs.
metadata
extra data merged into the submission (e.g. user id, tenant, feature flags).
headers
extra headers on the POST. use for auth tokens. accepts an object or a function that returns one.
onError
called if the endpoint POST fails.

what to do next

  • recipes: slack, github issues, vercel blob, postgres
  • payload: what's in the json
  • faq: common questions

requirements

  • react 18+
  • https for video and voice. localhost is fine in dev.
  • video recording is desktop-only. mobile safari has no screen-capture api.
  • cross-origin images and iframes render blank in screenshots.