Automated code generation from Payload CMS collections. This tool bridges the gap between Payload CMS collections and web app development by automatically generating TypeScript types, API client methods, React components, and route files.
- π Auto-detection: Automatically scans and analyzes Payload collection files
- π Type Generation: Generates TypeScript interfaces from collection schemas
- π API Clients: Creates type-safe API client methods for each collection
- βοΈ React Components: Generates React Router routes and components
- π¨ Code Formatting: Automatically formats generated code with Prettier
- π§ Framework Agnostic: Works with any frontend framework
- π¦ Zero Dependencies: Minimal dependencies for maximum compatibility
npm install @alloylab/collection-registry
# or
yarn add @alloylab/collection-registry
# or
pnpm add @alloylab/collection-registry
# Run with default paths
npx collection-registry
# Or specify custom paths
npx collection-registry \
--collections-path ./cms/src/collections \
--output-path ./web/app/lib \
--types-path ./cms/src/payload-types.ts
import { CollectionRegistry } from '@alloylab/collection-registry';
const registry = new CollectionRegistry({
collectionsPath: './src/collections',
outputPath: './generated',
typesPath: './payload-types.ts',
format: true,
});
await registry.generate();
Option | Description | Default |
---|---|---|
--collections-path |
Path to Payload collections directory | ./src/collections |
--output-path |
Path to output generated files | ./generated |
--types-path |
Path to Payload generated types | ./payload-types.ts |
--format |
Format generated files with Prettier | false |
--help |
Show help message | - |
const config = {
collectionsPath: './cms/src/collections', // Required
outputPath: './web/app/lib', // Required
typesPath: './cms/src/payload-types.ts', // Required
format: true, // Optional
baseUrl: 'process.env.CMS_API_URL', // Optional
};
The library is designed to be transparent and customizable. You can override its assumptions:
const config = {
// Basic configuration
collectionsPath: './src/collections',
outputPath: './generated',
typesPath: './payload-types.ts',
// Field Detection Customization
fieldMappings: {
slugField: 'urlSlug', // Use 'urlSlug' instead of 'slug'
statusField: 'publishStatus', // Use 'publishStatus' instead of 'status'
seoField: 'metaData', // Use 'metaData' instead of 'seo'
navigationField: 'showInMenu', // Use 'showInMenu' instead of 'showInNavigation'
featuredImageField: 'heroImage', // Use 'heroImage' instead of 'featuredImage'
excerptField: 'summary', // Use 'summary' instead of 'excerpt'
tagsField: 'categories', // Use 'categories' instead of 'tags'
authorField: 'writer', // Use 'writer' instead of 'author'
},
// Status Value Customization
statusValues: {
draft: 'draft',
published: 'live', // Use 'live' instead of 'published'
scheduled: 'scheduled',
archived: 'hidden', // Use 'hidden' instead of 'archived'
},
// Template Customization
templates: {
collectionType: `
export interface {{collectionName}} {
id: string;
{{#each fields}}
{{name}}: {{type}};
{{/each}}
}
`,
apiClient: `
export class {{collectionName}}Client {
async get{{collectionName}}s(): Promise<{{collectionName}}[]> {
// Custom implementation
}
}
`,
},
// Debug Mode
debug: true, // Enable detailed logging of the analysis process
};
The tool generates the following files in your output directory:
base.ts
- Base types and interfaces{collection}.ts
- Individual collection typesindex.ts
- Exports all types
base.ts
- Base client class{collection}.ts
- Individual collection clientsindex.ts
- Exports all clientspayloadClient.ts
- Main client aggregator
{collection}._index.tsx
- Collection index route{collection}.$slug.tsx
- Collection detail route
This library is not a black box. Here's exactly what it does:
The library reads your Payload CMS collection files and extracts:
- Collection metadata (slug, displayName, pluralName)
- Field definitions and types
- Common patterns (slug, status, SEO, navigation, etc.)
For each collection, it analyzes fields to detect:
- Slug fields - URL-friendly identifiers (
name: 'slug'
,type: 'text'
) - Status fields - Draft/published states (
name: 'status'
,type: 'select'
) - SEO fields - Meta data groups (
name: 'seo'
,type: 'group'
) - Navigation fields - Menu visibility (
name: 'showInNavigation'
,type: 'checkbox'
) - Media fields - Featured images (
name: 'featuredImage'
,type: 'upload'
) - Content fields - Rich text, excerpts (
name: 'excerpt'
,type: 'textarea'
) - Taxonomy fields - Tags, categories, authors (
name: 'tags'
,type: 'array'
)
Creates TypeScript interfaces based on:
- Payload field types β TypeScript types
- Detected patterns β Specialized types
- Collection structure β Complete interfaces
Generates ready-to-use code using configurable templates.
The tool automatically analyzes your Payload collections and detects:
- β Slug fields - For URL-based routing
- β Status fields - For draft/published content
- β SEO fields - For search optimization
- β Navigation fields - For menu generation
- β Featured images - For content previews
- β Excerpt fields - For content summaries
- β Tag fields - For content categorization
- β Author fields - For content attribution
// src/collections/Posts.ts
import type { CollectionConfig } from 'payload';
export const Posts: CollectionConfig = {
slug: 'posts',
admin: {
useAsTitle: 'title',
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'slug',
type: 'text',
required: true,
unique: true,
},
{
name: 'status',
type: 'select',
options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Published', value: 'published' },
],
defaultValue: 'draft',
},
{
name: 'excerpt',
type: 'textarea',
},
{
name: 'featuredImage',
type: 'upload',
relationTo: 'media',
},
{
name: 'seo',
type: 'group',
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'description',
type: 'textarea',
},
],
},
],
};
// types/posts.ts
export interface Post {
id: string;
title: string;
slug: string;
status: 'draft' | 'published';
excerpt?: string;
featuredImage?: Media;
seo?: {
title?: string;
description?: string;
};
createdAt: string;
updatedAt: string;
}
// clients/posts.ts
export class PostsClient extends BasePayloadClient {
async getPosts(options?: QueryOptions): Promise<PayloadResponse<Post>> {
// Implementation
}
async getPost(slug: string, draft = false): Promise<Post> {
// Implementation
}
async getPublishedPosts(
options?: Omit<QueryOptions, 'where'>
): Promise<Post[]> {
// Implementation
}
}
// app/routes/posts._index.tsx
import { postsClient } from '~/lib/clients';
export async function loader() {
const posts = await postsClient.getPublishedPosts();
return { posts };
}
// pages/posts/index.tsx
import { postsClient } from '../lib/clients';
export async function getStaticProps() {
const posts = await postsClient.getPublishedPosts();
return { props: { posts } };
}
// src/routes/posts/+page.server.ts
import { postsClient } from '$lib/clients';
export async function load() {
const posts = await postsClient.getPublishedPosts();
return { posts };
}
You can extend the tool with custom templates:
import { CollectionRegistry } from '@alloylab/collection-registry';
class CustomRegistry extends CollectionRegistry {
generateCustomFiles() {
// Your custom generation logic
}
}
Customize TypeScript type mapping:
import { getTypeScriptType } from '@alloylab/collection-registry';
// Extend the type mapping
const customTypeMap = {
...defaultTypeMap,
customField: 'CustomType',
};
The library uses pattern matching to detect common fields. Here's exactly how it works:
// Slug Detection
if (field.name === 'slug' && field.type === 'text') {
return { hasSlug: true, slugField: field.name };
}
// Status Detection
if (field.name === 'status' && field.type === 'select') {
const options = field.options || [];
const hasDraft = options.some((opt) => opt.value === 'draft');
const hasPublished = options.some((opt) => opt.value === 'published');
return { hasStatus: true, statusField: field.name };
}
// SEO Detection
if (field.name === 'seo' && field.type === 'group') {
const seoFields = field.fields || [];
const hasTitle = seoFields.some((f) => f.name === 'title');
const hasDescription = seoFields.some((f) => f.name === 'description');
return { hasSEO: true, seoField: field.name };
}
The library maps Payload field types to TypeScript types:
const typeMap = {
text: 'string',
textarea: 'string',
richText: 'any', // Rich text content
number: 'number',
date: 'string', // ISO date string
select: 'string | undefined',
checkbox: 'boolean',
upload: 'string | Media', // File ID or Media object
relationship: 'string | RelatedType', // ID or related object
array: 'ArrayType[]',
group: 'GroupType',
blocks: 'BlockType[]',
};
You can override any part of the analysis:
// Custom field analyzer
class CustomFieldAnalyzer extends FieldAnalyzer {
analyzeField(field: any): FieldAnalysis {
// Add your custom field detection logic
if (field.type === 'customField') {
return { type: 'CustomType', hasCustomField: true };
}
return super.analyzeField(field);
}
}
// Custom type mapper
class CustomTypeMapper extends TypeMapper {
mapPayloadTypeToTypeScript(payloadType: string): string {
if (payloadType === 'customField') {
return 'CustomType';
}
return super.mapPayloadTypeToTypeScript(payloadType);
}
}
-
Collections not found
- Ensure the collections path is correct
- Check that collection files have
.ts
extension - Verify collection files export a valid
CollectionConfig
-
Types not generated
- Run
payload generate:types
first - Check the types path is correct
- Ensure Payload types file exists
- Run
-
Formatting errors
- Install Prettier:
npm install prettier
- Check Prettier configuration
- Use
--no-format
to skip formatting
- Install Prettier:
-
Custom field names not detected
- Use
fieldMappings
configuration to map your custom field names - Check the field detection logic in the debug output
- Extend the
FieldAnalyzer
class for custom detection
- Use
-
Status values are hard-coded
- Use
statusValues
configuration to customize status values - The library detects status fields but uses configured values for types
- Use
Enable debug logging to see exactly what the library is doing:
DEBUG=collectionRegistry npx collection-registry
Or programmatically:
const registry = new CollectionRegistry({
// ... config
debug: true, // Enable detailed logging
});
This will show you:
- Which collections were found
- How each field was analyzed
- What patterns were detected
- How types were mapped
- What code was generated
- π Customization Examples - How to customize the library
- π§ Troubleshooting Guide - Common issues and solutions
- π― How It Works - Understanding the internal logic
This package uses Conventional Commits for automatic versioning:
feat:
β Minor version bump (1.0.0 β 1.1.0)fix:
β Patch version bump (1.0.0 β 1.0.1)feat!:
orBREAKING CHANGE:
β Major version bump (1.0.0 β 2.0.0)
See VERSIONING.md for detailed information.
Contributions are welcome! Please read our Contributing Guide for details.
- π‘οΈ Security Policy
- π€ Code of Conduct
- π Bug Reports
- β¨ Feature Requests
- β Questions
This project is licensed under the MIT License - see the LICENSE file for details.
- π Documentation
- π Issue Tracker
- π¬ Discussions
- Overland Stack - Full-stack template
- Payload CMS - Headless CMS
- React Router - Web framework