One wire format
Every platform produces the exact same GitHub issue body — pinned by a shared spec and a golden-fixture conformance suite that runs in every SDK’s CI. Apple, Android, and Web are byte-identical.
let feedback = FeedbackClient( appName: "Acme", transport: GitHubDirectTransport(owner: "acme", repo: "feedback", token: token))try await feedback.submit(.init(type: .bug, title: "Crash on launch", description: "Steps…"))import { mountFeedbackWidget } from '@appfeedback/widget'import { RelayTransport } from '@appfeedback/core'
mountFeedbackWidget(el, { transport: new RelayTransport({ endpoint: '/api/feedback' }), appName: 'Acme', appVersion: '1.0.0',})val client = androidFeedbackClient( transport = GitHubDirectTransport(owner = "acme", repo = "feedback", token = token), appName = "Acme", appVersion = "1.0.0",)client.submit(FeedbackReport(type = FeedbackType.BUG, title = "Crash on launch", description = "Steps…"))One wire format
Every platform produces the exact same GitHub issue body — pinned by a shared spec and a golden-fixture conformance suite that runs in every SDK’s CI. Apple, Android, and Web are byte-identical.
You hold the token
On the web, the SDK talks only to a relay you deploy — Cloudflare, Firebase, Appwrite, or your own. Your GitHub credential never ships to the browser.
Drop-in UI
A themeable feedback sheet on Apple and Android (Compose), plus a feedback widget + React component on the web — or go headless and submit from your own UI.
GitHub is the backend
No new service to run. Feedback lands as a labelled GitHub issue your team already triages; the AppFeedback inbox reads it straight back.
Native apps (Apple, Android) can talk to GitHub directly with a Keychain-held token, or use the same relay. See the security model →