Sometimes you want to give your users the ability to configure their HomeScreenWidgets to their liking. For example a weather widget that can show the weather for different locations or a todo list that can show different lists.
On iOS there are two ways to make your App Configurable. The new and recommended way by Apple is to use WidgetConfigurationIntent you can also provide backwards compatibility using SiriKit Intents
Create Widget Extension
If you create a new Widget add an extension by going
File
New
Target
Widget Extension
This time checking the box for
Include Configuration App Intent

The generated Widget code includes the following classes:
AppIntentTimelineProvider- Provides a Timeline of entries at which the System will update the Widget automatically using an AppIntent for configuration -TimelineEntry- Represents the Data Object used to build the Widget. Thedatefield is necessary and defines the point in time at which the Timeline would update -View- The Widget itself, which is built with SwiftUI -Widget- Configuration: Make note of thekindyou set in the Configuration as this is what's needed to update the Widget from Flutter.
In a separate file there is a
WidgetConfigurationIntentdefined that will determine the configuration options of the Widget inAppIntent.swift.Adjusting the Configuration Intent
You can add fields that appear in the configuration by changing the generated
ConfigurationAppIntentclass.swift@Parameter(title: "Name", default: "World") var name: StringYou can access the fields of the configuration when building the Widget UI
swiftstruct ConfigurableWidgetEntryView : View { var entry: Provider.Entry var body: some View { VStack { Text("Hello") Text(entry.configuration.name) } } }
</div>Getting Data from Flutter to the Configuration
In order for sending Data from your Flutter App to the Widget we need to use
.
InformationApp Groups require a paid Apple Developer Account
Visit the Apple Developer Portal and create a new App Group.
Add the App Group capability to
both the App Target (Runner) and your Widget Extension
in XCode.

Register App Group in Flutter
In your Flutter code register the App Group with
home_widgetdartimport 'package:home_widget/home_widget.dart'; void main() { WidgetFlutterBinding.ensureInitialized(); HomeWidget.setAppGroupId('group.YOUR_APP_GROUP_ID'); runApp(MyApp()); }Send Data
You can store data from Flutter for the Widget using
HomeWidget.saveWidgetDatato use it as options in the configuration panel.dartfinal punctuations = [ '!', '!!!', '.', '?', // Wave Emoji '\u{1F44B}', ]; await HomeWidget.saveWidgetData( 'punctuations', jsonEncode(punctuations), );Use Values from Flutter as Options in Customize Panel
In XCode and Swift add an
AppEntityand matchingEntityQueryswift@Parameter(title: "Punctuation") var punctuation: PunctuationEntity } // Make Entity Codable so home_widget // That way home_widget can best extract the values from a configuration struct PunctuationEntity: AppEntity, Codable { let id: String static var typeDisplayRepresentation: TypeDisplayRepresentation = "Punctuation" static var defaultQuery = PunctuationQuery() var displayRepresentation: DisplayRepresentation { DisplayRepresentation(title: "\(id)") } } struct PunctuationQuery: EntityQuery { func punctuations() -> [PunctuationEntity] { let userDefaults = UserDefaults(suiteName: "YOUR_APP_GROUP") do { let jsonPunctuations = (userDefaults?.string(forKey: "punctuations") ?? "[\"!\"]").data( using: .utf8)! let stringArray = try JSONDecoder().decode([String].self, from: jsonPunctuations) return stringArray.map { punctuation in PunctuationEntity(id: punctuation) } } catch { return [PunctuationEntity(id: "!")] } } func entities(for identifiers: [PunctuationEntity.ID]) async throws -> [PunctuationEntity] { let results = punctuations().filter { identifiers.contains($0.id) } return results } func suggestedEntities() async throws -> [PunctuationEntity] { return punctuations() } func defaultResult() async -> PunctuationEntity? { try? await suggestedEntities().first } }Now you can access this new parameter in your Widget Code
swiftstruct ConfigurableWidgetEntryView: View { var entry: Provider.Entry var body: some View { VStack { Text("Hello") Text(entry.configuration.name) Text(entry.configuration.punctuation.id) } } }Seeing Configuration in Flutter
You need to let home_widget know about the relation of the Intent and the
kind(what you need for updating the widget).Add
Runneras a Target of the WidgetOpen the Intent Swift File and in the Details Pane enable the
Target Membershipfor theRunnerTarget
Register Configuration in AppDelegate
In your
AppDelegate.swiftregister the Configuration Intent together with thekindof your Widgetswiftimport Flutter import UIKit import home_widget @main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) if #available(iOS 17.0, *) { HomeWidgetPlugin.setConfigurationLookup(to: [ "ConfigurableWidget": ConfigurationAppIntent.self ]) } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }To get the configuration a user made to a Widget you can use
HomeWidget.getInstalledWidgets().dartfinal configuration = await HomeWidget.getInstalledWidgets();this will give you a list of
HomeWidgetInfoin there there is aMap<String, dynamic>? configuration.WarningOn iOS Simulators
HomeWidget.getInstalledWidgets()will always return an empty list.To fully test this you should test on a real iOS Device
For our Greeting Example the Configuration would be:
json{ "name": "Documentation", "punctuation": { "id": "!!!" } }
SiriKit Intents
Before iOS 17 SiriKit Intents where used to build configurable widgets. You can use this to add backwards compatibility.
You could also use SiriKit Intents standalone however it is unclear how long Apple will support this.
Basic setup
Create Intent
Create a new SiriKit Intent File by going to
File
New
SiriKit Intent File

Create a new Intent using the
+
button in the bottom left.
In the Intent set
Category
to
Viewand enable
Intent is eligible for widgets
Add a simple parameter to the Intent. FOr our example a
Stringparameter calledname
Using Intent in Widget
To use our custom generated Intent in the generated Widget we need to to adjust the automatically generated code to conform to the correct APIs.
Adjust your
SimpleEntryto use properties instead of the Intent so we can use both the AppIntent and the SiriIntent.swiftstruct SimpleEntry: TimelineEntry { let date: Date let configuration: ConfigurationAppIntent let name: String var punctuation: String? = nil }Create an
IntentTimelineProviderswiftstruct IntentProvider: IntentTimelineProvider { typealias Entry = SimpleEntry typealias Intent = GreetingIntentIntent func placeholder(in context: Context) -> SimpleEntry { SimpleEntry(date: Date(), name: "World") } func getSnapshot( for configuration: GreetingIntentIntent, in context: Context, completion: @escaping (SimpleEntry) -> Void ) { completion(SimpleEntry(date: Date(), name: configuration.Name)) } func getTimeline( for configuration: GreetingIntentIntent, in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void ) { var entries: [SimpleEntry] = [] // Generate a timeline consisting of five entries an hour apart, starting from the current date. let currentDate = Date() for hourOffset in 0..<5 { let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! let entry = SimpleEntry(date: entryDate, name: configuration.Name) entries.append(entry) } completion(Timeline(entries: entries, policy: .atEnd)) } }In the Widget definition add code to use the appropriate configuration provider.
swiftvar body: some WidgetConfiguration { if #available(iOS 17.0, *) { AppIntentConfiguration( return AppIntentConfiguration( kind: kind, intent: ConfigurationAppIntent.self, provider: Provider() ) { entry in ConfigurableWidgetEntryView(entry: entry) .containerBackground(.fill.tertiary, for: .widget) } } else { return IntentConfiguration( kind: kind, intent: GreetingIntentIntent.self, provider: IntentProvider() ) { entry in ConfigurableWidgetEntryView(entry: entry) } } }Getting Data from Flutter to the Configuration
With SiriKit Intents you need to add a new
IntentHandlerthat can handle using Data you send from home_widget.For this add a new
Intents Extensionto your App. Using
File
New
Target
Intents Extension

In your SiriKit Intents definition add the newly created Extension as a Target

In the same configurations file add a new Type and configure it to the options you need.


Create a Field that uses this new type. Ensuring to enable
Options are provided dynamically


Implement the
IntentHandlerto handle the new Intentswiftimport Intents class IntentHandler: INExtension, GreetingIntentIntentHandling { func providePunctuationOptionsCollection(for intent: GreetingIntentIntent) async throws -> INObjectCollection<Punctuation> { let userDefaults = UserDefaults(suiteName: "YOUR_APP_GROUP") do { let jsonPunctuations = (userDefaults?.string(forKey: "punctuations") ?? "[\"!\"]").data(using: .utf8)! let stringArray = try JSONDecoder().decode([String].self, from: jsonPunctuations) let items = stringArray.map { punctuation in Punctuation(identifier: punctuation, display: punctuation) } return INObjectCollection(items: items) } catch { return INObjectCollection(items: [Punctuation(identifier: "!", display: "!")]) } } }Seeing Configuration in Flutter
Similar to the
WidgetConfigurationIntentyou can useHomeWidget.getInstalledWidgets()to get the configuration of the Widget.dartfinal configuration = await HomeWidget.getInstalledWidgets();this will give you a list of
HomeWidgetInfoin there there is aMap<String, dynamic>? configuration.For our Greeting Example the Configuration would be:
json{ "Name": "Siri", "Punctuation": { "identifier": "👋", "displayString": "👋" } }
Android
Use a dedicated FlutterActivity for widget configuration and a second Dart entry point (e.g. configureMain) so configuration always runs in a clean isolate and you do not mix “open app” with “configure widget.” The steps below describe that setup.
For a full example, check out the configurable_widget example app.
You can point android:configure at MainActivity and handle everything in main(), but that is easy to get wrong: main() runs once per process, while your activity can be recreated, resumed, or receive new intents when the user opens configuration again. You must detect “launched from widget configuration” in the right lifecycle moments—not only on the first main()—or you can show the wrong UI or miss a configure flow. Prefer the dedicated activity unless you have a strong reason not to.
Create a configuration Activity
Subclass
FlutterActivityand override the Dart entry point name so Flutter runsconfigureMaininstead ofmain:kotlinpackage es.antonborri.configurable_widget import io.flutter.embedding.android.FlutterActivity class WidgetConfigurationActivity : FlutterActivity() { override fun getDartEntrypointFunctionName(): String = "configureMain" }Keep your launcher
MainActivityon the defaultmainentry point.Register the Activity in the widget provider
In your widget’s
appwidget-providerXML, setreconfigurableand pointandroid:configureat the fully qualified class name of your configuration activity:xml<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" ... android:widgetFeatures="reconfigurable" android:configure="com.example.app.WidgetConfigurationActivity" />This enables the widget’s configuration / “Settings” flow from the system UI.
Declare the Activity in AndroidManifest.xml
Register the configuration activity with an intent filter for
android.appwidget.action.APPWIDGET_CONFIGURE(same action asAppWidgetManager.ACTION_APPWIDGET_CONFIGURE). Mirror the Flutter embedding setup you use onMainActivity(LaunchTheme,NormalTheme,configChanges, etc.):xml<activity android:name=".WidgetConfigurationActivity" android:exported="true" android:launchMode="singleTop" ... > <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" /> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> </intent-filter> </activity>Add `configureMain` and listen for configuration
Define a second entry point with
@pragma('vm:entry-point')so tree shaking keeps it. The name must matchgetDartEntrypointFunctionName():dart@pragma('vm:entry-point') Future<void> configureMain() async { WidgetsFlutterBinding.ensureInitialized(); final configuredWidgetId = await HomeWidget.initiallyLaunchedFromHomeWidgetConfigure(); if (configuredWidgetId != null) { return runApp(MaterialApp( home: YourConfigurationScreen(widgetId: configuredWidgetId), )); } return main(); }Call
HomeWidget.initiallyLaunchedFromHomeWidgetConfigure()as early as possible afterWidgetsFlutterBinding.ensureInitialized(). If the user opened this activity to configure a widget, theFutureresolves to that instance’s widget ID (string); otherwisenull(you can fall through tomain()).When a configure launch is detected, the plugin sets the activity result to
RESULT_CANCELEDuntil you finish successfully—so canceling or leaving the flow behaves correctly.Build the configuration UI and persist with HomeWidget
In your configuration screen, use the widget ID to scope stored keys per instance (for example
name.$widgetIdandpunctuation.$widgetId), read/write them withHomeWidget.getWidgetData/HomeWidget.saveWidgetData, and refresh the widget withHomeWidget.updateWidget(passqualifiedAndroidNamefor yourAppWidgetProvider/ receiver class if needed).Finish configuration
When the user saves (or you are done applying settings), call:
dartawait HomeWidget.finishHomeWidgetConfigure();This completes the configuration activity with
RESULT_OK, returns theEXTRA_APPWIDGET_IDto the system, and closes the activity.
For platform details and UI expectations, see the Android App Widget configuration guide.