How the CommonGrants Specification Is Built: From YAML to TypeScript SDK

The CommonGrants specification is built using a schema-first approach where YAML-based JSON-Schema files serve as the single source of truth, feeding into Ajv validators, Zod type mirrors, and a type-safe TypeScript SDK.

The hhs/simpler-grants-protocol repository implements the CommonGrants specification as a pipeline that transforms human-readable YAML definitions into runtime validation logic and strongly-typed client libraries. This architecture ensures that grant opportunity data remains consistent across web forms, API clients, and documentation.

Schema-First Foundation with YAML

The canonical data model lives in website/public/schemas/yaml/ as a collection of YAML-based JSON-Schema files. Each domain entity—proposals, opportunities, custom fields, and system metadata—is defined in reusable, versioned YAML files.

The root schema, ProposalBase.yaml, establishes the canonical shape for all proposal data. Other schemas reference this base definition to ensure consistency across the entire specification. This YAML-first approach guarantees that the data model remains human-readable while serving as the authoritative source for all downstream tooling.

Runtime Validation via Ajv

In website/src/lib/validation.ts, the system loads these YAML definitions at runtime to create an Ajv 2020 validator instance. The script reads every *.yaml file from the schemas directory, registers each using the filename as its $id, and exposes commonGrantsSchema for form-level validation.

This allows web applications to validate user submissions against the exact same CommonGrants specification used by the backend SDK, ensuring end-to-end data integrity without code duplication.

Build-Time Generation and CLI Compilation

A series of build scripts transforms the YAML schemas into developer-friendly assets. The website/src/scripts/generate-type-formatting.ts script computes stable hashes for each schema file and produces type-formatting caches used by the documentation site. Meanwhile, website/src/scripts/generate-schema-docs.ts maps each schema name to its corresponding MDX documentation path.

The CLI utility in lib/cli/src/commands/compile/compile.ts orchestrates the final SDK bundle generation, ensuring that the Zod schemas and TypeScript definitions stay synchronized with the YAML source files.

Type-Safe SDK Implementation

The TypeScript SDK in lib/ts-sdk/ maintains hand-written Zod schemas that mirror the YAML definitions exactly. These files provide runtime validation and static type inference for SDK users:

The Opportunities client in lib/ts-sdk/src/client/opportunities.ts wraps HTTP calls using these Zod schemas to parse and type-check API responses. It provides helper methods for get, list, and search operations, and supports extension via withCustomFields for custom schema validation.

Practical Implementation Examples

Applications consume the specification through both validation utilities and the SDK client.

Validating Forms Against the Specification

Use the validation library to ensure form submissions conform to the CommonGrants data model:

// -------------------------------------------------
// 1️⃣ Load a form and validate it against the
//    Common Grants schema (website side)
// -------------------------------------------------
import { validateCommonGrantsMappings } from "./validation";

await validateCommonGrantsMappings({
  formId: "my-proposal-form",
  formSchema: myFormJsonSchema,
  mappingToCommon: myMappingToCommon,   // form → Common Grants
  mappingFromCommon: myMappingFromCommon, // Common Grants → form
  defaultData: {},                     // empty form data
});

Fetching Typed Opportunities

The TypeScript SDK provides type-safe access to grant data with support for custom fields:

// -------------------------------------------------
// 2️⃣ Use the TypeScript SDK to fetch opportunities
// -------------------------------------------------
import { Client, withCustomFields } from "@common-grants/sdk";
import { OpportunityBaseSchema } from "@common-grants/sdk/schemas";
import { z } from "zod";

// Create a client pointing at the API
const client = new Client({ baseUrl: "https://api.common-grants.org" });

// ----- a) Simple get -------------------------------------------------
const opp = await client.opportunities.get(
  "083b4567-e89d-42c8-a439-6c1234567890"
);
console.log(opp.title);

// ----- b) Typed custom fields -----------------------------------------
const MyOppSchema = withCustomFields(OpportunityBaseSchema, [
  { key: "legacyId", fieldType: "integer", valueSchema: z.number().int() },
] as const);

const typedOpp = await client.opportunities.get(
  "083b4567-e89d-42c8-a439-6c1234567890",
  { schema: MyOppSchema }
);
console.log(typedOpp.customFields?.legacyId?.value); // typed as number

// ----- c) Search with auto‑pagination ----------------------------------
const results = await client.opportunities.search({
  query: "education",
  statuses: ["open"],
  maxItems: 200,   // stop after 200 items
});
console.log(`Found ${results.items.length} open education opportunities`);

Summary

  • The CommonGrants specification originates as YAML-based JSON-Schema files in website/public/schemas/yaml/, with ProposalBase.yaml serving as the root definition for all proposal data.
  • Runtime validation occurs through Ajv 2020 in website/src/lib/validation.ts, which dynamically loads YAML files and registers them using their filenames as schema IDs.
  • Build scripts like generate-type-formatting.ts and the CLI compiler in lib/cli/src/commands/compile/compile.ts generate documentation caches and SDK bundles automatically.
  • The TypeScript SDK uses hand-written Zod mirrors in lib/ts-sdk/src/schemas/zod/ to provide static type inference and runtime parsing for API responses.
  • The Opportunities client in lib/ts-sdk/src/client/opportunities.ts offers type-safe HTTP methods with support for custom field extensions via withCustomFields.

Frequently Asked Questions

How does the CommonGrants specification maintain consistency between frontend and backend?

The specification uses a single source of truth in the YAML schema files. Both the frontend validation (via Ajv in validation.ts) and the backend SDK (via Zod in lib/ts-sdk/src/schemas/zod/) derive their validation logic from these identical YAML definitions, ensuring that data validated on the client matches the server expectations.

What is the role of Zod in the CommonGrants SDK?

Zod provides runtime validation and static type inference for the TypeScript SDK. While the YAML files define the canonical schema, hand-written Zod schemas in fields.ts and models.ts mirror these definitions to enable type-safe API client operations and compile-time type checking for SDK consumers.

Can developers extend the CommonGrants schema with custom fields?

Yes. The SDK supports custom field extensions through the withCustomFields helper function. Developers can wrap base schemas like OpportunityBaseSchema with additional Zod validations, enabling type-safe access to custom grant opportunity attributes while maintaining conformance to the core specification.

How are the YAML schemas converted into documentation?

Build-time scripts in website/src/scripts/—specifically generate-type-formatting.ts and generate-schema-docs.ts—process the YAML files to generate formatted type caches and documentation mappings. This automation ensures that the documentation site always reflects the current state of the YAML schema definitions without manual updates.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →