Extenote
← All Docs

Configuration Reference

Extenote uses two directories for configuration:

Quick Navigation:


Project Configuration

Each project has a YAML file in projects/ defining its sources, visibility rules, lint settings, build, and deploy configuration.

Example files:

Key Fields

FieldDescription
projectProject identifier
defaultVisibilityDefault for new content: public | private | unlisted
visibilityFieldFrontmatter field name for visibility (default: visibility)
includesList of other project names to include objects from
sourcesContent source definitions (see below)
lintLinting rules and autofix settings
compatibilityPlatform-specific requirements (astro, quarto)
buildWebsite build configuration
deployDeployment platform configuration
discussionDiscussion plugin configuration
sembleSemble ATProto sync configuration
recipesNamed export configurations (see Export Configuration)

Environment Variables

Source paths support environment variable substitution with fallback syntax: ${VAR:-fallback}

VariablePurposeDefault
EXTENOTE_CONTENT_ROOTPrimary content vault../extenote-pub/content
EXTENOTE_PRIVATE_ROOTPrivate content vault../extenote-priv/content

Web Server Environment Variables

These variables control the web server behavior:

VariablePurposeDefault
EXTENOTE_PUBLIC_ONLYFilter out private content (see Security: Public-Only Mode)false
EXTENOTE_CACHE_TTLCache duration in milliseconds30000
EXTENOTE_CACHE_ENABLEDEnable/disable vault cachingtrue

Source Types

Currently only local sources are supported. See packages/core/src/types.ts for the LocalSourceConfig interface.

FieldRequiredDescription
idYesUnique identifier
typeYesSource type (local)
rootYesDirectory path (supports env vars)
includeNoGlob patterns to include (e.g., ["**/*.md"])
excludeNoGlob patterns to exclude
visibilityNoDefault visibility for this source
disabledNoSkip this source if true

Path handling: When creating new files, the CLI automatically detects if the source root already includes the project name (e.g., content/shared-references) and avoids adding a duplicate project prefix. If the schema’s subdirectory is ., files go directly in the source root.

Lint Rules

RuleDescriptionValues
required-visibilityCheck for visibility fieldoff, warn, error
compatibility:{target}Check platform requirementsoff, warn, error

Compatibility Targets

TargetDescription
astroAstro static site generator
quartoQuarto publishing system

Schema Configuration

Schema files in schemas/ define content types and their frontmatter fields.

Example files:

Schema Fields

FieldDescription
nameUnique schema identifier
descriptionHuman-readable description
subdirectoryDefault storage subdirectory for new objects (use . for root)
identityFieldField used as object ID (default: slug)
requiredList of required frontmatter fields
fieldsField definitions (see below)

Field Types

TypeDescriptionExample
stringText value"Hello"
numberNumeric value42
booleanTrue/falsetrue
dateISO 8601 date2024-12-17
arrayList with item type["a", "b"]

For arrays, specify the item type with items: string | number | date | boolean.

Schema Validation and Issue Tracking

Schema validation is performed automatically when the vault loads. Any validation issues are tracked and surfaced through:

Common validation issues include:

Each issue shows the file path, field name, and specific error message. Fix issues by editing the frontmatter directly or using the web UI’s object editor.

Project Ownership and Includes

Each object belongs to exactly one project, determined by its source. Projects can include objects from other projects using the includes field.

How objects get assigned to projects:

  1. Each source in a project config has an id
  2. When vault loads, objects from that source are assigned to the project
  3. The project name is inferred from the config file (e.g., projects/my-blog.yaml → project my-blog)

Example relationship:

projects/personal-website.yaml
├── sources:
│   └── id: "personal-website"         # Objects from here belong to "personal-website"
└── includes: ["shared-references"]    # Can also see objects from "shared-references"

projects/shared-references.yaml
└── sources:
    └── id: "shared-references"        # Objects from here belong to "shared-references"

When project A includes project B:

Common pattern: A “shared-references” project contains bibliography entries that multiple blog/website projects include. Each project builds with its own content plus the shared references.

See projects/data-leverage-blogs.yaml for a real example that includes shared-references and discussions.

Export Configuration

Export generates static files from your vault objects. This is useful for:

Export vs Build vs Sync

These three operations serve different purposes:

OperationWhat it doesOutput
ExportTransforms vault objects into static filesdist/export/ files (JSON, markdown, HTML, etc.)
BuildCompiles a website from source filesdist/ website ready for hosting
SyncPushes/pulls objects to/from ATProto (Semble)Remote records on ATProto PDS

Typical workflow:

  1. Export content to JSON/markdown files
  2. Build website that consumes those exported files
  3. Deploy the built website to hosting

Export and sync are independent—you can use either, both, or neither.

Supported Formats

FormatOutput FileDescription
jsonobjects.jsonAll objects as structured JSON with frontmatter and body
markdownMirrored file treePreserves original directory structure and filenames
htmlindex.htmlSingle HTML page listing all objects
bibtexreferences.bibBibliography file (only exports bibtex_entry type objects)
atprotorecords.jsonATProto-compatible records for federation

CLI Usage

# Basic export
bun run cli -- export-project <project> --format <format>

# With output directory
bun run cli -- export-project <project> --format json --output dist/data

# Filter by object type
bun run cli -- export-project <project> --format json --type blog_post

# Filter by source
bun run cli -- export-project <project> --format markdown --source local-notes

# BibTeX: only export cited references
bun run cli -- export-project <project> --format bibtex --detect-citations

Export Recipes (YAML)

Define named export configurations in your project file:

recipes:
  - name: public-json
    description: JSON bundle for website consumption
    sourceIds:
      - local-notes
    steps:
      - format: json
        outputDir: dist/export/recipes/public/json

  - name: bibliography
    steps:
      - format: bibtex
        outputDir: dist/export/refs
FieldDescription
nameRecipe identifier
descriptionHuman-readable description
sourceIdsLimit to specific sources (optional)
stepsList of export steps
steps[].formatExport format
steps[].outputDirOutput directory

Export in Build Pipeline

Use preRender CLI steps to run exports before building:

build:
  websiteDir: my-blog
  type: astro
  preRender:
    - type: cli
      command: export-project my-project --format json
      outputDir: src/data

This runs the export, placing objects.json in src/data/ before Astro builds. Your site can then import this data.

Default Output Directory

Without --output, exports go to: dist/export/{project}/{format}/

Build Configuration

Each project can define how its website is built. See packages/core/src/types.ts for BuildConfig interface.

Example: projects/data-leverage-blogs.yaml shows a Quarto build with rsync and CLI preRender steps.

FieldDescription
websiteDirDirectory in websites/ folder
typeBuild type: astro | quarto | custom
preRenderOptional steps before main build

Build Types

TypeBuild Command
astronpm install (if needed) + npm run build
quartoquarto render
customOnly runs preRender steps

PreRender Step Types

TypeDescriptionRequired Fields
rsyncSync files from source to destinationsrc, dst, include (optional)
cliRun extenote CLI commandcommand, outputDir (optional)
copyCopy a single filesrc, dst
shellRun arbitrary shell commandcommand
networkGenerate discussions page & related projectsoutputFormat (optional)

Network Step

The network step generates a discussions/network page for your static site, connecting it to discussions and related projects in the extenote ecosystem.

FieldDefaultDescription
outputFormatquartoOutput format: quarto, astro, or both
addToNavbartrueFor Quarto: add discussions page to navbar
includeProjectLinkstrueFor Quarto: include the “Project Links” section
relatedProjects[]Additional project names to include
excludeProjects[]Projects to exclude from auto-discovery

Auto-discovery: Related projects are automatically discovered from your includes list.

Example:

build:
  websiteDir: my-blog-quarto
  type: quarto
  preRender:
    - type: rsync
      src: ${EXTENOTE_CONTENT_ROOT}/my-blog
      dst: posts
    - type: network
      outputFormat: quarto
      addToNavbar: true
      relatedProjects: [another-project]

Generated output:

Deploy Configuration

Each project can define how it’s deployed. See packages/core/src/types.ts for DeployConfig interface.

Example: projects/data-leverage-blogs.yaml shows Cloudflare Pages deployment.

FieldDescription
platformDeploy platform (see table below)
configFilePlatform config file (e.g., wrangler.toml)
outputDirBuild output directory
repoGitHub Pages: target repository URL
branchGitHub Pages: target branch (default: gh-pages)

Deploy Platforms

PlatformConfig FileNotes
cloudflare-pageswrangler.tomlUses wrangler pages deploy
github-pages-Uses gh-pages package, requires repo
none-No deployment configured

GitHub Pages Configuration

For GitHub Pages, specify the target repository with repo and optionally branch (defaults to gh-pages).

Note: The website directory needs a package.json file (even a minimal one) for gh-pages to work.

Configuration Defaults

SettingDefault
defaultVisibility"private"
visibilityField"visibility"
identityField"slug"
includes[] (no included projects)
lint.rules.required-visibility"warn"
lint.autofixfalse
Export outputdist/export/{project}/{format}
build.type(required if build configured)
deploy.platform"none"
deploy.branch"gh-pages"

Semble Sync Configuration

Sync references bidirectionally with Semble, an ATProto-based social knowledge network for researchers. Semble is built on the AT Protocol (the same protocol behind Bluesky). ATProto sync is still very WIP and may change.

FieldRequiredDescription
enabledYesEnable sync for this project
identifierYesATProto handle or DID (e.g., user.bsky.social)
passwordNoApp password (or use SEMBLE_APP_PASSWORD env var)
pdsNoPersonal Data Server URL (default: https://bsky.social)
collectionNoSemble collection name (default: project name, null to disable)
typesNoObject types to sync (default: ["bibtex_entry"])
publicOnlyNoOnly sync objects with visibility: public

Example:

semble:
  enabled: true
  identifier: yourhandle.bsky.social
  collection: my-research-refs  # or null to disable collections
  types: ["bibtex_entry", "data_license_initiative"]
  publicOnly: false

Collections

By default, synced cards are added to a Semble collection named after your project. This groups your references together in the Semble UI.

Collections are created automatically if they don’t exist. The collection URI is cached in .semble-sync.json for efficiency.

CLI Commands

# List projects with Semble config
bun run cli -- sync --list

# List your Semble collections
bun run cli -- sync --list-collections

# Validate configuration
bun run cli -- sync --validate

# Dry run (see what would sync)
bun run cli -- sync <project> --dry-run

# Sync a project (bidirectional)
bun run cli -- sync <project>

# Push only (local → Semble)
bun run cli -- sync <project> --push-only

# Pull only (Semble → local)
bun run cli -- sync <project> --pull-only

# Force re-sync all objects
bun run cli -- sync <project> --force

Environment Variables

VariableDescription
SEMBLE_APP_PASSWORDApp password for ATProto authentication
ATPROTO_APP_PASSWORDAlternative env var name (fallback)

Security: Public-Only Mode

When generating screenshots, demos, or any public-facing content from the web UI, private content can accidentally leak if you have both public and private vaults loaded.

The Problem

The web server loads all configured content (public + private). Screenshots or screen recordings will capture:

The Solution: EXTENOTE_PUBLIC_ONLY

Set EXTENOTE_PUBLIC_ONLY=true when running the web server for screenshots or demos:

EXTENOTE_PUBLIC_ONLY=true bun run web

This filters out at the API level:

Screenshot Generation

The extenote-docs-astro screenshot generator already uses this mode. If you’re creating your own screenshots:

// In your screenshot script
const server = spawn('bun', ['run', 'web'], {
  env: {
    ...process.env,
    EXTENOTE_PUBLIC_ONLY: 'true',
  },
})

Verifying Clean Screenshots

After generating screenshots, verify they’re clean:

  1. Check dashboard shows no “private” visibility count
  2. Check no “private-content” or similar projects appear
  3. Check issues page has no private file paths
  4. Check search results show only public content

If Private Data Leaks

If screenshots with private data were committed/deployed:

  1. Wipe git history - Use an orphan branch to remove leaked files from history
  2. Force push - Replace the branch with clean content
  3. Invalidate caches - GitHub’s camo CDN may cache old images; wait for expiry or change URLs