Skip to content

install.yaml Schema Reference

View raw .md

Canonical schema for the declarative install.yaml manifest consumed by plugin:install's manifest flow.

On this page

Overview#

install.yaml is the declarative manifest that drives plugin:install's manifest flow. The file is parsed by ManifestParser (lib/src/installer/manifest_parser.dart) into a typed InstallManifest (lib/src/installer/install_manifest.dart) and consumed by ManifestInstaller, which translates each section into deferred InstallOperations on the PluginInstaller transaction. Nothing writes to disk until commit(dryRun:, force:) fires.

The schema is intentionally tolerant: every section except plugin_name is optional, every section factory returns a sensible empty default when absent, and partial manifests parse cleanly. Schema violations raise ManifestValidationException; raw YAML syntax errors raise FormatException.

Top-Level Fields#

Field Type Required Default Source
plugin_name string yes (none) manifest_parser.dart:112
dependencies map no empty manifest_parser.dart:124
publish map no empty manifest_parser.dart:127
json_merge map no empty manifest_parser.dart:128
magic map no empty manifest_parser.dart:129
native map no empty manifest_parser.dart:132
env map no empty manifest_parser.dart:135
prompts list no empty manifest_parser.dart:136
placeholders map no empty manifest_parser.dart:137
post_install map no empty manifest_parser.dart:138
bootstrap_command string no null manifest_parser.dart:141

plugin_name#

The plugin's pubspec package name. Used to scope .dowel/installed/<plugin>.json reversibility records, the plugins.json registry entry, and the generated lib/app/_plugins.g.dart import line.

Validation regex (manifest_parser.dart:31):

text
^[a-z_][a-z0-9_]*$

The value must start with a lowercase letter or underscore, followed by any number of lowercase letters, digits, or underscores. An empty string raises ManifestValidationException('plugin_name must not be empty.').

yaml
plugin_name: example_plugin

dependencies#

Pubspec mutations. The dependencies: map (note the YAML key is plural, but the typed model is PubspecDeps) carries three sub-keys, all optional.

Sub-key Type Translates to
pubspec Map<String, String> dependencies: entries in the consumer's pubspec.yaml
dev_pubspec Map<String, String> dev_dependencies: entries
pubspec_assets List<String> Paths appended under flutter.assets:

Empty maps and missing keys are interchangeable; both produce an empty PubspecDeps.

yaml
dependencies:
  pubspec:
    intl: ^0.20.0
  dev_pubspec:
    mocktail: ^1.0.0
  pubspec_assets:
    - assets/example/

publish#

Stub-name to target-path mapping. Each entry becomes a publishConfig operation: the named stub is read from the plugin's install/ directory, placeholder-substituted, and written to the target path inside the consumer project.

Key shape Value shape
Stub name (string) Target path (string)

Non-string values raise FormatException('publish entry "<key>" must map to a String target path; got <type>.').

yaml
publish:
  install/example_config.dart.stub: lib/config/example.dart

json_merge#

Map of target JSON file path to merge spec. Each entry triggers a mergeJson operation: the source stub is parsed as JSON, then merged into the target file according to additive.

Field Type Required Default Notes
source string yes (none) Stub key whose content is parsed as JSON. Missing or non-string raises FormatException('json_merge entry missing required "source" string.').
additive bool no true When true, existing keys in the target file survive a collision. When false, source keys overwrite target keys.
yaml
json_merge:
  assets/lang/en.json:
    source: install/lang/en.json
    additive: true

magic#

Magic-framework integration block. Every slot is optional; the section defaults to an empty integration when absent.

Field Type Translates to
provider string (PascalCase) injectProvider(provider)
config_factory string injectConfigFactory(name)
routes string injectRoute(name)

Validation regex for provider (manifest_parser.dart:34):

text
^[A-Z][A-Za-z0-9]*$

The value, when present, must be a PascalCase identifier (uppercase first letter, alphanumerics afterwards). A non-matching value raises ManifestValidationException('magic.provider "<value>" does not match PascalCase regex ^[A-Z][A-Za-z0-9]*$.').

yaml
magic:
  provider: ExampleServiceProvider
  config_factory: exampleConfig
  routes: registerExampleRoutes

native#

Per-platform native configuration. Each platform sub-section is independently optional.

native.android#

Field Type Translates to
permissions List<String> <uses-permission> entries in AndroidManifest.xml
meta_data Map<String, String> <meta-data> entries in AndroidManifest.xml
activities List<{name, exported?, task_affinity?, intent_filters?}> <activity> elements inside <application> in AndroidManifest.xml
gradle.plugins List<{id, version?}> Gradle plugin block entries
gradle.dependencies List<{scope, notation}> Gradle dependency block entries

Each activities entry needs name (an entry without it raises FormatException('Android activity missing required "name".')). exported is false when omitted. task_affinity sets android:taskAffinity; an empty string "" is an empty affinity and omitting the key omits the attribute. Each intent_filters entry takes auto_verify (bool), actions and categories (lists of strings) and data (a list of {scheme, host, path, path_prefix} maps, one <data> element each).

An activity is idempotent by content. The same name with an equal set of intent filters (in any order) is left alone, and the same name with different filters is never rewritten: the install finishes and warns with the block it expected. exported and task_affinity are not part of that comparison.

Gradle plugin entries without id raise FormatException('Gradle plugin missing required "id".'). Gradle dependency entries without both scope and notation raise FormatException('Gradle dependency entry requires both "scope" and "notation".').

yaml
native:
  android:
    permissions:
      - android.permission.INTERNET
    meta_data:
      io.flutter.embedded_views_preview: "true"
    activities:
      - name: com.example.CallbackActivity
        exported: true
        task_affinity: ""
        intent_filters:
          - auto_verify: true
            actions: [android.intent.action.VIEW]
            categories:
              - android.intent.category.DEFAULT
              - android.intent.category.BROWSABLE
            data:
              - {scheme: https, host: auth.example.com, path: /callback}
    gradle:
      plugins:
        - id: com.example.gradle.plugin
          version: "1.0.0"
      dependencies:
        - scope: implementation
          notation: "com.example:lib:1.0.0"

native.ios#

Field Type Notes
info_plist Map<String, Object> Values may be String, bool, num, or List. Runtime-type branching happens in the dispatcher.
url_schemes List<String> Custom URL schemes registered under CFBundleURLTypes, written without the :// suffix. One CFBundleURLTypes dict (role Editor) is appended per scheme; a scheme any existing dict already lists is skipped.
entitlements Map<String, Object> Values may be String, bool, or a list of strings. Written to every entitlements file the application target signs with; see below.
podfile.platform_version string (optional) Informational in v1: no chain method consumes it yet.
podfile.pods List<String> Pod declarations appended to target 'Runner'.
yaml
native:
  ios:
    info_plist:
      NSExampleUsageDescription: "Reason shown in iOS permission dialog"
      UIBackgroundModes: ["fetch"]
    url_schemes:
      - com.example.app
    entitlements:
      com.apple.security.keychain: true
      com.apple.developer.applesignin: [Default]
    podfile:
      platform_version: "13.0"
      pods:
        - "ExamplePod"

entitlements keys go into every entitlements file the application target signs with, one per distinct CODE_SIGN_ENTITLEMENTS value across its build configurations, and the Xcode build settings are left as they are. A project that signs with none gets Runner/Runner.entitlements written and the target pointed at it. A list value is merged entry by entry into the array the file already carries, so entries another plugin or the app put there survive. A value that is not a string, a bool, or a list of strings fails the install. Both the plist and project.pbxproj writes are helper-backed and sit outside the transaction's .tmp rollback; see PluginInstaller DSL for the cases that warn instead of write.

native.macos#

Shape mirrors native.ios exactly. Same field set (info_plist, url_schemes, entitlements, podfile.platform_version, podfile.pods), same dispatcher rules. A macOS Flutter project signs Debug and Release with different files (Runner/DebugProfile.entitlements, Runner/Release.entitlements), so an entitlement lands in both. See the Complete Example for a macOS block in context.

native.web#

Field Type Translates to
head_scripts List<String> HTML snippets injected before </head> in web/index.html
meta_tags List<Map<String, String>> <meta> attribute maps appended to web/index.html
yaml
native:
  web:
    head_scripts:
      - '<script src="example.js"></script>'
    meta_tags:
      - {name: "description", content: "Example plugin metadata"}

env#

Environment variables to inject into the consumer's .env. Map of var name to spec.

Field Type Required Notes
default scalar yes Default value written into .env. Coerced via toString(). Missing raises FormatException('env entry missing required "default".').
comment string no Advisory documentation. The v1 dispatcher does NOT emit the comment into the .env file.

Non-map entries raise FormatException('env entry "<key>" must be a map with "default" + optional "comment"; got <type>.').

yaml
env:
  EXAMPLE_KEY:
    default: example_value
    comment: "Example plugin runtime config"

prompts#

Ordered list of prompts driven immediately at the start of install (before any deferred operation runs). Prompt answers populate the prompt-result map that placeholders interpolates.

Field Type Required Notes
key string yes Unique within the prompts list. Duplicates raise ManifestValidationException('Duplicate prompt key "<key>" in prompts list.').
type string yes One of string, bool, choice.
question string yes Human-readable question text.
default scalar no Stored as a String regardless of type for uniform placeholder substitution.
options List<String> no For choice prompts: the valid option set. Empty for non-choice types.

Missing key, type, or question raises FormatException('Prompt entry requires "key", "type", and "question" strings.').

yaml
prompts:
  - {key: configPath, type: string, default: "~/.example.conf", question: "Config file path?"}
  - {key: mode, type: choice, options: [dev, staging, prod], default: dev, question: "Deployment mode?"}
  - {key: enableFeature, type: bool, default: false, question: "Enable optional feature?"}

placeholders#

Map of placeholder key to value template. Values may reference prompt answers via {{ prompts.KEY }}. Whitespace around the dotted reference is optional, so {{prompts.x}} and {{ prompts.x }} are both accepted.

Reference regex (manifest_parser.dart:39):

text
\{\{\s*prompts\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}

Every {{ prompts.X }} reference must resolve to a declared prompt key. Unknown references raise ManifestValidationException('Placeholder "<placeholderKey>" references unknown prompt key "<referenced>".').

yaml
placeholders:
  configFilePath: "{{ prompts.configPath }}"
  runtimeMode: "{{ prompts.mode }}"

post_install#

Post-install shell operations plus a final info message. Every sub-field is optional.

Field Type Notes
run List<{cmd, args?}> Shell commands run unconditionally at commit phase. cmd is required; missing raises FormatException('Shell spec missing required "cmd".').
ask_to_run List<{prompt, cmd, args?}> Shell commands run only after user confirmation. The prompt fires immediately at install time; on yes the shell call is deferred. Missing prompt or cmd raises FormatException('ask_to_run entry requires "prompt" and "cmd" strings.').
message string Optional info message emitted after a Success result.
yaml
post_install:
  run:
    - cmd: dart
      args: [format, lib/config/example.dart]
  ask_to_run:
    - prompt: "Run pub get now?"
      cmd: flutter
      args: [pub, get]
  message: |
    example_plugin installed.

bootstrap_command#

Optional plugin-specific bootstrap command name AUTO-RUN after a Success result. On a non-dry-run install, once the plugin is registered (plugins.json + lib/app/_plugins.g.dart regenerated), plugin:install spawns the declared command as a fresh dispatcher subprocess so the just-registered plugin command actually executes. The chained command is a plugin command that was only just written to _plugins.g.dart, so it is not loaded in the current process; a subprocess is the only way to reach it.

Dispatcher resolution:

  1. ./bin/dowel <name> --non-interactive when a bin/dowel wrapper exists at the consumer project root (the AOT fast-CLI path).
  2. Otherwise dart run :dispatcher <name> --non-interactive when bin/dispatcher.dart exists (Windows, or a failed fast-CLI build).
  3. When neither resolves, plugin:install prints the one-line bootstrap hint (Bootstrap with: dowel <name>) instead, so the operator can run it manually.

--non-interactive is ALWAYS forwarded to the chained command so an interactive bootstrap (e.g. starter:install) cannot hang waiting on stdin.

Override and opt-out:

  • --bootstrap-command=<name> on the plugin:install invocation overrides the manifest value (the override command runs instead of the declared one).
  • --no-bootstrap skips the auto-run entirely; the command falls back to printing the hint.

This was hint-only before 0.0.9: prior versions only printed Bootstrap with: dowel <name> and never executed the command.

yaml
bootstrap_command: example:install

Complete Example#

The example below is the canonical _fullYaml test fixture from test/installer/manifest_parser_test.dart. Every section above appears here in context.

yaml
plugin_name: example_plugin

dependencies:
  pubspec:
    intl: ^0.20.0
    crypto: ^3.0.0
  dev_pubspec:
    mocktail: ^1.0.0
  pubspec_assets:
    - assets/example/
    - assets/lang/en.json

publish:
  install/example_config.dart.stub: lib/config/example.dart
  install/example_routes.dart.stub: lib/routes/example.dart

json_merge:
  assets/lang/en.json:
    source: install/lang/en.json
    additive: true

magic:
  provider: ExampleServiceProvider
  config_factory: exampleConfig
  routes: registerExampleRoutes

native:
  android:
    permissions:
      - android.permission.INTERNET
    meta_data:
      io.flutter.embedded_views_preview: "true"
    activities:
      - name: com.example.CallbackActivity
        exported: true
        intent_filters:
          - auto_verify: true
            actions: [android.intent.action.VIEW]
            categories: [android.intent.category.DEFAULT, android.intent.category.BROWSABLE]
            data:
              - {scheme: https, host: auth.example.com, path: /callback}
    gradle:
      plugins:
        - id: com.example.gradle.plugin
          version: "1.0.0"
      dependencies:
        - scope: implementation
          notation: "com.example:lib:1.0.0"
  ios:
    info_plist:
      NSExampleUsageDescription: "Reason shown in iOS permission dialog"
      UIBackgroundModes: ["fetch"]
    url_schemes:
      - com.example.app
    entitlements:
      com.apple.security.keychain: true
    podfile:
      platform_version: "13.0"
      pods:
        - "ExamplePod"
  macos:
    info_plist:
      NSExampleUsageDescription: "Reason shown in macOS permission dialog"
    entitlements:
      com.apple.security.network.client: true
    podfile:
      platform_version: "11.0"
      pods:
        - "ExamplePod"
  web:
    head_scripts:
      - '<script src="example.js"></script>'
    meta_tags:
      - {name: "description", content: "Example plugin metadata"}

env:
  EXAMPLE_KEY:
    default: example_value
    comment: "Example plugin runtime config"

prompts:
  - {key: configPath, type: string, default: "~/.example.conf", question: "Config file path?"}
  - {key: mode, type: choice, options: [dev, staging, prod], default: dev, question: "Deployment mode?"}
  - {key: enableFeature, type: bool, default: false, question: "Enable optional feature?"}

placeholders:
  configFilePath: "{{ prompts.configPath }}"
  runtimeMode: "{{ prompts.mode }}"

post_install:
  run:
    - cmd: dart
      args: [format, lib/config/example.dart]
  ask_to_run:
    - prompt: "Run pub get now?"
      cmd: flutter
      args: [pub, get]
  message: |
    example_plugin installed.

bootstrap_command: example:install
esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.