Built by A to Z Tech Innovations LLC
A full-featured, open-source WordPress clone built from scratch with SvelteKit 2, Svelte 5, TypeScript, and SQLite. Familiar WordPress-style admin, Tiptap-powered rich text editor, plugin hooks, theme switching, and a clean public frontend — with zero PHP.
Version: 0.9.0-beta | License: MIT | Stack: SvelteKit 2 · Svelte 5 · TypeScript · SQLite · Drizzle ORM · Tailwind CSS v4 · Tiptap 3 · Vitest
node-cron publishes automatically every minute; "Scheduled" tab in adminsharp auto-generates thumbnail (150×150 crop), medium (300px), and large (1024px) sizesstatic/uploads/YYYY/MM/CAPABILITIES map with a can(user, cap) helper/YYYY/MM/), and search archives on the public frontendsp_session cookie, 30-day expiry/sp-login, /sp-register, /sp-forgot-password/sp-forgot-password?token= handles the reset form?p=123), Day+name, Month+name, Numeric (/archives/123), Post name, Custom/[slug] issues 301 → canonical URL when using a date-based structure/sp-admin/activity with color-coded action types| Page | Controls |
|---|---|
| General | Site title, tagline, admin email, registration, default role, timezone, date/time format |
| Reading | Homepage display (latest posts or static page), posts per page, feed settings |
| Writing | Default post category and format |
| Discussion | Comment notifications, moderation rules, avatar settings |
| Media | Thumbnail/medium/large pixel dimensions, upload path |
| Permalinks | URL structure (plain, day-name, month-name, numeric, post-name, custom) |
addAction / doAction / addFilter / applyFilters hook API; active plugins loaded from plugins/*/plugin.ts at server startup; activation state persisted in DBthemes/*/; active theme stored in options table; per-theme style.css dynamically loaded on frontend; switch via admin UI/api/v1/* for posts, pages, media, comments, users, categories, tags, forms, and form submissions; all write endpoints require authentication/api/oembed proxy resolves YouTube, Vimeo, Twitter/X, SoundCloud, Spotify, Instagram, TikTok embed HTMLdrizzle-kit$state, $derived, $effect; no legacy storesRequirements: Node.js 18+, pnpm
# 1. Install dependencies
pnpm install
# 2. Copy environment file and set BETTER_AUTH_SECRET
cp .env.example .env
# Edit .env and set BETTER_AUTH_SECRET to a random 32+ character string
# 3. Generate and run database migrations
pnpm db:generate
pnpm db:migrate
# 4. Seed the database (creates default admin, options, Uncategorized category)
pnpm db:seed
# 5. Start the dev server
pnpm dev
Migrating an existing database? Run the one-time migration script after applying migrations:
pnpm tsx scripts/migrate-to-better-auth.ts
This populates the account table with existing password hashes and migrates session tokens so active logins are preserved.
The app is now running at http://localhost:5173.
pnpm build
node build/index.js
| Field | Value |
|---|---|
| Username | admin |
| Password | password |
Change the default password immediately after first login via Settings → Profile.
SveltePress uses a Tiptap 3 (ProseMirror-based) rich text editor. Content is stored as Tiptap JSON ({ type: 'doc', content: [...] }) and rendered server-side via generateHTML from @tiptap/html. Legacy posts (old block format) continue to render correctly via backward-compatible rendering.
Toolbar — block type picker, bold/italic/underline/strikethrough, alignment, link, inline code, undo/redo.
Block inserter — 22 block types across six categories:
Text | Block | Description | |---|---| | Paragraph | Rich text with inline formatting | | Heading | H2–H4 with level switcher | | Quote | Blockquote | | Pullquote | Large centered pull quote | | Preformatted | Preserves whitespace, monospace | | Code Block | Fenced code block |
Lists | Block | Description | |---|---| | Bullet List | Unordered list | | Numbered List | Ordered list |
Media | Block | Description | |---|---| | Image | Upload or pick from media library | | Gallery | Multi-image grid — Svelte node view with image picker | | Video | URL input with caption — Svelte node view | | Embed | oEmbed URL (YouTube, Vimeo, Twitter/X, SoundCloud, Spotify, Instagram, TikTok) — Svelte node view |
Layout | Block | Description | |---|---| | Two Columns | Two-column nested editor panes | | Separator | Horizontal rule | | Spacer | Adjustable height — Svelte node view with drag slider |
Advanced | Block | Description | |---|---| | Button | CTA with fill/outline style, URL, target — Svelte node view | | Custom HTML | Raw HTML textarea — Svelte node view | | Shortcode | Shortcode text input — Svelte node view | | Table | Rows/columns with header row |
Interactive | Block | Description | |---|---| | Form | Visual form builder — add/reorder/configure fields, settings (submit label, success message, email notification), live preview tab; Svelte node view |
Node views — Spacer, Video, Embed, Gallery, Button, HTML, Shortcode, and Form blocks render as interactive Svelte 5 components inside the editor, mounted via a custom SvelteNodeViewRenderer.
All admin routes live under /sp-admin/:
| Route | Description |
|---|---|
/sp-admin/dashboard |
At-a-glance stats, quick draft, recent activity, welcome panel |
/sp-admin/posts |
Post list with status tabs (including Scheduled), search, bulk actions, hover actions |
/sp-admin/posts/new |
Tiptap editor for new post |
/sp-admin/posts/[id] |
Tiptap editor for existing post |
/sp-admin/pages |
Same as posts, filtered to pages |
/sp-admin/media |
Grid/list media library, drag-and-drop upload, bulk delete |
/sp-admin/media/[id] |
Attachment detail and edit |
/sp-admin/comments |
Comment moderation with status tabs and bulk actions |
/sp-admin/categories |
Split-panel add/edit/delete with hierarchy |
/sp-admin/tags |
Flat taxonomy management |
/sp-admin/menus |
Menu builder with pages/posts/custom links/categories |
/sp-admin/widgets |
Widget area management with drag-and-drop reordering |
/sp-admin/users |
User list with role filter tabs |
/sp-admin/users/new |
Create new user |
/sp-admin/users/[id] |
Edit user and upload avatar |
/sp-admin/profile |
Current user profile, password, avatar upload, 2FA setup |
/sp-admin/themes |
Theme grid with activate and details modal |
/sp-admin/plugins |
Plugin list with activate/deactivate (state persisted) |
/sp-admin/settings/general |
General settings |
/sp-admin/settings/reading |
Reading settings |
/sp-admin/settings/writing |
Writing settings |
/sp-admin/settings/discussion |
Comment and notification settings |
/sp-admin/settings/media |
Image size settings |
/sp-admin/settings/permalinks |
URL structure settings |
/sp-admin/form-submissions |
Form submission inbox — status tabs (All/Unread/Read/Spam/Trash), form filter, bulk actions, CSV export |
/sp-admin/form-submissions/[id] |
Full submission detail with field labels, status change, restore, delete |
/sp-admin/activity |
Admin activity log with filters and pagination |
/sp-admin/tools |
WXR import and export |
/sp-admin/revisions/[id] |
Revision browser and restore |
| Route | Description |
|---|---|
/ |
Blog home (paginated post listing) or static front page — configurable via Reading Settings |
/blog |
Paginated post listing (used when a static front page is set) |
/[slug] |
Single post or page with threaded comments |
/[slug] (password-protected) |
Password gate for private posts; unlocked via cookie |
/category/[slug] |
Category archive |
/tag/[slug] |
Tag archive |
/author/[username] |
Author archive with profile |
/search |
Full-text search results |
/[year]/[month]/ |
Date archive (e.g. /2026/02/) |
/[year]/[month]/[slug] |
Month+name permalink structure |
/[year]/[month]/[day]/[slug] |
Day+name permalink structure |
/archives/[id] |
Numeric permalink structure |
Frontend features: header with primary nav (pulls from assigned primary menu), sidebar with widgets (search, categories, archives), footer, threaded comment form on posts, Gravatar avatars, responsive layout, active theme CSS loaded dynamically.
Page templates — pages support three templates selectable from the editor sidebar:
Default Template — standard two-column layout with sidebarFull Width — single-column layout, sidebar hiddenBlank — no sidebar, minimal chromePlugins live in plugins/<name>/plugin.ts and are loaded at server startup. Only plugins listed in the active_plugins option are loaded. The admin UI allows toggling activation — state is persisted in the database.
// plugins/my-plugin/plugin.ts
import { hooks } from '$lib/server/plugins/hooks.js';
hooks.addAction('post_published', (post) => {
console.log('Post published:', post.title);
});
hooks.addFilter('post_content', (content) => {
return content.replace(/\[year\]/g, new Date().getFullYear().toString());
});
Two example plugins are included: plugins/seo/ (meta tag injection) and plugins/akismet/ (spam check stub).
Themes live in themes/<name>/. Each theme has a theme.json (metadata) and a style.css (CSS custom properties). The active theme is stored in the options table and switchable from the admin. Switching themes immediately changes the frontend's fonts, colors, and layout width.
Three themes ship by default:
| Theme | Description |
|---|---|
default |
Clean, readable — Georgia serif body, sans-serif headings, 780px width |
minimal |
Stark monospace — Courier New throughout, narrow 640px width |
magazine |
Editorial bold — Impact headings, red accent, wide 1100px layout |
// themes/my-theme/theme.json
{
"name": "My Theme",
"version": "1.0.0",
"description": "A custom theme",
"author": "You",
"screenshot": "/themes/my-theme/screenshot.png"
}
/* themes/my-theme/style.css */
:root {
/* Typography */
--theme-font-body: Georgia, serif;
--theme-font-heading: sans-serif;
/* Colors */
--theme-color-bg: #ffffff;
--theme-color-surface: #f9f9f9;
--theme-color-text: #1d2327;
--theme-color-muted: #646970;
--theme-color-accent: #2271b1;
--theme-color-border: #e0e0e0;
/* Layout */
--theme-max-width: 780px;
--theme-sidebar-width: 280px;
/* Header (optional — falls back to defaults if not set) */
--theme-header-bg: #ffffff;
--theme-header-text: #1d2327;
--theme-header-border: #e0e0e0;
/* Footer (optional) */
--theme-footer-bg: #f9f9f9;
--theme-footer-text: #646970;
}
All endpoints at /api/v1/ support standard CRUD. Authentication uses the same session cookie. Write endpoints require authentication; GET endpoints are public.
| Endpoint | Methods | Auth required |
|---|---|---|
/api/v1/posts |
GET, POST | POST: edit_posts |
/api/v1/posts/[id] |
GET, PUT, DELETE | PUT: edit_posts; DELETE: delete_posts |
/api/v1/pages |
GET, POST | POST: edit_pages |
/api/v1/pages/[id] |
GET, PUT, DELETE | PUT: edit_pages; DELETE: delete_pages |
/api/v1/media |
GET, POST | POST: upload_files |
/api/v1/media/[id] |
GET, PUT, DELETE | PUT/DELETE: upload_files |
/api/v1/comments |
GET, POST | POST: public (guest comments → pending) |
/api/v1/comments/[id] |
GET, PUT, DELETE | PUT/DELETE: moderate_comments |
/api/v1/users |
GET, POST | GET+POST: manage_users |
/api/v1/users/[id] |
GET, PUT, DELETE | GET: list_users; PUT/DELETE: manage_users |
/api/v1/categories |
GET, POST | POST: manage_categories |
/api/v1/categories/[id] |
GET, PUT, DELETE | PUT/DELETE: manage_categories |
/api/v1/tags |
GET, POST | POST: manage_categories |
/api/v1/tags/[id] |
GET, PUT, DELETE | PUT/DELETE: manage_categories |
/api/v1/forms |
GET, POST | POST: edit_posts |
/api/v1/forms/[id] |
GET, PUT, DELETE | PUT/DELETE: edit_posts |
/api/v1/form-submissions |
GET, POST | GET: manage_options; POST: public |
/api/upload |
POST | upload_files |
/api/oembed |
GET | Public |
In development, SveltePress automatically creates a free ethereal.email test account. Email preview URLs are logged to the console — no configuration needed.
For production, set these environment variables in .env:
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURE=false
[email protected]
SMTP_PASS=yourpassword
SMTP_FROM=SveltePress <[email protected]>
Emails sent:
This is a 0.9.0-beta release. Core functionality is complete and working end-to-end.
| Feature | Status |
|---|---|
| Akismet / spam filtering | The Akismet plugin stub exists but makes no real API calls |
| Import validation | WXR import does not validate for duplicate slugs or missing authors |
| Multisite | Single-site only |
svelte-press/
├── src/
│ ├── lib/
│ │ ├── components/
│ │ │ ├── editor/ # Tiptap editor components
│ │ │ │ ├── TiptapEditor.svelte
│ │ │ │ ├── TiptapToolbar.svelte
│ │ │ │ ├── BlockInserterMenu.svelte
│ │ │ │ ├── MediaPickerDialog.svelte
│ │ │ │ └── node-views/ # Svelte node view components
│ │ │ ├── frontend/
│ │ │ │ └── PostList.svelte # Shared paginated post listing component
│ │ │ ├── GalleryLightbox.svelte # Full-screen gallery lightbox
│ │ │ └── Comment.svelte # Recursive threaded comment component
│ │ ├── editor/ # Tiptap editor core
│ │ │ ├── use-editor.svelte.ts # Svelte 5 $state hook
│ │ │ ├── SvelteNodeViewRenderer.svelte.ts # ProseMirror node view renderer
│ │ │ ├── backward-compat.ts # Tiptap JSON vs legacy Block[] detection
│ │ │ ├── html-export.ts # generateHTML() wrapper (SSR-safe)
│ │ │ └── extensions/ # Custom Tiptap extensions + node views
│ │ ├── auth.ts # Better Auth config (drizzle adapter, twoFactor plugin, bcrypt)
│ │ ├── auth-client.ts # SvelteKit client helper (twoFactorClient plugin)
│ │ ├── server/
│ │ │ ├── activity/ # logActivity() helper + activity_log table
│ │ │ ├── api/ # REST API auth helpers
│ │ │ ├── db/ # Drizzle schema, migrations, seed
│ │ │ ├── email/ # nodemailer email service
│ │ │ ├── forms/ # Form sync, Zod schema generation, CSV export
│ │ │ ├── media/ # sharp image processing
│ │ │ ├── permissions/ # Role capabilities
│ │ │ ├── plugins/ # Hook system + loader
│ │ │ ├── render-content.ts # renderTiptapContent() — walks doc, injects raw HTML nodes
│ │ │ ├── scheduler/ # node-cron scheduled publishing
│ │ │ └── themes/ # Theme loader
│ │ └── utils.ts # slugify, formatDate, truncate, getPermalinkUrl, etc.
│ ├── params/
│ │ ├── year.ts # Route matcher for 4-digit years
│ │ ├── month.ts # Route matcher for month numbers (1–12)
│ │ └── day.ts # Route matcher for day numbers (1–31)
│ └── routes/
│ ├── (admin)/sp-admin/ # All admin routes
│ ├── (auth)/ # Login, register, forgot password
│ ├── (frontend)/ # Public blog routes (theme-aware)
│ │ ├── +page.svelte # Homepage (static front page or blog listing)
│ │ ├── blog/ # /blog — post listing (when static front page is set)
│ │ └── [slug]/ # Single post/page
│ ├── api/v1/ # REST API (collections + [id] CRUD for all resources)
│ ├── api/oembed/ # oEmbed proxy
│ └── themes/[theme]/ # Theme CSS file server
├── plugins/ # Plugin directory
├── themes/ # Theme directory (default, minimal, magazine)
├── static/uploads/ # Media upload destination
└── data/ # SQLite database (gitignored)
Pull requests are welcome. For significant changes, open an issue first.
# Run type checking
pnpm check
# Run unit tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run tests with coverage report
pnpm test:coverage
# Start dev server
pnpm dev
MIT License — see LICENSE for details.
Built with ❤️ by A to Z Tech Innovations LLC