@floo-one/nuxt-feedback
v0.2.4
Published
Drop-in in-app feedback dialog for Nuxt 4 — bugs and ideas to GitHub Issues, with a server-side submit hook.
Downloads
888
Readme
@floo-one/nuxt-feedback
A drop-in in-app feedback dialog for Nuxt 4. Your users press a shortcut, pick Bug or Feedback, and hit send:
- 🐞 Bug → a GitHub issue (labelled
bug) with the full context — reporter, page URL, app build, recent console errors — plus a type (crash / visual / data / performance) and a severity (critical → cosmetic). - 💬 Feedback → a GitHub issue on the repo you choose (ideas, praise, or anything else).
It's auth-agnostic (you give it a tiny function to identify the user — it never imports your auth), and when the user is already logged in it doesn't bother asking for their email.
Requirements
- Nuxt ≥ 4
@nuxt/ui≥ 4 (peer dependency — the dialog is built from Nuxt UI components your app already has)
Setup (3 steps)
1. Install
pnpm add @floo-one/nuxt-feedback2. Register and configure it
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/ui', '@floo-one/nuxt-feedback'],
feedback: {
github: { repo: 'your-org/your-repo' }, // where issues are filed
shortcut: 'g-f', // press g then f
resolveUserPath: './server/feedback-user.ts',
},
})3. Tell it who the user is
Create the file you referenced in resolveUserPath. It runs on the server, gets the request event, and returns the logged-in user (or null). Use whatever auth your app already has — the module never touches it.
// server/feedback-user.ts
import type { H3Event } from 'h3'
export default async function resolveUser(event: H3Event) {
const session = await getServerSession(event) // ← however your app reads the session
return session?.user
? { id: session.user.id, email: session.user.email, name: session.user.name }
: null
}Must never throw — return
nullwhen logged out. When it returns a user, the dialog hides the email field and attaches their identity to the report automatically. (OmitresolveUserPathentirely to always-anonymous + ask for an email.)
That's it. The dialog auto-mounts everywhere — press g then f, or open it from code:
const { open } = useFeedback()
open() // last-used type
open('bug') // or 'feature' (the Feedback tab)Environment variables
Only one secret is needed — the GitHub token:
| Variable | Required | What it's for |
| --- | --- | --- |
| NUXT_GITHUB_TOKEN | Yes | A GitHub token the server uses to create issues (bugs and ideas). Read from runtimeConfig — never exposed to the browser. |
Getting the GitHub token — create a fine-grained PAT scoped to only your target repo with Issues: Read and write, then:
# .env (local — git-ignored, never commit a real token)
NUXT_GITHUB_TOKEN=github_pat_xxxxxxxxIn production, set NUXT_GITHUB_TOKEN in your host's secret store (Coolify / Vercel / etc.) and redeploy. (No org-restricted PATs? A classic token with the repo scope works too.)
Options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| github.repo | string | — | Required. Target repo for issues, "owner/name". |
| shortcut | string | 'g-f' | Keybind in Nuxt UI defineShortcuts syntax. Pick one that doesn't clash with your app's chords. |
| github.labels.feature | string | 'enhancement' | Label for feedback issues. (Bugs use bug.) |
| resolveUserPath | string | — | Path to your identity hook (see step 3). Omit for always-anonymous. |
| resolveAppPath | string | — | Path to a hook mapping a submission to an "app bucket" (used for the app: label). Omit to fall back to context.app. |
| onSubmitPath | string | — | Path to a hook run server-side after a report is filed — notifications, logging, tracking. Receives (report, result); a throw is caught and ignored. See below. |
| enabled | boolean | true | Turn the whole thing off (e.g. per-environment). |
The GitHub token is never an option — it lives only in
runtimeConfig(NUXT_GITHUB_TOKEN) and never reaches the client bundle, responses, or logs.
How it routes
bug → GitHub issue (label: bug)
carries type (crash | visual | data | performance)
and severity (critical | blocking | annoying | cosmetic)
feedback → GitHub issue (label: enhancement)Prefixed label scheme (opt in by setting any
github.labels.*Prefixorbase): issues get<base>+type:<bug|idea>+app:<bucket>, and bugs also getseverity:<level>+category:<kind>.
A report is never silently dropped, and submit failures return a clean error toast (no provider internals leak to the client).
React to submissions (onSubmitPath)
Point onSubmitPath at a server file to run your own code after each report is
filed — ping Slack, write to a DB, track it for a "my reports" view. It runs
after the GitHub issue is created and never blocks the user's submission (a
throw is caught and logged).
// server/feedback-submit.ts
export default async function onSubmit(report, result) {
// report: { type, message, severity?, category?, user, email?, app, labels, context? }
// result: { channel: 'github', issue: { number, url } }
await fetch(process.env.SLACK_WEBHOOK!, {
method: 'POST',
body: JSON.stringify({ text: `New ${report.type}: ${result.issue.url}` }),
})
}Drop it into a repo with one prompt
Paste this into an AI coding agent (Claude Code, Cursor, …) inside the target Nuxt 4 repo:
Integrate the npm package @floo-one/nuxt-feedback into this Nuxt 4 app.
1. Install it: `pnpm add @floo-one/nuxt-feedback`. It needs @nuxt/ui v4 as a peer —
confirm this app already uses @nuxt/ui (it should).
2. In nuxt.config.ts: add '@floo-one/nuxt-feedback' to `modules`, and add a `feedback`
block with:
- github.repo: the "owner/name" of THIS app's GitHub repo (find it from the git remote)
- shortcut: 'g-f' — but first grep the codebase for existing `defineShortcuts` chords
and pick a non-colliding one if g-f is taken
- resolveUserPath: './server/feedback-user.ts'
3. Create server/feedback-user.ts that default-exports
`async (event: H3Event) => ({ id, email, name }) | null`. Wire it to THIS app's existing
auth — find how the app reads its session/user on the server and use that. Return null
when logged out; it must never throw.
4. Add `NUXT_GITHUB_TOKEN` to .env.example with a comment: server-only, a GitHub token with
"Issues: write" on the repo. Do NOT put a real token anywhere in the code or config —
I'll set the real value in the host's secret store.
5. Verify: `nuxt typecheck` (or build) passes, and the dialog opens with the shortcut.
Report what you changed and what I still need to do (set NUXT_GITHUB_TOKEN).
Hard rule: the GitHub token is a secret. Read it only via runtimeConfig (NUXT_GITHUB_TOKEN);
never a public option, never committed, never in the client bundle.Host the dashboard (live demo)
The repo ships a Dockerfile that builds the dashboard app — today a self-contained demo site with the module installed on a real page (dialog and API routes fully working), and long-term the dashboard for triaging the issues the module files.
On Coolify: create an application from this repo, pick the Dockerfile build pack (port 3000), and set one env var:
| Variable | Value |
| --- | --- |
| NUXT_GITHUB_TOKEN | A GitHub token with Issues: write on the repo demo submissions should land in (the dashboard targets floo-one/nuxt-feedback). |
Without the token the site still runs and the dialog opens — submits just fail with an error toast.
Or locally:
docker build -t nuxt-feedback-demo .
docker run -p 3000:3000 -e NUXT_GITHUB_TOKEN=github_pat_xxx nuxt-feedback-demoDevelop this module
pnpm install
pnpm dev # run the dashboard app locally
pnpm lint && pnpm test && pnpm test:types
pnpm prepack # build the distributableLicense
MIT
