@alloylab/collection-registry
v1.1.2
Published
Automated code generation from Payload CMS collections
Maintainers
Readme
@alloylab/collection-registry
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.
Features
- 🔍 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
Installation
npm install @alloylab/collection-registry
# or
yarn add @alloylab/collection-registry
# or
pnpm add @alloylab/collection-registryQuick Start
1. Basic Usage
# 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.ts2. Programmatic Usage
import { CollectionRegistry } from '@alloylab/collection-registry';
const registry = new CollectionRegistry({
collectionsPath: './src/collections',
outputPath: './generated',
typesPath: './payload-types.ts',
format: true,
});
await registry.generate();Configuration
Command Line Options
| 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 | - |
Programmatic Configuration
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
};Advanced Configuration Options
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
};Generated Files
The tool generates the following files in your output directory:
Types (types/)
base.ts- Base types and interfaces{collection}.ts- Individual collection typesindex.ts- Exports all types
API Clients (clients/)
base.ts- Base client class{collection}.ts- Individual collection clientsindex.ts- Exports all clientspayloadClient.ts- Main client aggregator
Routes (optional)
{collection}._index.tsx- Collection index route{collection}.$slug.tsx- Collection detail route
🔍 How It Works (Transparent Process)
This library is not a black box. Here's exactly what it does:
1. Collection Scanning
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.)
2. Field Analysis
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')
3. Type Generation
Creates TypeScript interfaces based on:
- Payload field types → TypeScript types
- Detected patterns → Specialized types
- Collection structure → Complete interfaces
4. Code Generation
Generates ready-to-use code using configurable templates.
Collection Analysis
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
Example Collection
// 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',
},
],
},
],
};Generated Output
Types
// 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;
}API Client
// 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
}
}Integration Examples
React Router v7
// app/routes/posts._index.tsx
import { postsClient } from '~/lib/clients';
export async function loader() {
const posts = await postsClient.getPublishedPosts();
return { posts };
}Next.js
// pages/posts/index.tsx
import { postsClient } from '../lib/clients';
export async function getStaticProps() {
const posts = await postsClient.getPublishedPosts();
return { props: { posts } };
}SvelteKit
// src/routes/posts/+page.server.ts
import { postsClient } from '$lib/clients';
export async function load() {
const posts = await postsClient.getPublishedPosts();
return { posts };
}Advanced Usage
Custom Templates
You can extend the tool with custom templates:
import { CollectionRegistry } from '@alloylab/collection-registry';
class CustomRegistry extends CollectionRegistry {
generateCustomFiles() {
// Your custom generation logic
}
}Field Type Mapping
Customize TypeScript type mapping:
import { getTypeScriptType } from '@alloylab/collection-registry';
// Extend the type mapping
const customTypeMap = {
...defaultTypeMap,
customField: 'CustomType',
};🔧 Understanding the Internal Logic
Field Detection Algorithm
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 };
}Type Mapping Logic
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[]',
};Customization Points
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);
}
}Troubleshooting
Common Issues
Collections not found
- Ensure the collections path is correct
- Check that collection files have
.tsextension - Verify collection files export a valid
CollectionConfig
Types not generated
- Run
payload generate:typesfirst - Check the types path is correct
- Ensure Payload types file exists
- Run
Formatting errors
- Install Prettier:
npm install prettier - Check Prettier configuration
- Use
--no-formatto skip formatting
- Install Prettier:
Custom field names not detected
- Use
fieldMappingsconfiguration to map your custom field names - Check the field detection logic in the debug output
- Extend the
FieldAnalyzerclass for custom detection
- Use
Status values are hard-coded
- Use
statusValuesconfiguration to customize status values - The library detects status fields but uses configured values for types
- Use
Debug Mode
Enable debug logging to see exactly what the library is doing:
DEBUG=collectionRegistry npx collection-registryOr 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
📚 Additional Resources
- 📖 Customization Examples - How to customize the library
- 🔧 Troubleshooting Guide - Common issues and solutions
- 🎯 How It Works - Understanding the internal logic
Versioning
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.
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
Related Projects
- Overland Stack - Full-stack template
- Payload CMS - Headless CMS
- React Router - Web framework
