SonicJS headless CMS knowledge base, coding standards, and architectural guidelines.
93
93%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Passed
No known issues
Diagnose and resolve common issues in SonicJS applications with this comprehensive troubleshooting guide.
This guide covers the most common issues developers encounter when working with SonicJS, organized by category with specific error messages and solutions.
<Note> When debugging issues, check the console logs first. SonicJS logs detailed error information that can help identify the root cause quickly. </Note>Error Message: "Authentication required" or "Invalid or expired token"
Causes:
Solutions:
// Correct format - Authorization header
fetch('/api/content', {
headers: {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
}
})
// Or use cookies (set by login endpoint)
fetch('/api/content', {
credentials: 'include' // Include cookies in request
})Error Message: "Your session has expired, please login again"
Causes:
exp timestamp has passedSolutions:
// Call refresh endpoint before token expires
const response = await fetch('/auth/refresh', {
method: 'POST',
credentials: 'include'
})
if (response.ok) {
// New token automatically set in cookie
console.log('Token refreshed')
} else {
// Redirect to login
window.location.href = '/admin/login'
}Error Message: "Insufficient permissions"
Causes:
Solutions:
// Available roles (most to least privileged)
const roles = ['admin', 'editor', 'author', 'viewer']
// Create user with specific role
await db.prepare(`
INSERT INTO users (email, password, role)
VALUES (?, ?, ?)
`).bind(email, hashedPassword, 'editor').run()Error Message: "Invalid email or password"
Causes:
Solutions:
// SonicJS normalizes emails to lowercase
const normalizedEmail = email.toLowerCase().trim()
// Ensure consistency when checking credentialsError Message: "A content item with this slug already exists in this collection"
Causes:
Solutions:
// Auto-generate unique slug
function generateUniqueSlug(title: string, existingSlugs: string[]): string {
let slug = title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '')
let counter = 1
let uniqueSlug = slug
while (existingSlugs.includes(uniqueSlug)) {
uniqueSlug = `${slug}-${counter}`
counter++
}
return uniqueSlug
}Error Message: "User with this email or username already exists"
Causes:
Solutions:
// Check before registration
const existing = await db
.prepare('SELECT id FROM users WHERE email = ? OR username = ?')
.bind(email.toLowerCase(), username)
.first()
if (existing) {
return c.json({ error: 'User already exists' }, 400)
}Error Message: "Content not found"
Causes:
Solutions:
Error Message: "UNIQUE constraint failed: [table].[column]"
Causes:
Solutions:
try {
await db.prepare('INSERT INTO users (email) VALUES (?)').bind(email).run()
} catch (error) {
if (error.message.includes('UNIQUE constraint failed')) {
// Handle duplicate gracefully
return c.json({ error: 'Email already exists' }, 400)
}
throw error
}Symptoms:
Causes:
Solutions:
# wrangler.toml - Ensure KV binding exists
[[kv_namespaces]]
binding = "CACHE_KV"
id = "your-kv-namespace-id"// Verify cache is working
const cacheService = new CacheService(env.CACHE_KV)
// Test cache operations
await cacheService.set('test-key', { value: 'test' }, 60)
const result = await cacheService.get('test-key')
if (!result) {
console.error('Cache write/read failed')
}Symptoms:
Solutions:
import { CacheInvalidationService } from '@sonicjs-cms/core/plugins'
const invalidation = new CacheInvalidationService(env.CACHE_KV)
// Invalidate content cache
await invalidation.invalidateContent(contentId)
// Invalidate user cache
await invalidation.invalidateUser(userId)
// Invalidate collection cache
await invalidation.invalidateCollection(collectionId)Symptoms:
Solutions:
// Short TTL for frequently changing data
await cache.set('user-session', data, 300) // 5 minutes
// Longer TTL for stable content
await cache.set('collection-schema', schema, 3600) // 1 hour
// Very long TTL for static assets
await cache.set('site-config', config, 86400) // 24 hoursError Message: "Email plugin not available"
Causes:
Solutions:
# wrangler.toml
[vars]
DEFAULT_FROM_EMAIL = "noreply@yourdomain.com"
DEFAULT_FROM_NAME = "Your App"
# Set API key as secret
# npx wrangler secret put SENDGRID_API_KEYError Message: "Failed to send OTP email" or "Error sending OTP email"
Causes:
Solutions:
// In admin: Plugins > Enable Email Templates
// Or programmatically:
const settings = await getPluginSettings('email-templates')
console.log('Email enabled:', settings.enabled)Causes:
Solutions:
// List available templates
const templates = await db
.prepare('SELECT slug, name, is_active FROM email_templates')
.all()
console.log('Available templates:', templates.results)
// Ensure template is active
await db
.prepare('UPDATE email_templates SET is_active = 1 WHERE slug = ?')
.bind('welcome-email')
.run()Error Message: "Failed to install plugin [name]"
Causes:
Solutions:
import { PluginValidator } from '@sonicjs-cms/core/plugins'
const validation = PluginValidator.validate(myPlugin)
if (!validation.valid) {
console.error('Plugin validation errors:', validation.errors)
// Fix issues before installing
}Reserved plugin names (cannot be used):
core, system, admin, api, auth, content, media, users, collectionsReserved table names (cannot be used by plugins):
users, collections, content, content_versions, media, api_tokensError Message: "Invalid plugin name"
Requirements:
/^[a-z0-9-]+$/// Valid
'my-plugin'
'analytics-v2'
'custom-auth'
// Invalid
'MyPlugin' // No uppercase
'my plugin' // No spaces
'my_plugin' // No underscoresError Message: "Hook recursion detected"
Causes:
Solutions:
// Bad - causes recursion
hooks.register('content:save', async (data) => {
// This triggers content:save again!
await db.prepare('UPDATE content SET ...').run()
return data
})
// Good - use flag to prevent recursion
hooks.register('content:save', async (data, context) => {
if (context.fromHook) return data
// Safe to update with flag
await db.prepare('UPDATE content SET ...').run()
return data
})Error Message: "Invalid collection config: missing required fields"
Required fields:
name - Collection identifierdisplayName - Human-readable nameschema - Field definitionsimport { defineCollection } from '@sonicjs-cms/core'
export default defineCollection({
name: 'blog_posts', // Required
displayName: 'Blog Posts', // Required
schema: { // Required
title: { type: 'string', required: true },
content: { type: 'richtext' }
}
})Error Message: "Collection name must contain only lowercase letters, numbers, underscores, and hyphens"
Requirements:
/^[a-z0-9_-]+$/// Valid
'blog_posts'
'user-profiles'
'faq'
// Invalid
'BlogPosts' // No uppercase
'blog posts' // No spaces
'blog.posts' // No dotsSymptoms:
Solutions:
Bootstrap errors are caught but logged. Check console for:
// Force re-bootstrap on specific errors
import { runMigrations } from '@sonicjs-cms/core'
// Re-run migrations
await runMigrations(db)
// Re-sync collections
await collectionSync.syncAll()Issue: Cannot log in, no admin user exists
Solutions:
# Set initial admin via environment
# wrangler.toml or secrets
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your-secure-passwordOr create via API (if registration is enabled):
curl -X POST https://your-app.workers.dev/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "admin@example.com",
"password": "your-secure-password",
"username": "admin"
}'SonicJS includes a comprehensive logging service:
import { Logger } from '@sonicjs-cms/core'
const logger = new Logger(db, {
level: 'debug', // debug, info, warn, error, fatal
category: 'my-feature'
})
// Log with context
logger.debug('Processing request', { userId, action })
logger.info('Operation completed', { duration: Date.now() - start })
logger.error('Operation failed', { error: error.message })// Get recent logs
const logs = await db
.prepare(`
SELECT * FROM logs
WHERE created_at > datetime('now', '-1 hour')
ORDER BY created_at DESC
LIMIT 100
`)
.all()
// Filter by category
const authLogs = await db
.prepare(`
SELECT * FROM logs
WHERE category = 'auth'
ORDER BY created_at DESC
`)
.all()Enable debug mode for verbose logging:
# wrangler.toml
[env.development.vars]
ENVIRONMENT = "development"
DEBUG = "true"Each plugin gets a namespaced logger:
// In your plugin
builder.setLogger((msg, level) => {
console.log(`[Plugin:my-plugin] [${level}] ${msg}`)
})| Code | Meaning | Common Causes |
|---|---|---|
| 400 | Bad Request | Validation failed, missing fields, invalid JSON |
| 401 | Unauthorized | No token, invalid token, expired token |
| 403 | Forbidden | Insufficient role/permissions |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate slug, unique constraint violation |
| 500 | Server Error | Unhandled exception, database error |
All API errors return a consistent format:
{
"error": "Human-readable error message",
"details": "Technical details (in development)",
"code": "ERROR_CODE"
}| Code | Message | Solution |
|---|---|---|
AUTH_REQUIRED | Authentication required | Add valid JWT token |
AUTH_EXPIRED | Token expired | Refresh or re-login |
AUTH_INVALID | Invalid credentials | Check email/password |
PERM_DENIED | Insufficient permissions | Check user role |
NOT_FOUND | Resource not found | Verify ID/slug |
DUPLICATE | Already exists | Use unique value |
VALIDATION | Validation failed | Check required fields |
If you're still stuck:
docs
skills