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

@shotstack/shotstack-studio

v2.1.5

Published

A video editing library for creating and editing videos with Shotstack

Readme

Shotstack Studio

npm version License TypeScript

A JavaScript SDK for browser-based video editing with timeline, canvas preview, and export.

Interactive Examples

Try Shotstack Studio in your preferred framework:

TypeScript React Vue Angular Next.js

Features

  • Template-driven editing with undo/redo command model
  • Canvas preview rendering
  • Visual timeline with drag, resize, selection, and snapping
  • Extensible UI via UIController button API
  • Browser export pipeline via VideoExporter

Installation

npm install @shotstack/shotstack-studio
yarn add @shotstack/shotstack-studio

Quick Start

import { Edit, Canvas, Controls, Timeline, UIController } from "@shotstack/shotstack-studio";

// 1) Load a template
const response = await fetch("https://shotstack-assets.s3.amazonaws.com/templates/hello-world/hello.json");
const template = await response.json();

// 2) Create core components
const edit = new Edit(template);
const canvas = new Canvas(edit);
const ui = UIController.create(edit, canvas);

// 3) Load canvas and edit
await canvas.load();
await edit.load();

// 4) Register toolbar buttons
ui.registerButton({
  id: "text",
  icon: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3H13"/><path d="M8 3V13"/><path d="M5 13H11"/></svg>`,
  tooltip: "Add Text"
});

// 5) Handle button clicks
ui.on("button:text", ({ position }) => {
  edit.addTrack(0, {
    clips: [
      {
        asset: {
          type: "rich-text",
          text: "Title",
          font: { family: "Work Sans", size: 72, weight: 600, color: "#ffffff", opacity: 1 },
          align: { horizontal: "center", vertical: "middle" }
        },
        start: position,
        length: 5,
        width: 500,
        height: 200
      }
    ]
  });
});

// 6) Initialize the Timeline
const timelineContainer = document.querySelector("[data-shotstack-timeline]") as HTMLElement;
const timeline = new Timeline(edit, timelineContainer, { resizable: true });
await timeline.load();

// 7) Add keyboard controls
const controls = new Controls(edit);
await controls.load();

// 8) Add event handlers
edit.events.on("clip:selected", data => {
  console.log("Clip selected:", data);
});

Your HTML must include both containers:

<div data-shotstack-studio></div>
<div data-shotstack-timeline></div>

Main Components

Edit

Edit is the runtime editing session and source of truth for document mutations.

import { Edit } from "@shotstack/shotstack-studio";

const edit = new Edit(templateJson);
await edit.load();

await edit.loadEdit(nextTemplateJson);

// Playback (seconds)
edit.play();
edit.pause();
edit.seek(2);
edit.stop();

// Mutations
await edit.addTrack(0, { clips: [] });
await edit.addClip(0, {
  asset: { type: "image", src: "https://example.com/image.jpg" },
  start: 0,
  length: 5
});
await edit.updateClip(0, 0, { length: 6 });
await edit.deleteClip(0, 0);

// History
await edit.undo();
await edit.redo();

// Clip operations
await edit.deleteTrack(0);

// Output settings
await edit.setOutputSize(1920, 1080);
await edit.setOutputFps(30);
await edit.setOutputFormat("mp4");
await edit.setOutputResolution("hd");
await edit.setOutputAspectRatio("16:9");
await edit.setTimelineBackground("#000000");

// Read state
const time = edit.playbackTime;
const playing = edit.isPlaying;
const clip = edit.getClip(0, 0);
const track = edit.getTrack(0);
const snapshot = edit.getEdit();
const durationSeconds = edit.totalDuration;

Events

Listen using string event names:

const unsubscribeClipSelected = edit.events.on("clip:selected", data => {
  console.log("Selected clip", data.trackIndex, data.clipIndex);
});

edit.events.on("clip:updated", data => {
  console.log("Updated from", data.previous, "to", data.current);
});

edit.events.on("playback:play", () => {
  console.log("Playback started");
});

// Unsubscribe when no longer needed
unsubscribeClipSelected();

Available event names:

| Category | Event Names | | --- | --- | | Playback | playback:play, playback:pause | | Timeline | timeline:updated, timeline:backgroundChanged, timeline:resized | | Clip lifecycle | clip:added, clip:selected, clip:updated, clip:deleted, clip:restored, clip:copied, clip:loadFailed, clip:unresolved | | Selection | selection:cleared | | Edit state | edit:changed, edit:undo, edit:redo | | Track | track:added, track:removed | | Duration | duration:changed | | Output | output:resized, output:resolutionChanged, output:aspectRatioChanged, output:fpsChanged, output:formatChanged, output:destinationsChanged | | Merge fields | mergefield:changed |

Canvas

Canvas renders the current edit.

import { Canvas } from "@shotstack/shotstack-studio";

const canvas = new Canvas(edit);
await canvas.load();

canvas.centerEdit();
canvas.zoomToFit();
canvas.setZoom(1.25);
canvas.resize();
const zoom = canvas.getZoom();
canvas.dispose();

UIController

UIController manages built-in UI wiring and extensible button events.

import { UIController } from "@shotstack/shotstack-studio";

const ui = UIController.create(edit, canvas, { mergeFields: true });

ui.registerButton({
  id: "add-title",
  icon: `<svg viewBox="0 0 16 16">...</svg>`,
  tooltip: "Add Title"
});

const unsubscribe = ui.on("button:add-title", ({ position }) => {
  console.log("Button clicked at", position, "seconds");
});

ui.unregisterButton("add-title");
unsubscribe();
ui.dispose();

Timeline

Timeline provides visual clip editing.

The container must have an explicit CSS height (e.g. height: 300px) and overflow: hidden. Avoid flex-grow or !important on height — the resize handle sets height via inline style.

import { Timeline } from "@shotstack/shotstack-studio";

const container = document.querySelector("[data-shotstack-timeline]") as HTMLElement;
const timeline = new Timeline(edit, container, { resizable: true });

await timeline.load();
timeline.zoomIn();
timeline.zoomOut();
timeline.dispose();

Pass { resizable: false } to hide the drag handle. When enabled (default), a timeline:resized event fires with { height } after the user finishes dragging or double-clicks to reset.

Controls

Controls enables keyboard playback/edit shortcuts.

import { Controls } from "@shotstack/shotstack-studio";

const controls = new Controls(edit);
await controls.load();

VideoExporter

VideoExporter exports a timeline render from the browser runtime.

import { VideoExporter } from "@shotstack/shotstack-studio";

const exporter = new VideoExporter(edit, canvas);
await exporter.export("my-video.mp4", 25);

Merge Fields

Merge fields are template placeholders, typically in the form {{ FIELD_NAME }}.

{
  "asset": {
    "type": "text",
    "text": "{{ TITLE }}"
  }
}

When merge-field-aware UI is required, enable it via UIController options:

const ui = UIController.create(edit, canvas, { mergeFields: true });

You can also subscribe to merge field events when integrations update merge data:

edit.events.on("mergefield:changed", ({ fields }) => {
  console.log("Merge fields updated:", fields.length);
});

Custom UI Buttons

Use UIController to register and handle custom button actions.

ui.registerButton({
  id: "text",
  icon: `<svg viewBox="0 0 16 16">...</svg>`,
  tooltip: "Add Text",
  dividerBefore: true
});

ui.on("button:text", ({ position, selectedClip }) => {
  console.log("Current time (seconds):", position);
  console.log("Current selection:", selectedClip);
});

ui.unregisterButton("text");

API Reference

For schema-level details and type definitions, see the Shotstack API Reference.

License

PolyForm Shield License 1.0.0