Code Generation
The foir pull command generates TypeScript types, GraphQL documents, typed operations, and optionally React hooks, Remix loaders, or Swift types from your platform models. This gives your frontend code type-safe access to your content schema.
Basic Usage
foir pullThis fetches all models from the platform and generates output into your project. By default, files are written to ./src/generated/.
Command Options
foir pull [options]| Option | Description |
|---|---|
--config <path> | Path to config file (default: auto-discovers from project root) |
--only <models> | Comma-separated model keys to generate (e.g., page,blog-post) |
--no-prettier | Skip Prettier formatting on generated files |
--dry-run | Show what would be generated without writing any files |
--out <dir> | Override the output directory for types |
--swift <dir> | Generate Swift type files to the specified directory |
Examples
# Generate for all models
foir pull
# Generate only specific models
foir pull --only page,blog-post
# Preview without writing files
foir pull --dry-run
# Override output directory
foir pull --out ./src/types
# Generate Swift types alongside TypeScript
foir pull --swift ./ios/GeneratedConfiguration File
Create a foir.config.ts in your project root for persistent configuration. The CLI searches for config files in this order: foir.config.ts, foir.config.js, foir.config.mjs, .foirrc.ts, .foirrc.js, .foirrc.mjs.
import { defineConfig } from '@eide/foir-cli/config';
export default defineConfig({
pull: {
output: {
types: './src/generated/types',
documents: './src/generated/documents',
operations: './src/generated/operations',
hooks: './src/generated/hooks',
loaders: './src/generated/loaders',
swift: './ios/Generated',
},
targets: ['react'],
only: ['blog-post', 'page'],
domains: true,
includeInline: true,
prettier: true,
},
});Configuration Options
output
Controls where generated files are written.
| Property | Default | Description |
|---|---|---|
types | ./src/generated/types | Directory for TypeScript type files |
documents | ./src/generated/documents | Directory for GraphQL document files |
operations | Sibling of types | Directory for typed operation modules |
hooks | Sibling of types | Directory for React hooks (requires 'react' target) |
loaders | Sibling of types | Directory for Remix loaders (requires 'remix' target) |
swift | — | Directory for Swift type files (omit to skip Swift generation) |
targets
An array of code generation targets. Each target enables additional output:
'react'— Generates Apollo Client hooks (useQuery,useMutation) for each model. Output is written to thehooksdirectory.'remix'— Generates typed loader functions for Remix routes. Output is written to theloadersdirectory.
export default defineConfig({
pull: {
targets: ['react', 'remix'],
},
});domains
Controls which platform domain documents are generated. Set to true to generate all domains (the default), false to skip all domain documents, or provide a selective object:
export default defineConfig({
pull: {
domains: {
auth: true,
authProviders: true,
files: true,
sync: true,
notifications: true,
operations: true,
schedules: true,
sharing: true,
search: true,
analytics: true,
},
},
});only
Filter code generation to specific model keys. When omitted or empty, all models are generated.
export default defineConfig({
pull: {
only: ['blog-post', 'page', 'author'],
},
});This can also be set from the command line with --only blog-post,page,author. CLI flags override the config file.
includeInline
When true (the default), inline-only models are included for type resolution. These are models that are embedded within other models rather than stored as independent records. Including them ensures type completeness.
prettier
When true (the default), generated files are formatted with Prettier using your project’s Prettier configuration. Pass --no-prettier on the command line to skip formatting (useful in CI for faster builds).
Generated Output Structure
After running foir pull, your generated directory will contain:
src/generated/
├── types/
│ ├── index.ts # Re-exports all types
│ ├── field-types.ts # Shared value types (ImageValue, LinkValue, etc.)
│ ├── config.ts # ModelConfig interface and defineModel helper
│ ├── customer-profile.ts # Customer profile types (if schema exists)
│ └── models/
│ ├── index.ts # Re-exports all model types
│ ├── blog-post.ts # BlogPostData type + blogPostConfig
│ └── page.ts # PageData type + pageConfig
├── documents/
│ ├── blog-post.graphql # GetBlogPost, ListBlogPosts, Create/Update/Delete
│ ├── page.graphql # GetPage, ListPages, Create/Update/Delete
│ ├── customer-profile.graphql # Customer profile operations
│ ├── _shared.graphql # Shared fragments (if sharing is enabled)
│ └── public-schema.graphql # Full public GraphQL schema
├── operations/
│ ├── _common.ts # Shared operation types
│ ├── index.ts # Re-exports all operations
│ ├── blog-post.ts # Typed operations for blog posts
│ ├── page.ts # Typed operations for pages
│ └── customer-profile.ts # Customer profile operations
├── hooks/ # (when target includes 'react')
│ ├── index.ts # Re-exports all hooks
│ ├── blog-post.ts # useBlogPost, useBlogPosts, etc.
│ ├── page.ts # usePage, usePages, etc.
│ └── customer-profile.ts # useCustomerProfile, etc.
└── loaders/ # (when target includes 'remix')
├── index.ts # Re-exports all loaders
├── blog-post.ts # loadBlogPost, loadBlogPosts, etc.
├── page.ts # loadPage, loadPages, etc.
└── customer-profile.ts # loadCustomerProfile, etc.When Swift generation is enabled (--swift or output.swift), an additional directory is created:
ios/Generated/
├── FieldTypes.swift # Shared Swift value types
├── ModelKeys.swift # Enum of all model keys
├── BlogPost.swift # BlogPost struct
├── Page.swift # Page struct
└── CustomerProfile.swift # CustomerProfile struct (if schema exists)Using Generated Types
TypeScript Types
import type { BlogPostData, PageData } from './generated/types';
import { blogPostConfig } from './generated/types';
// Type-safe data access
function renderPost(post: BlogPostData) {
return <h1>{post.title}</h1>;
}
// Config includes field definitions and capability flags
console.log(blogPostConfig.versioning); // true
console.log(blogPostConfig.publicApi); // trueReact Hooks (with 'react' target)
import { useBlogPosts, useBlogPost } from './generated/hooks';
function BlogList() {
const { data, loading } = useBlogPosts({ limit: 10 });
if (loading) return <p>Loading...</p>;
return (
<ul>
{data?.items.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}Remix Loaders (with 'remix' target)
import { loadBlogPosts } from './generated/loaders';
export const loader = async ({ request }: LoaderFunctionArgs) => {
return loadBlogPosts({ limit: 10 });
};GraphQL Documents
The generated .graphql files can be used with any GraphQL client or toolchain:
# From generated documents/blog-post.graphql
query GetBlogPost($id: ID!) {
record(id: $id) {
id
naturalKey
data
}
}Dry Run Mode
Use --dry-run to preview what files would be generated without writing anything:
foir pull --dry-runThis prints a list of all files that would be created along with a total count, which is useful for verifying configuration changes before committing to a full generation.