How Arkivel is built.
A fixed core, one generic data engine, first-party modules, and presets that combine them. This is the technical tour; the docs cover installation.
Core
The core is not a module and cannot be turned off. It is what every Arkivel instance is: a Next.js server talking to one PostgreSQL database through Prisma.
articleswiki linksbacklinksspacestagssearchrevisionsuserssessionsaudit logadminsettings
Articles are Tiptap documents with wiki links. Links are stored as rows, so backlinks, the graph, and redirects are queries rather than parsing. Every save writes a revision with diff, blame, and restore. The ⌘K palette searches pages, jumps to sections, and switches theme or skin.
Collections engine
Three tables cover tasks, reading lists, course trackers, CRMs, and a film library. A collection carries a typed property schema; items store values validated against it; views are saved queries over the items. An item may also be a real article.
Collection { slug, name, categoryId?, schema: PropertyDefinition[] }
CollectionItem { collectionId, articleId?, title, properties: Json, sortOrder }
CollectionView { collectionId, slug, kind, config: ViewConfig, isDefault }
ViewConfig = { filters: Filter[], sorts: Sort[], groupBy?: propertyId, visible: propertyId[] }Property types:
titletextnumberselectmulti_selectdatecheckboxurlpersonpagerelationcreated_timeupdated_time
View kinds: table, board, list, calendar. Unknown keys are dropped and wrong types rejected on every write. Relation writes check that targets exist in the bound collection, and item patches lock the row before merging so concurrent edits keep unrelated fields.
Modules
Modules are Arkivel's plugin system, kept first-party: no marketplace, no third-party code. Each one is a folder that declares what it owns. The sidebar, palette, help page, and feature list are composed from the registry, never hand-listed.
type ModuleDefinition = {
id: ModuleId; // "graph" | "assets" | "import" | …
name: string;
description: string;
routes: string[]; // owned path prefixes; disabled → 404
nav: NavEntry[]; // sidebar entries by section and order
commands: PaletteCommand[]; // ⌘K entries, optionally role-gated
docs: { help?: string; features: string[] };
defaultEnabled: boolean; // used when ARKIVEL_MODULES is unset
};Resolution, per request and cached: the ARKIVEL_MODULES comma list (or each module's default), then the admin override stored from /admin/modules. Pages call requireModule(id) and API routes return 404 when disabled. A module never imports another module; route handlers and kits are where they compose.
| Module | Owns | Default |
|---|---|---|
collections | /collections/api/collections | On |
graph | /graph/api/graph | On |
assets | /assets/api/assets/api/upload | On |
import | /import/api/import/api/articles/import | On |
export | /export/api/export | On |
api | /api/v1/api-docs | On |
feeds | /feed.xml/feed/atom | On |
share | /share/api/articles/*/share-token | On |
media | /discover/api/media | Off |
Starter kits
A kit is { modules, skin, seed }. ARKIVEL_KIT picks one for a fresh database; /admin/kits applies a kit's seed later. Applying is idempotent by collection slug and never overwrites existing collections. Nothing in a kit is code.
| Kit | Modules | Skin | Seeds |
|---|---|---|---|
documentation | collections, api, assets, import, export | editorial | Documentation collection: version, section, topic key, reading order |
wiki | graph, api, feeds, share | wiki | Nothing; the plain knowledge base |
notes-and-tasks | collections, graph | folio | Tasks (table + board by status) and a reading list |
team-knowledge-base | Every module | folio | An empty tasks collection |
course-workspace | collections, graph, import, export | folio | Linked courses and coursework with course-sync imports |
watchlist | collections, media, assets, export, import | folio | Watchlist and episodes collections from a starter catalogue |
Site modes
A site mode is a shell, meaning pages and presentation, over the shared backend. The built-in modes below are examples, not a fixed set. A mode is chosen at build time with ARKIVEL_SITE_MODE, and each instance keeps its own domain, environment, database, and storage credentials.
| Built-in mode | What it is |
|---|---|
wiki | A knowledge base: articles, wiki links, backlinks, spaces, tags, search, and a link graph. |
docs | A versioned reading index over published articles, grouped by section with previous and next links. |
media | A film and series library on the same backend, database, accounts, and admin as the wiki. |
Media mode is the worked example of Arkivel serving a domain that is not a wiki. The watchlist kit seeds two collection templates, the media module adds TMDB metadata with a bundled catalogue fallback, and src/media/ adapts collection rows into the library the shell reads. Mood picks use Claude only when ANTHROPIC_API_KEY is set and otherwise match locally.
Skins
Skins change layout, typography, density, and component presentation. Editors, permissions, and collection behaviour stay shared. The request resolves the reader's cookie, then their saved preference, then NEXT_PUBLIC_ARKIVEL_SKIN. Light and dark themes are separate from skins.
folio- The default: lowercase, quiet, written in place with a / block menu.
wiki- The classic encyclopedia layout.
editorial- Warm paper tones and serif reading typography.
compact- A denser workspace with a narrow sidebar and tighter tables.
Identity and access
Arkivel owns its users, roles (viewer, editor, admin), and sessions in its own database. Local passwords and optional GitHub or Google OAuth both end in an ordinary revocable database session; role changes apply immediately.
The first owner is created with npm run setup:owner under a PostgreSQL lock. ARKIVEL_REGISTRATION=closed is the default, and opening it only ever creates viewers. ARKIVEL_ACCESS=private puts pages, APIs, search, feeds, and files behind sign-in and fails closed on a database error.
Personal access tokens (Authorization: Bearer ark_…) make every /api route usable from scripts with the token owner's role. They are created and revoked under /settings/tokens and logged to the audit trail. See the API reference.
Storage
Uploads go to local disk (the default), any S3-compatible bucket, or legacy Vercel Blob. Local and S3 files are served through /api/files/… and pass instance authorization; non-image types download as attachments with sandbox and no-sniff headers. Stable object keys let you move from disk to a bucket without rewriting URLs.
ARKIVEL_STORAGE=s3
ARKIVEL_S3_BUCKET=arkivel-uploads
ARKIVEL_S3_REGION=eu-west-1
ARKIVEL_S3_ENDPOINT=https://… # optional, any S3-compatible serviceOperations
Schema changes ship as reviewed Prisma migrations. Builds never migrate production; containers apply pending migrations on start and other hosts run npm run db:deploy. npm run backup writes a PostgreSQL dump, uploads, table counts, and SHA-256 checksums, with a guarded restore rehearsal.
Admins get health endpoints, maintenance mode, read-only mode, an audit log, and Markdown, JSON, and ZIP exports. The installation guide and the private self-hosting guide cover the details.