vocalstack

payload plugin

by vocalstack
AI content generation
v3
^3.0.0
MIT

Overview

Summary generated from the README

Adds a Transcribe button to upload collections that accept audio, sending files to the VocalStack service for speech-to-text. The transcript and timed sentences with speaker labels are saved in a vocalstack field group on the document. It adds a settings global and endpoints, and connects with an API key or one-click OAuth. Requires a VocalStack account and Payload 3.

Features

  • Sidebar Transcribe button on audio documents with live progress and a Stop option
  • Saves editable transcript text plus sentences with start, end, speaker and text
  • Transcribes audio held in array field rows that are not upload documents
  • Connect with an API key or one-click OAuth with PKCE from the admin settings global
  • Configurable spoken language, file URL resolver and access check
  • Endpoints for transcribe, stop, status and Server-Sent Events progress stream

Use cases

  • Generate searchable transcripts for podcast episodes uploaded to a media collection
  • Add speaker-labelled timed captions data to interview recordings
  • Transcribe imported audio attachments referenced by filename in array fields

From the author

VocalStack for Payload CMS: a Transcribe button on every audio upload.

Installation

npm i @vocalstack/payload

0

Stars

0

Forks

0

Open Issues

Today

Last Updated

0

Weekly Downloads

v0.1.0

npm Version

143.6 KB

Unpacked Size

0

Dependencies

GitHub stars

Trends appear after 7 days of data. Collecting since Oct 22 — 1 daily snapshot so far.

npm weekly downloads

Trends appear after 7 days of data. Collecting since Oct 22 — 1 daily snapshot so far.

Each point is npm's rolling 7-day download count.

Health Score

63/100Good

Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.

Badge for your README

Payload Directory health badge
Markdown
[![Payload Directory](https://payloaddirectory.dev/badge/1423030201-root.svg)](https://payloaddirectory.dev/plugins/1423030201-root)
HTML
<a href="https://payloaddirectory.dev/plugins/1423030201-root"><img src="https://payloaddirectory.dev/badge/1423030201-root.svg" alt="Payload Directory"></a>
Updated Oct 21, 2026Created Oct 17, 2026Published Oct 21, 2026

README

@vocalstack/payload

VocalStack for Payload CMS 3: a Transcribe button on every audio upload. The transcript is saved on the document as text plus timed sentences.

Install

pnpm add @vocalstack/payload
// payload.config.ts
import { vocalstackPlugin } from '@vocalstack/payload'

export default buildConfig({
  plugins: [
    vocalstackPlugin({
      // Optional. Without it, connect from Admin → Settings → VocalStack.
      apiKey: process.env.VOCALSTACK_API_KEY,
    }),
  ],
})

Then run payload generate:importmap. The plugin adds fields to the targeted collections and a vocalstack global, so with Postgres also run payload migrate:create.

What it adds

  • Every upload collection that accepts audio (audio/*, a wildcard, or no restriction), or the ones in collections, gets:
    • a sidebar Transcribe button, shown on audio/* documents only. It shows progress and survives a reload mid-job, and Stop ends a job you no longer want: the document goes back to how it was (an earlier transcript stays), and VocalStack keeps what it transcribed before the stop.
    • a vocalstack group:
      • transcript: text you can edit; paragraphs break on a speaker change or a pause.
      • sentences: [{ start, end, speaker, text }] in seconds.
      • status, language, error, recordingId, transcriptionId and transcribedAt.
    • POST /api/{collection}/:id/vocalstack/transcribe, POST …/vocalstack/stop, GET …/vocalstack/status and GET …/vocalstack/stream (live progress as Server-Sent Events).
  • Settings → VocalStack (a global), where an editor connects an account. The key is stored server-side and is unreadable over the REST and GraphQL APIs.

Options

OptionDefault
apiKeynoneA vs_key_… key with Read and Write. It wins over a key connected in the admin.
collectionsevery upload collection that accepts audioWhich upload collections get the button.
audioFieldsnoneAudio that isn't an upload document; see below.
spokenLanguageauto-detectA Whisper language code (en, ro, …).
fileURLthe doc's url, made absolute({ doc, collection, req }) => string: the address VocalStack fetches. Point it at your bucket's public URL to skip the Payload file proxy.
accesssigned-in users who can update the docExtra check for transcribing and for changing the connection.
oneClickConnectfalseShow Connect VocalStack (see below).
apiURL / dashboardURLhttps://api.vocalstack.com / https://app.vocalstack.com
enabledtruefalse keeps the schema but removes the button and the endpoints.

Audio that isn't an upload

Imported content often points at files by name or link instead of upload documents. audioFields gives each row of an array field its own button and vocalstack transcript:

vocalstackPlugin({
  audioFields: [
    {
      collection: 'episodes',
      field: 'attachments', // a top-level array (tabs, rows and collapsibles are looked through)
      condition: (row) => row.type === 'file',
      audioURL: ({ row }) => (row.filename ? `https://cdn.example.com/episodes/${encodeURIComponent(String(row.filename))}` : undefined),
    },
  ],
})

Rows are addressed by their id: POST /api/episodes/:id/vocalstack/transcribe?field=attachments&row=<row id>. A new row needs saving first.

How the audio reaches VocalStack. If the file has a public address, VocalStack fetches it (POST /v1/transcriptions { url }). Otherwise (localhost, a private network) the plugin uploads the bytes. Either way the job runs on VocalStack, and the admin polls every 3 s. The account is billed per second, with a 5-minute minimum per transcription.

One-click connect (protocol)

The alternative to pasting a key: Connect VocalStack opens the dashboard in a new tab, the person clicks Authenticate, and the tab closes. This is OAuth 2.0 authorization code + PKCE (RFC 7636) with an API key as the access token. There is no client registration: the consent screen shows the redirect_uri host instead.

CMS admin                           CMS server                         app.vocalstack.com / api.vocalstack.com
──────────                          ──────────                         ───────────────────────────────────────
click Connect ── POST /api/vocalstack/connect ─▶ state, verifier (10 min)
◀──────────── { url } ──────────────
new tab ─▶ GET app/connect?response_type=code&client_name=…&redirect_uri=…&state=…
                                    &code_challenge=…&code_challenge_method=S256&scope=read write
           (sign in if needed; pick the organization; "example.com wants to transcribe with VocalStack")
           click Authenticate ─▶ API stores a single-use grant (code hash, challenge, redirect, scopes, principal; 2 min)
           ◀── 302 redirect_uri?code=…&state=…
tab ─────── GET /api/vocalstack/callback ─▶ check state (+ admin session)
                                    POST api/v1/connect/token (form) ─▶ grant_type=authorization_code, code,
                                                                        code_verifier, redirect_uri
                                                                        verify, burn the code, create the key
                                    ◀── { access_token: "vs_key_…", token_type: "api_key", key_id, account: { name } }
                                    stores the key (never sent to a browser)
tab: "Connected", BroadcastChannel('vocalstack') ─▶ admin refreshes; tab closes

Cancel redirects with ?error=access_denied&state=…. The key appears in the dashboard's API keys list as example.com (Payload CMS), where it can be revoked.

Similar plugins