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:
lib/ts-sdk/src/schemas/zod/fields.ts– Defines primitives likeEventSchema,MoneySchema, andCustomFieldlib/ts-sdk/src/schemas/zod/models.ts– Contains domain models includingOpportunityBaseSchema, sorting, and filters
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/, withProposalBase.yamlserving 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.tsand the CLI compiler inlib/cli/src/commands/compile/compile.tsgenerate 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.tsoffers type-safe HTTP methods with support for custom field extensions viawithCustomFields.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →