npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@alloylab/collection-registry

v1.1.2

Published

Automated code generation from Payload CMS collections

Readme

@alloylab/collection-registry

npm version License: MIT

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-registry

Quick 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.ts

2. 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 types
  • index.ts - Exports all types

API Clients (clients/)

  • base.ts - Base client class
  • {collection}.ts - Individual collection clients
  • index.ts - Exports all clients
  • payloadClient.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

  1. Collections not found

    • Ensure the collections path is correct
    • Check that collection files have .ts extension
    • Verify collection files export a valid CollectionConfig
  2. Types not generated

    • Run payload generate:types first
    • Check the types path is correct
    • Ensure Payload types file exists
  3. Formatting errors

    • Install Prettier: npm install prettier
    • Check Prettier configuration
    • Use --no-format to skip formatting
  4. 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
  5. Status values are hard-coded

    • Use statusValues configuration to customize status values
    • The library detects status fields but uses configured values for types

Debug Mode

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

📚 Additional Resources

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!: or BREAKING 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