DEV Community

Alex Spinov
Alex Spinov

Posted on

Payload CMS Has a Free API You Should Know About

Payload CMS auto-generates REST and GraphQL APIs from your TypeScript config — and it's fully open-source.

Define Your Schema

// payload.config.ts
import { buildConfig } from 'payload'

export default buildConfig({
  collections: [
    {
      slug: 'posts',
      auth: false,
      fields: [
        { name: 'title', type: 'text', required: true },
        { name: 'content', type: 'richText' },
        { name: 'status', type: 'select', options: ['draft', 'published'] },
        { name: 'author', type: 'relationship', relationTo: 'users' },
        { name: 'tags', type: 'array', fields: [
          { name: 'tag', type: 'text' }
        ]},
        { name: 'featuredImage', type: 'upload', relationTo: 'media' }
      ],
      hooks: {
        beforeChange: [({ data }) => {
          data.slug = data.title.toLowerCase().replace(/\s+/g, '-')
          return data
        }]
      }
    }
  ]
})
Enter fullscreen mode Exit fullscreen mode

Auto-Generated REST API

# List posts
GET /api/posts?limit=10&where[status][equals]=published&sort=-createdAt

# Get single post
GET /api/posts/POST_ID

# Create post
POST /api/posts
{ "title": "New Post", "content": {...}, "status": "draft" }

# Update post
PATCH /api/posts/POST_ID
{ "status": "published" }

# Delete
DELETE /api/posts/POST_ID
Enter fullscreen mode Exit fullscreen mode

Local API — Direct Database Access

// No HTTP overhead — direct database calls
import { getPayloadClient } from 'payload'

const payload = await getPayloadClient()

const posts = await payload.find({
  collection: 'posts',
  where: { status: { equals: 'published' } },
  sort: '-createdAt',
  limit: 10,
  depth: 2 // Populate relationships 2 levels deep
})

const newPost = await payload.create({
  collection: 'posts',
  data: { title: 'Created via Local API', status: 'published' }
})
Enter fullscreen mode Exit fullscreen mode

Access Control

{
  slug: 'posts',
  access: {
    read: () => true, // Everyone can read
    create: ({ req }) => req.user?.role === 'admin', // Only admins create
    update: ({ req, id }) => {
      if (req.user?.role === 'admin') return true
      return { author: { equals: req.user?.id } } // Authors edit own posts
    },
    delete: ({ req }) => req.user?.role === 'admin'
  }
}
Enter fullscreen mode Exit fullscreen mode

Real-World Use Case

An agency was using Strapi but hit limits with complex relations and custom logic. They switched to Payload: TypeScript-native config, the Local API eliminated N+1 queries, and access control replaced 500 lines of middleware. Same features, half the code, full type safety.

Payload CMS is what happens when backend developers build a CMS.


Build Smarter Data Pipelines

Need to scrape websites, extract APIs, or automate data collection? Check out my ready-to-use scrapers on Apify — no coding required.

Custom scraping solution? Email me at spinov001@gmail.com — fast turnaround, fair prices.

Top comments (0)