# Data Validation in the TypeScript SDK for CommonGrants: A Complete Guide

> Explore dual-layer data validation in the CommonGrants TypeScript SDK using Zod for runtime and compile-time safety. Ensure API requests and responses conform to the protocol.

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

---

**The CommonGrants TypeScript SDK implements a dual-layer validation strategy using Zod schemas for runtime type safety and compile-time type inference, ensuring all API requests and responses strictly conform to the Common Grants Protocol specification.**

The Common Grants Protocol provides a standardized API specification for grant opportunities, and its official TypeScript SDK delivers robust data validation to prevent runtime errors. This article examines how the SDK handles data validation TypeScript SDK CommonGrants implementations across compile-time and runtime layers, referencing the actual source code in the `hhs/simpler-grants-protocol` repository.

## How the TypeScript SDK Validates Data: Architecture Overview

The validation architecture relies on three interconnected layers that work together to ensure data integrity. This design provides **static type safety** during development and **runtime assurance** when processing API responses.

### Compile-Time Validation via Zod Type Inference

The SDK generates TypeScript types automatically from Zod schemas using `z.infer`. This approach ensures that TypeScript definitions in [`lib/ts-sdk/src/types.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/types.ts) remain synchronized with the runtime validation rules defined in the schema files. When developers use SDK methods, they receive full IntelliSense and compile-time checking against the exact shapes defined in the Common Grants Protocol.

### Runtime Validation with Zod Schemas

Every data structure in the SDK—whether request parameters, response bodies, or custom fields—has a corresponding Zod schema in [`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). These schemas enforce constraints such as string length, URL format, enum values, and required fields. When the SDK receives data from the API, it calls `.parse()` on the appropriate schema, which either returns a typed object or throws a `ZodError` if validation fails.

## Key Validation Components and Source Files

### Schema Definitions in [`models.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/models.ts)

The file [`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) serves as the single source of truth for all data structures. It defines schemas such as `OpportunityBaseSchema`, `PaginatedBodyParamsSchema`, and `OppSearchRequestSchema`. These definitions include field types, optional markers, and validation constraints that apply to both requests and responses.

### Type Inference in [`types.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/types.ts)

Located at [`lib/ts-sdk/src/types.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/types.ts), this file exports TypeScript interfaces derived from the Zod schemas. For example, the `OppSearchRequest` type is inferred directly from its Zod counterpart, ensuring that TypeScript definitions automatically update when schemas change. This file typically references lines 67-78 for core request type definitions.

### Response Parsing in [`client.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/client.ts)

The [`lib/ts-sdk/src/client/client.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/client/client.ts) file contains the `parseItem` helper function (lines 295-304) that validates API responses. When the SDK receives data, this helper calls `schema.parse(item)` for each response entry. If parsing succeeds, the method returns a typed value; if it fails, the SDK captures the `ZodError` and propagates it for error handling.

### Custom Field Validation in [`with-custom-fields.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/with-custom-fields.ts)

For applications requiring domain-specific fields, [`lib/ts-sdk/src/extensions/with-custom-fields.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/extensions/with-custom-fields.ts) (lines 14-30) provides the `withCustomFields` extension. This utility accepts optional Zod schemas via the `valueSchema` property for each custom field. When validating, the SDK checks custom field values against these schemas, ensuring that extended opportunity data meets application-specific constraints.

### JSON Schema Cross-Validation in Test Utilities

To maintain fidelity with the official Common Grants Protocol specification, the SDK includes [`lib/ts-sdk/__tests__/utils/ajv-validator.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/__tests__/utils/ajv-validator.ts). This file implements an AJV validator that cross-checks Zod schemas against upstream JSON-Schema definitions during the fuzz-testing suite. This ensures that runtime validation rules remain compatible with the protocol's canonical specification.

## Practical Validation Examples

### Validating Request Bodies Before API Calls

Before sending data to the API, you can validate request objects using the exported Zod schemas:

```typescript
import { z } from "zod";
import { OppSearchRequest } from "@common-grants/sdk/types";
import { client } from "@common-grants/sdk";

// Build a request (could be user-provided)
const request: Partial<OppSearchRequest> = {
  search: "climate",
  filters: {
    status: { in: ["open", "forecasted"] },
  },
};

// Validate using the generated Zod schema (exported from models.ts)
const requestSchema = z.object({
  search: z.string().optional(),
  filters: z.object({
    status: z
      .object({ in: z.array(z.string()) })
      .optional(),
  }).optional(),
  sorting: z.any().optional(),
  pagination: z.any().optional(),
});

const parsed = requestSchema.parse(request);   // throws ZodError if invalid

// Safe call – the SDK will re-validate the response automatically
const result = await client.searchOpportunities(parsed);

```

*Source: request type definition – [types.ts line 67-78](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/types.ts#L67-L78)*

### Automatic Response Validation

The SDK automatically validates API responses using the `parseItem` helper in the client:

```typescript
import { client } from "@common-grants/sdk";

async function listOpenOpportunities() {
  // The client internally parses each item with OpportunityBaseSchema
  const response = await client.listOpportunities({
    filters: { status: { in: ["open"] } },
  });

  // `response.items` is now typed as OpportunityBase[]
  response.items.forEach((opp) => {
    console.log(opp.id, opp.title, opp.status.value);
    // If any field didn't match the schema, the call would have thrown earlier.
  });
}

```

*Underlying validation:* [`client.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/client.ts) uses `schema.parse(item)` for each response entry – see the `parseItem` helper in [client.ts line 295-304](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/client/client.ts#L295-L304).

### Validating Custom Field Values

For opportunities with custom fields, use the `with-custom-fields` extension to validate domain-specific constraints:

```typescript
import { withCustomFields } from "@common-grants/sdk/extensions";

// Define a custom field with a Zod schema that only accepts positive integers
const myCustomField = {
  key: "priority",
  value: 5,
  // Optional Zod schema for the value
  valueSchema: z.number().int().positive(),
};

// Extend a base opportunity with the custom field
const opp = await client.getOpportunity("123e4567-e89b-12d3-a456-426614174000");

// Validate custom fields
withCustomFields(opp.customFields).validate(); // throws if `priority` is not a positive int

```

*Source: custom-field handling – [with-custom-fields.ts line 14-30](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/extensions/with-custom-fields.ts#L14-L30)*

## Summary

- **Dual-layer validation strategy**: The SDK combines Zod schemas for runtime parsing with `z.infer` for compile-time TypeScript types, ensuring end-to-end type safety.
- **Single source of truth**: All data structures are defined in [`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), from which both runtime validators and TypeScript interfaces are derived.
- **Automatic response validation**: The `parseItem` helper in [`lib/ts-sdk/src/client/client.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/client/client.ts) validates every API response entry against Zod schemas, throwing descriptive errors for mismatches.
- **Extensible custom field support**: The `with-custom-fields` extension allows developers to attach Zod schemas to custom opportunity fields, enabling domain-specific validation logic.
- **Specification fidelity**: AJV-based cross-validation in [`lib/ts-sdk/__tests__/utils/ajv-validator.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/__tests__/utils/ajv-validator.ts) ensures Zod schemas remain compatible with the official Common Grants Protocol JSON Schema definitions.

## Frequently Asked Questions

### What validation library does the CommonGrants TypeScript SDK use?

The SDK uses **Zod** as its primary validation library. All schemas are defined in [`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) and related files, providing a single source of truth for data shapes and constraints. The SDK leverages Zod's type inference capabilities to generate TypeScript definitions automatically, ensuring that static types and runtime validators never diverge.

### How does the SDK handle validation errors at runtime?

When parsing fails, Zod throws a `ZodError` containing detailed issue information. The SDK catches these errors in [`lib/ts-sdk/src/client/client.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/client/client.ts) through the `parseItem` helper (lines 295-304), which calls `schema.parse(item)` for each response entry. If validation fails, the error propagates to the caller, where CLI utilities transform the technical Zod error into user-friendly validation messages indicating exactly which fields failed validation and why.

### Can I add custom validation rules for opportunity fields?

Yes. The SDK supports custom field validation through the `with-custom-fields` extension located in [`lib/ts-sdk/src/extensions/with-custom-fields.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/extensions/with-custom-fields.ts) (lines 14-30). When defining a custom field, you can provide an optional `valueSchema` property containing a Zod schema. The extension's `validate()` method then checks custom field values against these schemas, allowing you to enforce domain-specific constraints such as numeric ranges, specific string patterns, or complex object structures beyond the base protocol specification.

### How does the SDK ensure TypeScript types match the API specification?

The SDK maintains specification fidelity through two mechanisms. First, TypeScript types in [`lib/ts-sdk/src/types.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/src/types.ts) are derived directly from Zod schemas using `z.infer`, ensuring they automatically update when schemas change. Second, the test suite includes [`lib/ts-sdk/__tests__/utils/ajv-validator.ts`](https://github.com/hhs/simpler-grants-protocol/blob/main/lib/ts-sdk/__tests__/utils/ajv-validator.ts), which implements an AJV validator to cross-check Zod schemas against upstream JSON-Schema definitions during fuzz testing. This ensures the SDK's validation logic remains faithful to the official Common Grants Protocol specification.