Getting Started
Assemble type-safe TanStack pieces for your data layers.
Assemble the Pieces
Every data layer is a puzzle. Your API schema defines the shapes, but you still need perfectly-fitting pieces for your applications - TanStack provides type-safe queryOptions, mutationOptions, formOptions, etc that snap right into place.
Tangrams generates those pieces from your GraphQL or OpenAPI schemas.
The Puzzle Pieces
Tangrams generates code that integrates seamlessly with the TanStack ecosystem:
| Piece | What It Generates | Fits Into |
|---|---|---|
| Query Options | queryOptions() with typed variables and responses | useQuery, useSuspenseQuery |
| Mutation Options | mutationOptions() with typed inputs | useMutation |
| Form Options | formOptions() with schema validation | useForm |
| DB Collections | Collection options with persistence handlers | TanStack DB |
Quick Start
# Install the CLI
bun add -D tangrams
# Initialize your config (interactive)
bunx tangrams initThe init command will guide you through setting up your configuration, including:
- Choosing a validation library (Zod, Valibot, ArkType, or Effect)
- Selecting your API source type (GraphQL or OpenAPI)
- Pointing to your schema or spec
Once configured, generate your code:
# Generate the pieces
bunx tangrams generateTip: Use
bunx tangrams init --skipto generate a template config file for manual editing.
Installation
bun add -D tangramsnpm install -D tangramspnpm add -D tangramsAdditional peer dependencies are required based on which TanStack library you're generating for. See the individual library guides for details.
How It Works
- Bring your schema - Point Tangrams at your GraphQL SDL or OpenAPI spec
- Configure what to generate - Choose which pieces you need (Query, Form, and DB are currently supported)
- Run the generator - Get fully-typed, ready-to-use code
- Import and use - The pieces fit perfectly into your TanStack hooks
// Generated pieces snap right in
import { useQuery, useMutation } from "@tanstack/react-query"
import { getUserQueryOptions, updateUserMutationOptions } from "./tangrams/my-api/query/options"
function UserProfile({ id }: { id: string }) {
// Type-safe query with automatic cache keys
const { data } = useQuery(getUserQueryOptions({ id }))
// Type-safe mutation
const { mutate } = useMutation(updateUserMutationOptions())
return <div>{data?.user.name}</div>
}Configuration Reference
Tangrams uses a tangrams.config.ts file in your project root. Run bunx tangrams init to create one, or create it manually.
Complete Configuration Example
Here's a comprehensive example showing all available configuration options:
import { defineConfig } from "tangrams"
export default defineConfig({
// Directory where the tangrams folder will be generated (default: ".")
// With ".", files are generated at ./tangrams/<source>/...
// With "./src", files are generated at ./src/tangrams/<source>/...
output: ".",
// Validation library for generated schemas (default: "zod")
// Supported: "zod", "valibot", "arktype", "effect"
// All four support Standard Schema for TanStack Form compatibility
validator: "zod",
// Array of data sources to generate from
sources: [
// ===================
// GraphQL Source
// ===================
{
// Unique name for this source (lowercase alphanumeric with hyphens)
name: "graphql",
// Source type discriminator
type: "graphql",
// Schema configuration - choose ONE of the following:
// Option A: URL-based (introspection)
schema: {
url: "http://localhost:4000/graphql",
// Optional headers for introspection request
headers: {
"x-api-key": process.env.API_KEY,
},
},
// Option B: File-based (local SDL files)
// schema: {
// file: "./schema.graphql",
// // Or multiple files with glob patterns:
// // file: ["./schema.graphql", "./extensions/**/*.graphql"],
// },
// Glob pattern(s) for GraphQL document files (.graphql)
documents: "./src/graphql/**/*.graphql",
// Or multiple patterns:
// documents: ["./src/graphql/**/*.graphql", "./src/queries/**/*.graphql"],
// What to generate from this source
// Options: "query", "form", "db"
// Note: "db" automatically enables "query"
generates: ["query", "form", "db"],
// Optional overrides
overrides: {
// Custom scalar mappings (GraphQL only)
// Values must be valid expressions for the configured validator
scalars: {
DateTime: "z.string()", // Use z.coerce.date() for Date objects
JSON: "z.unknown()",
Cursor: "z.string()",
},
// TanStack DB overrides
db: {
collections: {
// Key is the entity name (e.g., "User", "Post")
User: {
// Override the key field (default: auto-detected "id")
keyField: "rowId",
// Sync mode: "full" (default) or "on-demand"
syncMode: "on-demand",
// Predicate mapping preset for on-demand mode
// Options: "hasura", "prisma", "rest-simple", "jsonapi"
predicateMapping: "hasura",
// Override selector path for extracting array from response
// Use dot notation for nested paths
selectorPath: "users.data",
},
},
},
// TanStack Form overrides
form: {
// Validator timing (default: "onSubmitAsync")
// Options: "onChange", "onChangeAsync", "onBlur", "onBlurAsync",
// "onSubmit", "onSubmitAsync", "onDynamic"
validator: "onDynamic",
// Validation logic (only used with "onDynamic" validator)
validationLogic: {
// When to run initial validation: "change", "blur", "submit"
mode: "submit",
// When to revalidate after first submission
modeAfterSubmission: "change",
},
},
},
},
// ===================
// OpenAPI Source
// ===================
{
name: "api",
type: "openapi",
// OpenAPI spec - local file path or remote URL
spec: "./openapi.yaml",
// spec: "https://api.example.com/openapi.json",
// Optional headers for fetching remote spec
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
},
// Optional path filtering with glob patterns
include: ["/users/**", "/posts/**"],
exclude: ["/internal/**", "/admin/**"],
generates: ["query", "form"],
overrides: {
db: {
collections: {
Pet: {
keyField: "petId",
syncMode: "on-demand",
predicateMapping: "rest-simple",
},
},
},
form: {
validator: "onBlurAsync",
},
},
},
],
})Root Options
| Option | Type | Default | Description |
|---|---|---|---|
output | string | "." | Directory where the tangrams folder will be generated |
validator | "zod" | "valibot" | "arktype" | "effect" | "zod" | Validation library for generated schemas |
sources | SourceConfig[] | (required) | Array of data sources (minimum 1 required) |
Validator Libraries
Tangrams supports four validation libraries that all implement Standard Schema:
| Library | Import | Description |
|---|---|---|
| Zod | zod | The default. Full-featured schema validation with great TypeScript inference. |
| Valibot | valibot | Lightweight alternative with modular design and smaller bundle size. |
| ArkType | arktype | Type-first validation with runtime safety and excellent TypeScript integration. |
| Effect | effect | Part of the Effect ecosystem. Requires Schema.standardSchemaV1() wrapper for TanStack Form. |
All four libraries work seamlessly with TanStack Form since they implement the Standard Schema protocol. Choose based on your preferences for bundle size, API style, or existing usage in your project.
Note: Effect Schema requires wrapping with
Schema.standardSchemaV1()for Standard Schema compliance. Tangrams handles this automatically in generated form options.
// Use Zod (default)
export default defineConfig({
validator: "zod",
sources: [/* ... */],
})
// Use Valibot for smaller bundles
export default defineConfig({
validator: "valibot",
sources: [/* ... */],
})
// Use ArkType for type-first validation
export default defineConfig({
validator: "arktype",
sources: [/* ... */],
})
// Use Effect for the Effect ecosystem
export default defineConfig({
validator: "effect",
sources: [/* ... */],
})Install your chosen validator as a peer dependency:
bun add zodbun add valibotbun add arktypebun add effectGraphQL Source Options
| Option | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique source name (lowercase alphanumeric with hyphens, starting with a letter) |
type | "graphql" | Yes | Source type discriminator |
schema | object | Yes | Schema configuration (see below) |
documents | string | string[] | Yes | Glob pattern(s) for .graphql operation files |
generates | array | Yes | What to generate: ["query"], ["query", "form"], ["db"], etc. |
overrides | object | No | Override scalars, DB, and form settings (see below) |
GraphQL Schema Options
Configure your GraphQL schema using one of these approaches:
URL-based (introspection):
| Option | Type | Required | Description |
|---|---|---|---|
schema.url | string | Yes | GraphQL endpoint URL for introspection |
schema.headers | Record<string, string> | No | Headers to send with introspection request |
File-based (local SDL files):
| Option | Type | Required | Description |
|---|---|---|---|
schema.file | string | string[] | Yes | Path or glob pattern(s) for .graphql schema files |
OpenAPI Source Options
| Option | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique source name (lowercase alphanumeric with hyphens, starting with a letter) |
type | "openapi" | Yes | Source type discriminator |
spec | string | Yes | Path to OpenAPI spec (local file or URL) |
headers | Record<string, string> | No | Headers for fetching remote spec |
include | string[] | No | Glob patterns for paths to include (e.g., ["/users/**"]) |
exclude | string[] | No | Glob patterns for paths to exclude |
generates | array | Yes | What to generate: ["query"], ["query", "form"], ["db"], etc. |
overrides | object | No | Override DB and form settings (see below) |
Generates Options
The generates array specifies which TanStack artifacts to generate:
| Value | Description | Dependencies |
|---|---|---|
"query" | TanStack Query (queryOptions, mutationOptions) | None |
"form" | TanStack Form (formOptions with schema validation) | None |
"db" | TanStack DB (queryCollectionOptions) | Auto-enables "query" |
Examples:
generates: ["query"] // Query only
generates: ["query", "form"] // Query and Form
generates: ["db"] // DB (auto-enables Query)
generates: ["query", "form", "db"] // All threeOverrides Configuration
Scalars (GraphQL only)
Map custom GraphQL scalars to validator expressions. Values must be valid expressions for your configured validator library:
overrides: {
scalars: {
DateTime: "z.string()", // ISO date string
JSON: "z.unknown()", // Unknown JSON data
Cursor: "z.string()", // Pagination cursor
},
}overrides: {
scalars: {
DateTime: "v.string()",
JSON: "v.unknown()",
Cursor: "v.string()",
},
}overrides: {
scalars: {
DateTime: 'type("string")',
JSON: 'type("unknown")',
Cursor: 'type("string")',
},
}overrides: {
scalars: {
DateTime: "Schema.String",
JSON: "Schema.Unknown",
Cursor: "Schema.String",
},
}Note: Scalar values must start with a valid prefix for your validator:
z.for Zod,v.for Valibot,type(/type.for ArkType, orSchema.for Effect. Using raw TypeScript types like"string"or"Date"will throw a validation error with a helpful suggestion.
Built-in Scalar Mappings:
The following GraphQL scalars are handled automatically and don't need to be configured:
| GraphQL Scalar | Generated Schema |
|---|---|
ID | z.string() |
String | z.string() |
Int | z.number() |
Float | z.number() |
Boolean | z.boolean() |
DateTime | z.string() |
Date | z.string() |
JSON | z.unknown() |
BigInt | z.bigint() |
UUID | z.string() |
DB Overrides
Configure TanStack DB collection generation:
overrides: {
db: {
collections: {
EntityName: {
keyField: "uuid", // Override key field (default: auto-detected "id")
syncMode: "on-demand", // "full" (default) or "on-demand"
predicateMapping: "hasura", // Preset for on-demand mode
selectorPath: "data.items", // Path to extract array from response
},
},
},
}| Option | Type | Default | Description |
|---|---|---|---|
keyField | string | Auto-detected "id" | Field to use as unique key |
syncMode | "full" | "on-demand" | "full" | Data sync strategy |
predicateMapping | string | Auto-detected | Predicate translation preset |
selectorPath | string | Auto-detected | Dot-notation path to extract array from response |
Predicate Mapping Presets:
| Preset | Use Case | Example Output |
|---|---|---|
"hasura" | GraphQL Hasura | where: { field: { _eq: value } } |
"prisma" | GraphQL Prisma | where: { field: { equals: value } } |
"rest-simple" | REST APIs | field_eq=value, sort=field:asc |
"jsonapi" | JSON:API | filter[field]=value, sort=-field |
Form Overrides
Configure TanStack Form generation:
overrides: {
form: {
validator: "onDynamic",
validationLogic: {
mode: "submit",
modeAfterSubmission: "change",
},
},
}| Option | Type | Default | Description |
|---|---|---|---|
validator | string | "onSubmitAsync" | When validation runs |
validationLogic | object | - | Config for "onDynamic" validator only |
validationLogic.mode | string | "submit" | Initial validation trigger |
validationLogic.modeAfterSubmission | string | "change" | Revalidation trigger after first submit |
Available Validators:
| Validator | Description |
|---|---|
"onChange" | Validate on every change (sync) |
"onChangeAsync" | Validate on every change (async) |
"onBlur" | Validate when field loses focus (sync) |
"onBlurAsync" | Validate when field loses focus (async) |
"onSubmit" | Validate on form submit (sync) |
"onSubmitAsync" | Validate on form submit (async) - default |
"onDynamic" | Dynamic validation with revalidateLogic |
Output Directory Structure
Generated files are organized by source name:
tangrams/
└── <source-name>/
├── client.ts # API client (GraphQL or REST)
├── schema.ts # Validation schemas + TypeScript types
├── functions.ts # Standalone fetch functions
├── query/
│ └── options.ts # queryOptions, mutationOptions
├── form/
│ └── options.ts # formOptions
└── db/
└── collections.ts # TanStack DB collectionsCLI Reference
tangrams init
Initialize a new tangrams.config.ts file:
tangrams init [options]
Options:
-f, --force Overwrite existing config file
-s, --skip Skip interactive prompts and generate a template configBy default, the init command runs interactively, prompting you for configuration values. Use --skip to generate a template config file with placeholder values that you can edit manually.
tangrams generate
Generate code from your configured sources:
tangrams generate [options]
Options:
-c, --config <path> Path to config file
-f, --force Force regeneration of all files including client
-w, --watch Watch for file changes and regenerate
--clean Remove stale source directories from previous generations
-y, --yes Skip confirmation prompts (use with --clean)
--env-file <path> Path to env file (can be specified multiple times)
--no-dotenv Disable automatic .env file loadingCleanup Mode:
When using --clean, Tangrams detects and removes stale source directories from previous generations. This is useful when you rename or remove sources from your configuration:
# Remove stale artifacts (prompts for confirmation)
tangrams generate --clean
# Remove stale artifacts without prompting
tangrams generate --clean --yesIf Tangrams detects that a source was renamed (same schema/spec, different name), it will automatically copy the client.ts file to the new source directory before removing the old one. This preserves any customizations you've made to the client.
Watch Mode:
When using --watch, Tangrams watches your config file, GraphQL documents, and OpenAPI specs for changes. Press r to force a full refresh, or q to quit. When combined with --clean, stale artifacts are automatically removed on config changes without prompting.
Next Steps
Choose your TanStack library to get started:
- TanStack Query - Generate
queryOptionsandmutationOptions - TanStack Form - Generate
formOptionswith schema validation - TanStack DB - Generate collections with local-first data sync
