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

@basementstudio/sanity-plugin-csv-export

v0.1.0

Published

Portable Sanity Studio plugin for exporting documents as CSV — pick types and fields, preview, download.

Downloads

92

Readme

@basementstudio/sanity-plugin-csv-export

A Sanity Studio v5 tool that lets you select document types and fields (via searchable dropdowns), preview results, and download them as a clean RFC-4180 CSV or XML file — all from within the Studio.

Installation

bun add @basementstudio/sanity-plugin-csv-export
pnpm add @basementstudio/sanity-plugin-csv-export
npm install @basementstudio/sanity-plugin-csv-export

Usage

Add csvExport() to your plugins array in sanity.config.ts (or .js):

import {defineConfig} from 'sanity'
import {csvExport} from '@basementstudio/sanity-plugin-csv-export'

export default defineConfig({
  // ...
  plugins: [csvExport()],
})

A CSV Export entry (with a download icon) will appear in the Studio top navigation.

With options

plugins: [
  csvExport({
    includedTypes: ['post', 'author', 'company'],
    defaultDelimiter: ';',
    filenamePrefix: 'acme-export',
  }),
],

Options

All options are optional.

| Option | Type | Default | Description | |---|---|---|---| | includedTypes | string[] \| (ctx: {currentUser}) => string[] \| undefined | all document types | Allowlist of document type names to show in the type picker. The function form receives the current user, so you can mirror role-based Studio structure (return undefined to allow all types). Namespaced system documents (sanity.imageAsset, media.tag, translation.metadata, …) are hidden by default — an explicit allowlist fully controls visibility, so you can deliberately re-add one. | | excludedTypes | string[] | [] | Document type names to hide. Applied after includedTypes. Useful for hiding internal/system types. | | serializers | Partial<Record<FieldKind, Serializer>> | — | Override the built-in cell serializer for one or more field kinds. | | defaultFormat | 'csv' \| 'xml' | 'csv' | Output format pre-selected in the options bar. Users can switch it per export. | | defaultDelimiter | ',' \| ';' \| '\t' | ',' | Default CSV delimiter pre-selected in the options bar (CSV only). Users can still change it per export. | | filenamePrefix | string | workspace title (slugified) | Prefix for the downloaded file, used as-is (only path-illegal characters are stripped). Result: <prefix>-<types>-<timestamp>.csv — e.g. accel-company-2026-07-27-1432.csv. When every type is selected the type list collapses to all; more than 3 types are capped with a +N count. |

Matching the Structure navigation

To offer the same document types a user sees in the Structure sidebar, pass a function to includedTypes using the same role logic your structure resolver uses:

csvExport({
  includedTypes: ({currentUser}) => {
    if (isAdmin(currentUser)) return undefined // all types
    const roles = currentUser?.roles?.map((r) => r.name) ?? []
    for (const role of roles) {
      const allowed = ROLE_ALLOWED_TYPES[role]
      if (allowed) return [...allowed]
    }
    return undefined
  },
  excludedTypes: ['metadata'], // internal types hidden from the sidebar
})

All documents / All fields

Both pickers start with an All documents / All fields toggle. Neither is on by default — the tool starts with no types selected and only _id pre-checked.

Union field list & name collisions

With multiple types selected, the field list is the union across all selected types; fields absent on a given type render as empty cells. If two types declare the same field name with a different shape (e.g. status is a string on one type and a reference on another), each conflicting type gets its own disambiguated column — labeled e.g. Status (Ticket) — so no type's data is exported through another type's projection.

Serializer override example

Note: slug, reference, image, and file values are flattened inside the GROQ projection (slug.current, ref->title, asset->url), so a serializer override for those kinds receives the already-resolved string, not the raw object — it acts as a string formatter. Overrides for portableText, array, object, and geopoint receive the raw value.

Override how Portable Text fields are serialized — for example to strip all markup and join paragraphs with a pipe character:

import {csvExport} from '@basementstudio/sanity-plugin-csv-export'
import type {Serializer} from '@basementstudio/sanity-plugin-csv-export'

interface Block {
  children?: {text?: unknown}[]
}

const myPortableTextSerializer: Serializer = (value) => {
  if (!Array.isArray(value)) return ''
  return value
    .map((block) => {
      const children = (block as Block)?.children ?? []
      return children.map((child) => (child?.text == null ? '' : String(child.text))).join('')
    })
    .filter(Boolean)
    .join(' | ')
}

export default defineConfig({
  plugins: [
    csvExport({
      serializers: {
        portableText: myPortableTextSerializer,
      },
    }),
  ],
})

Field types

The plugin resolves complex Sanity field types to flat strings before writing CSV cells:

| Field kind | Serialization | |---|---| | primitive | String(value) — covers string, number, boolean, date | | slug | The .current value (resolved via GROQ projection) | | reference | Resolved referenced document title (via GROQ -> dereference); multi-target references coalesce across each target's title field | | image | Asset CDN URL (via GROQ asset->url dereference) | | file | Asset CDN URL | | portableText | Plain text extracted from block children, paragraphs joined with newline | | array | Primitive items joined with ; — complex items serialized as JSON | | object | JSON.stringify(value) | | geopoint | lat,lng coordinate pair |

Output formats

Pick the format in the options bar:

  • CSV — RFC-4180, UTF-8 BOM, configurable delimiter (, / ; / tab), optional header row.
  • XML — one <document> per row; each cell is <field name="key" label="Label">value</field> with the column identifier in an attribute (so any field key is represented safely) and all values XML-escaped.

The same field selection and serialization (references → title, images → URL, Portable Text → plain text, …) feed both formats.

CSV injection protection

Cell values that would execute as spreadsheet formulas (starting with =, @, or with +/- when not a plain number) are prefixed with a single quote (') so Excel and Google Sheets treat them as literal text (CWE-1236).

Drafts / perspective

The tool offers an Include drafts toggle. When enabled the GROQ query runs with perspective: 'drafts', which returns the draft version of documents where one exists, falling back to the published version. When disabled, only published documents are returned.

How it works

Field discovery is schema-driven: the plugin reads the live Sanity schema via useSchema() and walks each selected document type's field tree to produce a flat list of FieldDescriptor objects (key, path, label, kind). Those descriptors are compiled into a GROQ projection that fetches only the chosen fields, using -> dereferencing for references and images. Large datasets are fetched in pages using keyset pagination (ordering by _id), inspired by Sanity's Exporting content as CSV guide. The collected rows are encoded client-side as RFC-4180 CSV with a UTF-8 BOM () so the file opens correctly in Excel and Numbers without manual encoding steps.

Requirements

  • Sanity Studio v5+
  • React 19+

License

MIT © Basement Studio