Open-source project
trevorwang/retrofit.dart avatar
trevorwang/retrofit.dart

retrofit.dart: a typed dio client generated from annotated Dart interfaces

retrofit.dart is an dio client generator using source_gen and inspired by Chopper and Retrofit.

1,182 stars290 forksDartMIT

At a glance

What is it?
retrofit.dart is a code generator for the Dart http client dio, in the tradition of Chopper and the Java Retrofit, and it works the way those libraries do. You declare an abstract class with annotations, the generator writes the implementation, and the contract between URL and model is checked by the compiler instead of by a test you forgot to write.
Who is it for?
retrofit.dart fits a Flutter or Dart service that talks to a REST API and wants the request and response types in one place, checked by the analyzer rather than by hand written tests.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Dart, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A generator in the shape of Retrofit and Chopper

retrofit.dart is a type conversion dio client generator using source_gen and inspired by Chopper and Retrofit. The two packages are published separately, retrofit for the runtime side and retrofit_generator for the code generation, and both appear in the badges at the top of the README.

The model is the familiar one. You write an abstract class, annotate it, and the generator produces a concrete class that does the HTTP work. The name and the inspiration are deliberate, and the payoff for a Dart developer is that a REST client ends up looking like the ones written in Java or in the Dart Chopper library rather than like a pile of futures and maps.

The project is MIT licensed, the last push to the repository was on 2026-10-13, and the release cadence is fast, with v10.2.12 on 2026-10-13, v10.2.11 on 2026-09-04 and v10.2.10 on 2026-08-31. The version number to note is the generator's, since the runtime and the generator are released on their own schedules and the README's dependency examples pair retrofit 4.9.0 with retrofit_generator 10.0.1.

The repository also carries the paperwork of a Dart project, with a .commitlintrc.yml, husky hooks, a .travis.yml, a .vscode directory and several example projects, which is a reasonable shape for a package that most users will never read the source of.

The three packages you add

The installation is a normal Dart dependency block, with the generator in the dev dependencies.

yaml
dependencies:
  retrofit: ^4.9.0
  logger: ^2.6.0  # for logging purpose
  json_annotation: ^4.9.0

dev_dependencies:
  retrofit_generator: ^10.0.1
  build_runner: ^2.6.0
  json_serializable: ^6.10.0

Four of those entries are the interesting ones. The retrofit package holds the annotations and the base types at runtime, and the generator does the work. build_runner is what actually runs the generator, so it is not optional in the current state of the project. And json_serializable is the default serialisation tool, which the README recommends for the type conversion.

The logger entry is only there for the example, and json_annotation is what carries the annotation the generator reads on your model classes.

The split between dependencies and dev_dependencies is the one to get right. The generator and the serialisation builder are build time tools and do not belong in your published app, and a package that accidentally ships build_runner in its dependencies is a common and avoidable embarrassment.

Writing the interface the generator reads

The API definition is an abstract class with a factory, and the model classes carry the serialisation annotations.

dart
import 'package:dio/dio.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:retrofit/retrofit.dart';

part 'example.g.dart';

@RestApi(baseUrl: 'https://5d42a6e2bc64f90014a56ca0.mockapi.io/api/v1/')
abstract class RestClient {
  factory RestClient(Dio dio, {String? baseUrl}) = _RestClient;

  @GET('/tasks')
  Future<List<Task>> getTasks();
}

The structure is worth reading closely. The class is abstract, and the factory redirects to a generated implementation, _RestClient, which is the class the generator writes. The part directive names the generated file, and the generator is what creates it, so the file is generated output rather than something you edit.

The base URL goes on the annotation, and the factory takes an optional baseUrl override, which is how you point the same client at a different environment. The Dio instance is passed in, which is the important design decision: the generated code holds no HTTP state of its own, it delegates to the Dio you give it, so your interceptors, timeouts, certificates and headers apply to every call.

And the return type is the whole point. The method says it returns a Future of a list of Task, and the generated implementation is what makes that true, with the JSON decoded into Task objects rather than into a map of dynamic values.

build.yaml is where the real options live

The generator is configured with a build.yaml file in the project root, and the options are documented with their defaults in the file itself.

yaml
targets:
  $default:
    builders:
      retrofit_generator:
        options:
          # Control whether to add '// dart format off/on' comments (default: true)
          format_output: true
          # Enable automatic response type casting (default: true)
          auto_cast_response: true
          # Generate empty request body for methods without parameters (default: false)
          empty_request_body: false
          # Enable useResult annotation for methods (default: false)
          use_result: false
          # Global parser for type conversion (default: json_serializable)
          # Supported values: dart_mappable, json_serializable, map_serializable, flutter_compute, dart_json_mapper
          parser: dart_mappable

Two of those are worth explaining, because the defaults are the right ones and the reason for the option is not obvious.

format_output is on by default because the generator wraps its output in dart format off and dart format on comments, which preserves the formatting the generator produced. The reason to turn it off is stated in the README: if you are combining retrofit with other generators, such as riverpod, and need more control over formatting, disabling it avoids the conflict that arises when two generators both want to own the formatting of the same file.

auto_cast_response is on by default and means the response is cast to the declared type for you. empty_request_body and use_result are off by default, and both change the shape of the generated request, so they are opt-in.

The parser option is the one that decides what your model classes look like, and the class level annotation takes precedence over the global setting, so you can override it per API class.

Running the generator, and the experimental fast path

The generator runs through build_runner, and the README gives both invocations.

sh
# dart
dart pub run build_runner build

# for watch mode (recommended during development)
dart pub run build_runner watch

The watch mode recommendation is the normal advice for any source generator, and it matters more here than for most, because the generated file is part of your type surface. Every time you change an annotation or a model, the generated code changes with it, and without watch mode you are running a full build by hand after every edit.

The project also has experimental support for lean_builder, a faster build system for Dart. The infrastructure has been added for future use, and the README is clear on two points. lean_builder is an optional dependency and is not required to use retrofit_generator, it is only needed if you want to try the experimental path. And for now, the advice is to continue using build_runner, because full integration will come in a future release once lean_builder reaches stability.

To prepare, you add it to the dev dependencies.

yaml
dev_dependencies:
  lean_builder: ^0.1.2  # Optional - only if you want to use lean_builder

That framing is the right one for an experimental feature. You can try it, you can leave it out, and nothing about your build breaks either way.

Type conversion, the parser choice and enums

The types section explains what has to be true before the conversion works, and the answer is that each model class needs a factory constructor that takes a map.

Before you use the type conversion, make sure that a factory Task.fromJson(Map<String, dynamic> json) must be provided for each model class, and json_serializable is recommended as the serialisation tool.

The dart_mappable route is the alternative, and it is a different shape rather than a different name. You set the parser to Parser.DartMappable on the RestApi annotation, and the model uses the Mappable mixin.

dart
@RestApi(
  baseUrl: 'https://api.example.com',
  parser: Parser.DartMappable,
)
abstract class ApiService {
  factory ApiService(Dio dio) = _ApiService;
}

The model then becomes a MappableClass with the TaskMappable mixin, and the dependencies change to dart_mappable and dart_mappable_builder. A complete example lives in the example_dartmappable directory, and there are further example directories for dart_mapper, protobuf, a relative base URL and a Flutter example.

Enums have their own rule, and it is the one detail to read twice. For enums, the project relies on the toString method to convert it to a string, and you override toString to return the value you want.

That is a small constraint with a wide blast radius, because a path parameter carrying an enum will be serialised through toString, so an enum whose toString returns the Dart name and not the wire value will send the wrong thing.

Typed extras, and a two line repository manifest

There is a last feature worth knowing about, and it is the one that touches every request.

Typed extras let you add static extra to all requests, and the shape is a class extending TypedExtras with the fields you want attached, and a MetaData class with an id and a region is the example in the README.

That is how you would carry a tenant id, a trace id or an API version on every call without threading it through every method signature, and it is the kind of thing that retrofit cannot infer from the interface and you would otherwise write by hand in an interceptor.

The repository's own package.json is a curiosity, and it explains the shape of the project. The root manifest is named retrofit.dart at version 1.0.0, and its only dev dependencies are commit tooling, commitlint, the all-contributors CLI and husky, with scripts for preparing husky and adding contributors.

In other words, the root of the repository is not the package you depend on. The published packages are the ones under retrofit/ and generator/, and the root manifest exists to run the commit conventions and the contributors tooling. That is a monorepo where the root is deliberately inert, and it is a small example of a repository that keeps its housekeeping separate from its code.

Editorial conclusion

retrofit.dart fits a Flutter or Dart service that talks to a REST API and wants the request and response types in one place, checked by the analyzer rather than by hand written tests. It does not fit a project that needs a hand written client for one unusual endpoint, because the setup cost of the generator is only worth it once the interface count grows, and it does not fit a project that has to stay on a specific build system other than build_runner without waiting for the lean_builder work to land. Verify three things before you commit: which parser you will use, because the default is json_serializable and changing it later touches every model class, whether you need the generated code to be format-stable, since format_output exists precisely because combining retrofit with other generators over a formatted file causes conflicts, and that the Dio instance you pass in is configured the way you expect, since the generated client inherits its headers, base URL handling and interceptors from it.

Frequently asked questions

What does retrofit.dart actually generate?

A typed dio client. You declare an abstract class annotated with @RestApi, give it a factory that redirects to a generated implementation, and annotate the methods with @GET and the like. The generator writes the class that performs the requests and decodes the responses into the declared model types, so the URL and model contract is checked by the analyzer rather than by a test.

Which packages do I need to add to a Flutter project?

The retrofit package, json_annotation and a logger for the example go in dependencies, and retrofit_generator, build_runner and json_serializable go in dev_dependencies. The README pairs retrofit 4.9.0 with retrofit_generator 10.0.1, and the generator and the serialisation builder are build time tools that should not ship in your app.

How do I run the code generator?

With build_runner, using dart pub run build_runner build for a one off run, and dart pub run build_runner watch for the watch mode the README recommends during development. The project also has experimental support for lean_builder, but lean_builder is an optional dependency and the advice is to keep using build_runner until lean_builder reaches stability.

What can I configure in build.yaml?

The generator options are format_output, which adds the dart format off and on comments and defaults to true, auto_cast_response, which defaults to true, empty_request_body, which defaults to false, use_result, which defaults to false, and parser, which sets the serialisation parser globally and defaults to json_serializable, with dart_mappable, map_serializable, flutter_compute and dart_json_mapper also supported. A class level @RestApi(parser: ...) annotation takes precedence over the global setting.

How do enums work as path parameters in retrofit.dart?

The project relies on the toString method to convert an enum to a string, so you override toString to return the value you want. The README shows an enum with a toString override returning name, used with a @Path() annotation on the method parameter.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. trevorwang/retrofit.dart on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/trevorwang-retrofit-dart.svg)](https://hysenlabs.com/projects/trevorwang-retrofit-dart)

Was this useful?