A modern, self-hosted CMS + CRM built for developers. Block editor Β· CRM Β· Email campaigns Β· In-browser image editor Β· 2FA Β· Headless API Β· No cloud lock-in.
Lambda CMS is a clean, fast, fully self-hosted content management system with a built-in CRM and email marketing toolkit. No subscriptions, no SaaS lock-in β you own your data and your stack. Built with Laravel 12, Vue 3, and Inertia.js, it feels like a modern SPA while keeping the server-side simplicity of a traditional Laravel app.
| Layer | Technology |
|---|---|
| Backend | Laravel 12, PHP 8.2+ |
| Frontend | Vue 3 (Composition API), Inertia.js 2, Tailwind CSS 4, Vite 7 |
| UI | shadcn-vue, reka-ui, Lucide icons |
| Rich Text | Tiptap |
| Database | SQLite (default) or MySQL |
- Posts β full CRUD with Tiptap rich text or the drag-and-drop block editor
- Pages β static pages built entirely with the block editor
- Categories & Tags β many-to-many on posts; categories support custom colors
- Scheduled publishing β set a future date and posts go live automatically via the scheduler
- Autosave β drafts save quietly in the background while you write
- Revisions β full history with one-click restore (up to 25 per post/page)
- Draft previews β shareable token-based URLs, no login required
- Bulk actions β publish, draft, or delete multiple posts at once
30+ block types across six categories:
| Category | Blocks |
|---|---|
| Content | Paragraph, Heading, Quote, Code, Divider, Spacer, HTML, Accordion, Tabs, Embed |
| Media | Image, Gallery, Video |
| Layout | Section, Container (flex / grid / inline-flex) |
| Interactive | Button, CTA, Link, Navigation, Search, Filter Link, Active Filter, Icon List |
| Data | Loop, Pagination, Post Card, Post Title, Post Body, Post Featured Image, Post Meta, Post Author, Post Taxonomy, Post Comments, Archive Title |
| Site | Nav Header, Site Footer, Masthead, Band, Template |
What makes it nice to use:
- Drag-and-drop canvas with cross-list nesting and a layers panel tree
- Per-block Style tab β typography, colors, background (solid / gradient / image), border, shadow, spacing
- Dynamic field bindings β link block content to loop or post-context data sources
- Per-block Advanced settings β custom CSS classes, inline CSS, font family
- Conditional block visibility based on loop field values
- Block labels shown in canvas and layers panel
- Upload images, documents, video, and audio (configurable size limit, default 10 MB)
- In-browser image editor β crop with 9 aspect ratio presets, rotate, flip, 8 filter presets (Normal, Vivid, Muted, B&W, Warm, Cool, Fade, Drama), and manual brightness / contrast / saturation / blur controls. Opens before upload and on any existing library item.
- Auto-resize images larger than the configured limit (default 1920 px wide)
- Alt text and description per file; UUID-based filenames with dimension tracking
- Admins see all files; users see their own
- 7 pre-shipped system templates β Post Card, Default Blog Index, Default Single Post, Default Archive, Default Search Results, Default Header, Default Footer
- Custom templates share the same block editor, autosave, and revisions
- Loop blocks correctly inherit context for dynamic bindings
- Every public page rendered from editable block templates
- Header and footer rendered from dedicated block templates
- Blog index, single post, category/tag archives, and full-text search
- RSS feed at
/feedand XML sitemap at/sitemap.xml - Design token system β accent color applies live to both admin and blog
- Admin bar visible to authenticated users only
- Public submission with honeypot spam protection and rate limiting
- Admin moderation β approve, reject, or delete individually or in bulk
- Nested replies with email notifications
- Per-post toggle to enable/disable comments
A built-in customer relationship management system with fine-grained, permission-based access control. Each CRM module has its own permission that can be granted independently to non-admin users.
- Contacts β manage people with name, email, phone, position, notes, and company association. Statuses: active, inactive, archived. Full-text search across all fields. Activity timeline on the edit page.
- Companies β organize contacts under companies with domain, phone, and address. Contacts and deals counts shown in the list view.
- Deals β track sales opportunities through a 5-stage pipeline: Lead, Qualified, Proposal, Won, Lost. Each deal tracks monetary value, expected close date, and links to a contact and company. Closing a deal as Won or Lost auto-stamps
closed_at; reopening clears it. Activity timeline per deal. - Call Lists β create lists of contacts for outreach campaigns. Add/remove contacts, track call status per contact (not called, called, no answer, callback, completed), record notes per call, and work through the list with a dedicated calling interface.
- Activities β log notes, calls, emails, meetings, and tasks against any contact, company, or deal. Shown as a timeline on the entity's edit page.
- CRM Import β upload a CSV, preview rows, map columns to contact or company fields, choose a conflict strategy (skip or overwrite), and import in bulk.
- CRM Export β download contacts, companies, or call lists as CSV files.
Permissions: manage contacts, manage companies, manage deals, manage call lists β each grantable independently to any user role.
Customizable transactional emails, subscriber management, and email campaigns β all in one place.
- Editable system email templates β 5 built-in templates (password reset, email verification, welcome, new comment, comment reply) stored in the database with a WYSIWYG editor. Each template supports merge tags (e.g.
{{user_name}},{{reset_url}}), live preview, and one-click reset to default. All Laravel system emails (password reset, verification, welcome, comment notifications) route through these templates automatically. - Subscriber management β a standalone subscribers table (separate from CRM contacts) for newsletter signups. Public subscribe form (Blade page at
/subscribe) and token-based unsubscribe link. Admin interface with search, status filtering (active / unsubscribed), bulk delete, CSV export, and CSV import with column mapping and conflict strategy. - Email campaigns β create newsletters with the Tiptap editor, save as draft, and send to all active subscribers. Sending is handled by a queued job that processes each recipient individually. Per-recipient tracking with sent/failed/pending status and error messages. Campaign report page with stat cards and a recipients table.
- Campaign scheduling β schedule campaigns for a future date and time instead of sending immediately. The Laravel scheduler checks every minute and auto-dispatches due campaigns. Scheduled campaigns can be edited (resets to draft), unscheduled back to draft, or sent immediately.
- Unsubscribe footer β every campaign email includes an unsubscribe link with the subscriber's unique token.
Permission: manage email β covers templates, subscribers, and campaigns.
- Two roles: administrator and user
- Admins manage all content, users, settings, pages, and templates
- Users manage their own posts, categories, tags, and media
- Fine-grained permissions:
manage contacts,manage companies,manage deals,manage call lists,manage emailβ each independently assignable - Avatar upload per profile
- User invite flow β auto-generated password + welcome email
- User banning with reason and optional expiry (auto-lifted on next login)
- Online status tracking
- Email + password login with rate limiting (5 attempts / 60 s per email + IP)
- Two-factor authentication β TOTP-based 2FA compatible with Google Authenticator, Authy, 1Password, and any RFC 6238 app. Includes 8 single-use recovery codes.
- Forgot / reset password via email token
- Email verification required before dashboard access
- Author-scoped post and media access
- Events:
post.published,post.updated,post.deleted,page.published,page.updated,page.deleted - HMAC-SHA256 request signature (
X-Lambda-Signature) when a secret is set - Dispatched via queued jobs;
last_triggered_attracked per webhook
- Month-view calendar with published, scheduled, and draft posts
- Unscheduled drafts panel alongside the calendar
- Admins see all; users see their own
- Site name, URL, timezone, and date format
- SEO defaults (meta description, OG image, keywords, title separator)
- SMTP mail configuration with test-send
- Media upload limits and image resize threshold
- Comments (enabled toggle, items per page)
- Appearance: accent color applied live to admin and blog frontend
- UI density: default, compact, or comfortable
- Custom JS injection on every public page
GET /api/v1/posts
GET /api/v1/posts/{slug}
GET /api/v1/categories
GET /api/v1/categories/{slug}
GET /api/v1/tags
GET /api/v1/tags/{slug}
POST /api/v1/query <- block editor loop data source
Export your content as a portable ZIP and re-import it into any Lambda CMS instance.
Supported entities: Posts, Pages, Categories, Tags, Media, Templates
Export β pick which entities to include, optionally bundle media files, and download a .zip. Import β upload a ZIP, preview what's inside, select entities, choose a conflict strategy (skip, overwrite, or duplicate), and run.
π ZIP structure and JSON schemas
The export ZIP contains a manifest.json, one JSON file per entity type, and an optional media/ folder:
lambda-cms-export-2026-06-15-120000.zip
βββ manifest.json
βββ posts.json
βββ pages.json
βββ categories.json
βββ tags.json
βββ media.json
βββ templates.json
βββ media/
βββ a1b2c3d4-photo.jpg
βββ e5f6g7h8-document.pdf
{
"version": "1.0",
"app": "lambda-cms",
"exported_at": "2026-06-15T12:00:00.000000Z",
"entities": ["posts", "pages", "categories", "tags", "media", "templates"],
"include_media_files": true,
"counts": {
"posts": 12,
"pages": 3,
"categories": 5,
"tags": 8,
"media": 20,
"templates": 4
}
}[
{
"title": "My First Post",
"slug": "my-first-post",
"excerpt": "A short summary",
"body": "<p>HTML content</p>",
"body_format": "html",
"status": "published",
"featured": false,
"published_at": "2026-06-01T10:00:00.000000Z",
"comments_enabled": true,
"use_block_editor": false,
"blocks": [],
"meta_title": null,
"meta_description": null,
"meta_keywords": null,
"custom_js": null,
"categories": ["tutorials", "laravel"],
"tags": ["php", "cms"],
"featured_image": "a1b2c3d4-photo.jpg"
}
]| Field | Type | Notes |
|---|---|---|
title |
string | Required |
slug |
string | Used for conflict detection. Auto-generated on duplicate strategy |
excerpt |
string|null | Plain text summary |
body |
string|null | HTML content (when body_format is html) |
body_format |
"html" | "blocks" |
Determines whether body or blocks holds the content |
status |
"draft" | "published" | "scheduled" |
Defaults to draft on import |
featured |
boolean | |
published_at |
ISO 8601|null | |
comments_enabled |
boolean | |
use_block_editor |
boolean | |
blocks |
array | Block editor JSON (when body_format is blocks) |
meta_title |
string|null | SEO title override |
meta_description |
string|null | SEO description |
meta_keywords |
string|null | SEO keywords |
custom_js |
string|null | Per-post JavaScript |
categories |
string[] | Array of category slugs β resolved on import |
tags |
string[] | Array of tag slugs β resolved on import |
featured_image |
string|null | Media filename β resolved to a media ID on import |
[
{
"title": "About Us",
"slug": "about-us",
"status": "published",
"blocks": [
{ "type": "heading", "data": { "text": "About Us", "level": 1 } },
{ "type": "paragraph", "data": { "text": "Welcome to our site." } }
],
"meta_title": null,
"meta_description": null,
"meta_keywords": null,
"custom_js": null
}
]| Field | Type | Notes |
|---|---|---|
title |
string | Required |
slug |
string | Used for conflict detection |
status |
"draft" | "published" |
Defaults to draft on import |
blocks |
array | Block editor JSON |
meta_title |
string|null | SEO title override |
meta_description |
string|null | SEO description |
meta_keywords |
string|null | SEO keywords |
custom_js |
string|null | Per-page JavaScript |
[
{
"name": "Tutorials",
"slug": "tutorials",
"description": "Step-by-step guides",
"color": "#5e81ac",
"hue": 213
}
][
{
"name": "PHP",
"slug": "php"
}
][
{
"id": 1,
"filename": "a1b2c3d4-photo.jpg",
"original_filename": "photo.jpg",
"disk": "public",
"path": "media/a1b2c3d4-photo.jpg",
"mime_type": "image/jpeg",
"type": "image",
"size": 204800,
"width": 1920,
"height": 1080,
"alt": "A landscape photo",
"description": null
}
]When include_media_files is enabled, the actual files are bundled under media/ in the ZIP, keyed by filename.
[
{
"title": "Default Blog Index",
"type": "index",
"loop_source": "posts",
"status": "published",
"blocks": [],
"meta_title": null,
"meta_description": null,
"meta_keywords": null
}
]| Field | Type | Notes |
|---|---|---|
title |
string | Used with type for conflict detection |
type |
"index" | "single" | "archive" | "search" | "header" | "footer" | "partial" |
Template type |
loop_source |
string|null | Data source for loop blocks (e.g. "posts") |
status |
"draft" | "published" |
When importing a published non-partial template, other published templates of the same type are demoted to draft |
blocks |
array | Block editor JSON |
| Strategy | Behavior |
|---|---|
| Skip | Leave existing records untouched. Only create records that don't exist yet. |
| Overwrite | Update existing records with the imported data. Matched by slug (posts, pages, categories, tags), filename (media), or title + type (templates). |
| Duplicate | Always create new records, generating a unique slug when needed. |
- 4-step browser wizard: Database > Site > Admin > Mail
- Supports SQLite and MySQL; tests the DB connection before writing
.env - Runs migrations, seeds system templates, and creates the first admin account
- PHP 8.2+
- Composer
- Node.js 20+ and npm
- SQLite (bundled with PHP) or MySQL 8+
git clone https://github.com/mariusberget92/lambda-cms.git
cd lambda-cms
composer install
npm install
cp .env.example .env
php artisan key:generate
npm run buildOpen the site in a browser β you'll be redirected to /install to run the setup wizard.
php artisan serve
npm run devphp artisan config:cache
php artisan route:cache
php artisan view:cache
npm run buildAdd a cron entry to run the scheduler (handles auto-publishing scheduled posts and dispatching scheduled campaigns):
* * * * * cd /path/to/lambda-cms && php artisan schedule:run >> /dev/null 2>&1
For email campaigns, webhook dispatches, and other queued jobs, start a queue worker:
php artisan queue:workphp artisan testThe test suite covers 677 tests across content management, CRM, email system, campaign scheduling, and public-facing features.
- Full import / export (posts, pages, categories, tags, media, templates)
- CRM (contacts, companies, deals, call lists, activities, import/export)
- Email system (editable templates, subscribers, campaigns with scheduling)
- Comprehensive test suite (677 tests)
- API write access β token-based auth for creating and updating content
- Multi-language / i18n content support
MIT β do whatever you want with it.








