Dart & Flutter DevTools Extensions

A guide for building custom tooling in Dart & Flutter DevTools

Kenzie Davisson
Flutter
9 min readNov 15, 2023

--

Have you ever wanted to build developer tooling for Dart and Flutter but didn’t know where to start? Or maybe you didn’t want to go through all the work of establishing a connection to a running Dart or Flutter application to access debugging data? Then, even if you did create a development tool, how would you deploy it or give users easy access to it? Well, we have some good news for you: now you can create developer tooling without all these hurdles.

With the new Dart & Flutter DevTools extensions framework, you can easily build developer tooling that is tightly integrated with the existing DevTools tooling suite. Extensions are built using Flutter web and leverage existing frameworks and utilities from DevTools to simplify the developer tool authoring experience.

Example DevTools extension for package:foo

How do DevTools extensions work?

Extensions are shipped as part of a pub package. You can add a DevTools extension to an existing pub package, or you can create a new package that provides a DevTools extension only. In both these scenarios, the end-user must list a dependency on the package providing the DevTools extension in order to see the extension in DevTools.

For example, imagine we have some package:foo, and this package provides a DevTools extension. When a user depends on package:foo in their app, they automatically get access to the DevTools extension provided by this package. When this user is debugging their app with DevTools, they will see a new tab “Foo” that contains the developer tools provided by package:foo.

Some examples of packages that have added a DevTools extension to an existing package are package:provider, package:patrol, and package:drift.

Writing a DevTools extension: a step-by-step guide

Before you get started

What you will need:

  • Flutter SDK >= 3.17.0–0.0.pre & Dart SDK >= 3.2.
  • A Pub package (existing or new) to add a DevTools extension to.

It is recommended to develop your extension from the Flutter master channel in order to use the latest devtools_extensions and devtools_app_shared packages

Step 1: Set up your package hierarchy

Standalone extensions

If you are adding a DevTools extension to an existing Dart package, proceed to the next step to “Configure your extension”.

If you are creating a standalone DevTools extension as a new package (i.e. not part of an existing pub package), then you can build your extension in the same package that it will be published with. Since the extension must be built as a Flutter web app, you can use the following flutter create template:

flutter create --template app --platforms web my_new_tool

Now you can use the my_new_tool package to configure your extension in the next step.

Configure your extension

In the Dart package that will provide the DevTools extension to users, add a top-level extension directory:

foo/
extension/
lib/
...

Under the extension directory, create the following structure (exactly as shown):

extension/
devtools/
build/
config.yaml

The config.yaml file should contain metadata that DevTools needs to load the extension:

name: foo
version: 0.0.1
issueTracker: <link_to_your_issue_tracker.com>
materialIconCodePoint: '0xe0b1'

Copy the config.yaml file content above and paste it into the config.yaml file you just created in your package. For each key, fill in the appropriate value for your package.

  • name: the package name that this DevTools extension belongs to. The value of this field is used in the extension page title bar. [required]
  • version: the version of your DevTools extension. This version number should evolve over time as you ship new features for your extension. The value of this field is used in the extension page title bar. [required]
  • issueTracker: the URL for your issue tracker. When a user clicks the “Report an issue” link in the DevTools UI, they are directed to this URL. [required]
DevTools extension screen title bar
  • materialIconCodePoint: corresponds to the codepoint value of an icon from material/icons.dart. This icon is used for the extension’s tab in the top-level DevTools tab bar. [required]
DevTools extension tab icon

For the most up-to-date documentation on the config.yaml spec, see extension_config_spec.md.

Now it is time to build your extension.

Step 2: Create a DevTools extension

DevTools extensions must be written as Flutter web apps. This is because DevTools embeds extensions in an iFrame to display them dynamically in DevTools.

Where to put your extension source code

Only the pre-compiled output of your extension needs to be shipped with your pub package in order for DevTools to load it.

Standalone extensions

For a standalone extension (an extension that is not being shipped as part of an existing pub package), it is acceptable to include your source code in the same package that the extension is shipped with. This will simplify development, and since users of your package will add a dependency on your package as a dev_dependency, the size of your package will not affect the user's app size.

my_new_tool
extension/
devtools/
build/
... # pre-compiled output of the Flutter web app under lib/
config.yaml
lib/ # source code for your extension
src/
...

Extensions that are part of an existing package

To keep the size of your pub package small, we recommend that you develop your DevTools extension outside of your existing pub package. We recommend the following package structure:

foo/  # formerly the repository root of your pub package
packages/
foo/ # your pub package
extension/
devtools/
build/
... # pre-compiled output of foo_devtools_extension
config.yaml
foo_devtools_extension/ # source code for your extension

Create the extension web app

  1. Create the Flutter web app

Skip this step if you are building a standalone extension, since you already did this when you set up your package hierarchy.

From the directory where you want your extension source code to live, run the following command, replacing foo_devtools_extension with <your_package_name>_devtools_extension:

flutter create --template app --platforms web foo_devtools_extension

2. Add the devtools_extensions dependency to your Flutter web app.

In foo_devtools_extension/pubspec.yaml, add the following:

devtools_extensions: ^0.0.14

3. Add the DevToolsExtension widget at the root of your Flutter web app.

In foo_devtools_extension/lib/main.dart, add the following:

import 'package:devtools_extensions/devtools_extensions.dart';
import 'package:flutter/material.dart';

void main() {
runApp(const FooDevToolsExtension());
}

class FooDevToolsExtension extends StatelessWidget {
const FooDevToolsExtension({super.key});

@override
Widget build(BuildContext context) {
return const DevToolsExtension(
child: Placeholder(), // Build your extension here
);
}
}

The DevToolsExtension widget automatically performs all extension initialization required to interact with DevTools. From anywhere in your extension web app, you can access the globals:

  • extensionManager: a manager for interacting with DevTools or the extensions framework
  • serviceManager: a manager for interacting with the connected VM service, if present
  • dtdManager: a manager for interacting with the Dart Tooling Daemon, if present

Utilize helper packages

Use package:devtools_app_shared for access to service managers, common widgets, DevTools theming, utilities, and more. See devtools_app_shared/example for sample usages.

Step 3: Debug a DevTools extension

When developing and maintaining your DevTools extension, you’ll want to run, debug, and test your extension Flutter web app. You have a couple of different options for this, outlined below.

Option A: Use the Simulated DevTools Environment (recommended for development)

For debugging purposes, you will likely want to use the “simulated DevTools environment”. This is a simulated environment that allows you to build your extension without having to develop it as an embedded iFrame in DevTools. Running your extension this way will wrap your extension with an environment that simulates the DevTools-to-DevTools extension connection. It also gives you access to hot restart and a faster development cycle.

Debugging an extension with the Simulated DevTools Environment
  1. Your DevTools extension.
  2. The VM service URI for a test app that your DevTools extension will interact with. This app should depend on your extension’s parent package (package:foo in this example).
  3. Buttons to perform actions that a user may trigger from DevTools.
  4. Logs showing the messages that will be sent between your extension and DevTools.

The simulated environment is enabled by the environment parameter use_simulated_environment. To run your extension web app with this flag enabled, add a configuration to your launch.json file in VS code:

{
...
"configurations": [
...
{
"name": "foo_devtools_extension + simulated environment",
"cwd": "packages/foo_devtools_extension",
"request": "launch",
"type": "dart",
"args": [
"--dart-define=use_simulated_environment=true"
],
},
]
}

or launch your app from the command line with the added flag:

flutter run -d chrome - dart-define=use_simulated_environment=true

Option B: Use a real DevTools environment

Once you develop your extension to a point where you are ready to test your changes in a real DevTools environment, you need to perform a series of setup steps:

1. Build your Flutter web app and copy the built assets from the your_extension_web_app/build/web directory to the parent package’s extension directory (your_pub_package/extension/devtools/build). To do this, use the build_and_copy command from package:devtools_extensions:

cd your_extension_web_app;
flutter pub get;
dart run devtools_extensions build_and_copy --source=. --dest=path/to/your_pub_package/extension/devtools

Note: if you are using the recommended package structure from above for adding an extension to an existing pub package, the value for — dest should be ../your_pub_package/extension/devtools.

To ensure that your extension is setup properly for loading in DevTools, run the validate command from package:devtools_extensions. The --package argument should point to the root of the Dart package that this extension will be published with.

cd your_extension_web_app;
flutter pub get;
dart run devtools_extensions validate --package=path/to/your_pub_package

2. Prepare and run a test application that depends on your pub package. In the test application’s pubspec.yaml file, you’ll need to change the dependency on your package to be a path dependency that points to your local package source code. Once you have done this, run flutter pub get on the test app, and run the application.

3. Start DevTools. Open the DevTools instance that was just started by running your test application. You can open DevTools from either the url printed to the command line or from the IDE where you ran your test app. Optionally, you can also run dart devtools from the command line.

Note: If you need local or unreleased changes from DevTools, you’ll need to build and run DevTools from source (server + front end). See the DevTools CONTRIBUTING guide.

4. Connect DevTools to your test app if it is not connected already, and you should see a tab in the DevTools app bar for your extension. The enabled or disabled state of your extension is managed by DevTools. The extension-enabled states are exposed from an “Extensions” menu in DevTools, available from the action buttons in the upper right corner of the screen.

DevTools Extensions menu button
DevTools Extensions menu

Step 4: Publish your package with a DevTools extension

In order for a package to provide a DevTools extension to its users, it must be published with the expected content in the your_pub_package/extension/devtools/ directory (see the setup instructions above).

  1. Ensure that the your_pub_package/extension/devtools/config.yaml file exists and is configured per the specifications above.
  2. Use the build_and_copy command provided by package:devtools_extensions to build your extension and copy the output to the extension/devtools directory:
cd your_extension_web_app;
flutter pub get;
dart run devtools_extensions build_and_copy --source=. --dest=path/to/your_pub_package/extension/devtools

Then publish your package on pub.dev: flutter pub publish. When running pub publish, you will see a warning if you do not have the config.yaml file and a non-empty build directory as required.

For additional guidance around publishing your package, see the package:devtools_extensions publishing guide.

Conclusion

That’s it! Now, when a user depends on the latest version of your package, they will automatically get access to the tools you provide in your DevTools extension. This feature is hot off the press, so we are eager to hear your feedback.

For issues and feature requests, please file an issue on the DevTools issue tracker.

For general support and access to the community of DevTools extension authors, check out the #devtools-extension-authors Discord channel (you will first need to join the Flutter Discord server).

--

--