MiteSDK

Feedback

Send bugs, questions, ideas, and other messages from your users with device context and attachments.

Feedback is any message a user sends you: a bug, a question, an idea, or anything else. Send it with mite.feedback.send() or the useFeedback() hook, or mount ShakeToReport and let the SDK run the whole bug flow.

Only message is required. The SDK adds device info, the current identity, the build, and the navigation trail to every message.

await mite.feedback.send({ type: 'question', message: 'How do I export my data?' })

Feedback types

type tells your dashboard what kind of message it is. It defaults to 'other'.

typeUse it forBug-only fields allowed
'bug'Something is brokenYes: steps, expected, actual
'question'The user needs helpNo
'idea'A suggestion for youNo
'other'Anything else. The defaultNo

feedback.send does not post to the public feature board. To post a request there, where other users vote on it, use mite.features.request().

steps, expected, and actual exist only on type: 'bug'. The input type is a discriminated union, so TypeScript rejects them on any other type:

await mite.feedback.send({
  type: 'bug',
  message: 'Checkout button does nothing',
  steps: '1. Add an item\n2. Tap checkout',
  expected: 'The payment sheet opens',
  actual: 'Nothing happens',
})

await mite.feedback.send({
  type: 'question',
  message: 'Can I pay yearly?',
  steps: '...', // Type error: steps is only allowed when type is 'bug'
})

Using the hook

useFeedback() wraps mite.feedback.send() with its submission state.

import { useFeedback } from '@usemite/sdk'
import { useState } from 'react'

export default function FeedbackScreen() {
  const { send, sending, error, refusal } = useFeedback()
  const [message, setMessage] = useState('')

  const handleSend = async () => {
    const result = await send({ type: 'question', message })
    if (result.ok) setMessage('')
  }

  return <Button onPress={handleSend} disabled={sending} title="Send" />
}

send rejects when the request fails, and also sets error. Wrap the call in try/catch if you handle the failure inline.

A plan quota refusal does not reject. It is an expected state, not a fault. It comes back as ok: false in the result and on refusal.

Return value

FieldTypeDescription
send(input: SendFeedbackInput) => Promise<SendFeedbackResult>Send feedback
sendingbooleanWhether a request is in flight
errorError | nullLast request error
lastResponseSubmitBugReportResponse | nullResponse from the last successful send
refusalMiteQuotaRefusal | nullSet when the account is over a plan limit
reset() => voidClear error, lastResponse, and refusal

Input fields

type FeedbackType = 'bug' | 'question' | 'idea' | 'other'

type SendFeedbackInput = {
  message: string
  title?: string
  reporter?: { name?: string; email?: string }
  screenshot?: string
  attachments?: MiteAttachment[]
  context?: Record<string, string>
} & (
  | { type: 'bug'; steps?: string; expected?: string; actual?: string }
  | { type?: 'question' | 'idea' | 'other' }
)
FieldDescription
messageWhat the user wrote. Required
typeOne of the feedback types. Defaults to 'other'
titleShown in the dashboard. The server uses the message's first line when you omit it
reporterName and email the user typed in. Defaults to the identified user
screenshotLocal URI of a screenshot. Shorthand for the first attachment
attachmentsFiles to upload. See Attachments
contextFlat string keys of your own, such as a feature flag or an experiment bucket
steps, expected, actualSteps to reproduce, expected result, and actual result. type: 'bug' only

There is no priority field. Priority is set during triage in the Mite dashboard, not by the app.

The result

feedback.send resolves to the same result as the 1.0 submitBug:

type SendFeedbackResult =
  | {
      ok: true
      report: SubmitBugReportResponse
      /** Set when the attachments did not fit in the storage quota. */
      droppedAttachments?: { count: number; refusal: MiteQuotaRefusal }
    }
  | { ok: false; refusal: MiteQuotaRefusal }

interface SubmitBugReportResponse {
  id: string
  status: 'NEEDS_TRIAGE'
}

New feedback always lands as NEEDS_TRIAGE. ok: false means the account is over a plan limit and Mite created nothing.

const result = await mite.feedback.send({ type: 'bug', message })

if (result.ok) {
  console.log(result.report.id)
}

What the SDK fills in automatically

You do not set these yourself:

  • Identity. The current anonymous ID, and the identified user when there is one.
  • Device info. Brand, model, OS name and version, memory, and CPU architectures, read with expo-device.
  • Build. app_version from expo-application or expo-constants. eas_update_id, channel, and runtime_version from expo-updates when it is installed. Mite files the message under the release the user was running.
  • Triage context. current_route, last_error_message, last_error_stack, and network_state when the SDK knows them. network_state needs the optional @react-native-community/netinfo peer dependency. Your context keys win over these.
  • Navigation trail. The last screens visited, when breadcrumbs are on.

In anonymous-only mode the SDK sends the anonymous ID only. It leaves out the user ID, reporter, and device info.

Attachments

Pass local file URIs. The SDK requests an upload URL, uploads each file, and sends the message with references to the stored files.

await mite.feedback.send({
  type: 'bug',
  message: 'The avatar overlaps the username at small font scales.',
  screenshot: screenshotUri,
  attachments: [{ uri: logFileUri, type: 'text/plain', name: 'log.txt' }],
})

screenshot becomes the first attachment. uri is anything fetch() can read: an expo-image-picker result, a react-native-view-shot capture, or a file:// path. type defaults to the file's own MIME type, then image/jpeg.

Attachments cannot be uploaded offline. When the offline queue holds a message, the SDK drops its attachments and logs a [Mite] warning. The message itself is still sent on retry.

Error handling

feedback.send throws when the API key is missing, when the server rejects the request, or when the network fails. On a network error with the offline queue on, the SDK queues the message and still throws, so your UI decides what to show:

try {
  await mite.feedback.send({ type: 'bug', message })
  showToast('Thanks for the report!')
} catch {
  showToast(
    mite.pendingRequestCount > 0
      ? "Saved. We'll send it when you're back online."
      : 'Could not send your report.',
  )
}

Server requirement

feedback.send posts to /api/v1/feedback, so it needs a Mite server that has that route. If you point endpoint at your own server, update the server first. The deprecated submitBug keeps posting to /api/v1/bug-reports.

Moving from submitBug

submitBug and useBugReport still work. submitBug is deprecated and is removed in 2.0.

1.0Now
mite.submitBug({ title, description })mite.feedback.send({ type: 'bug', message })
descriptionmessage
steps_to_reproduce, expected_behavior, actual_behaviorsteps, expected, actual
reporter_name, reporter_emailreporter: { name, email }
environmentcontext
useBugReport() → submitBug, submittinguseFeedback() → send, sending

The result shape is the same, so code that reads result.ok, result.report, and result.refusal does not change.

On this page