Creating Templates
How to create custom DeesseJS templates
Creating Templates
This guide explains how to create your own DeesseJS templates that can be shared with the community or used across multiple projects.
Template Structure
A DeesseJS template is a structured npm package:
@deessejs/template-my-template/
├── collections/
│ ├── index.ts
│ └── posts.ts
├── plugins/
│ ├── index.ts
│ └── config.ts
├── extensions/
│ └── index.ts
├── pages/
│ └── index.tsx
├── components/
│ └── PostCard.tsx
├── styles/
│ └── globals.css
├── config/
│ ├── deesse.config.ts
│ └── settings.schema.ts
├── scripts/
│ ├── postinstall.ts
│ └── migrate.ts
├── template.json
├── package.json
├── tsconfig.json
└── README.mdGetting Started
Create Template Skeleton
Use the CLI to generate a template:
npx deessejs template create my-template
# Interactive prompts:
? Template name: My Template
? Description: A brief description
? Category: Blog
? Author: Your Name
? License: MIT
✓ Template skeleton createdManual Setup
Or create from scratch:
mkdir my-template
cd my-template
npm init -y
npm install @deessejs/core
npm install -D typescript @types/reactCore Files
template.json
Define template metadata:
{
"name": "my-template",
"version": "1.0.0",
"description": "A brief description of your template",
"author": "Your Name",
"license": "MIT",
"category": "Blog",
"keywords": ["blog", "cms", "content"],
"homepage": "https://github.com/username/my-template",
"repository": "https://github.com/username/my-template",
"demo": "https://my-template-demo.vercel.app",
"deessejs": ">=1.0.0",
"features": [
"Blog with posts",
"Category support",
"SEO optimization",
"Comments system"
]
}package.json
{
"name": "@deessejs/template-my-template",
"version": "1.0.0",
"description": "My awesome DeesseJS template",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": ["dist", "collections", "plugins", "extensions", "pages", "components"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"prepublishOnly": "npm run build"
},
"peerDependencies": {
"@deessejs/core": "^1.0.0"
},
"devDependencies": {
"@deessejs/core": "workspace:*",
"typescript": "^5.0.0",
"tsup": "^8.0.0"
},
"keywords": [
"deessejs",
"template",
"cms",
"headless"
]
}Defining Collections
Collections Index
// collections/index.ts
export { default as blog } from './blog'
export const collections = [blog]Blog Collection
// collections/blog.ts
import { CollectionDefinition } from '@deessejs/core'
export const blog: CollectionDefinition[] = [
{
name: 'posts',
fields: [
{
name: 'title',
type: 'string',
required: true,
},
{
name: 'slug',
type: 'string',
admin: {
description: 'URL-friendly version of the title',
},
},
{
name: 'content',
type: 'richtext',
required: true,
},
{
name: 'excerpt',
type: 'text',
admin: {
description: 'Short description for listings',
},
},
{
name: 'featuredImage',
type: 'media',
},
{
name: 'status',
type: 'enum',
enum: ['draft', 'published', 'archived'],
defaultValue: 'draft',
},
{
name: 'publishedAt',
type: 'datetime',
},
{
name: 'author',
type: 'reference',
relation: 'users',
admin: {
description: 'Post author',
},
},
],
admin: {
columns: ['title', 'status', 'publishedAt', 'author'],
defaultSort: { field: 'publishedAt', direction: 'desc' },
pagination: 20,
},
},
{
name: 'categories',
fields: [
{
name: 'name',
type: 'string',
required: true,
},
{
name: 'slug',
type: 'string',
},
{
name: 'description',
type: 'text',
},
{
name: 'parent',
type: 'reference',
relation: 'categories',
admin: {
description: 'Parent category for hierarchy',
},
},
],
admin: {
columns: ['name', 'parent'],
},
},
]Defining Plugins
Plugins Index
// plugins/index.ts
export { default as plugins } from './config'Plugin Configuration
// plugins/config.ts
import { seoPlugin } from '@deessejs/plugin-seo'
import { mediaPlugin } from '@deessejs/plugin-media'
export const plugins = [
seoPlugin({
features: {
sitemap: true,
rss: true,
openGraph: true,
twitterCard: true,
},
}),
mediaPlugin({
maxFileSize: 10485760, // 10MB
allowedFormats: ['jpg', 'jpeg', 'png', 'gif', 'webp'],
}),
]Defining Extensions
Extensions Index
// extensions/index.ts
export { default as extensions } from './config'Extension Configuration
// extensions/config.ts
import { RedisCacheProvider } from '@deessejs/extensions-cache-redis'
import { PinoLoggerProvider } from '@deessejs/extensions-logger-pino'
import { RedisQueueProvider } from '@deessejs/extensions-queue-bullmq'
export const extensions = {
cache: {
provider: new RedisCacheProvider({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
}),
options: {
prefix: 'deesse:',
defaultTTL: 3600,
},
},
logger: {
provider: new PinoLoggerProvider({
level: 'info',
transport: {
target: 'pino/file',
options: {
destination: './logs/app.log',
},
},
}),
},
queue: {
provider: new RedisQueueProvider({
connection: {
host: process.env.REDIS_HOST || 'localhost',
port: 6379,
},
}),
options: {
defaultJobOptions: {
attempts: 3,
backoff: {
type: 'exponential',
delay: 1000,
},
},
},
},
}Creating Admin Pages
Pages Index
// pages/index.ts
export { default as pages } from './pages'Page Definitions
// pages/pages.ts
import { AdminPage } from '@deessejs/admin'
export const pages = [
{
id: 'analytics',
title: 'Analytics',
path: '/analytics',
component: './AnalyticsPage',
icon: 'BarChart',
permissions: ['analytics:view'],
layout: 'default',
},
{
id: 'comments',
title: 'Comments',
path: '/comments',
component: './CommentsPage',
icon: 'MessageSquare',
permissions: ['comments:moderate'],
},
]Page Component
// pages/AnalyticsPage.tsx
import { useCollection } from '@deessejs/query'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
export default function AnalyticsPage() {
const { data: posts } = useCollection('posts')
return (
<div className="p-6 space-y-6">
<h1 className="text-3xl font-bold">Analytics</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card>
<CardHeader>
<CardTitle>Total Posts</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold">{posts?.length || 0}</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Published</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold">
{posts?.filter(p => p.status === 'published').length || 0}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Drafts</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold">
{posts?.filter(p => p.status === 'draft').length || 0}
</div>
</CardContent>
</Card>
</div>
</div>
)
}Creating Components
Components Index
// components/index.ts
export { PostCard } from './PostCard'
export { CategoryList } from './CategoryList'
export { TagCloud } from './TagCloud'Post Card Component
// components/PostCard.tsx
import Link from 'next/link'
import Image from 'next/image'
interface PostCardProps {
post: {
id: string
title: string
slug: string
excerpt: string
featuredImage: string | null
publishedAt: string
author: {
name: string
avatar?: string | null
}
}
}
export function PostCard({ post }: PostCardProps) {
return (
<article className="border rounded-lg overflow-hidden hover:shadow-lg transition">
{post.featuredImage && (
<Link href={`/blog/${post.slug}`}>
<Image
src={post.featuredImage}
alt={post.title}
width={800}
height={400}
className="w-full h-48 object-cover"
/>
</Link>
)}
<div className="p-6">
<h2 className="text-xl font-bold mb-2">
<Link href={`/blog/${post.slug}`} className="hover:underline">
{post.title}
</Link>
</h2>
<p className="text-gray-600 mb-4 line-clamp-2">
{post.excerpt}
</p>
<div className="flex items-center justify-between text-sm text-gray-500">
<div className="flex items-center gap-2">
{post.author.avatar && (
<Image
src={post.author.avatar}
alt={post.author.name}
width={32}
height={32}
className="rounded-full"
/>
)}
<span>{post.author.name}</span>
</div>
<time>
{new Date(post.publishedAt).toLocaleDateString()}
</time>
</div>
</div>
</article>
)
}Adding Styles
Global Styles
/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
}
@layer components {
/* Custom component styles */
}
body {
color: rgb(var(--foreground));
background: rgb(var(--background));
}Configuration Files
DeesseJS Config
// config/deesse.config.ts
import { defineConfig } from '@deessejs/core'
import { collections } from '../../collections'
import { plugins } from '../../plugins'
import { extensions } from '../../extensions'
export const config = defineConfig({
collections,
plugins,
extensions,
admin: {
branding: {
title: 'My Blog',
logo: '/logo.png',
favicon: '/favicon.ico',
},
},
// Template-specific settings
template: {
name: 'my-template',
version: '1.0.0',
features: {
comments: true,
rss: true,
seo: true,
},
},
})Settings Schema
// config/settings.schema.ts
export const settingsSchema = {
type: 'object',
properties: {
blogName: {
type: 'string',
title: 'Blog Name',
default: 'My Blog',
},
postsPerPage: {
type: 'number',
title: 'Posts Per Page',
default: 10,
minimum: 1,
maximum: 100,
},
enableComments: {
type: 'boolean',
title: 'Enable Comments',
default: true,
},
enableRSS: {
type: 'boolean',
title: 'Enable RSS Feed',
default: true,
},
},
}Installation Scripts
Post-Install Script
// scripts/postinstall.ts
import { copyFile, mkdir } from 'node:fs/promises'
export async function postinstall() {
console.log('Setting up My Template...')
// Create necessary directories
await mkdir('./public/uploads', { recursive: true })
// Copy configuration files
await copyFile('./template/env.example', './.env.example')
console.log('✓ Template setup complete')
console.log('✓ Run npm install to install dependencies')
console.log('✓ Copy .env.example to .env.local')
}Add to package.json:
{
"scripts": {
"postinstall": "ts-node scripts/postinstall.ts"
}
}Migration Scripts
Version Migration
// scripts/migrate.ts
export async function migrate(from: string, to: string) {
console.log(`Migrating from ${from} to ${to}`)
// Perform migration steps
await step1_addAuthorField()
await step2_updateRoutes()
await step3_migrateData()
console.log('✓ Migration complete')
}
async function step1_addAuthorField() {
// Add author field to posts collection
}
async function step2_updateRoutes() {
// Update route structure
}
async function step3_migrateData() {
// Migrate existing data
}Documentation
README.md
# My Template
A beautiful blog template for DeesseJS.
## Features
- 📝 Blog posts with rich text editor
- 🏷️ Categories and tags
- 🔍 SEO optimization
- 💬 Comments system
- 📊 Analytics dashboard
- 🎨 Beautiful design
## Installation
\`\`\`bash
npx create-deesse-app my-app --template my-template
\`\`\`
## Usage
See [Documentation](https://github.com/username/my-template#readme) for detailed usage instructions.
## Screenshots



## Customization
See [Customization Guide](https://github.com/username/my-template/blob/main/docs/customizing.md) for customization options.
## Support
- **Issues**: [GitHub Issues](https://github.com/username/my-template/issues)
- **Discussions**: [GitHub Discussions](https://github.com/username/my-template/discussions)Changelog
# Changelog
## [1.0.0] - 2025-01-15
### Added
- Initial release
- Blog with posts and categories
- SEO plugin
- Comments system
- Analytics dashboard
### Features
- Rich text editor (TipTap)
- Image uploads
- Category hierarchy
- Tag support
- SEO meta tags
- RSS feed
- Sitemap generationPublishing
Build Template
npm run buildPublish to npm
npm publish --access publicSubmit to Marketplace
- Go to templates.deessejs.com/submit
- Fill in template details
- Upload screenshots
- Submit for review
Best Practices
Template Design
- Keep it Simple: Don't over-engineer
- Be Flexible: Allow for customization
- Document Well: Comprehensive documentation
- Test Thoroughly: Test on fresh projects
- Maintain Regularly: Keep template updated
Code Quality
- TypeScript: Use TypeScript throughout
- Clean Code: Follow best practices
- Comments: Add helpful comments
- Consistency: Follow consistent patterns
- Performance: Optimize for performance
User Experience
- Clear Instructions: Easy to follow setup
- Sensible Defaults: Good default values
- Error Messages: Helpful error messages
- Examples: Provide usage examples
- Support: Be responsive to issues
Template Checklist
Before publishing, ensure:
- All features work correctly
- TypeScript compiles without errors
- No console warnings or errors
- Mobile responsive
- Accessible (WCAG 2.1 AA)
- SEO optimized
- Documentation complete
- Screenshots provided
- License specified
- Support channel available
Next Steps
- Explore the Template Marketplace
- Browse Available Templates
- Return to Templates Overview