Migrate Frontend FormsCAUTION
Developer-first error tracking and performance monitoring
Overview
Developer-first error tracking and performance monitoring
42a3375c14f5OBSERVED · 2026-09-29What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
---
name: migrate-frontend-forms
description: Guide for migrating forms from the legacy JsonForm/FormModel system to the new TanStack-based form system.
---
# Form Migration Guide
This skill helps migrate forms from Sentry's legacy form system (JsonForm, FormModel) to the new TanStack-based system.
## Feature Mapping
| Old System | New System | Notes |
| -------------------- | --------------------- | ---------------------------------------------------- |
| `saveOnBlur: true` | `AutoSaveForm` | Default behavior |
| `confirm` | `confirm` prop | `string \| ((value) => string \| undefined)` |
| `showHelpInTooltip` | `variant="compact"` | On layout components |
| `disabledReason` | `disabled="reason"` | String shows tooltip |
| `extraHelp` | JSX in layout | Render `<Text>` below field |
| `getData` | `mutationFn` | Transform data in mutation function |
| `mapFormErrors` | Request error adapter | Explicit for regular forms; provided for auto-save |
| `saveMessage` | `onSuccess` | Show toast in mutation onSuccess callback |
| `formatMessageValue` | `onSuccess` | Control toast content in onSuccess callback |
| `resetOnError` | `onError` | Call form.reset() in mutation onError |
| `saveOnBlur: false` | `useScrapsForm` | Use regular form with explicit Save button |
| (automatic) | `form.reset()` | Call after successful mutation if form stays on page |
| `help` | `hintText` | On layout components |
| `label` | `label` | On layout components |
| `required` | `required` | On layout + Zod schema |
## Feature Details
### confirm → `confirm` prop
**Old:**
```tsx
{
name: 'require2FA',
type: 'boolean',
confirm: {
true: 'Enable 2FA for all members?',
false: 'Allow members without 2FA?',
},
isDangerous: true,
}
```
**New:**
```tsx
<AutoSaveForm
name="require2FA"
confirm={value =>
value
? 'Enable 2FA for all members?'
: 'Allow members without 2FA?'
}
{...}
>
```
### showHelpInTooltip → `variant="compact"`
**Old:**
```tsx
{
name: 'field',
help: 'This is help text',
showHelpInTooltip: true,
}
```
**New:**
```tsx
<field.Layout.Row
label="Field"
hintText="This is help text"
variant="compact"
>
```
### disabledReason → `disabled="reason"`
**Old:**
```tsx
{
name: 'field',
disabled: true,
disabledReason: 'Requires Business plan',
}
```
**New:**
```tsx
<field.Input
disabled="Requires Business plan"
{...}
/>
```
### extraHelp → JSX
**Old:**
```tsx
{
name: 'sensitiveFields',
help: 'Main help text',
extraHelp: 'Note: These fields apply org-wide',
}
```
**New:**
```tsx
<field.Layout.Stack label="Sensitive Fields" hintText="Main help text">
<field.TextArea {...} />
<Text size="sm" variant="muted">
Note: These fields apply org-wide
</Text>
</field.Layout.Stack>
```
### getData → `mutationFn`
The `getData` function transformed field data before sending to the API. In the new system, handle this in the `mutationFn`.
**Old:**
```tsx
// Wrap field value in 'options' key
{
name: 'sentry:csp_ignored_sources_defaults',
type: 'boolean',
getData: data => ({options: data}),
}
// Or extract/transform specific fields
{
name: 'slug',
getData: (data: {slug?: string}) => ({slug: data.slug}),
}
```
**New:**
```tsx
<AutoSaveForm
name="sentry:csp_ignored_sources_defaults"
schema={schema}
initialValue={project.options['sentry:csp_ignored_sources_defaults']}
mutationOptions={{
mutationFn: data => {
// Transform data before API call (equivalent to getData)
const transformed = {options: data};
return fetchMutation({
url: `/projects/${organization.slug}/${project.slug}/`,
method: 'PUT',
data: transformed,
});
},
}}
>
{field => (
<field.Layout.Row label="Use default ignored sources">
<field.Switch checked={field.state.value} onChange={field.handleChange} />
</field.Layout.Row>
)}
</AutoSaveForm>
```
**Simpler pattern** - If you just need to wrap the value:
```tsx
mutationOptions={{
mutationFn: fieldData => {
return fetchMutation({
url: `/projects/${org}/${project}/`,
method: 'PUT',
data: {options: fieldData}, // getData equivalent
});
},
}}
```
**Important: Typing mutations correctly**
The `mutationFn` should be typed with the API's data type (e.g., `Partial<Organization>`, `Partial<Project>`), **not** the schema-inferred type. The schema is for client-side field validation only — the mutation receives whatever the API endpoint accepts. Tying the mutation to the schema couples two unrelated concerns and can cause type errors when the schema types don't exactly match the API types.
```tsx
// ❌ Don't use generic types - breaks field type narrowing
mutationOptions={{
mutationFn: (data: Record<string, unknown>) => {
return fetchMutation({url: '/user/', method: 'PUT', data: {options: data}});
},
}}
// ❌ Don't tie mutation type to the zod schema
mutationOptions={{
mutationFn: (data: Partial<z.infer<typeof preferencesSchema>>) => {
return fetchMutation({url: '/user/', method: 'PUT', data: {options: data}});
},
}}
// ✅ Use the API's data type
mutationOptions={{
mutationFn: (data: Partial<UserDetails>) => {
return fetchMutation({url: '/user/', method: 'PUT', data: {options: data}});
},
}}
```
Make sure the zod schema's types are compatible with (i.e., assignable to) the API type. For example, if the API expects a string unioTrust audit
CAUTIONgrade B · trust 89/100 Install with care. The audit found things worth knowing before you trust its output.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | WARN |
| L1 | Static analysis of the code | NA |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (2)
.claude/skills
api-docs/.node-version
Gates applied: no_behavioural_pass.
42a3375c14f5full audit observations/trust-audit/skill/getsentry__migrate-frontend-forms.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-09-29 | 42a3375c14f5 | CAUTION | B | 89 | first audit |
Questions
What does the Migrate Frontend Forms skill do?
Developer-first error tracking and performance monitoring
Is Migrate Frontend Forms safe to install?
With care. The audit graded it B (89/100) and found 2 things worth knowing before you trust this skill, listed below with the exact line each was found on.
What can Migrate Frontend Forms access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
How current is this page?
The grade is for one exact copy of the source (42a3375c14f5), read on 2026-09-29. The repository is watched, and a new audit runs when it changes — this is the first audit.