
Company News
Jerod Santo Joins Socket as Head of Media
Allow myself to introduce... myself.
@stll/folio-core
Advanced tools
Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.
The headless, framework-neutral core of folio:
the OOXML (.docx) parser, the document model, the ProseMirror integration, and
the page-layout engine. It opens a real .docx, models it, paginates it, and
writes a real .docx back — preserving tables, headers and footers, tracked
changes, and footnotes.
There is no React in the import graph, so the core runs on a server or under
any framework. The React editor lives in
@stll/folio-react, and the
Vue editor lives in @stll/folio-vue.
Part of stella, an open-source legal workspace.
bun add @stll/folio-core
| Import | What it is |
|---|---|
@stll/folio-core | the headless public API — document creation, representation-neutral comparison, the document model, AI-suggestion primitives, and ProseMirror plugins |
@stll/folio-core/markdown | DOCX ↔ Markdown conversion |
@stll/folio-core/server | DOM-free document review, explicit tracked edits, comparison, creation, and package helpers |
@stll/folio-core/redline | Compare two .docx buffers and generate a native Word redline |
@stll/folio-core/* | the source-mirrored module tree (e.g. @stll/folio-core/types/document, @stll/folio-core/prosemirror/schema) for adapters that need lower-level building blocks |
Create a legal-shaped blank document with the built-in preset:
import { createDocx, createEmptyDocument, createStellaStyleDocumentPreset } from "@stll/folio-core";
const document = createEmptyDocument({
preset: createStellaStyleDocumentPreset(),
});
const docx = await createDocx(document);
Style sets are content-free JSON values. Inspect a source file before presenting styles for selection, extract the selected dependency closure, persist the result, and load it into any later document:
import {
createEmptyDocument,
extractDocumentStyleSetFromDocx,
inspectDocumentStylesFromDocx,
} from "@stll/folio-core";
const catalog = await inspectDocumentStylesFromDocx(sourceDocx);
const styleSet = await extractDocumentStyleSetFromDocx(sourceDocx, {
name: "Firm contract styles",
styleIds: catalog.styles
.filter(({ role }) => role === "default" || role === "quick")
.map(({ styleId }) => styleId),
});
const document = createEmptyDocument({ styleSet });
Extraction excludes document content, metadata, relationships, media, comments, and revision data. It keeps only the selected styles and the numbering, theme, font-table, and settings data required to reproduce their formatting.
compareContent compares ordered blocks from any document model without first
serializing them to another format. Map durable source anchors to stable block
IDs and include structural ancestry when blocks live in containers:
import { compareContent, type FolioContentBlock } from "@stll/folio-core";
type SourceBlock = {
anchorId: string;
type: "clause" | "heading";
text: string;
sectionId: string;
};
type ComparableSourceBlock = FolioContentBlock<SourceBlock["type"]> & {
source: SourceBlock;
};
const toFolioBlocks = (blocks: readonly SourceBlock[]) =>
blocks.map(
(source) =>
({
id: source.anchorId,
idStability: "stable",
kind: source.type,
text: source.text,
containerPath: [{ kind: "section", id: source.sectionId }],
source,
}) satisfies ComparableSourceBlock,
);
const result = compareContent({
base: { blocks: toFolioBlocks(baseRevision) },
revised: { blocks: toFolioBlocks(revisedRevision) },
});
if (result.isErr()) throw result.error;
for (const event of result.value.events) {
renderComparisonEvent(event);
const revisedSource = event.revisedBlocks.at(0)?.source;
if (revisedSource) persistSourceAnchor(revisedSource.anchorId);
}
const rejectedBlocks = result.value.events.flatMap(({ baseBlocks }) => baseBlocks);
const acceptedBlocks = result.value.events.flatMap(({ revisedBlocks }) => revisedBlocks);
Events retain the caller's complete block subtype, including custom metadata,
and are already in full-document render order. Modified and edited-move segments
use UTF-16 offsets compatible with JavaScript string slicing; move halves share
a moveId, and table row or column events reference their grouped entry in
structuralChanges. FOLIO_CONTENT_COMPARISON_LIMITS publishes the block,
text, attribute, container, run, and result ceilings. Input ceilings are
checked before alignment; the result ceiling returns the same typed
FolioContentComparisonLimitError while constructing the ordered stream.
Generate a reviewable .docx whose text and supported inline-formatting
differences are native tracked changes:
import { generateRedlineDocx } from "@stll/folio-core/redline";
const result = await generateRedlineDocx(originalDocx, revisedDocx, {
author: "Reviewer",
});
await store(result.buffer);
console.log(result.applied, result.skipped, result.unprocessedStories);
For deterministic operations against one document, use FolioDocxReviewer
from @stll/folio-core/server. Its operations default to tracked changes and
can be enumerated, accepted, or rejected before saving. getChanges() includes
inline edits, formatting, paragraph marks, and paragraph, section, table, row,
and cell property changes.
folio began as a private fork of Eigenpal's
docx-editor; the original license and
copyright are preserved in NOTICE.md.
FAQs
Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.
The npm package @stll/folio-core receives a total of 5,427 weekly downloads. As such, @stll/folio-core popularity was classified as popular.
We found that @stll/folio-core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Company News
Allow myself to introduce... myself.

Research
/Security News
A Twitch browser extension on Chrome and Firefox forwards users’ live OAuth session tokens through proxies controlled by a Russian bot service.

Security News
Anthropic found biased reasoning and recklessness drove Claude Mythos 5 to publish malware on PyPI and compromise a security vendor.