npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@portabletext/plugin-sdk-value

v7.2.1

Published

Connect a Portable Text Editor with a Sanity document using the SDK

Readme

@portabletext/plugin-sdk-value

Connect a Portable Text Editor with a Sanity document using the SDK

Two-way synchronization between a Portable Text Editor and a field in a Sanity document, plus presence: other people's carets show up in the field, and the local user's caret shows up for them, including in the Studio.

Installation

npm install @portabletext/plugin-sdk-value

Features

  • Two-way synchronization: Changes in the editor update the document, and document changes update the editor
  • Real-time updates: Automatically handles patches from external sources (other users, mutations, etc.)
  • Optimistic updates: Provides smooth user experience with immediate local updates
  • Presence: Reports where the local user is editing, and draws other people's carets

Usage

Use SDKPortableTextEditable in place of PortableTextEditable, inside the EditorProvider. It names the document and field once and wires up value sync, presence reporting, and remote carets:

import {defineSchema, EditorProvider} from '@portabletext/editor'
import {SDKPortableTextEditable} from '@portabletext/plugin-sdk-value'

function MyEditor() {
  return (
    <EditorProvider initialConfig={{schemaDefinition: defineSchema({})}}>
      <SDKPortableTextEditable
        documentId="my-document-id"
        documentType="myDocumentType"
        path="content"
      />
    </EditorProvider>
  )
}

That is all of it. Remote carets are drawn with a built-in caret, so nothing has to be styled to get presence working.

Every other prop is forwarded to PortableTextEditable untouched. Any rangeDecorations you pass are kept and merged with the presence carets rather than replaced.

Styling the caret

The built-in caret is deliberately plain: a coloured line with a dot above it and the participant's name on hover. Colours come from a small fixed palette, keyed on the user, so one person in two tabs gets one colour. Pass renderCursor to replace it:

<SDKPortableTextEditable
  {...documentHandle}
  path="content"
  renderCursor={({user}) =>
    (props) => (
      <MyCaret label={user.profile.displayName}>{props.children}</MyCaret>
    )}
/>

Your component receives the decorated text as children and should render it. Pass renderCursor={null} to draw no carets at all while still reporting the local user's presence, which is what you want if you only care about keeping the document in sync.

Rendering the editable yourself

SDKValuePlugin still works as a sibling component if you would rather keep control of PortableTextEditable. Pair it with SDKPresencePlugin to report presence, and useSDKPresenceCursors for remote carets:

function MyEditor(props: DocumentHandle) {
  const cursors = useSDKPresenceCursors({
    ...props,
    path: 'content',
    renderCursor:
      ({user}) =>
      (cursorProps) => <Caret user={user}>{cursorProps.children}</Caret>,
  })

  return (
    <EditorProvider initialConfig={{schemaDefinition: defineSchema({})}}>
      <PortableTextEditable rangeDecorations={cursors} />
      <SDKValuePlugin {...props} path="content" />
      <SDKPresencePlugin {...props} path="content" />
    </EditorProvider>
  )
}

Props

SDKPortableTextEditable accepts a Document Handle, every PortableTextEditable prop, and:

| Prop | Type | Description | | -------------- | ----------------------------- | -------------------------------------------------------------------------------- | | documentId | string | The document ID | | documentType | string | The document type | | path | string | JSONMatch path expression to the Portable Text field | | renderCursor | function \| null (optional) | Draws one remote participant's caret. Omit for the built-in one, null for none | | dataset | string (optional) | Dataset name (if different from configured default) | | projectId | string (optional) | Project ID (if different from configured default) | | perspective | string \| object (optional) | Which document to sync and report: draft, published, or a release |

Notes on presence

Carets are drawn with the built-in component unless you pass renderCursor, and are collapsed to a single point at the participant's focus. Presence answers where someone is, so decorating their whole selection would highlight text the local user never selected. The Studio does the same.

Participants are counted by session, not by person. The same user in two tabs draws two carets, in the same colour. Group by user.sanityUserId in your own renderCursor if you would rather show one.

The local user is never included, so an app does not draw its own caret.

Which document is reported follows the handle's perspective, or the ambient one from ResourceProvider. Pass the plain document id and let the perspective select the draft, the published document, or a release version. This matters for the Studio, whose field indicators compare the exact document id its form is on.

Requirements

This plugin requires:

  • @sanity/sdk-react 2.19 or newer, where the presence hooks were added
  • The document must exist in the Sanity dataset