Skip to content

Obsidian ​

Documents ​

Source: packages/laikacms/src/impl/documents-obsidian

A DocumentsRepository implementation backed by an Obsidian vault. Each markdown note is a document keyed by its vault-relative path; published vs. unpublished state is read from frontmatter (the publish property, matching the Obsidian Publish convention) rather than from separate directories.

Pair it with a StorageRepository pointed at the vault root — typically a FileSystemStorageRepository configured with the markdown serializer.

Usage ​

ts
import { ObsidianDocumentsRepository } from 'laikacms/documents/obsidian';
import { markdownSerializer } from 'laikacms/serializers/markdown';
import { FileSystemStorageRepository } from 'laikacms/storage/fs';

const storage = new FileSystemStorageRepository(
  '/path/to/vault',
  { md: markdownSerializer },
  'md',
);

const repo = new ObsidianDocumentsRepository(storage);

Custom frontmatter properties ​

ts
const repo = new ObsidianDocumentsRepository(storage, {
  publishProperty: 'published', // read/write 'published' instead of 'publish'
  statusProperty: 'stage', // read/write 'stage' instead of 'status'
  defaultStatus: 'idea', // status when 'stage' is absent; default 'draft'
});

Constructor ​

ts
new ObsidianDocumentsRepository(
  storageRepository: StorageRepository,
  options?: ObsidianDocumentsRepositoryOptions,
)
ParameterTypeDescription
storageRepositoryStorageRepositoryUnderlying storage, typically FileSystemStorageRepository + markdownSerializer.
optionsObsidianDocumentsRepositoryOptionsOptional. See table below.

Options ​

OptionTypeDefaultDescription
publishPropertystring'publish'Frontmatter property that marks a note as published. A note is a published Document when this property is strictly true; otherwise it is an Unpublished draft.
statusPropertystring'status'Frontmatter property that records the editorial status of a draft (draft, pending_review, …).
defaultStatusstring'draft'Status reported for a draft note that has no explicit statusProperty value.

Frontmatter conventions ​

Published note ​

yaml
---
publish: true
language: en
title: My Note
---
  • publish: true — required; anything other than strict true is treated as unpublished. Also preserved in the document's content.
  • language — optional BCP 47 tag (e.g. en, nl). Omitted or empty resolves to 'und' (undetermined).
  • All frontmatter keys — including publish, language, and status — are passed through as document content. The implementation reads publish and language to derive document state and language, but does not strip them from the content map.

Draft / unpublished note ​

yaml
---
publish: false
status: pending_review
language: en
title: My Draft
---
  • publish: false (or absent / any non-true value) — note is an Unpublished draft. Also preserved in the document's content.
  • status — editorial state. Common values: draft, pending_review, published (though published is normally expressed via publish: true). Missing value falls back to defaultStatus. Also preserved in the document's content.
  • language — optional BCP 47 tag. Also preserved in the document's content.

When a note is published via publish() the publish property is set to true and the status property is removed. When it is unpublished via unpublish() the reverse happens. Note that publish, language, and status are accessible both as top-level Document fields and within the content object.

Behaviour notes ​

  • Keys. A document's key is its vault-relative path without the file extension (e.g. blog/2024-hello-world). Keys are case-sensitive and use / as the path separator regardless of OS.
  • Listing reads full objects. Because published state is stored in frontmatter, listRecords and listRecordSummaries must read full file content — unlike storage implementations that use separate directories, listAtomSummaries alone cannot distinguish published notes from drafts.
  • Language. A note without a language frontmatter key is returned with language: 'und'. Setting language to 'und' on create/update removes the key from the frontmatter.
  • Capabilities. Pagination capabilities are delegated to the underlying StorageRepository.

Limitations ​

Obsidian is a local markdown editor with no built-in version history. The revision API surface is therefore unsupported:

MethodBehaviour
getRevisionAlways throws BadRequestError
createRevisionAlways throws BadRequestError
listRevisionsAlways returns an empty stream (total: 0)

No built-in DocumentsRepository with revision history exists yet. If you need file-level history, pair a DocumentsRepository that delegates to a storage backend with change tracking — for example GithubStorageRepository from @laikacms/github (a StorageRepository) preserves each write as a GitHub commit, giving you a per-file audit trail at the storage layer.

Assets ​

Source: packages/laikacms/src/impl/assets-obsidian

Obsidian-vault-backed implementation of AssetsRepository.

Runtime: Node.js / Bun only. This module imports node:fs, node:path, and node:stream. It cannot run on Cloudflare Workers or other edge runtimes. Use laikacms/assets/r2 for edge-compatible asset storage.

Usage ​

ts
import { ObsidianAssetsRepository } from 'laikacms/assets/obsidian';

// minimal — vault path only
const assets = new ObsidianAssetsRepository('/path/to/your/obsidian-vault');

// with options
const assets = new ObsidianAssetsRepository('/path/to/your/obsidian-vault', {
  attachmentsDirectory: 'attachments',
  documentExtensions: ['md', 'canvas'],
  ignore: ['.obsidian', '.git'],
  createUrl: key => `https://cdn.example.com/${key}`,
});

Constructor ​

ts
new ObsidianAssetsRepository(vaultPath: string, options?: ObsidianAssetsRepositoryOptions)
ParameterTypeDescription
vaultPathstringAbsolute path to the Obsidian vault.
optionsObsidianAssetsRepositoryOptionsOptional configuration (see below).

Options ​

OptionTypeDefaultDescription
attachmentsDirectorystring'' (vault root)Subdirectory treated as the asset root. Asset keys are resolved relative to it.
documentExtensionsstring[]['md']File extensions (without the leading dot) excluded from listings because they belong to the documents layer.
ignorestring[]['.obsidian', '.trash', '.git', '.DS_Store', 'Thumbs.db']Directory / file basenames skipped while listing.
createUrl(key: string) => stringReturns the key unchangedBuilds a serving URL for an asset key. Supply this to point at a static host or CDN.
createVariations(key: string) => Record<string, AssetVariation>Returns {}Returns named variations (thumbnails, WebP versions, etc.) for an asset key. See Image variations below.

Every non-markdown file in the vault (images, PDFs, audio, …) is exposed as an Asset keyed by its vault-relative path. The implementation is read-oriented — for write-heavy workloads prefer laikacms/assets/r2 or another object-storage backend.

Filtering ​

ObsidianAssetsRepository supports a named filter[search] query parameter when listing resources via the assets-api:

GET /resources?filter[search]=logo

This performs a case-insensitive substring match on the resource key — the example above returns every asset whose key contains "logo" (e.g. attachments/logo.png, images/logo-dark.svg).

Sending an undeclared filter name (anything other than search) returns 400 Bad Request. Inspect GET /capabilities (attributes.filtering.filters) to see the current list of supported filter names at runtime.

Image variations ​

createVariations lets you expose pre-generated responsive variants (thumbnails, WebP copies, size tiers) for each asset. The callback receives the asset key and returns a named map of AssetVariation objects; these are served via GET /resources/:key?include=variations on the assets-api.

ts
import { ObsidianAssetsRepository } from 'laikacms/assets/obsidian';

const assets = new ObsidianAssetsRepository('/path/to/vault', {
  createUrl: key => `https://cdn.example.com/${key}`,
  createVariations: key => ({
    thumb: {
      variant: 'thumb',
      url: `https://cdn.example.com/thumb/${key}`,
      width: 200,
      height: 200,
    },
    webp: {
      variant: 'webp',
      url: `https://cdn.example.com/webp/${key.replace(/\.[^.]+$/, '.webp')}`,
      mimeType: 'image/webp',
    },
  }),
});

Return {} (or omit the option) for assets that have no variations.

Limitations ​

Obsidian vaults store no per-file custom metadata or cache headers, so updateAsset is unsupported:

MethodBehaviour
updateAssetAlways throws BadRequestError. Use createAsset to replace the file instead.

Released under the MIT License.