{"id":35223304,"url":"https://github.com/shotstack/shotstack-studio-sdk","last_synced_at":"2026-03-11T06:01:38.197Z","repository":{"id":241875537,"uuid":"808079162","full_name":"shotstack/shotstack-studio-sdk","owner":"shotstack","description":"A JavaScript library for creating and editing videos in the browser.","archived":false,"fork":false,"pushed_at":"2026-03-10T13:05:41.000Z","size":4440,"stargazers_count":20,"open_issues_count":3,"forks_count":16,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-10T15:53:26.027Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://shotstack.io","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shotstack.png","metadata":{"files":{"readme":"readme.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-05-30T10:41:55.000Z","updated_at":"2026-03-10T13:04:53.000Z","dependencies_parsed_at":"2025-02-28T06:34:07.091Z","dependency_job_id":"18ddbf0e-68e2-49f9-a75f-20414cf1303b","html_url":"https://github.com/shotstack/shotstack-studio-sdk","commit_stats":null,"previous_names":["shotstack/shotstack-studio-sdk"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/shotstack/shotstack-studio-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shotstack%2Fshotstack-studio-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shotstack%2Fshotstack-studio-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shotstack%2Fshotstack-studio-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shotstack%2Fshotstack-studio-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shotstack","download_url":"https://codeload.github.com/shotstack/shotstack-studio-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shotstack%2Fshotstack-studio-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30372534,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T21:41:54.280Z","status":"online","status_checked_at":"2026-03-11T02:00:07.027Z","response_time":84,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-30T00:43:08.093Z","updated_at":"2026-03-11T06:01:38.184Z","avatar_url":"https://github.com/shotstack.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Shotstack Studio\n\n[![npm version](https://img.shields.io/npm/v/@shotstack/shotstack-studio.svg)](https://www.npmjs.com/package/@shotstack/shotstack-studio)\n[![License](https://img.shields.io/badge/license-PolyForm_Shield-blue.svg)](https://polyformproject.org/licenses/shield/1.0.0/)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.6-blue.svg)](https://www.typescriptlang.org/)\n\nA JavaScript SDK for browser-based video editing with timeline, canvas preview, and export.\n\n## Interactive Examples\n\nTry Shotstack Studio in your preferred framework:\n\n[![TypeScript](https://img.shields.io/badge/TypeScript-StackBlitz-blue?style=for-the-badge\u0026logo=typescript)](https://stackblitz.com/fork/github/shotstack/shotstack-studio-sdk-demos/tree/master/typescript)\n[![React](https://img.shields.io/badge/React-StackBlitz-blue?style=for-the-badge\u0026logo=react)](https://stackblitz.com/fork/github/shotstack/shotstack-studio-sdk-demos/tree/master/react)\n[![Vue](https://img.shields.io/badge/Vue-StackBlitz-blue?style=for-the-badge\u0026logo=vue.js)](https://stackblitz.com/fork/github/shotstack/shotstack-studio-sdk-demos/tree/master/vue)\n[![Angular](https://img.shields.io/badge/Angular-StackBlitz-blue?style=for-the-badge\u0026logo=angular)](https://stackblitz.com/fork/github/shotstack/shotstack-studio-sdk-demos/tree/master/angular)\n[![Next.js](https://img.shields.io/badge/Next.js-StackBlitz-blue?style=for-the-badge\u0026logo=next.js)](https://stackblitz.com/fork/github/shotstack/shotstack-studio-sdk-demos/tree/master/nextjs)\n\n## Features\n\n- Template-driven editing with undo/redo command model\n- Canvas preview rendering\n- Visual timeline with drag, resize, selection, and snapping\n- Extensible UI via `UIController` button API\n- Browser export pipeline via `VideoExporter`\n\n## Installation\n\n```bash\nnpm install @shotstack/shotstack-studio\n```\n\n```bash\nyarn add @shotstack/shotstack-studio\n```\n\n## Quick Start\n\n```typescript\nimport { Edit, Canvas, Controls, Timeline, UIController } from \"@shotstack/shotstack-studio\";\n\n// 1) Load a template\nconst response = await fetch(\"https://shotstack-assets.s3.amazonaws.com/templates/hello-world/hello.json\");\nconst template = await response.json();\n\n// 2) Create core components\nconst edit = new Edit(template);\nconst canvas = new Canvas(edit);\nconst ui = UIController.create(edit, canvas);\n\n// 3) Load canvas and edit\nawait canvas.load();\nawait edit.load();\n\n// 4) Register toolbar buttons\nui.registerButton({\n  id: \"text\",\n  icon: `\u003csvg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\u003e\u003cpath d=\"M3 3H13\"/\u003e\u003cpath d=\"M8 3V13\"/\u003e\u003cpath d=\"M5 13H11\"/\u003e\u003c/svg\u003e`,\n  tooltip: \"Add Text\"\n});\n\n// 5) Handle button clicks\nui.on(\"button:text\", ({ position }) =\u003e {\n  edit.addTrack(0, {\n    clips: [\n      {\n        asset: {\n          type: \"rich-text\",\n          text: \"Title\",\n          font: { family: \"Work Sans\", size: 72, weight: 600, color: \"#ffffff\", opacity: 1 },\n          align: { horizontal: \"center\", vertical: \"middle\" }\n        },\n        start: position,\n        length: 5,\n        width: 500,\n        height: 200\n      }\n    ]\n  });\n});\n\n// 6) Initialize the Timeline\nconst timelineContainer = document.querySelector(\"[data-shotstack-timeline]\") as HTMLElement;\nconst timeline = new Timeline(edit, timelineContainer, { resizable: true });\nawait timeline.load();\n\n// 7) Add keyboard controls\nconst controls = new Controls(edit);\nawait controls.load();\n\n// 8) Add event handlers\nedit.events.on(\"clip:selected\", data =\u003e {\n  console.log(\"Clip selected:\", data);\n});\n```\n\nYour HTML must include both containers:\n\n```html\n\u003cdiv data-shotstack-studio\u003e\u003c/div\u003e\n\u003cdiv data-shotstack-timeline\u003e\u003c/div\u003e\n```\n\n## Main Components\n\n### Edit\n\n`Edit` is the runtime editing session and source of truth for document mutations.\n\n```typescript\nimport { Edit } from \"@shotstack/shotstack-studio\";\n\nconst edit = new Edit(templateJson);\nawait edit.load();\n\nawait edit.loadEdit(nextTemplateJson);\n\n// Playback (seconds)\nedit.play();\nedit.pause();\nedit.seek(2);\nedit.stop();\n\n// Mutations\nawait edit.addTrack(0, { clips: [] });\nawait edit.addClip(0, {\n  asset: { type: \"image\", src: \"https://example.com/image.jpg\" },\n  start: 0,\n  length: 5\n});\nawait edit.updateClip(0, 0, { length: 6 });\nawait edit.deleteClip(0, 0);\n\n// History\nawait edit.undo();\nawait edit.redo();\n\n// Clip operations\nawait edit.deleteTrack(0);\n\n// Output settings\nawait edit.setOutputSize(1920, 1080);\nawait edit.setOutputFps(30);\nawait edit.setOutputFormat(\"mp4\");\nawait edit.setOutputResolution(\"hd\");\nawait edit.setOutputAspectRatio(\"16:9\");\nawait edit.setTimelineBackground(\"#000000\");\n\n// Read state\nconst time = edit.playbackTime;\nconst playing = edit.isPlaying;\nconst clip = edit.getClip(0, 0);\nconst track = edit.getTrack(0);\nconst snapshot = edit.getEdit();\nconst durationSeconds = edit.totalDuration;\n```\n\n#### Events\n\nListen using string event names:\n\n```typescript\nconst unsubscribeClipSelected = edit.events.on(\"clip:selected\", data =\u003e {\n  console.log(\"Selected clip\", data.trackIndex, data.clipIndex);\n});\n\nedit.events.on(\"clip:updated\", data =\u003e {\n  console.log(\"Updated from\", data.previous, \"to\", data.current);\n});\n\nedit.events.on(\"playback:play\", () =\u003e {\n  console.log(\"Playback started\");\n});\n\n// Unsubscribe when no longer needed\nunsubscribeClipSelected();\n```\n\nAvailable event names:\n\n| Category | Event Names |\n| --- | --- |\n| Playback | `playback:play`, `playback:pause` |\n| Timeline | `timeline:updated`, `timeline:backgroundChanged`, `timeline:resized` |\n| Clip lifecycle | `clip:added`, `clip:selected`, `clip:updated`, `clip:deleted`, `clip:restored`, `clip:copied`, `clip:loadFailed`, `clip:unresolved` |\n| Selection | `selection:cleared` |\n| Edit state | `edit:changed`, `edit:undo`, `edit:redo` |\n| Track | `track:added`, `track:removed` |\n| Duration | `duration:changed` |\n| Output | `output:resized`, `output:resolutionChanged`, `output:aspectRatioChanged`, `output:fpsChanged`, `output:formatChanged`, `output:destinationsChanged` |\n| Merge fields | `mergefield:changed` |\n\n### Canvas\n\n`Canvas` renders the current edit.\n\n```typescript\nimport { Canvas } from \"@shotstack/shotstack-studio\";\n\nconst canvas = new Canvas(edit);\nawait canvas.load();\n\ncanvas.centerEdit();\ncanvas.zoomToFit();\ncanvas.setZoom(1.25);\ncanvas.resize();\nconst zoom = canvas.getZoom();\ncanvas.dispose();\n```\n\n### UIController\n\n`UIController` manages built-in UI wiring and extensible button events.\n\n```typescript\nimport { UIController } from \"@shotstack/shotstack-studio\";\n\nconst ui = UIController.create(edit, canvas, { mergeFields: true });\n\nui.registerButton({\n  id: \"add-title\",\n  icon: `\u003csvg viewBox=\"0 0 16 16\"\u003e...\u003c/svg\u003e`,\n  tooltip: \"Add Title\"\n});\n\nconst unsubscribe = ui.on(\"button:add-title\", ({ position }) =\u003e {\n  console.log(\"Button clicked at\", position, \"seconds\");\n});\n\nui.unregisterButton(\"add-title\");\nunsubscribe();\nui.dispose();\n```\n\n### Timeline\n\n`Timeline` provides visual clip editing.\n\nThe 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.\n\n```typescript\nimport { Timeline } from \"@shotstack/shotstack-studio\";\n\nconst container = document.querySelector(\"[data-shotstack-timeline]\") as HTMLElement;\nconst timeline = new Timeline(edit, container, { resizable: true });\n\nawait timeline.load();\ntimeline.zoomIn();\ntimeline.zoomOut();\ntimeline.dispose();\n```\n\nPass `{ 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.\n\n### Controls\n\n`Controls` enables keyboard playback/edit shortcuts.\n\n```typescript\nimport { Controls } from \"@shotstack/shotstack-studio\";\n\nconst controls = new Controls(edit);\nawait controls.load();\n```\n\n### VideoExporter\n\n`VideoExporter` exports a timeline render from the browser runtime.\n\n```typescript\nimport { VideoExporter } from \"@shotstack/shotstack-studio\";\n\nconst exporter = new VideoExporter(edit, canvas);\nawait exporter.export(\"my-video.mp4\", 25);\n```\n\n## Merge Fields\n\nMerge fields are template placeholders, typically in the form `{{ FIELD_NAME }}`.\n\n```json\n{\n  \"asset\": {\n    \"type\": \"text\",\n    \"text\": \"{{ TITLE }}\"\n  }\n}\n```\n\nWhen merge-field-aware UI is required, enable it via `UIController` options:\n\n```typescript\nconst ui = UIController.create(edit, canvas, { mergeFields: true });\n```\n\nYou can also subscribe to merge field events when integrations update merge data:\n\n```typescript\nedit.events.on(\"mergefield:changed\", ({ fields }) =\u003e {\n  console.log(\"Merge fields updated:\", fields.length);\n});\n```\n\n## Custom UI Buttons\n\nUse `UIController` to register and handle custom button actions.\n\n```typescript\nui.registerButton({\n  id: \"text\",\n  icon: `\u003csvg viewBox=\"0 0 16 16\"\u003e...\u003c/svg\u003e`,\n  tooltip: \"Add Text\",\n  dividerBefore: true\n});\n\nui.on(\"button:text\", ({ position, selectedClip }) =\u003e {\n  console.log(\"Current time (seconds):\", position);\n  console.log(\"Current selection:\", selectedClip);\n});\n\nui.unregisterButton(\"text\");\n```\n\n## API Reference\n\nFor schema-level details and type definitions, see the [Shotstack API Reference](https://shotstack.io/docs/api/#tocs_edit).\n\n## License\n\nPolyForm Shield License 1.0.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshotstack%2Fshotstack-studio-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshotstack%2Fshotstack-studio-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshotstack%2Fshotstack-studio-sdk/lists"}