Site Configuration
Comprehensive guide to site configuration management, templates, and customization options
Site Configuration Management
Antler CMS provides a comprehensive site configuration system that allows you to customize your site’s behavior, appearance, and content structure through both the admin interface and configuration files. The system supports multiple configuration levels and templates for different site types.
Configuration Architecture
Configuration Levels
- Default Configuration - Base settings defined in code
- Site Templates - Pre-configured setups for different site types
- Site Configuration - Custom settings in
site.config.json - Runtime Configuration - Dynamic settings managed through admin interface
Configuration Files
Primary Configuration (site.config.json)
Located in the project root, this file contains your site’s main configuration:
{
"site": {
"title": "Your Site Title",
"description": "Site description",
"url": "https://yoursite.com",
"author": "Your Name"
},
"theme": {
"defaultTheme": "blue",
"allowUserThemeSelection": true
},
"navigation": {
"main": [
{ "name": "Home", "href": "/" },
{ "name": "Blog", "href": "/blog" },
{ "name": "Projects", "href": "/projects" }
]
},
"contentTypes": {
"blog": { "enabled": true },
"projects": { "enabled": true },
"docs": { "enabled": true }
}
}
Site Configuration Options
Basic Site Settings
Site Information
site.title- Site title displayed in header and meta tagssite.description- Site description for SEO and meta tagssite.url- Canonical site URL for absolute linkssite.author- Default author for content and meta tagssite.logo- Site logo URL or pathsite.favicon- Favicon URL or path
SEO and Meta Settings
seo.defaultImage- Default social sharing imageseo.twitterHandle- Twitter handle for Twitter Cardsseo.googleAnalytics- Google Analytics tracking IDseo.googleSiteVerification- Google Search Console verificationseo.robots- Default robots meta tag content
Theme Configuration
Theme Settings
theme.defaultTheme- Default color theme for new visitorstheme.allowUserThemeSelection- Enable user theme switchingtheme.darkModeDefault- Default to dark modetheme.systemThemeDetection- Respect system theme preference
Available Themes
The system includes 16+ built-in themes:
- Blue Family:
blue,indigo,sky,cyan - Green Family:
green,emerald,teal - Warm Family:
red,orange,amber,yellow - Purple Family:
purple,violet,fuchsia,pink - Neutral:
gray,slate,stone
Navigation Configuration
Main Navigation
{
"navigation": {
"main": [
{
"name": "Home",
"href": "/",
"external": false
},
{
"name": "Blog",
"href": "/blog",
"external": false,
"children": [
{ "name": "All Posts", "href": "/blog" },
{ "name": "Categories", "href": "/blog/categories" }
]
}
]
}
}
Content Type Configuration
Enabling/Disabling Content Types
{
"contentTypes": {
"blog": {
"enabled": true,
"slug": "blog",
"title": "Blog",
"description": "Latest articles and insights"
},
"projects": {
"enabled": true,
"slug": "projects",
"title": "Projects",
"description": "Portfolio of work"
},
"docs": {
"enabled": true,
"slug": "docs",
"title": "Documentation",
"description": "Technical documentation"
},
"resume": {
"enabled": false,
"slug": "resume",
"title": "Resume",
"description": "Professional resume"
}
}
}
Site Templates
Available Templates
Blog-Only Template
Optimized for blogging with minimal additional features:
{
"template": "blog-only",
"contentTypes": {
"blog": { "enabled": true },
"projects": { "enabled": false },
"docs": { "enabled": false }
}
}
Portfolio Template
Focused on showcasing projects and work:
{
"template": "portfolio",
"contentTypes": {
"projects": { "enabled": true },
"blog": { "enabled": true },
"docs": { "enabled": false }
}
}
Documentation Template
Optimized for technical documentation:
{
"template": "documentation",
"contentTypes": {
"docs": { "enabled": true },
"blog": { "enabled": false },
"projects": { "enabled": false }
}
}
Full-Featured Template
Includes all content types and features:
{
"template": "full-featured",
"contentTypes": {
"blog": { "enabled": true },
"projects": { "enabled": true },
"docs": { "enabled": true },
"resume": { "enabled": true }
}
}
Admin Interface Configuration
Site Configuration Panel
Located at /admin/config/, provides:
- Visual configuration editor for all site settings
- Real-time preview of configuration changes
- Template selection and application
- Validation of configuration values
- Export/import of configuration files
Configuration Management Features
Template Application
- One-click template application with confirmation
- Backup creation before applying templates
- Selective template application (choose which parts to apply)
- Template comparison to see differences
Validation and Testing
- Real-time validation of configuration syntax
- URL validation for links and references
- Theme compatibility checking
- Navigation structure validation
API Integration
Configuration API Endpoints
Get Current Configuration
GET /admin/api/config
Returns the complete site configuration.
Update Configuration
POST /admin/api/config
Content-Type: application/json
{
"site": {
"title": "Updated Site Title"
}
}
Apply Template
POST /admin/api/config/template
Content-Type: application/json
{
"template": "blog-only",
"backup": true
}
Content Schema Configuration
Antler uses Astro’s Content Collections API with Zod validation for content management.
Content Configuration File
All content configuration is centralized in src/content/config.ts. This file defines:
- Content collection schemas
- Validation rules
- Type definitions
- Field requirements
Basic Structure
import { defineCollection, z } from 'astro:content';
// Define individual collections
const blog = defineCollection({
type: 'content',
schema: z.object({
// Schema definition
}),
});
// Export all collections
export const collections = {
blog,
projects,
docs,
};
Schema Definition with Zod
Antler uses Zod for schema validation, providing powerful type checking and validation.
Basic Field Types
import { z } from 'astro:content';
const schema = z.object({
// String fields
title: z.string(),
description: z.string().min(10).max(160), // With validation
// Number fields
readingTime: z.number().positive(),
order: z.number().int(),
// Boolean fields
featured: z.boolean(),
published: z.boolean().default(true),
// Date fields
publicationDate: z.coerce.date(), // Converts string to Date
updatedAt: z.date().optional(),
// Array fields
tags: z.array(z.string()),
categories: z.array(z.string()).min(1), // At least one item
// Optional fields
author: z.string().optional(),
featuredImage: z.string().optional(),
});
Advanced Field Validation
const blogSchema = z.object({
// Email validation
authorEmail: z.string().email().optional(),
// URL validation
externalLink: z.string().url().optional(),
// Enum values
status: z.enum(['draft', 'published', 'archived']),
// Custom validation
slug: z.string().regex(/^[a-z0-9-]+$/, {
message: "Slug must contain only lowercase letters, numbers, and hyphens"
}),
// Conditional fields
publishedAt: z.date().optional(),
scheduledFor: z.date().optional(),
}).refine(data => {
// Custom validation logic
if (data.status === 'published' && !data.publishedAt) {
return false;
}
return true;
}, {
message: "Published posts must have a publication date",
path: ["publishedAt"]
});
Current Collection Configurations
Blog Collection
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
publicationDate: z.coerce.date(),
featuredImage: z.string().optional(),
tags: z.array(z.string()),
author: z.string().optional(),
readingTime: z.number().optional(),
featured: z.boolean().optional(),
}),
});
Customization Options:
// Add validation rules
title: z.string().min(5).max(100),
description: z.string().min(20).max(160),
// Add default values
featured: z.boolean().default(false),
author: z.string().default("Anonymous"),
// Add new fields
excerpt: z.string().optional(),
seoTitle: z.string().optional(),
canonicalUrl: z.string().url().optional(),
Projects Collection
const projects = defineCollection({
type: 'content',
schema: z.object({
projectName: z.string(),
projectImage: z.string(),
description: z.string(),
technologies: z.array(z.string()),
githubLink: z.string().optional(),
liveUrl: z.string().optional(),
featured: z.boolean().optional(),
createdAt: z.coerce.date().optional(),
}),
});
Customization Options:
// Add validation for URLs
githubLink: z.string().url().optional(),
liveUrl: z.string().url().optional(),
// Add status tracking
status: z.enum(['planning', 'development', 'completed', 'archived']),
// Add team information
team: z.array(z.object({
name: z.string(),
role: z.string(),
url: z.string().url().optional(),
})).optional(),
Documentation Collection
const docs = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string().optional(),
group: z.string(),
order: z.number(),
}),
});
Customization Options:
// Add navigation control
sidebar: z.boolean().default(true),
toc: z.boolean().default(true),
// Add versioning
version: z.string().optional(),
deprecated: z.boolean().default(false),
// Add difficulty level
difficulty: z.enum(['beginner', 'intermediate', 'advanced']).optional(),
Environment-Specific Configuration
Development vs Production
// config.ts
const isDev = import.meta.env.DEV;
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
// Only require description in production
description: isDev ? z.string().optional() : z.string(),
// Allow draft posts in development
draft: z.boolean().default(false),
}).refine(data => {
// In production, don't allow draft posts
if (!isDev && data.draft) {
return false;
}
return true;
}),
});
Custom Validation Functions
Creating Reusable Validators
// utils/validators.ts
import { z } from 'astro:content';
export const slugValidator = z.string().regex(/^[a-z0-9-]+$/, {
message: "Must be lowercase with hyphens only"
});
export const imagePathValidator = z.string().refine(
(path) => path.startsWith('/images/'),
{ message: "Image path must start with /images/" }
);
export const tagValidator = z.array(z.string()).refine(
(tags) => tags.every(tag => tag.length >= 2),
{ message: "All tags must be at least 2 characters long" }
);
Using Custom Validators
import { slugValidator, imagePathValidator, tagValidator } from '../utils/validators';
const blog = defineCollection({
type: 'content',
schema: z.object({
slug: slugValidator,
featuredImage: imagePathValidator.optional(),
tags: tagValidator,
}),
});
Configuration Best Practices
1. Schema Design
// ✅ Good: Clear, specific field names
const schema = z.object({
title: z.string(),
metaDescription: z.string(),
featuredImage: z.string().optional(),
});
// ❌ Avoid: Vague or confusing names
const schema = z.object({
name: z.string(),
desc: z.string(),
img: z.string().optional(),
});
2. Validation Rules
// ✅ Good: Meaningful validation with helpful messages
title: z.string()
.min(5, "Title must be at least 5 characters")
.max(100, "Title must be less than 100 characters"),
// ✅ Good: Sensible defaults
featured: z.boolean().default(false),
publishedAt: z.coerce.date().default(() => new Date()),
3. Optional vs Required Fields
// ✅ Good: Clear distinction between required and optional
const schema = z.object({
// Always required
title: z.string(),
content: z.string(),
// Optional but recommended
description: z.string().optional(),
author: z.string().optional(),
// Optional metadata
seoTitle: z.string().optional(),
canonicalUrl: z.string().url().optional(),
});
Troubleshooting Configuration Issues
Common Errors
-
Schema Validation Errors
Error: Invalid frontmatter in blog/my-post.md- Check that all required fields are present
- Verify field types match the schema
- Ensure date formats are correct (YYYY-MM-DD)
-
Type Errors
Property 'newField' does not exist on type...- Run
npx astro syncafter schema changes - Restart your TypeScript server
- Check that the field is defined in the schema
- Run
-
Build Failures
Content collection validation failed- Review all content files for schema compliance
- Check for missing required fields
- Validate date and URL formats
Debugging Tips
-
Enable Verbose Logging
// astro.config.mjs export default defineConfig({ // Enable detailed error messages vite: { logLevel: 'info' } }); -
Test Schema Changes
# Validate content after schema changes npx astro sync npx astro check -
Use TypeScript Strict Mode
// tsconfig.json { "compilerOptions": { "strict": true, "noImplicitAny": true } }
Migration Guide
Updating Existing Content
When changing schemas, you may need to update existing content:
-
Adding Required Fields
# Find files missing the new field grep -L "newField:" src/content/blog/*.md -
Changing Field Types
// Before: string publishedAt: z.string(), // After: date (with migration) publishedAt: z.coerce.date(), -
Renaming Fields
# Use sed to rename fields across files sed -i 's/oldFieldName:/newFieldName:/g' src/content/blog/*.md
Schema Versioning
For major changes, consider versioning your schemas:
const blogV1 = z.object({
title: z.string(),
date: z.string(),
});
const blogV2 = z.object({
title: z.string(),
publicationDate: z.coerce.date(),
version: z.literal(2).default(2),
});
// Use union for backward compatibility
const blog = defineCollection({
type: 'content',
schema: z.union([blogV1, blogV2]),
});