Skip to Content

Dart schemas for apps & structured AI

Trust the boundary.Keep the types.

Define the shape once. Guide structured AI output, validate every response at runtime, decode wire values into Dart, and generate types when you want them.

  • Runtime-first validation
  • Structured AI output
  • Provider-ready model
DARTuser_schema.dart
safeParse()
final userSchema = Ack.object({
  'name': Ack.string().minLength(2),
  'email': Ack.string().email(),
  'age': Ack.integer().min(0).optional(),
});

final result = userSchema.safeParse({
  'name': 'Ada',
  'email': 'not-an-email',
});
Validation failed1 issue · 0 exceptions
#/emailSchemaError Value must be a valid email address.
ONE READABLE PATH

From unknown input to a useful result.

The same schema follows your data through the entire boundary. Each step is explicit, inspectable, and close to the Dart code—or generated output—that consumes it.

01 / DEFINE

Write the contract once.

Compose small schemas into the exact object your application accepts. Constraints stay beside the fields they protect.

Explore schemas
user_schema.dartCONTRACT
final userSchema = Ack.object({
  'name': Ack.string().minLength(2),
  'email': Ack.string().email(),
  'createdAt': Ack.datetime(),
});
02 / VALIDATE

Check data where it enters.

Use safeParse at an API, form, storage, or AI boundary. The result makes success and failure deliberate—without a required try/catch.

Choose a parsing strategy
incoming payloadUNKNOWN → CHECKED
final result = userSchema
    .safeParse(request.body);

if (result.isFail) {
  logger.warn(result.getError());
}
01BOUNDARY VALUEObject?
02RUN THE SCHEMAsafeParse()
03TYPED OUTCOMESchemaResult
03 / USE THE RESULT

Handle both outcomes with context.

Consume the validated value or route a structured error. Every failure preserves the path and reason your UI, logs, or API response needs.

Work with SchemaResult
OKresult.getOrThrow()
Validated map

Only the shape you described moves forward.

FAILresult.getError()
SchemaError

#/email pinpoints the invalid field.

ONE SCHEMA, EVERY AI BOUNDARY

Built for structured AI. Grounded in runtime validation.

Use one Ack schema to guide generation, verify model output, recover rich Dart values, and adapt the contract to the provider you use.

Firebase AI / Gemini

Guide generation. Then verify it.

The shipped Firebase AI adapter turns your Ack schema into responseJsonSchema. After generation, the same schema remains the authoritative runtime validator and codec boundary.

Explore structured output
structured_output.dartGUIDE → GENERATE → VERIFY
final responseSchema = Ack.object({
  'answer': Ack.string(),
  'sources': Ack.list(Ack.uri()),
  'generatedAt': Ack.datetime(),
});

final config = GenerationConfig(
  responseJsonSchema:
    responseSchema.toFirebaseAiResponseJsonSchema(),
);

final output = responseSchema.parse(
  jsonDecode(response.text!),
);
PROVIDER ADAPTERS

One model. Provider-specific output.

AckSchemaModel is the target-independent adapter boundary. Use the built-in toJsonSchema() path, the Firebase AI adapter, or author a focused adapter for OpenAI and other provider SDKs.

Build a provider adapter
CANONICAL MODELAckSchemaModel
SHIPPEDFirebase AI
BUILT INJSON Schema
EXTENDYour provider adapter
OPTIONAL CODE GENERATION

Generate types, not duplicate models.

Annotate the schema you already trust. Ack generates a lightweight wrapper with typed getters and parse helpers—without changing the runtime schema.

Generate typed schemas
user_schema.dartOPTIONAL GENERATOR
@AckType()
final userSchema = Ack.object(...);

final ada = UserType.parse(json);
print(ada.email); // String
AI-READY CODECS

Decode the wire. Keep rich Dart values.

AI providers exchange JSON strings. Ack.datetime() validates that wire value, decodes it to UTC DateTime, and encodes it back for the next boundary.

Meet codecs
Ack.datetime()VALIDATE · DECODE · ENCODE
WIRE VALUEString
"2026-07-15T12:00:00Z"
DART VALUEDateTime
DateTime.utc(2026, 7, 15, 12)
START WITH ONE BOUNDARY

Make unknown data explicit.

Add Ack, describe the shape you expect, and keep the rest of your Dart code focused on trusted values.

dart pub add ack
Read installation