--- title: 'Memory Graph' sidebarTitle: "Memory Graph" description: 'Interactive visualization for documents, memories and connections' icon: "network" --- Memory Graph is a React component that visualizes your Supermemory documents and memories as an interactive network. Documents appear as rectangular nodes, memories as hexagonal nodes, and connections between them show relationships and similarity. Check out the NPM page for more details ## Installation ```bash npm install @supermemory/memory-graph ``` **Requirements:** React 18.0.0 or higher ## Quick Start ```tsx 'use client'; // For Next.js App Router import { MemoryGraph } from '@supermemory/memory-graph'; import type { GraphApiDocument } from '@supermemory/memory-graph'; import { useEffect, useState } from 'react'; export default function GraphPage() { const [documents, setDocuments] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { fetch('/api/graph') .then(res => res.json()) .then(data => { setDocuments(data.documents); setIsLoading(false); }) .catch(err => { setError(err); setIsLoading(false); }); }, []); return ( ); } ``` `documents` must be `GraphApiDocument[]`, the shape the API route below returns, with `documentType` and `memories` fields (not the raw API's `type` and `memoryEntries`). ## Backend API Route Create an API route to fetch documents from Supermemory. The `/v3/documents/documents` response uses `type` and `memoryEntries`; `` expects `documentType` and `memories`, so the route normalizes the shape before returning it: ```typescript Next.js App Router // app/api/graph/route.ts import { NextResponse } from 'next/server'; import type { GraphApiDocument, GraphApiMemory } from '@supermemory/memory-graph'; interface RawMemoryEntry { id: string; memory?: string | null; content?: string | null; spaceId?: string | null; isStatic?: boolean; isLatest?: boolean; isForgotten?: boolean; forgetAfter?: string | null; forgetReason?: string | null; version?: number; parentMemoryId?: string | null; rootMemoryId?: string | null; createdAt: string; updatedAt: string; relation?: 'updates' | 'extends' | 'derives' | null; updatesMemoryId?: string | null; nextVersionId?: string | null; memoryRelations?: Record | null; spaceContainerTag?: string | null; } interface RawDocument { id: string; title: string | null; summary?: string | null; type: string; createdAt: string; updatedAt: string; memoryEntries: RawMemoryEntry[]; } interface RawDocumentsResponse { documents: RawDocument[]; pagination: { currentPage: number; limit: number; totalItems: number; totalPages: number; }; } // Maps the raw API document (`type` / `memoryEntries`) onto the shape // expects (`documentType` / `memories`), filling in the // defaults for fields the API may omit. function toGraphDocument(doc: RawDocument): GraphApiDocument { return { id: doc.id, title: doc.title, summary: doc.summary ?? null, documentType: doc.type, createdAt: doc.createdAt, updatedAt: doc.updatedAt, memories: doc.memoryEntries.map((mem): GraphApiMemory => ({ id: mem.id, memory: mem.memory ?? mem.content ?? '', content: mem.content ?? null, isStatic: mem.isStatic ?? false, spaceId: mem.spaceId ?? '', isLatest: mem.isLatest ?? true, isForgotten: mem.isForgotten ?? false, forgetAfter: mem.forgetAfter ?? null, forgetReason: mem.forgetReason ?? null, version: mem.version ?? 1, parentMemoryId: mem.parentMemoryId ?? null, rootMemoryId: mem.rootMemoryId ?? null, createdAt: mem.createdAt, updatedAt: mem.updatedAt, relation: mem.relation ?? null, updatesMemoryId: mem.updatesMemoryId ?? null, nextVersionId: mem.nextVersionId ?? null, memoryRelations: mem.memoryRelations ?? null, spaceContainerTag: mem.spaceContainerTag ?? null, })), }; } export async function GET() { const response = await fetch('https://api.supermemory.ai/v3/documents/documents', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`, }, body: JSON.stringify({ page: 1, limit: 500, sort: 'createdAt', order: 'desc', }), }); const data: RawDocumentsResponse = await response.json(); return NextResponse.json({ documents: data.documents.map(toGraphDocument), pagination: data.pagination, }); } ``` ```typescript Next.js Pages Router // pages/api/graph.ts import type { NextApiRequest, NextApiResponse } from 'next'; import type { GraphApiDocument, GraphApiMemory } from '@supermemory/memory-graph'; interface RawMemoryEntry { id: string; memory?: string | null; content?: string | null; spaceId?: string | null; isStatic?: boolean; isLatest?: boolean; isForgotten?: boolean; forgetAfter?: string | null; forgetReason?: string | null; version?: number; parentMemoryId?: string | null; rootMemoryId?: string | null; createdAt: string; updatedAt: string; relation?: 'updates' | 'extends' | 'derives' | null; updatesMemoryId?: string | null; nextVersionId?: string | null; memoryRelations?: Record | null; spaceContainerTag?: string | null; } interface RawDocument { id: string; title: string | null; summary?: string | null; type: string; createdAt: string; updatedAt: string; memoryEntries: RawMemoryEntry[]; } interface RawDocumentsResponse { documents: RawDocument[]; pagination: { currentPage: number; limit: number; totalItems: number; totalPages: number; }; } // Maps the raw API document (`type` / `memoryEntries`) onto the shape // expects (`documentType` / `memories`), filling in the // defaults for fields the API may omit. function toGraphDocument(doc: RawDocument): GraphApiDocument { return { id: doc.id, title: doc.title, summary: doc.summary ?? null, documentType: doc.type, createdAt: doc.createdAt, updatedAt: doc.updatedAt, memories: doc.memoryEntries.map((mem): GraphApiMemory => ({ id: mem.id, memory: mem.memory ?? mem.content ?? '', content: mem.content ?? null, isStatic: mem.isStatic ?? false, spaceId: mem.spaceId ?? '', isLatest: mem.isLatest ?? true, isForgotten: mem.isForgotten ?? false, forgetAfter: mem.forgetAfter ?? null, forgetReason: mem.forgetReason ?? null, version: mem.version ?? 1, parentMemoryId: mem.parentMemoryId ?? null, rootMemoryId: mem.rootMemoryId ?? null, createdAt: mem.createdAt, updatedAt: mem.updatedAt, relation: mem.relation ?? null, updatesMemoryId: mem.updatesMemoryId ?? null, nextVersionId: mem.nextVersionId ?? null, memoryRelations: mem.memoryRelations ?? null, spaceContainerTag: mem.spaceContainerTag ?? null, })), }; } export default async function handler(req: NextApiRequest, res: NextApiResponse) { const response = await fetch('https://api.supermemory.ai/v3/documents/documents', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`, }, body: JSON.stringify({ page: 1, limit: 500, sort: 'createdAt', order: 'desc' }), }); const data: RawDocumentsResponse = await response.json(); res.json({ documents: data.documents.map(toGraphDocument), pagination: data.pagination, }); } ``` ```javascript Express app.get('/api/graph', async (req, res) => { const response = await fetch('https://api.supermemory.ai/v3/documents/documents', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`, }, body: JSON.stringify({ page: 1, limit: 500, sort: 'createdAt', order: 'desc' }), }); const data = await response.json(); // Map the raw API document (`type` / `memoryEntries`) onto the shape // expects (`documentType` / `memories`), filling in the // defaults for fields the API may omit. const documents = data.documents.map((doc) => ({ id: doc.id, title: doc.title, summary: doc.summary ?? null, documentType: doc.type, createdAt: doc.createdAt, updatedAt: doc.updatedAt, memories: doc.memoryEntries.map((mem) => ({ id: mem.id, memory: mem.memory ?? mem.content ?? '', content: mem.content ?? null, isStatic: mem.isStatic ?? false, spaceId: mem.spaceId ?? '', isLatest: mem.isLatest ?? true, isForgotten: mem.isForgotten ?? false, forgetAfter: mem.forgetAfter ?? null, forgetReason: mem.forgetReason ?? null, version: mem.version ?? 1, parentMemoryId: mem.parentMemoryId ?? null, rootMemoryId: mem.rootMemoryId ?? null, createdAt: mem.createdAt, updatedAt: mem.updatedAt, relation: mem.relation ?? null, updatesMemoryId: mem.updatesMemoryId ?? null, nextVersionId: mem.nextVersionId ?? null, memoryRelations: mem.memoryRelations ?? null, spaceContainerTag: mem.spaceContainerTag ?? null, })), })); res.json({ documents, pagination: data.pagination }); }); ``` Never expose your Supermemory API key to the client. Always fetch data through your backend. --- ## Variants **Console Variant** - Full-featured dashboard view (0.8x initial zoom): ```tsx ``` **Consumer Variant** - Embedded widget view (0.5x initial zoom): ```tsx ``` --- ## Examples ### With Pagination ```tsx 'use client'; import { MemoryGraph } from '@supermemory/memory-graph'; import type { GraphApiDocument } from '@supermemory/memory-graph'; import { useCallback, useEffect, useState } from 'react'; export default function PaginatedGraph() { const [documents, setDocuments] = useState([]); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [isLoading, setIsLoading] = useState(true); const [isLoadingMore, setIsLoadingMore] = useState(false); useEffect(() => { fetchPage(1, false); }, []); const fetchPage = async (pageNum, append) => { pageNum === 1 ? setIsLoading(true) : setIsLoadingMore(true); const res = await fetch(`/api/graph?page=${pageNum}&limit=100`); const data = await res.json(); append ? setDocuments(prev => [...prev, ...data.documents]) : setDocuments(data.documents); setHasMore(data.pagination.currentPage < data.pagination.totalPages); setIsLoading(false); setIsLoadingMore(false); }; const loadMore = useCallback(async () => { if (!isLoadingMore && hasMore) { const nextPage = page + 1; setPage(nextPage); await fetchPage(nextPage, true); } }, [page, hasMore, isLoadingMore]); return ( ); } ``` ### Highlighting Search Results ```tsx 0} /> ``` ### Custom Empty State ```tsx No memories yet Add content to see your knowledge graph ``` --- ## Props Reference ### Core Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `documents` | `GraphApiDocument[]` | `[]` | Documents to display - pass this for direct data mode | | `isLoading` | `boolean` | `false` | Whether data is loading | | `error` | `Error \| null` | `null` | Error from data fetching | | `variant` | `"console" \| "consumer"` | `"console"` | Visual variant | | `children` | `ReactNode` | - | Rendered when there are no documents | ### Pagination Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `isLoadingMore` | `boolean` | `false` | Whether more data is being loaded | | `hasMore` | `boolean` | `false` | Whether there are more documents to load | | `onLoadMore` | `() => void` | - | Callback to load more documents | | `totalCount` | `number` | - | Total count, used by the loading indicator | ### Display Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `highlightDocumentIds` | `string[]` | - | Document IDs to highlight | | `highlightsVisible` | `boolean` | - | Whether highlights are visible | | `legendId` | `string` | - | Optional legend element id | | `showFps` | `boolean` | - | Show FPS counter overlay | | `canvasRef` | `RefObject` | - | External ref to the canvas (e.g. for screenshot export) | | `colors` | `Partial` | - | Custom theme colors, merged with CSS-variable/default values | | `labels` | `MemoryGraphLabels` | - | Custom user-facing labels, merged with defaults | | `layering` | `MemoryGraphLayering` | - | Overlay layering controls (e.g. hover popover z-index) | ### Filtering Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `containerTags` | `string[]` | - | Container tags for filtering (used by apps with their own API hooks) | | `documentIds` | `string[]` | - | Specific document IDs to show | | `maxNodes` | `number` | - | Max nodes to display | ### Slideshow Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `isSlideshowActive` | `boolean` | - | Whether slideshow mode is active | | `onSlideshowNodeChange` | `(nodeId: string \| null) => void` | - | Called as the slideshow moves between nodes | | `onSlideshowStop` | `() => void` | - | Called when the slideshow ends | ### Other | Prop | Type | Default | Description | |------|------|---------|-------------| | `onOpenDocument` | `(documentId: string) => void` | - | Called when the user wants to view a document's full content | --- ## Data Types ### GraphApiDocument / GraphApiMemory This is the shape `documents` actually takes: `documentType` and `memories`, matching what the API route in [Backend API Route](#backend-api-route) normalizes into: ```typescript interface GraphApiDocument { id: string; title: string | null; summary: string | null; documentType: string; createdAt: string; updatedAt: string; memories: GraphApiMemory[]; } interface GraphApiMemory { id: string; memory: string; content?: string | null; isStatic: boolean; spaceId: string; isLatest: boolean; isForgotten: boolean; forgetAfter: string | null; forgetReason: string | null; version: number; parentMemoryId: string | null; rootMemoryId: string | null; createdAt: string; updatedAt: string; relation?: 'updates' | 'extends' | 'derives' | null; updatesMemoryId?: string | null; nextVersionId?: string | null; memoryRelations?: Record | null; spaceContainerTag?: string | null; } ``` ### DocumentWithMemories / MemoryEntry Also exported for backward compatibility with older API response shapes. Not what `documents` takes today, but still useful for typing a raw fetch response before mapping it to `GraphApiDocument`: ```typescript interface DocumentWithMemories { id: string; title: string | null; url: string | null; documentType: string; createdAt: string; updatedAt: string; summary?: string | null; memories: MemoryEntry[]; } interface MemoryEntry { id: string; memory: string; content?: string | null; createdAt: string; updatedAt: string; spaceId?: string | null; isStatic?: boolean; isForgotten?: boolean; forgetAfter?: string | null; forgetReason?: string | null; version?: number; parentMemoryId?: string | null; rootMemoryId?: string | null; isLatest?: boolean; relation?: 'updates' | 'extends' | 'derives' | null; spaceContainerTag?: string | null; } ``` --- ## Exports ### Components ```typescript import { MemoryGraph, GraphCanvas } from '@supermemory/memory-graph'; ``` ### Hooks ```typescript import { useGraphData, useGraphTheme } from '@supermemory/memory-graph'; ``` ### Engine Classes For advanced usage: driving the force simulation, viewport, or hit-testing directly: ```typescript import { ForceSimulation, ViewportState, SpatialIndex, VersionChainIndex, } from '@supermemory/memory-graph'; ``` ### Constants ```typescript import { DEFAULT_COLORS, DEFAULT_HOVER_POPOVER_Z_INDEX, DEFAULT_LABELS, FORCE_CONFIG, GRAPH_SETTINGS, } from '@supermemory/memory-graph'; ``` ### Mock Data `@supermemory/memory-graph/mock-data` generates fake `GraphApiDocument[]` data for local development and stress testing, without a live API key: ```typescript import { generateMockGraphData } from '@supermemory/memory-graph/mock-data'; const { documents } = generateMockGraphData({ documentCount: 100, memoriesPerDoc: [2, 5], seed: 12345, }); ``` --- ## Performance The graph handles hundreds of nodes efficiently through: - Canvas-based rendering (not DOM elements) - Viewport culling (only draws visible nodes) - Level-of-detail optimization (simplifies when zoomed out) - Change-based rendering (only redraws when state changes) For very large datasets (1000+ documents), use pagination to load data in chunks. ## Browser Support Works in all modern browsers supporting Canvas 2D API, ES2020, and CSS custom properties. Tested on Chrome, Firefox, Safari, and Edge.
Add content to see your knowledge graph