mango-cms
v0.3.37
Published
Mango is a powerful, code-first CMS built on MongoDB that emphasizes configuration through code. This documentation covers how to configure and use Mango CMS through the `config` folder.
Readme
Mango CMS Documentation
Mango is a powerful, code-first CMS built on MongoDB that emphasizes configuration through code. This documentation covers how to configure and use Mango CMS through the config folder.
Vibe (in-page AI coding): to activate the optional ⌘K "edit your live site" experience on a Mango project, follow docs/VIBE_ACTIVATION.md — install/upgrade → flip
settings.vibe.enabled→ setVITE_VIBE_SHELL→ run the bundled orchestrator + gateway behind the infra templates in default/infra/vibe/. No source copying required.
Table of Contents
Collections
Collections are the foundation of Mango CMS. Each collection represents a MongoDB collection and is defined in the config/collections directory.
Basic Collection Structure
export default {
// Required: Singular form of collection name
singular: 'project',
// Optional: Enable real-time updates via WebSocket
subscribe: true,
// Define collection fields
fields: {
title: String,
description: String,
// ... more fields
},
}Collection Options
singular: Required. The singular form of your collection name (e.g., 'project' for projects)subscribe: Optional. Enable WebSocket subscriptions for real-time updatesformatRequest: Optional. Custom request formatting before processing
Request Formatting
Use formatRequest to modify incoming requests before they're processed:
export default {
singular: 'project',
formatRequest(request) {
// Example 1: Add default search criteria
if (request.method === 'read') {
request.search = {
...request.search,
status: 'active'
}
}
// Example 2: Convert create to update for duplicate unique fields
if (request.method === 'create' && request.document.address) {
const existing = await readEntry({
collection: 'project',
search: { address: request.document.address }
})
if (existing) {
request.method = 'update'
request.document.id = existing.id
}
}
return request
}
}Fields
Mango provides several built-in field types and allows for custom field creation.
Built-in Fields
import fields from '@cms/1. build/fields'
const { Relationship, Timestamp, File, Image } = fields
export default {
fields: {
// Basic Types
title: String,
count: 'Int',
isActive: Boolean,
// Complex Types
createdAt: Timestamp(),
thumbnail: Image(),
attachment: File(),
author: Relationship({
collection: 'user',
single: true,
}),
tags: Relationship({
collection: 'tag',
}),
},
}Field Options
Relationship Fields
Relationship({
collection: 'user', // Required: Target collection
single: true, // Optional: Single relationship (default: false)
required: true, // Optional: Field is required
default: null, // Optional: Default value
})Computed Fields
{
fields: {
fullName: {
type: String,
computed: (document) => {
return `${document.firstName} ${document.lastName}`
},
// Optional: Cache expiration in seconds
expiresCache: 3600,
// Optional: Values to provide to the computed function
provide: ['firstName', 'lastName']
}
}
}Field Translation
Fields can transform data on input and output using translateInput and translateOutput:
{
fields: {
price: {
inputType: String, // Input comes as string
type: 'Int', // Stored as integer
// Transform input before saving
translateInput: (value, { request, index, parentValue }) => {
// Convert string price to number and handle currency
return parseFloat(value.replace('$', '')) * 100
},
// Transform output before sending to client
translateOutput: (value, { request, document }) => {
// Convert stored cents to dollars with currency format
return `$${(value / 100).toFixed(2)}`
}
}
}
}Custom Fields
Create custom fields in config/fields/ directory:
// config/fields/currency.js
export default {
type: 'Currency',
inputType: String,
translateInput(value, { request }) {
return parseFloat(value.replace('$', '')) * 100
},
translateOutput(value, { request, document }) {
return `$${(value / 100).toFixed(2)}`
},
validate(value, { request }) {
if (typeof value !== 'number' || isNaN(value)) {
throw new Error('Invalid currency value')
}
},
}Using custom fields in collections:
import Currency from '../fields/currency'
export default {
singular: 'product',
fields: {
name: String,
price: Currency(),
salePrice: Currency(),
},
}Permissions
Mango provides flexible permission control at the collection level.
Basic Permissions
export default {
permissions: {
// Define permissions for each role in member.roles
public: ['create'],
owner: ['read', 'update', 'delete'],
},
}Function-based Permissions
export default {
async permissions(request) {
// For create requests, always check member permissions
if (!request.id) {
return {
authorized: request.member?.roles.includes('editor'),
}
}
// For existing documents, check ownership or team access
const document = request.originalDocument
return {
authorized:
// Member is the owner
document.createdBy === request.member?.id ||
// Member is part of the document's team
document.team?.includes(request.member?.id) ||
// Member has admin role
request.member?.roles.includes('admin'),
}
},
}Hooks
Hooks allow you to execute code at different stages of the CRUD operations.
export default {
hooks: {
created: async ({ request, response, document }) => {
// Handle post-creation tasks
console.log('New document created:', document.id)
},
read: async ({ request, response, document }) => {
// Handle during read operation
console.log('Document accessed:', document.id)
},
updated: async ({ request, response, document, originalDocument }) => {
// Handle post-update tasks with access to previous version
console.log('Document updated from:', originalDocument, 'to:', document)
},
deleted: async ({ request, response, document, originalDocument }) => {
// Handle post-deletion tasks with access to deleted document
console.log('Document deleted:', document.id)
console.log('Original state:', originalDocument)
},
},
}Endpoints
Create custom endpoints by exporting an object structure in config/endpoints/index.js:
import { sendEmail } from '../services/email'
export default {
contact: {
// POST /endpoints/contact
async post(req) {
const { name, email, message } = req.body
// Validate required fields
if (!name || !email || !message) {
return {
success: false,
error: 'Missing required fields',
}
}
// Send email
await sendEmail({
to: '[email protected]',
subject: `Contact Form: ${name}`,
text: `From: ${name} (${email})\n\n${message}`,
})
return {
success: true,
message: 'Thank you for your message',
}
},
support: {
// POST /endpoints/contact/support
async post(req) {
const { ticketId, message } = req.body
// Create support ticket
const ticket = await createSupportTicket({
id: ticketId,
message,
member: req.member.id,
})
return {
success: true,
ticket,
}
},
},
},
}Plugins
Plugins extend Mango's functionality through a structured folder in config/plugins:
config/plugins/
my-plugin/
collections/
widget.js
gadget.js
fields/
customField.js
endpoints/
index.jsExample plugin structure:
// config/plugins/my-plugin/collections/widget.js
export default {
singular: 'widget',
fields: {
name: String,
configuration: {...}
}
}
// config/plugins/my-plugin/fields/customField.js
export default {
type: 'CustomField',
// field implementation
}
// config/plugins/my-plugin/endpoints/index.js
export default {
widgets: {
async get(req) {
return { success: true }
}
}
}User Configuration
Extend the built-in member collection by creating config/config/users.js:
import fields from '@cms/1. build/fields'
const { Relationship, Image } = fields
export default {
// Extend the member collection with custom fields
fields: {
// Built-in fields: email, password, roles
// Add custom fields
displayName: String,
avatar: Image(),
timezone: {
type: String,
default: 'UTC',
},
teams: Relationship({
collection: 'team',
}),
preferences: {
fields: {
theme: String,
notifications: Boolean,
},
default: {
theme: 'light',
notifications: true,
},
},
},
// Add member-specific hooks
hooks: {
created: async ({ document }) => {
// Send welcome email
await sendWelcomeEmail(document.email)
},
updated: async ({ document, originalDocument }) => {
// Handle email change verification
if (document.email !== originalDocument.email) {
await sendEmailVerification(document.email)
}
},
},
// Custom member permissions
permissions: {
owner: ['read', 'update'],
admin: ['create', 'read', 'update', 'delete'],
},
}The user configuration allows you to:
- Add custom fields to the member collection
- Define member-specific hooks
- Set up member permissions
- Handle user-related business logic
Member documents will automatically include these custom fields alongside the built-in authentication fields.
Best Practices
- Collection Names: Use singular form in collection definition
- Field Organization: Group related fields together
- Permissions: Start restrictive and open up as needed
- Validation: Implement thorough field validation
- Error Handling: Use try-catch blocks in hooks and custom logic
Fresh Ubuntu 24 Install
# Install nginx
sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl status nginx
# Install Node
sudo apt update
sudo apt install -y nodejs
sudo apt install -y npm
sudo npm install -g n
sudo n stable
export PATH="/usr/local/bin:$PATH"
source ~/.bashrc # or source ~/.zshrc
sudo apt remove -y nodejs
node -v
# Install MongoDB
sudo apt-get install gnupg curl
curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg --dearmor
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
sudo systemctl start mongod
sudo systemctl enable mongod
# Install PM2
sudo npm install -g pm2
pm2 startup
# Run ssh-keygen and add the public key to your keys in GitHub
# Clone your repo and build
# Add your dist folder to the nginx config at /etc/nginx/sites-available/default
# Configure Permissions
sudo chown -R www-data:www-data /root/Sites/{{repo}}/front/dist
sudo chmod -R 755 /root/Sites/{{repo}}/front/dist
sudo chmod +x /root
sudo chmod +x /root/Sites
sudo chmod +x /root/Sites/{{repo}}
sudo chmod +x /root/Sites/{{repo}}/front
# Ensure the site is working by going to the IP address in your browser
# Add your site DNS to CloudFlare and point it to the IP address
# Install Certbot
apt install certbot -y
sudo apt install python3-certbot-nginx
certbot
# Install redis
sudo apt install redis-server -y
sudo systemctl enable redis-server