Documentation
Everything you need to build, govern, and deploy sites with Stablemount.
Installation
Stablemount requires Node.js 20+ and an Anthropic API key.
git clone https://github.com/OWNER/stablemount.git
cd stablemount
npm install
Create a .env file in the project root:
ANTHROPIC_API_KEY=sk-ant-your-key-here
PORT=3000
Start the server:
npm start
This runs node --watch server.js, which auto-restarts on file changes. For production, use npm run start:no-watch.
Open http://localhost:3000/dashboard to begin.
Your first page
From the dashboard, click "+ Generate Page". Give it a title and describe what you want:
The AI returns:
- A complete HTML page with inline CSS, real copy, and placeholder images
- Design decisions (color tokens, fonts, spacing values)
- Reusable components (header and footer partials)
Everything is saved as flat files in content/. Click "View & Edit" to open the page with the editing overlay.
Editing pages
The editing overlay appears on every page. It provides three ways to edit:
Inline text editing
Click any element with a data-content attribute to edit it directly. A formatting toolbar appears with bold, italic, link, heading levels, and list options. Press Enter to split paragraphs, Backspace at the start to merge.
Conversational AI
Click the AI button in the top bar to open the chat panel. Describe changes in natural language: "Make the hero section darker", "Add a testimonials section after features", "Change the CTA text to Get Started Free".
The AI makes surgical string-replacement patches to the page HTML. It can also create pages, collections, entries, and server functions in a single response.
Section-level prompts
Click any section or partial on the page to select it. A context bar appears at the bottom where you can type a prompt scoped to just that section. The AI sees the selected HTML and focuses changes there.
Click Save (or check the unsaved badge) to persist changes to disk. A version snapshot is created automatically on every save.
Decisions
Decisions are named constraints that govern AI behavior across your site. Every time the AI generates a page, it proposes decisions. You can also create them manually.
Kinds
| Kind | What it is | Example |
|---|---|---|
| Token | A CSS value the AI must use | Primary Color: #4f46e5 |
| Instruction | Prose guidance for the AI | "Use a warm, conversational tone. Never use corporate jargon." |
| Asset | A reference to a media file | Logo: /media/uploads/logo.svg |
Weight
- Rule — the AI must follow this exactly
- Guide — the AI should follow this but can deviate with good reason
Scope
- Global — applies to all pages and collections
- Page — applies only to a specific page (e.g.,
page:about) - Collection — applies only to a specific collection (e.g.,
collection:blog)
Tokens are automatically converted to CSS custom properties and injected into the AI's system prompt.
Components
Components come in two types:
Partials
Reusable HTML bundles injected server-side via directives. A page's HTML contains only the directive comment — the server resolves it at render time:
<!-- @partial:header -->
<main>...page content...</main>
<!-- @partial:footer -->
Partials are self-contained bundles: HTML first, then a <style> block, then an optional <script> block. Edit a partial once and it updates across every page that uses it.
Patterns
Reference templates the AI uses as guidance when generating similar elements. Unlike partials, patterns are not injected into pages. They teach the AI your preferred patterns — card layouts, hero structures, pricing tables — so it maintains consistency.
Both types have weight (rule/guide) and scope (global/page/collection) just like decisions.
Collections
Collections are structured content types. Each collection has a schema (typed fields) and entries.
// Example: Blog collection schema
[
{ "name": "title", "type": "text", "required": true },
{ "name": "excerpt", "type": "text" },
{ "name": "body", "type": "richtext", "required": true },
{ "name": "image", "type": "image" },
{ "name": "published", "type": "boolean" }
]
After creating a collection and adding entries, click "Generate Templates" to have the AI create listing and detail page templates.
Embed collections in any page using directives:
<!-- @collection:blog limit=3 sort=created order=desc -->
<article data-each-entry>
<h2>{{title}}</h2>
<p>{{excerpt}}</p>
<a href="{{entry.slug}}">Read more</a>
</article>
<!-- @/collection:blog -->
Collections are served at clean URLs: /blog for listing, /blog/my-post for detail.
Functions
Sandboxed server-side logic. Functions live in content/functions/ and are served at /api/fn/{name}.
// content/functions/contact.js
async function POST({ body, query }) {
await store.create('submissions', {
slug: body.email,
data: body
});
return { success: true };
}
Functions run in a Node.js VM sandbox with three APIs:
| API | Methods |
|---|---|
store | list(col), get(col, slug), create(col, {slug, data}), collections(), site() |
http | get(url), post(url, body), put(url, body), delete(url) |
env | get(key) |
No filesystem access, no imports, no require. Functions are safe enough for AI to generate.
File structure
Directives
Stablemount uses HTML comment directives that are resolved server-side at render time and stripped during export.
Partial directives
<!-- @partial:header -->
Replaced with the HTML content of the partial named "header". The partial's <style> and <script> blocks are aggregated and injected once per page.
Collection directives
<!-- @collection:blog limit=3 sort=created order=desc -->
...template with {{field}} placeholders...
<!-- @/collection:blog -->
Modes: dynamic (limit/sort/order) or curated (entries=slug1,slug2). The template element with data-each-entry is cloned for each entry.
Security
- Authentication — cookie-based sessions with
scryptpassword hashing. Optional (no password = open access for local dev). - Path traversal — all user-supplied path segments validated against their base directory.
- Body limits — 2 MB for JSON requests, 20 MB for file uploads.
- Upload validation — file type allowlist prevents executable uploads.
- Atomic writes — write-to-temp-then-rename prevents file corruption.
- HTTPS —
Securecookie flag set automatically behind a reverse proxy. Production should always use HTTPS via nginx, Caddy, or Cloudflare. - Function sandbox — server functions run in
node:vmwith no filesystem access, no module imports, and strict timeouts.
Static export
npm run export
This:
- Iterates all published pages (or all pages if none are published)
- Resolves partial and collection directives inline
- Strips all editing overlay artifacts (
data-content,data-section, etc.) - Copies media files to
dist/media/ - Writes clean HTML to
dist/with clean URL structure
The output in dist/ is a fully static site you can deploy to any host — Cloudflare Pages, Netlify, Vercel, S3, or a plain web server.
Production setup
Stablemount is a single Node.js process. Recommended production setup:
# Using PM2
pm2 start server.js --name stablemount
# Using systemd
# Create a service file at /etc/systemd/system/stablemount.service
# Using Docker
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Always put a reverse proxy in front for HTTPS. Caddy is the simplest option:
# Caddyfile
yourdomain.com {
reverse_proxy localhost:3000
}
Extensions
Extensions are full Node.js modules in extensions/*/extension.js. They have access to everything the sandbox does not: the filesystem, npm packages, external services.
// extensions/my-ext/extension.js
export default {
id: 'my-ext',
name: 'My Extension',
routes: {
'/my-endpoint': {
GET: async ({ query }) => {
return { body: { message: 'Hello from extension' } };
}
}
},
hooks: {
onPageSave: async ({ slug, html }) => {
console.log(`Page saved: ${slug}`);
}
}
};
Extension routes are served under /x/. They are hot-reloaded when the file changes — no server restart needed.
See extensions/example/extension.js for a complete example.