Invert
← docs

docs

Adapters

How the adapter system works, the built-in adapters, and how to build your own.

Adapters

Adapters are the integration point between Invert and your content sources. Each adapter implements the InvertAdapter interface and is responsible for fetching and transforming content into the normalized InvertContent shape.

The interface

interface InvertAdapter {
  name: string;
  getAll(): Promise<InvertContent[]>;
  getBySlug(slug: string): Promise<InvertContent | null>;
  getByType(contentType: string): Promise<InvertContent[]>;
  isDynamic?: boolean;
}

All adapters must implement getAll, getBySlug, and getByType. The optional isDynamic flag is reserved for future use with live/on-request content.

Registering adapters

Adapters are registered in src/lib/config.ts. Multiple adapters run simultaneously — content merges from all sources. If two adapters return content with the same contentType and slug, the first adapter in the array wins.

export const invertConfig = {
  adapters: [
    new JsonAdapter({ contentDir: join(root, 'content') }),
    new MarkdownAdapter({ source: 'local', contentDir: join(root, 'markdown') }),
  ],
};

Built-in adapters

JsonAdapter

Reads .json files from a local directory. Each file is one content item. Directory structure maps to content types:

content/
  posts/
    my-post.json       → contentType: "posts", slug: "my-post"
  pages/
    about.json         → contentType: "pages", slug: "about"

Each JSON file conforms directly to the InvertContent shape. Fields not present in the file are inferred from the directory structure and filename.

new JsonAdapter({ contentDir: join(root, 'content') })

MarkdownAdapter

Reads .md files and parses YAML frontmatter into content fields. The body is converted from Markdown to HTML via remark.

Local source:

new MarkdownAdapter({ source: 'local', contentDir: join(root, 'markdown') })

GitHub source — fetches markdown files from a GitHub repository via the Contents API:

new MarkdownAdapter({
  source: 'github',
  repo: 'owner/repo',
  contentDir: 'content/posts',
  branch: 'main',
  token: process.env.GITHUB_TOKEN,
})

Frontmatter example:

---
title: My Post
slug: my-post
contentType: posts
date: 2026-04-06
author: Chris
tags: [astro, invert]
---

Body content here.

DocsAdapter

A purpose-built adapter for documentation directories. Forces contentType: "docs" on all content regardless of frontmatter, and falls back to extracting the first <h1> from the body as a title. Included as a worked example of building a custom adapter.

new DocsAdapter({ contentDir: join(root, 'docs') })

See src/adapters/docs.ts for the full annotated implementation.

Building a custom adapter

Copy src/adapters/docs.ts as a starting point. The minimum viable adapter:

import type { InvertAdapter, InvertContent } from './interface.ts';

export class MyAdapter implements InvertAdapter {
  name = 'my-adapter';

  async getAll(): Promise<InvertContent[]> {
    // fetch your content, return InvertContent[]
    return [];
  }

  async getBySlug(slug: string): Promise<InvertContent | null> {
    const all = await this.getAll();
    return all.find((c) => c.slug === slug) ?? null;
  }

  async getByType(contentType: string): Promise<InvertContent[]> {
    const all = await this.getAll();
    return all.filter((c) => c.contentType === contentType);
  }
}

Register it in src/lib/config.ts alongside existing adapters.