Skip to content

Data Sources

Data sources are the bridge between your backend and the library. There are two interfaces: CollectionSource (fetches schema and view metadata) and DocumentSource (CRUD for rows). You pass both to useCollectionManager or initializeCollection, and the library handles the rest.

interface CollectionSource {
fetch(collectionId: string): Promise<CollectionJSON>;
update(ctx: CollectionContext, changes: Partial<CollectionJSON>): Promise<void>;
subscribe?: (
collectionId: string,
applyRemote: (events: CollectionSourceEvent[]) => void,
) => () => void;
}
interface DocumentSource {
fetch(collectionId: string, params: FetchDocumentsParams): Promise<PageResult<DocumentJSON>>;
create(ctx: { collectionId: string; document: Document }): Promise<void>;
update(ctx: DocumentContext, changes: Partial<DocumentJSON>): Promise<void>;
delete(ctx: { collectionId: string; documentId: string }): Promise<void>;
subscribe?: (
collectionId: string,
applyRemote: (events: DocumentSourceEvent[]) => void,
) => () => void;
}

The subscribe method is optional. Implement it to enable real-time updates from other clients.

The simplest option. Data lives in memory — no backend, no network. Good for demos, tests, and prototyping.

import {
InMemoryCollectionSource,
InMemoryDocumentSource,
} from "@blocknote/block-view/core/sources/in-memory";
const collectionSource = new InMemoryCollectionSource({
id: "tasks",
name: "Tasks",
schema: {
title: {
id: "title",
collectionId: "tasks",
label: "Title",
type: "string",
description: undefined,
icon: undefined,
readonly: false,
enum: undefined,
minimum: undefined,
maximum: undefined,
format: undefined,
createdBy: { type: "user", id: "me" },
createdAt: Date.now(),
updatedAt: Date.now(),
updatedBy: { type: "user", id: "me" },
},
done: {
id: "done",
collectionId: "tasks",
label: "Done",
type: "checkbox",
description: undefined,
icon: undefined,
readonly: false,
enum: undefined,
minimum: undefined,
maximum: undefined,
format: undefined,
createdBy: { type: "user", id: "me" },
createdAt: Date.now(),
updatedAt: Date.now(),
updatedBy: { type: "user", id: "me" },
},
},
documents: [],
size: 2,
createdBy: { type: "user", id: "me" },
createdAt: Date.now(),
updatedAt: Date.now(),
updatedBy: { type: "user", id: "me" },
role: "editor",
viewId: "view-1",
views: [
{
id: "view-1",
type: "table",
collectionId: "tasks",
manualSortOrder: ["doc-1", "doc-2"],
filters: undefined,
sorts: undefined,
columns: [
{ propertyId: "title", width: 250, visible: true },
{ propertyId: "done", width: 100, visible: true },
],
createdBy: { type: "user", id: "me" },
createdAt: Date.now(),
updatedAt: Date.now(),
updatedBy: { type: "user", id: "me" },
role: "editor",
},
],
});
const documentSource = new InMemoryDocumentSource([
{
id: "doc-1",
collectionId: "tasks",
props: {
title: { value: "Buy groceries", ref: undefined },
done: { value: "false", ref: undefined },
},
createdBy: { type: "user", id: "me" },
createdAt: Date.now(),
updatedBy: { type: "user", id: "me" },
updatedAt: Date.now(),
role: "editor",
meta: undefined,
},
{
id: "doc-2",
collectionId: "tasks",
props: {
title: { value: "Walk the dog", ref: undefined },
done: { value: "true", ref: undefined },
},
createdBy: { type: "user", id: "me" },
createdAt: Date.now(),
updatedBy: { type: "user", id: "me" },
updatedAt: Date.now(),
role: "editor",
meta: undefined,
},
]);

Then pass them to useCollectionManager:

const state = useCollectionManager({
collectionId: "tasks",
user: "me",
initialize: async () => ({
collectionSource,
documentSource,
}),
});

Connect to a REST API. The library provides RestCollectionSource and RestDocumentSource that map to standard HTTP endpoints.

import { RestCollectionSource, RestDocumentSource } from "@blocknote/block-view/core/sources/rest";
const options = {
baseUrl: "https://api.example.com",
};
const collectionSource = new RestCollectionSource(options);
const documentSource = new RestDocumentSource(options);
OperationMethodDefault URL
Fetch collectionGET{baseUrl}/collections/{collectionId}
Update collectionPATCH{baseUrl}/collections/{collectionId}
Fetch documentsGET{baseUrl}/collections/{collectionId}/documents
Create documentPOST{baseUrl}/collections/{collectionId}/documents
Update documentPATCH{baseUrl}/collections/{collectionId}/documents/{documentId}
Delete documentDELETE{baseUrl}/collections/{collectionId}/documents/{documentId}

Override any route by providing a routes object:

const options = {
baseUrl: "https://api.example.com",
routes: {
collection: (collectionId) => `/projects/${collectionId}/schema`,
documents: (collectionId) => `/projects/${collectionId}/rows`,
document: (collectionId, documentId) => `/projects/${collectionId}/rows/${documentId}`,
},
};

Replace the fetch implementation entirely — useful for adding auth headers or using a different HTTP client:

const options = {
baseUrl: "https://api.example.com",
fetch: async (request) => {
const headers = new Headers(request.headers);
headers.set("Authorization", `Bearer ${getToken()}`);
return fetch(new Request(request, { headers }));
},
};

A decorator that wraps any other source to add cross-tab synchronization via the BroadcastChannel API. When a user has multiple tabs open, changes in one tab instantly appear in the others.

import {
BroadcastCollectionSource,
BroadcastDocumentSource,
} from "@blocknote/block-view/core/sources/broadcast";
import { RestCollectionSource, RestDocumentSource } from "@blocknote/block-view/core/sources/rest";
const restOptions = { baseUrl: "https://api.example.com" };
// Wrap REST sources with broadcast synchronization
const collectionSource = new BroadcastCollectionSource(new RestCollectionSource(restOptions));
const documentSource = new BroadcastDocumentSource(new RestDocumentSource(restOptions));

The broadcast sources are decorators — they delegate all CRUD operations to the wrapped source and add a BroadcastChannel subscription layer on top.

Implement the DocumentSource and CollectionSource interfaces to connect to any backend:

import type {
CollectionSource,
DocumentSource,
FetchDocumentsParams,
PageResult,
DocumentJSON,
CollectionJSON,
} from "@blocknote/block-view/core/sources";
class MyCollectionSource implements CollectionSource {
async fetch(collectionId: string): Promise<CollectionJSON> {
const response = await myApi.getCollection(collectionId);
return response;
}
async update(ctx, changes): Promise<void> {
await myApi.updateCollection(ctx.collectionId, changes);
}
// Optional: enable real-time updates
subscribe(collectionId, applyRemote) {
const ws = new WebSocket(`wss://api.example.com/collections/${collectionId}`);
ws.onmessage = (event) => applyRemote(JSON.parse(event.data));
return () => ws.close();
}
}
class MyDocumentSource implements DocumentSource {
async fetch(
collectionId: string,
params: FetchDocumentsParams,
): Promise<PageResult<DocumentJSON>> {
const response = await myApi.getDocuments(collectionId, params);
return {
data: response.documents,
totalCount: response.total,
next: response.nextCursor ? { cursor: response.nextCursor } : undefined,
};
}
async create(ctx): Promise<void> {
await myApi.createDocument(ctx.collectionId, ctx.document.toJSON());
}
async update(ctx, changes): Promise<void> {
await myApi.updateDocument(ctx.collectionId, ctx.documentId, changes);
}
async delete(ctx): Promise<void> {
await myApi.deleteDocument(ctx.collectionId, ctx.documentId);
}
}

The only requirement is that fetch returns data matching the CollectionJSON and DocumentJSON shapes. See the Core API Reference for the full type definitions.