---
title: "Programmatic document editing in Document Authoring"
canonical_url: "https://www.nutrient.io/guides/document-authoring/working-with-documents/programmatic-editing/"
md_url: "https://www.nutrient.io/guides/document-authoring/working-with-documents/programmatic-editing.md"
last_updated: "2026-05-30T02:20:01.181Z"
description: "Learn how to use document transactions in Document Authoring to format lists, manage comment threads, and run review-mode edits programmatically."
---

# Edit documents programmatically

Before you start, ensure you have the Document Authoring library installed and running. For more information, check out the [getting started](https://www.nutrient.io/sdk/document-authoring/getting-started.md) guides.

Document Authoring includes a programmatic API for reading and updating document content through [`DocAuthDocument.transaction()`](https://www.nutrient.io/api/document-authoring/types/docauthdocument/#transaction). Transactions are atomic and isolated until committed, and they provide access to a draft document you can inspect or modify.

Any error handling in the examples below is left out for brevity.

## Running a transaction

Use [`DocAuthDocument.transaction()`](https://www.nutrient.io/api/document-authoring/types/docauthdocument/#transaction) to work with a draft document:

```js

// Assuming the `editor` instance exists.

const currentDoc = editor.currentDocument();

await currentDoc.transaction(async ({ draft }) => {
  const firstSection = draft.body().sections()[0];
  const paragraph = firstSection.content().addParagraph();

  paragraph.asTextView().setText('Created in a transaction.');

  return { commit: true };
});

```

## Read paragraph list formatting

As of 1.14.0, you can inspect paragraph list state with [`Paragraph.list()`](https://www.nutrient.io/api/document-authoring/types/programmatic/paragraph/#list):

```js

const currentDoc = editor.currentDocument();

await currentDoc.transaction(async ({ draft }) => {
  const firstSection = draft.body().sections()[0];
  const blockLevels = firstSection.content().blocklevels();

  for (const blockLevel of blockLevels) {
    if (blockLevel.type!== 'paragraph') continue;

    const list = blockLevel.list();

    if (!list) {
      console.log('Not a list item');
      continue;
    }

    if ('unsupported' in list) {
      console.log('List format is present, but not classified by the API');
      continue;
    }

    console.log(`List kind: ${list.kind}, level: ${list.level}`);
  }

  return { commit: false };
});

```

`Paragraph.list()` returns:

- `null` when the paragraph isn’t a list item.

- A supported list state with `kind` (`'bullet'` or `'numbered'`) and `level`.

- An `unsupported` result for imported or custom list formats the API can’t safely classify.

## Apply paragraph list formatting

Use [`BlockLevelContainer.setParagraphList()`](https://www.nutrient.io/api/document-authoring/types/programmatic/blocklevelcontainer/#setparagraphlist) to apply or clear list formatting for a contiguous range of paragraphs:

```js

const currentDoc = editor.currentDocument();

await currentDoc.transaction(async ({ draft }) => {
  const content = draft.body().sections()[0].content();
  const start = content.blocklevels().length;

  content.addParagraph().asTextView().setText('Draft announcement');
  content.addParagraph().asTextView().setText('Prepare demo');
  content.addParagraph().asTextView().setText('Send follow-up');

  content.setParagraphList(
    { index: start, count: 3 },
    { kind: 'bullet' },
  );

  return { commit: true };
});

```

Within the same transaction, you can remove list formatting while keeping the paragraph text by using `kind: 'none'`:

```js

content.setParagraphList(
  { index: start, count: 3 },
  { kind: 'none' },
);

```

You can also copy list formatting from an existing paragraph with [`BlockLevelContainer.applyParagraphListFrom()`](https://www.nutrient.io/api/document-authoring/types/programmatic/blocklevelcontainer/#applyparagraphlistfrom). In the example below, `start` comes from the previous snippet. This is useful when you want target paragraphs to continue the same list formatting or start a new list instance with the same format:

```js

const sourceIndex = start;

const fourth = content.addParagraph();
fourth.asTextView().setText('Share notes');

const fifth = content.addParagraph();
fifth.asTextView().setText('Schedule review');

content.applyParagraphListFrom(
  sourceIndex,
  { index: start + 3, count: 2 },
  { list: 'startNew' },
);

```

## Query and manage comment threads

As of 1.14.0, you can query document comment threads through [`draft.commentThreads()`](https://www.nutrient.io/api/document-authoring/types/programmatic/commentthreadcollection/):

```js

const currentDoc = editor.currentDocument();

await currentDoc.transaction(async ({ draft }) => {
  const openThreads = draft.commentThreads().all({ status: 'open' });

  for (const thread of openThreads) {
    console.log(thread.comment.text);
  }

  return { commit: false };
});

```

Create a new text-anchored comment thread by passing a plain-text body and one or more text anchor input ranges:

```js

const currentDoc = editor.currentDocument();

await currentDoc.transaction(async ({ draft }) => {
  const paragraph = draft.body().sections()[0].content().addParagraph();
  const textView = paragraph.asTextView();

  textView.setText('The supplier must provide reviewed text before signing.');

  const match = textView.searchText('reviewed text');

  if (!match) {
    return { commit: false };
  }

  draft.commentThreads().add({
    body: 'Can Legal confirm this wording?',
    anchor: {
      type: 'text',
      ranges: [{ textView, range: match.range }],
    },
  });

  return { commit: true };
});

```

After querying or creating a thread, you can manage it with [`CommentThread`](https://www.nutrient.io/api/document-authoring/types/programmatic/commentthread/) methods such as:

- `reply(body)`

- `edit(body)`

- `resolve()`

- `unresolve()`

- `remove()`

For example:

```js

await currentDoc.transaction(async ({ draft }) => {
  const [thread] = draft.commentThreads().all({ status: 'open' });

  if (thread) {
    thread.reply('Checked by automated review.');
    thread.resolve();
  }

  return { commit: true };
});

```

## Use review mode in transactions

As of 1.14.0, [`TransactionOptions.review`](https://www.nutrient.io/api/document-authoring/types/transactionoptions/) lets you run supported document mutations in review mode so they’re authored as tracked revisions:

```js

const currentDoc = editor.currentDocument();

await currentDoc.transaction(
  async ({ draft }) => {
    const paragraph = draft.body().sections()[0].content().addParagraph();
    paragraph.asTextView().setText('This change is authored in review mode.');

    return { commit: true };
  },
  {
    review: {
      author: 'Nutrient Docs',
    },
  },
);

```

Supported mutations are recorded as revisions for the supplied author. Mutations without tracked-change markup support still apply directly.

Comment thread mutations also work inside review transactions, but they don’t create tracked-change revisions.

## Learn more

- [Document Authoring API reference](https://www.nutrient.io/api/document-authoring/)

- [Programmatic API types](https://www.nutrient.io/api/document-authoring/modules/programmatic/)
---

## Related pages

- [First-class JSON support with DocJSON](/guides/document-authoring/working-with-documents/docjson.md)
- [Import and export DOCX files effectively](/guides/document-authoring/working-with-documents/docx.md)
- [Seamlessly export PDF files in Document Authoring](/guides/document-authoring/working-with-documents/pdf.md)
- [Working with documents](/guides/document-authoring/working-with-documents/overview.md)

