@floo-one/nuxt-feedback

Drop-in in-app feedback dialog for Nuxt 4 — bugs and ideas to GitHub Issues, with a server-side submit hook.
1,400
Install
npm install @floo-one/nuxt-feedback
Latest Version:0.2.4
Vue:3
License:MIT
Last Updated:Jul 15, 2026
Links: GitHub  ·  npm
Maintainer: floo-one

@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-feedback

2. 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 null when logged out. When it returns a user, the dialog hides the email field and attaches their identity to the report automatically. (Omit resolveUserPath entirely 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_xxxxxxxx

In 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.*Prefix or base): issues get <base> + type:<bug|idea> + app:<bucket>, and bugs also get severity:<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-demo

Develop this module

pnpm install
pnpm dev          # run the dashboard app locally
pnpm lint && pnpm test && pnpm test:types
pnpm prepack      # build the distributable

License

MIT

Version History

Version Released Vue License
0.2.4 ^3.0 MIT
0.2.3 ^3.0 MIT
0.2.2 ^3.0 MIT
0.2.0 ^3.0 MIT
0.1.0 ^3.0 MIT