Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Widget Configuration

Perry widgets support user-configurable parameters. On iOS/watchOS, these compile to AppIntent configurations (the “Edit Widget” sheet). On Android/Wear OS, they compile to a Configuration Activity.

Status: the full TopSitesWidget declaration below compile-links cleanly on the host LLVM target via docs/examples/widgets/snippets.ts, so the config: { ... } shape is verified against parse_config_params in crates/perry-hir/src/lower.rs. The shorter fragments lower on the page (just a provider: body, just a config: object) are rendered as plain text — they’re not standalone declarations. The cross-compile targets themselves (--target ios-widget/ android-widget/watchos-widget/wearos-tile) still aren’t driven by the doc-tests harness — each needs --app-bundle-id and a platform SDK (#194).

Defining Config Fields

Add a config object to your Widget() declaration. Each field specifies a type, allowed values, a default, and a display title.

Widget({
    kind: "TopSitesWidget",
    displayName: "Top Sites",
    description: "Your top performing sites",
    supportedFamilies: ["systemSmall", "systemMedium"],
    appGroup: "group.com.example.shared",

    config: {
        sortBy: {
            type: "enum",
            values: ["clicks", "impressions", "ctr", "position"],
            default: "clicks",
            title: "Sort By",
        },
        dateRange: {
            type: "enum",
            values: ["7d", "28d", "90d"],
            default: "7d",
            title: "Date Range",
        },
    },

    entryFields: {
        total: "number",
        label: "string",
    },

    provider: async (config: { sortBy: string; dateRange: string }) => {
        const res = await fetch(
            `https://api.example.com/stats?sort=${config.sortBy}&range=${config.dateRange}`,
        )
        const data = await res.json()
        return {
            entries: [{ total: data.total, label: data.label }],
            reloadPolicy: { after: { minutes: 30 } },
        }
    },

    render: (entry) =>
        VStack([
            Text(`${entry.total}`, { font: "title", fontWeight: "bold" }),
            Text(entry.label, { font: "caption", color: "secondary" }),
        ]),
})

Supported Parameter Types

TypeTypeScriptDescription
Enum{ type: "enum", values: [...], default: "...", title: "..." }Picker with fixed choices
Boolean{ type: "bool", default: true, title: "..." }Toggle switch
String{ type: "string", default: "...", title: "..." }Free-text input

Accessing Config in the Provider

The provider function receives the current config values as its argument. The config object keys match the field names you defined:

provider: async (config: { sortBy: string; dateRange: string }) => {
  // config.sortBy === "clicks" | "impressions" | "ctr" | "position"
  // config.dateRange === "7d" | "28d" | "90d"
  const url = `https://api.example.com/data?sort=${config.sortBy}`;
  const res = await fetch(url);
  const data = await res.json();
  return { entries: [data] };
},

When the user changes a config value, the system calls your provider again with the updated config.

Boolean Config Example

config: {
  showDetails: {
    type: "bool",
    default: true,
    title: "Show Details",
  },
},

Platform Mapping

iOS / watchOS (AppIntent)

Perry generates a Swift WidgetConfigurationIntent struct with @Parameter properties and AppEnum types for each enum field. The widget uses AppIntentConfiguration instead of StaticConfiguration.

Generated output (auto-generated, not hand-written):

  • {Name}Intent.swift – contains the AppEnum cases and the intent struct
  • The provider conforms to AppIntentTimelineProvider instead of TimelineProvider
  • Config values are serialized to JSON and passed to the native provider function

Users configure the widget by long-pressing and selecting “Edit Widget”, which presents the system-generated AppIntent UI.

Android / Wear OS (Configuration Activity)

Perry generates a {Name}ConfigActivity.kt with Spinner controls for enum fields and Switch controls for boolean fields. Values are persisted in SharedPreferences keyed by widget ID.

Generated output:

  • {Name}ConfigActivity.kt – Activity with UI controls and a Save button
  • widget_info_{name}.xml – includes android:configure pointing to the config activity
  • AndroidManifest snippet includes an <activity> entry with APPWIDGET_CONFIGURE intent filter

The config activity launches automatically when the user first adds the widget.

Build Commands

# iOS
perry widget.ts --target ios-widget --app-bundle-id com.example.app -o widget_out

# Android
perry widget.ts --target android-widget --app-bundle-id com.example.app -o widget_out

Next Steps