Customizing Templates
Add, edit, remove, or reorganize AI templates.
Every template in ContentAI lives in a single file: lib/templates.ts. No database, no admin UI — just plain TypeScript, which makes templates easy to edit and version-control.
Anatomy of a template
{
id: "blog-post", // unique, kebab-case
name: "Blog Post", // shown to the user
description: "Generate a full blog post with title, intro, body, and conclusion",
category: "blog", // one of the TemplateCategory values
icon: "FileText", // Lucide icon name
prompt: `Write a comprehensive blog post about "{topic}".
Tone: {tone}.
Target audience: {audience}.
Include a title, intro, body with subheadings, and conclusion.`,
fields: [
{ name: "topic", label: "Topic", type: "text", placeholder: "e.g. Benefits of remote work", required: true },
{ name: "tone", label: "Tone", type: "select", placeholder: "Select tone",
options: ["Professional", "Casual", "Friendly", "Authoritative"] },
{ name: "audience", label: "Target Audience", type: "text", placeholder: "e.g. SMB owners" },
],
}Prompt placeholders
Anything inside curly braces like {topic} is replaced with the value of the field whose name matches. The Generate page handles interpolation automatically.
Field types
| Type | Rendered as |
|---|---|
text | <Input /> |
textarea | <Textarea /> |
select | <Select /> |
select requires an options array. All field types support required: true.
Add a new template
Append to the templates array in lib/templates.ts:
export const templates: Template[] = [
// ... existing templates
{
id: "tweet-from-blog",
name: "Tweet from Blog Post",
description: "Turn a full blog post into a punchy Tweet",
category: "social",
icon: "Twitter",
prompt: `Summarize this blog post into a single Tweet (max 280 chars).
Tone: {tone}.
Post:
{post}`,
fields: [
{ name: "post", label: "Blog post", type: "textarea",
placeholder: "Paste your blog post", required: true },
{ name: "tone", label: "Tone", type: "select", placeholder: "Select",
options: ["Clever", "Professional", "Casual"] },
],
},
];The template is immediately available in Templates, Generate, and the Dashboard quick actions. No rebuild needed in dev — hot reload will pick it up.
Add a new category
Add an entry to categories in the same file:
export const categories = [
// ...
{ id: "legal", label: "Legal", icon: "Gavel" },
];Then add "legal" to the TemplateCategory union:
export type TemplateCategory =
| "blog"
| "marketing"
| /* ... */
| "legal";Any template you assign to category: "legal" will show up under the new category.
Remove a template
Delete the entry from the templates array. If it was referenced from the Dashboard's "popular templates" list, remove that reference from app/(dashboard)/dashboard/page.tsx too.
Reorder templates
Templates display in the order they appear in the templates array. Reorder the array however you like.
Best practices for writing prompts
- Be specific about the output format. "Include a title, intro, body with subheadings, and conclusion" beats "Write a blog post".
- Always provide at least one tone field. Users expect control over voice.
- Keep fields minimal. 2–4 fields feels great; 7+ feels like a form from 2003.
- Use
{placeholder}sparingly. Every{var}is one more thing the user has to fill. - Test with short and long inputs. Models sometimes over- or under-produce based on input length.
Example: a high-quality SEO prompt
{
id: "seo-cluster",
name: "SEO Topic Cluster",
description: "Generate a pillar topic + 8 supporting article ideas for internal linking",
category: "seo",
icon: "Network",
prompt: `Act as an SEO strategist. Build a topic cluster around the pillar topic "{pillar}".
Return:
1. A pillar title (H1, max 60 chars).
2. A pillar description (140–160 chars).
3. 8 supporting article titles that link back to the pillar, each with:
- Primary keyword
- Search intent (informational/commercial/transactional)
- A 1-sentence angle
Format as Markdown.`,
fields: [
{ name: "pillar", label: "Pillar topic", type: "text",
placeholder: "e.g. Remote work productivity", required: true },
],
}Next: Security →