Skip to content

Ferry Custom Type Mapping

Ferry Custom Type Mapping

Configure Ferry’s build.yaml to map GraphQL scalar types directly to Dart classes, eliminating the need for built_value serialization on custom scalars.

By default, Ferry generates built_value classes for GraphQL types. With custom type mapping in build.yaml, you can:

  • Map GQL scalar types directly to standard Dart classes (e.g., DateTime, Map<String, dynamic>)
  • Eliminate manual conversion extensions for scalar types
  • Prevent the “forgotten field” problem where manual extensions miss newly added fields
  • Keep generated code using familiar Dart types

In the root of your datasource package (or project root), configure Ferry’s type overrides:

targets:
$default:
builders:
ferry_generator:
options:
schema: lib/src/core/remote/gql/schema.graphql
type_overrides:
# Map DateTime scalar to Dart's DateTime
DateTime:
name: DateTime
import: 'dart:core'
# Map JSON scalar to Map
JSON:
name: Map<String, dynamic>
import: 'dart:core'
# Map Upload scalar to MultipartFile (for file uploads)
Upload:
name: MultipartFile
import: 'package:http/http.dart'
# Map custom scalars as needed
BigInt:
name: int
import: 'dart:core'
# Map UUID to String
UUID:
name: String
import: 'dart:core'
Terminal window
dart run build_runner build --delete-conflicting-outputs

After generation, verify that the generated Dart classes use your mapped types:

// Before (without type mapping) - uses built_value GDateTime
class GQueryEventData_event {
GDateTime? get date; // GDateTime needs manual conversion
}
// After (with type mapping) - uses standard Dart DateTime
class GQueryEventData_event {
DateTime? get date; // Standard Dart DateTime, no conversion needed
}
GraphQL ScalarDart TypeImport
DateTimeDateTimedart:core
JSONMap<String, dynamic>dart:core
UploadMultipartFilepackage:http/http.dart
BigIntintdart:core
UUIDStringdart:core
Decimaldoubledart:core

Custom type mapping operates at Layer 1 (GQL Raw Models):

  1. Ferry generates Dart classes with mapped types (e.g., DateTime instead of GDateTime)
  2. Repository model convertors still transform raw models to domain models, but scalar conversion is already handled
  3. Domain models receive properly typed values from the repository
// Manual conversion extension - prone to forgotten fields
extension EventExtension on GQueryEventData_event {
EventModel toDomain() {
return EventModel(
id: id ?? '',
title: title ?? '',
date: date?.toDateTime() ?? DateTime.now(), // Manual GDateTime conversion
metadata: json != null ? jsonDecode(json!) : {}, // Manual JSON parsing
);
}
}
// Model convertor - scalar types already resolved
@riverpod
Convertor<EventModel, GQueryEventData_event>
eventModelConvertor(Ref ref) {
return Convertor((raw) {
return EventModel(
id: raw.id ?? '',
title: raw.title ?? '',
date: raw.date ?? DateTime.now(), // Already a DateTime
metadata: raw.metadata ?? {}, // Already a Map<String, dynamic>
);
});
}

For complex GQL types (objects, lists), Ferry generates proper Dart classes. The model convertor in the repository handles the transformation to domain models:

@riverpod
Convertor<AttendeeModel, GQueryAttendeeData_attendee>
attendeeModelConvertor(Ref ref) {
return Convertor((raw) {
return AttendeeModel(
id: raw.id ?? '',
name: raw.fullName ?? 'Unknown',
email: raw.email ?? '',
// Nested object conversion
organization: raw.organization != null
? OrganizationModel(
id: raw.organization!.id ?? '',
name: raw.organization!.name ?? '',
)
: null,
// List conversion
tags: raw.tags?.nonNulls.map((t) => t.name ?? '').toList() ?? [],
);
});
}

Ensure the import path is correct in build.yaml. For third-party packages, use the full package import:

Upload:
name: MultipartFile
import: 'package:http/http.dart' # Full package path

Ferry’s cache requires JSON-compatible types. If you map a scalar to a custom Dart class, ensure it serializes correctly. Standard types (DateTime, String, int, double, Map, List) work without issues.

If you see conflicting output errors, use the --delete-conflicting-outputs flag:

Terminal window
dart run build_runner build --delete-conflicting-outputs