GitHub Source

Load Markdown straight from a GitHub repository.

The github source fetches content from a GitHub repository through the GitHub API. It's the recommended way to ship applications whose source-of-truth lives in another repo.

github(options)

Creates a source that reads from a GitHub repository.

content.ts
import { comarkContent } from 'comark-content'
import github from 'comark-content/sources/github'

const content = comarkContent({
  source: github({
    repo: 'comarkdown/comark',
    branch: 'main',
    path: 'docs/content',
  }),
})

Parameters:

Returns: Source. Pass it to comarkContent({ source }), or into a named sources map.

Options

GithubSource extends unstorage's github driver options and adds the source-level fields:

OptionTypeDefaultDescription
repostring-owner/name slug of the repository. Required.
branchstring'main'Branch, tag, or commit SHA to read from.
pathstring''File or directory inside the repo to use as the content root.
tokenstringundefinedGitHub token. Required for private repos; recommended for public ones to lift rate limits.
ttlnumber600Cache duration for the underlying driver, in seconds.
prefixstringundefinedPrepended to each entry's path (e.g. /blog).
excludestring[]undefinedPicomatch globs of keys to drop before parsing.
schemaJsonSchemaundefinedExplicit document data schema for types and query columns.
Any other option is forwarded to unstorage/drivers/github: apiURL, cdnURL, and so on.

repo

The owner/name slug of the repository to read from. This is the only required option.

branch

The branch, tag, or commit SHA to read from. Defaults to main. Pin it to an immutable commit SHA when you need every instance to render the exact same snapshot.

path

The file or directory inside the repo to use as the content root. The Content instance only sees what's underneath it:

scoped.ts
github({
  repo: 'nuxt/nuxt',
  branch: 'main',
  path: 'docs/1.getting-started',
})

await content.list()  // only entries under docs/1.getting-started/**

token

A GitHub token. Public repos work without one but share a 60-request/hour rate limit per IP. For anything serious, pass a personal access token (fine-grained Contents: read is enough):

private-repo.ts
const content = comarkContent({
  source: github({
    repo: 'org/private-docs',
    branch: 'main',
    path: 'content',
    token: process.env.GITHUB_TOKEN,
  }),
})

ttl

How long the underlying driver caches responses, in seconds. Defaults to 600.

prefix

Prepended to every entry's public path. Combine it with path to remap the public path space:

mounted.ts
github({
  repo: 'org/handbook',
  branch: 'main',
  path: 'content/policies',
  prefix: '/policies',
})

await content.get('/policies/security')  // → content/policies/security.md

The prefix does not affect the key (which stays <source>/<stem> inside the manifest), only the public path returned by content.get() and content.list().

exclude

Picomatch globs matched against each entry's key. Excluded files are dropped before parsing, so they never enter the manifest:

drafts.ts
github({
  repo: 'org/docs',
  branch: 'main',
  path: 'content',
  exclude: ['drafts/**', '**/*.draft.md'],
})

schema

An explicit JSON Schema for the source's data. When set, the Content instance reports Markdown, JSON, and YAML frontmatter validation issues during source load and on direct updates, and uses the schema to generate types and query columns.

Refresh

GitHub sources don't watch for changes. The driver caches responses for ttl seconds, so new commits surface once that window expires and the Content instance is re-initialized. To pick them up immediately, you must build a fresh Content instance.

reload.ts
import { comarkContent } from 'comark-content'
import github from 'comark-content/sources/github'

export function buildContent(ref: string) {
  return comarkContent({
    source: github({ repo: 'org/docs', branch: ref, path: 'content' }),
  })
}