ContentQL ContentQL Docs

Create Your Own Theme

When you install ContentQL locally, sample blog-related data (such as authors, tags, blogs, and blog details) will be available by default. To start with a clean slate without this sample data, follow the steps below. Otherwise, proceed directly to Block Creation.

Open a new terminal window while running the local server.

Run the command pnpm run:seed. During the installation, you'll encounter the following prompt:

Do you want to drop the database before seeding?
WARNING: Dropping the database will permanently delete all data. This action
cannot be undone.
 ○ Yes / ○ No

Select Yes to drop the existing database and start fresh.

 Are you sure you want to run the seeding process?
 ○ Yes / ○ No

Select No to prevent the random sample data from being seeded.

File Naming Convention

To create a block, you need to know the folder structure and file naming convention that needs to be followed:

component.tsx
config.ts
index.ts

Pre-Requisite for Block Creation

  • Before creating blocks, ensure you have a basic understanding of Fields in Payload CMS.

Block Creation

hero Block Next, we’ll demonstrate how to create the above block, which includes a background image, heading, description, and two buttons(primary, secondary). To start, create a folder inside src/payload/blocks, and name it according to the block you’re creating. In this case, we’ll call it Hero.

Define the configuration for the block in config.ts:

blocks/Hero/config.ts
import { Block } from 'payload';
 
const HeroConfig: Block = {
  slug: 'Hero',
  interfaceName: 'HeroType',
  fields: [
    {
      name: 'heading',
      type: 'text',
      label: 'Heading',
    },
    {
      name: 'description',
      type: 'text',
      label: 'Description',
    },
    {
      name: 'buttonGroup',
      type: 'group',
      fields: [
        {
          name: 'primaryButton',
          type: 'text',
          label: 'Primary Button',
        },
        {
          name: 'secondaryButton',
          type: 'text',
          label: 'Secondary Button',
        },
      ],
    },
    {
      name: 'image',
      type: 'upload',
      label: 'Image',
      relationTo: 'media',
    }
  ],
};
 
export default HeroConfig;

Implement the UI for the block in component.tsx:

blocks/Hero/component.tsx
import { HeroType } from '@payload-types';
 
const Hero = ({ ...block }: HeroType) => { 
  return (
    <div className='bg-gray-900'>
      <div className='relative isolate overflow-hidden pt-14'>
        <img
          src='https://images.unsplash.com/photo-1521737604893-d14cc237f11d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2830&q=80&blend=111827&sat=-100&exp=15&blend-mode=multiply'
          alt=''
          className='absolute inset-0 -z-10 h-full w-full object-cover'
        />
        <div
          className='absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80'
          aria-hidden='true'
        >
          <div className='relative left-[calc(50%-11rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-20 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]'></div>
        </div>
        <div className='mx-auto max-w-2xl py-32 sm:py-48 lg:py-56'>
          <div className='text-center'>
            <h1 className='text-4xl font-bold tracking-tight text-white sm:text-6xl'>
              {block?.heading}
            </h1>
            <p className='mt-6 text-lg leading-8 text-gray-300'>
              {block?.description}
            </p>
            <div className='mt-10 flex items-center justify-center gap-x-6'>
              <a
                href='#'
                className='rounded-md bg-indigo-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-400'
              >
                {block?.buttonGroup?.primaryButton}
              </a>
              <a href='#' className='text-sm font-semibold leading-6 text-white'>
                {block?.buttonGroup?.secondaryButton}
                <span aria-hidden='true'>→</span>
              </a>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};
 
export default Hero;

Export the slug and component in index.ts:

blocks/Hero/index.ts
export { default as Hero } from './component';
export { default as HeroConfig } from './config';

Import the block configuration and component into the main blocks/index.ts file:

blocks/index.ts
import { Details, DetailsConfig } from './Details'
import { Home, HomeConfig } from './Home'
import { List, ListConfig } from './List'
import { Hero, HeroConfig } from './Hero'
 
export const blocksJSX = {
  Home,
  Details,
  List,
  Hero, 
}
 
export const blocks = [HomeConfig, DetailsConfig, ListConfig, HeroConfig] 

You’ve successfully created a block! The types for it will be automatically generated in payload-types.ts using the interfaceName defined in the config.tsfile.

info

If types didn't auto-generated in payload-types.ts, run the following command: pnpm generate:types

Admin Panel

Once logged in to the admin panel, navigate to the “Pages” section and create a new page. After providing a title, you’ll see an option to add a “Page Layout.” All previously created blocks will appear in this list with respective label names.

sign up

Now select hero block and save it. Once you fill in the field values, you can now go to the page that you've created and see the magic! You’re now equipped to create themes and blocks in ContentQL!

On this page