Skip to content

Custom fields overview

The CommonGrants protocol defines a core set of fields that are widely applicable across federal, state, local, and private grant opportunities. However, many implementations need fields that are relevant to their specific context but aren’t universal enough to belong in the core protocol.

Custom fields solve this problem by providing a structured way to extend CommonGrants models with additional data. For the formal rules governing how custom fields work (e.g., which models support them, what implementations MUST and SHALL NOT do), see the custom fields section of the specification.

Each custom field includes:

PropertyDescription
nameName of the custom field
fieldTypeThe JSON schema type used when de-serializing the value
schema(Optional) Link to the full JSON schema for the field
valueThe field’s value
description(Optional) Description of the custom field’s purpose

Custom fields live in the customFields property of any CommonGrants model, allowing implementations to carry additional data while staying compatible with the base protocol.

The custom field catalog is a shared repository of pre-defined custom fields that are not part of the core CommonGrants library but are reused across implementations.

The catalog has two primary goals:

  1. Standardize semantically equivalent fields. Without a shared catalog, two different teams might define fields like “agency” with slightly different names, types, or structures. This makes interoperability harder and increases integration costs. The catalog gives implementers a single place to find and reuse existing field definitions before creating their own.

  2. Create a path for promoting fields into the core protocol. As custom fields gain adoption across implementations, they become candidates for inclusion in the base protocol. The catalog provides visibility into which fields are widely used, creating a foundation for a “field promotion” pipeline where popular custom fields can move from the catalog into @common-grants/core.

Both the TypeScript and Python SDKs provide helper functions for extending base models with typed custom fields.

Before adding custom fields to a model, you define a spec for each field that describes its type and (optionally) a schema for validating its value.

import { z } from "zod";
import { OpportunityBaseSchema } from "@common-grants/sdk/schemas";
import { CustomFieldType } from "@common-grants/sdk/constants";
import { withCustomFields } from "@common-grants/sdk/extensions";
// Define a value schema for structured types
const AgencyValueSchema = z.object({
code: z.string().optional(),
name: z.string().optional(),
});
// Extend the base schema with typed custom fields
const OpportunitySchema = withCustomFields(OpportunityBaseSchema, {
agency: {
fieldType: CustomFieldType.object,
value: AgencyValueSchema,
description: "The agency offering this opportunity",
},
category: {
fieldType: CustomFieldType.string,
description: "Grant category",
},
} as const);

Once you’ve defined your extended schema, use it to parse and validate incoming data. Registered custom fields get full type safety, while unregistered fields pass through validation with the base CustomField type.

import { getCustomFieldValue } from "@common-grants/sdk/extensions";
// Parse incoming data
const opportunity = OpportunitySchema.parse(responseData);
// Access custom fields directly on the parsed object
const agencyField = opportunity.customFields?.agency;
console.log(agencyField?.value.code); // typed as string | undefined
console.log(agencyField?.value.name); // typed as string | undefined
// Or use getCustomFieldValue() to extract just the value
const agency = getCustomFieldValue(
opportunity.customFields,
"agency",
AgencyValueSchema,
);
console.log(agency?.code); // typed as string | undefined
const category = getCustomFieldValue(
opportunity.customFields,
"category",
z.string(),
);
console.log(category); // typed as string | undefined

The custom field catalog is open to contributions. If you have a field that could be useful across multiple CommonGrants implementations, we encourage you to propose it.

Consider contributing a custom field when:

  • You’ve defined a field that other implementers are likely to need
  • An existing field in the catalog doesn’t quite fit your use case (propose a modification)
  • You want to help standardize a field that multiple implementations are already defining independently

New custom fields are proposed by opening a pull request against the CommonGrants repository. At a high level, the process involves:

  1. Define the field in TypeSpec. Create a new .tsp file in website/src/specs/custom-fields/ that extends CustomField with your field’s metadata.

    Here’s an example based on the agency field:

    import "@common-grants/core";
    import "@typespec/json-schema";
    import "./index.tsp";
    using CommonGrants.Fields;
    using JsonSchema;
    namespace CustomFields.Agency;
    @extension("x-tags", #[Tags.organization])
    @extension("x-author", "CommonGrants")
    @extension("x-version", "0.1.0")
    @extension(
    "x-valid-schemas",
    #[ValidSchemas.OpportunityBase, ValidSchemas.CompetitionBase]
    )
    model CustomAgency extends CustomField {
    name: "agency";
    fieldType: CustomFieldType.object;
    @example(#{
    code: "CMS",
    name: "Centers for Medicare & Medicaid Services",
    })
    value: {
    /** The agency's acronym or code */
    code?: string;
    /** The full name of the agency */
    name?: string;
    };
    description: "Information about the agency offering this opportunity";
    }
  2. Register the field. Add an import for your new file in website/src/specs/custom-fields/index.tsp and add an entry to website/src/content/custom-fields/index.json with a schema reference and timestamps.

  3. Open a pull request. Submit your PR following the contributing guidelines. Include context about why the field is useful and which implementations could benefit from it.

For detailed instructions on building and testing the website locally (including compiling TypeSpec and previewing your changes), see the website’s DEVELOPMENT.md.

  • Check the catalog first. Before defining a new field, browse the custom field catalog to see if a similar field already exists. If one is close but not quite right, consider proposing a modification instead.
  • Use descriptive names. Field names should clearly convey their purpose. Prefer specific names like federalOpportunityNumber over generic ones like refNumber.
  • Choose the right type. Use the most specific CustomFieldType for your field’s value. For structured data, use object with well-documented properties.
  • Tag appropriately. Use the tags defined in index.tsp (e.g., identifier, federal, organization) to help users discover your field through search and filtering.
  • Specify valid schemas. Indicate which base models your field applies to (e.g., OpportunityBase, CompetitionBase) using the x-valid-schemas extension.
  • Include examples. Add @example decorators so that users can see realistic sample values in the catalog and generated documentation.