Ack is a schema validation library for Dart and Flutter. It validates data with a fluent API. Ack is short for "acknowledge".
For AI agents: start at /llms.txt.
- Validate external payloads: Guard API and user inputs by validating required fields, types, and constraints at boundaries
- Single source of truth: Define data structures and rules in one place
- Less boilerplate: Minimize repetitive validation and JSON conversion code
- Type safety: Generate immutable models for hand-written Ack schemas with
@Schemable() - Class-first generation: Derive validated codec schemas from hand-written Dart classes with the same
@Schemable()annotation
This repository is a monorepo containing:
- ack: Core validation library with a fluent schema-building API, codecs, JSON Schema export, and the code-generation annotations
- ack_generator: Generates models from schemas and schemas from hand-written models
- ack_firebase_ai: Firebase AI (Gemini) schema converter for structured-output generation
- ack_json_schema_builder: Converter to
json_schema_builderschemas - ack_mcp_dart: MCP tool schemas and typed argument validation for
mcp_dart - example: Example projects demonstrating usage of all packages
- Read CONTRIBUTING.md before proposing a change.
- Use SUPPORT.md for questions, bug reports, and feature requests.
- Report vulnerabilities privately as described in SECURITY.md.
- Participation is governed by the Code of Conduct.
Add Ack to your project:
dart pub add ackDefine and use a schema:
import 'package:ack/ack.dart';
final userSchema = Ack.object({
'name': Ack.string().minLength(2).maxLength(50),
'email': Ack.string().email(),
'age': Ack.integer().min(0).max(120).optional(),
});
final result = userSchema.safeParse({
'name': 'John Doe',
'email': 'john@example.com',
'age': 30
});
if (result.isOk) {
final validData = result.getOrThrow();
print('Valid user: $validData');
} else {
final error = result.getError();
print('Validation failed: $error');
}Use .optional() when a field may be omitted entirely. Chain .nullable() if a present field may hold null, or combine both for an optional-and-nullable value.
For complex validation:
import 'package:ack/ack.dart';
// Complex nested object validation
final orderSchema = Ack.object({
'id': Ack.string().uuid(),
'customer': Ack.object({
'name': Ack.string().minLength(2),
'email': Ack.string().email(),
}),
'items': Ack.list(Ack.object({
'product': Ack.string(),
'quantity': Ack.integer().positive(),
'price': Ack.double().positive(),
})).minLength(1),
'total': Ack.double().positive(),
}).refine(
(order) {
// Custom validation: total should match sum of items
final items = order['items'] as List;
final calculatedTotal = items.fold<double>(0, (sum, item) {
final itemMap = item as Map<String, Object?>;
final quantity = itemMap['quantity'] as int;
final price = itemMap['price'] as double;
return sum + (quantity * price);
});
final total = order['total'] as double;
return (calculatedTotal - total).abs() < 0.01;
},
message: 'Total must match sum of item prices',
);
// Validate complex data
final result = orderSchema.safeParse(orderData);
if (result.isOk) {
final validOrder = result.getOrThrow();
print('Valid order: ${validOrder['id']}');
} else {
print('Validation failed: ${result.getError()}');
}Generate immutable models for hand-written schemas with @Schemable(). Add
ack to dependencies and ack_generator + build_runner to
dev_dependencies, then annotate a top-level schema. @Schemable() comes
from package:ack/ack.dart; constraint annotations such as @MinLength() come
from package:ack/annotations.dart:
import 'package:ack/ack.dart';
part 'user.ack.dart';
part 'user.ack.g.dart';
@Schemable()
final userSchema = Ack.object({
'name': Ack.string().minLength(2),
'email': Ack.string().email(),
});Run the generator:
dart run build_runner buildThis emits a User class with stored typed fields, validation helpers, and a
JSON boundary:
final user = User.parse({'name': 'Alice', 'email': 'alice@example.com'});
print(user.name); // String
print(user.toJson()); // {'name': 'Alice', 'email': 'alice@example.com'}A @Schemable() schema supports objects, primitives, lists, enums, bidirectional codecs,
named recursion, and discriminated unions. One-way transforms are rejected
because a generated model must be encodable. See the
Model Code Generation guide.
@AckType() remains available for source compatibility and keeps the Ack 1.1
extension-type API and .g.dart output unchanged. It is deprecated and will be
removed in Ack 2.0:
import 'package:ack/ack.dart';
import 'package:ack/annotations.dart';
part 'legacy_user.g.dart';
@AckType()
final userSchema = Ack.object({'name': Ack.string()});This still generates UserType, including its Map interface, typed getters,
parse / safeParse, and .args. New code should use @Schemable().
| Ack 1.1 source | Optional immutable-model migration |
|---|---|
Keep @AckType() |
Rename it to @Schemable() |
Keep part 'file.g.dart'; |
Add file.ack.dart and file.ack.g.dart parts |
Use *Type, Map access, and .args |
Use the generated class, typed fields, parse, fromJson, and toJson |
Legacy and modern declarations may coexist when they are unrelated. A nested reference graph cannot cross between them; migrate that connected graph together.
Already own the model class? Put @Schemable() on it to derive a codec schema from
constructor-backed fields while keeping the class hand-written. A class named
Account receives an AccountSchema facade for parsing, encoding, schema
export, and nested composition; the backing codec remains private:
import 'package:ack/ack.dart';
import 'package:ack/annotations.dart';
@Schemable()
final class Account with _$AccountAck {
const Account({required this.name});
@MinLength(2)
final String name;
static final fromJson = AccountSchema.fromJson;
}Account.fromJson({'name': 'Ada'}) validates and constructs the model, while
account.toJson() validates and encodes it.
A field whose type declares a static schema needs no annotation: a Slot
field resolves to Slot.schema, a List<Slot> to Ack.list(Slot.schema),
and a generic Command<Action> to Command.schema<Action>(). See the
Model Code Generation guide.
Upgrading from 1.6.x: drop ack_annotations, import
package:ack/ack.dart for @Schemable(), and replace
package:ack_annotations/ack_annotations.dart with
package:ack/annotations.dart (and .../format_annotations.dart with
package:ack/format_annotations.dart) where you use constraint annotations.
Rename @Pattern to @Matches, and replace the deprecated @AckInfer() /
@AckModel() with @Schemable(). A field renamed with @JsonKey(name: ...)
now imports JsonKey from package:json_annotation, with json_annotation
in the app's dependencies.
Codecs decode boundary values (the JSON you receive) into rich Dart runtime types and encode them back. Ack ships built-in codecs and lets you define your own:
// Built-in codec: ISO 8601 String boundary <-> UTC DateTime runtime
final when = Ack.datetime();
final dt = when.parse('2026-01-01T00:00:00Z'); // DateTime
final iso = when.encode(dt); // back to an ISO 8601 String
// Other built-ins: Ack.date(), Ack.uri(), Ack.duration(), Ack.enumCodec(...)
// Custom bidirectional codec
final csv = Ack.codec<String, String, List<String>>(
input: Ack.string(),
decode: (s) => s.split(','),
encode: (list) => list.join(','),
);
csv.parse('a,b,c'); // ['a', 'b', 'c']
csv.encode(['a', 'b', 'c']); // 'a,b,c'Use .transform<R>(...) for one-way (parse-only) conversions. See the
Codecs guide.
- Human docs: concepta.dev/ack
- AI agent index: AI & llms.txt
- Canonical plaintext source: raw.githubusercontent.com/btwld/ack/main/llms.txt
This project uses Melos to manage the monorepo.
# Resolve the workspace-local Melos dependency
dart pub get
# Bootstrap the workspace (installs dependencies for all packages)
dart run melos bootstrap# Run tests across all packages
dart run melos run test
# Format code across all packages
dart run melos run format
# Analyze code across all packages
dart run melos run analyze
# Check for outdated dependencies
dart run melos run deps-outdated
# Run build_runner for packages that need it (e.g., ack_generator, example)
dart run melos run build
# Clean build artifacts
dart run melos run clean
# Propose/apply version and changelog updates
dart run melos version
# Dry-run pub.dev validation for every package, requiring zero warnings
dart scripts/publish_dry_run.dartPublishing runs only from a v* tag through GitHub Actions. See
PUBLISHING.md.
# JSON Schema validation (JSON Schema Draft-7 compatibility)
dart run melos run validate-jsonschema
# API compatibility check (for semantic versioning)
dart run melos run api-check -- 1.1.0
# See all available scripts
dart run melos run --listAdditional development documentation is available in the tools/ directory.
This project uses GitHub Releases to manage versioning and publishing. See PUBLISHING.md for instructions.
Contributions are welcome. Follow these steps:
- Fork the repository
- Create a feature branch
- Add your changes
- Run tests with
dart run melos run test - Follow Conventional Commits in your commit messages
- Submit a pull request