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

> Discover how the CommonGrants specification is built from YAML to a TypeScript SDK using a schema-first approach. Learn about the tools and processes involved.

- Repository: [U.S. Department of Health & Human Services/simpler-grants-protocol](https://github.com/hhs/simpler-grants-protocol)
- Tags: internals
- Published: 2026-03-03

---

**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`](https://github.com/hhs/simpler-grants-protocol/blob/main/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`](https://github.com/hhs/simpler-grants-protocol/blob/main/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`](https://github.com/hhs/simpler-grants-protocol/blob/main/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`](https://github.com/hhs/simpler-grants-protocol/blob/main/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`](https://github.com/hhs/simpler-grants-protocol/blob/main/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`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/schemas/zod/fields.ts)** – Defines primitives like `EventSchema`, `MoneySchema`, and `CustomField`
- **[`lib/ts-sdk/src/schemas/zod/models.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/schemas/zod/models.ts)** – Contains domain models including `OpportunityBaseSchema`, sorting, and filters

The `Opportunities` client in [`lib/ts-sdk/src/client/opportunities.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/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:

```ts
// -------------------------------------------------
// 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:

```ts
// -------------------------------------------------
// 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`](https://github.com/hhs/simpler-grants-protocol/blob/main/ProposalBase.yaml) serving as the root definition for all proposal data.
- **Runtime validation** occurs through Ajv 2020 in [`website/src/lib/validation.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/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`](https://github.com/hhs/simpler-grants-protocol/blob/main/generate-type-formatting.ts) and the CLI compiler in [`lib/cli/src/commands/compile/compile.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/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`](https://github.com/hhs/simpler-grants-protocol/blob/main/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`](https://github.com/hhs/simpler-grants-protocol/blob/main/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`](https://github.com/hhs/simpler-grants-protocol/blob/main/fields.ts) and [`models.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/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`](https://github.com/hhs/simpler-grants-protocol/blob/main/generate-type-formatting.ts) and [`generate-schema-docs.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/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.