App SDK

Editing documents

Explore different methods and patterns for editing documents with the App SDK.

The Sanity App SDK ships with everything you need to build powerful document editing interfaces. The variety of options available, however, might make you wonder which is best suited for your use case.

This guide is designed to inform your decision making by showcasing in detail the different React hooks, components, and patterns that can be used in the course of building document editing interfaces with the App SDK. After reading this guide, you’ll be equipped to build a variety of document editing workflows. All that will be left for you to do is evaluate which of these options best matches the needs of your application.

Prerequisites:

  • Basic familiarity with content operations
  • A project with at least one document
  • A custom app built with the Sanity App SDK
  • Basic familiarity with Document Handles
  • For the Portable Text section: @portabletext/editor v8 or later and @portabletext/plugin-sdk-value v6 or later

Basic document editing with useEditDocument

The useEditDocument hook is the first hook you should look to for editing document content. This hook can be used to edit an entire document, or a single field within a document.

Further reading

Additionally, this hook can be used for functional updates based on the document or document field’s current state.

Functional updates

Edit a document field

In the example below, we export a component that implements editing of a single document field. The component accepts a Document Handle as a prop, and renders a text input for displaying and editing the ‘SKU’ field in the document referenced by the Document Handle.

Edit multiple document fields (multiple getters & setters)

In the example below, we enable editing of multiple document fields by defining multiple ‘getters’ (with the useDocument hook) and multiple ‘setters’ (with the useEditDocument hook). This also demonstrates the use of dot notation to access nested paths, i.e. price.standard and price.sale.

Edit one or more document fields (functional updates)

The useEditDocument hook can be used without a path parameter to return the data for an entire document. When combined with a functional update, this enables editing one or more fields on a document in a single operation.

In the example below, we demonstrate this pattern for a single, dynamic field edit:

The functional update pattern can also be used to edit multiple fields at once, as in the example below:

Edit published documents with liveEdit

The useEditDocument hook is designed to apply edits to draft documents by default, in order to avoid pushing changes to published documents unexpectedly.

Depending on the document referenced by the document handle passed to useEditDocument, the invocation of the returned edit function will either:

  • apply edits to the current draft if one already exists, or
  • create a new draft (copying from the published version) and apply edits to this new draft.

If you instead want to apply edits directly to a published document, this draft creation can be bypassed by setting the liveEdit field on the document handle to true, as in the example below.

Publish first

Compose editing workflows with useApplyDocumentActions

Under the hood, the useEditDocument hook uses the lower level useApplyDocumentActions hook to apply edits to documents. If your use case goes beyond what’s available with the useEditDocument hook as demonstrated above, you can opt to leverage the useApplyDocumentActions hook and the associated document action functions to get things done.

Further reading

Below, you’ll find two examples of workflows that can be created this way.

Create a document with initial field values

By default, the createDocument document action function simply creates a new document with nothing more than the basic fields required by its Document Handle (document ID and type). However, an object of field values can be passed as an optional second parameter, enabling the document to be created with some initial field values.

This is demonstrated in the example below:

Create and publish a new document

Multiple document actions can be combined in a single call to the apply function returned by useApplyDocumentActions. This enables the creation of multistep workflows within a single transaction.

For example, you might want to create a new document, populate it with some initial values, and then publish the new document immediately. This is demonstrated below:

Edit rich text fields with Portable Text

Another way to edit fields on a document with the App SDK is with the SDK Value Plugin for the Portable Text Editor. SDKPortableTextEditable wires the editor to a document field: two-way sync, real-time updates from edits made by other users, optimistic updates, and other people’s carets.

Install the editor and the plugin. Their major versions must match, because the plugin declares the editor as a peer dependency:

Building an editor takes three pieces:

  • A schema declares what content the field accepts, through defineSchema.
  • Node registrations own how each piece of that content renders. Create them with defineTextBlock, defineDecorator, defineAnnotation, and their siblings, then mount them with NodePlugin.
  • SDKPortableTextEditable renders the editable surface and syncs it with the document field.

Use this component to edit any document field configured with the block type.

The schema and the registrations do different jobs, and both are required. The schema declares what the editor allows; a registration renders what the schema already permits. A decorator declared in the schema but never registered still applies to the text and still saves to the document, but it renders through the engine’s default, which passes the text through unchanged, so the formatting is invisible to the person typing. Custom block objects and inline objects work the same way: declare them in blockObjects or inlineObjects, then register them with defineBlockObject or defineInlineObject. See Rendering for the full model, and Custom blocks and inline objects for those two.

Sync is handled for you

Show other people’s carets

SDKPortableTextEditable reports the local user’s caret and draws everyone else’s, using a built-in caret that needs no styling of your own. Pass renderCursor to draw your own instead:

<SDKPortableTextEditable
  {...articleHandle}
  path={path}
  renderCursor={({user}) => (props) => (
    <span
      style={{borderLeft: '2px solid currentColor'}}
      title={user.profile.displayName}
    >
      {props.children}
    </span>
  )}
/>

Pass renderCursor={null} to report presence without drawing any carets.

To render the editable surface yourself instead, use PortableTextEditable with the lower-level SDKValuePlugin and SDKPresencePlugin mounted alongside it.

Edit documents with Agent Actions

Experimental feature

Documents can be edited (or ‘patched’) using a hook that leverages the Agent Actions APIuseAgentPatch. This hook applies patches to your document in the same manner as the Patch Agent Action, meaning it validates paths and ensures that the provided values are compatible with the target schema.

A basic example of this is shown below:

Further reading

Summary

In this guide, we’ve demonstrated editing single document fields, multiple document fields, using functional updates, editing published documents live, composing editing and publishing workflows, using Portable Text to edit block fields, and the experimental useAgentPatch to apply edits via the Agent Actions API.

With such a variety of ways to handle document editing, the Sanity App SDK is well equipped to power both traditional and unique editing use cases.

If, however, you have a custom application interface or use case that the App SDK doesn’t seem equipped to handle, we’d love to hear from you! Feel free to drop into our Discord community, and find us in the #app-sdk channel to let us know.

Was this page helpful?