Feedback

What a report can carry, how screenshots are uploaded, and what happens when a workspace reaches its monthly allowance.

A report is one call. Everything but message is optional.

const { feedback, warnings } = await studio.feedback.submit({
  message: 'Checkout spins forever on the last step.',
  rating: 2,
  tags: ['Bug', 'Checkout'],
  screen: '/checkout/confirm',
  appVersion: '2.4.1',
  build: '241',
  metadata: { cartSize: 3, plan: 'gold' },
});
FieldWhat it's for
messageWhat the user wrote. 1–5,000 characters.
rating1–5, when you ask for one. Ratings of 2 or lower are flagged in the inbox.
tagsUp to 5. Unknown tags are created, up to 100 per project.
screenWhere the user was. A route works well.
appVersion, buildFilled from the device when left out.
metadataYour own context, as a JSON object. Pro and Team.
attachmentIdsScreenshots, from uploads.screenshot().
countryISO-3166 alpha-2, when your app knows it better than the device locale.

The device context — platform, OS, model, locale — comes from the device and doesn't need passing.

Warnings

A report that is accepted with something left out resolves normally and says so in warnings. Each entry starts with a code:

WarningMeaning
metadata_ignoredThe workspace is on Free, so metadata was dropped.
tags_droppedThe project already has 100 tags, so the new names weren't created.
over_quotaThe workspace is past this month's allowance. The report was still saved.

Warnings are for you, not your users. Log them; don't show them.

Screenshots

Uploading is its own step, so a slow upload never holds up the report:

const attachmentId = await studio.uploads.screenshot({ uri: capture.uri });

await studio.feedback.submit({
  message: 'The chart overlaps the legend.',
  attachmentIds: [attachmentId],
});

uri is a local file (file://…) or anything fetch can read. PNG, JPEG or WebP, up to 5 MB. The bytes go straight to private storage; Studio only ever shows them to your team through links that expire within minutes.

Each plan allows a number of screenshots per report — 1 on Free, 3 on Pro and Team — and a total amount of storage across the workspace. Once storage is full, uploads.screenshot() fails with storage_full and reports still go through without images.

Monthly allowance

Reports count against the workspace's allowance for the calendar month (UTC). Going over doesn't lose anything your users send:

  • Free: reports past the allowance are saved but held out of the inbox until the workspace upgrades.
  • Pro and Team: reports keep arriving for another 20%, and the workspace's owners get an email.

Past that, submit() fails with quota_exceeded until the next month or an upgrade.

Rate limits are separate and the same on every plan: 5 reports a minute and 50 a day per install. A limited call fails with rate_limited and carries retryAfter in seconds.

Handling errors

Every failure is a StudioError with a stable code. Branch on the code, never on the message:

import { isStudioError } from 'panelui-studio';

try {
  await studio.feedback.submit({ message });
} catch (error) {
  if (isStudioError(error) && error.code === 'rate_limited') {
    showToast(`Try again in ${error.retryAfter ?? 60} seconds.`);
  } else {
    showToast("Couldn't send that. Check your connection and try again.");
  }
}

The full list is in the API reference.

On this page