Skip to main content
This guide walks you through updating storyboard elements after creating a preview. You’ll learn how to replace background visuals, modify scene text, change background music, and render the final video with your updates.

What You’ll Learn

Replace Backgrounds

Swap scene background images or videos with your own media

Update Text

Modify scene text and title content

Change Music

Replace background music with a different track

Render Updates

Render the final video with all your modifications

Before You Begin

Make sure you have:
npm install axios

Workflow Overview

The update workflow fits between creating a storyboard preview and rendering the final video:
You can only update existing elements — no new elements can be added. Each element is identified by a unique id from the preview response.

Step-by-Step Guide

Step 1: Create a Storyboard Preview

First, create a storyboard preview to generate the elements you will modify:
import axios from "axios";

const API_BASE_URL = "https://api.pictory.ai/pictoryapis";
const API_KEY = "YOUR_API_KEY";

async function createPreview() {
  const response = await axios.post(
    `${API_BASE_URL}/v2/video/storyboard`,
    {
      videoName: "my_editable_video",
      voiceOver: {
        enabled: true,
        aiVoices: [{ speaker: "Brian", speed: 100 }]
      },
      backgroundMusic: {
        enabled: true,
        autoMusic: true,
        volume: 0.1
      },
      scenes: [
        {
          story: "AI is transforming how educators and creators work. By automating tasks like content generation, visual design, and video editing, AI will save time and enhance consistency.",
          createSceneOnEndOfSentence: true
        },
        {
          story: "This allows creators to focus on higher-level strategies and ensures a cohesive brand presence. AI can analyze social media interactions to tailor content to individual learner needs.",
          createSceneOnEndOfSentence: true
        }
      ]
    },
    {
      headers: {
        "Content-Type": "application/json",
        Authorization: API_KEY
      }
    }
  );

  return response.data.data.jobId;
}

Step 2: Poll for Completion and Review Elements

Wait for the preview job to complete and review the renderParams.elements array to identify elements you want to update:
async function getPreviewElements(jobId) {
  while (true) {
    const response = await axios.get(
      `${API_BASE_URL}/v1/jobs/${jobId}`,
      { headers: { Authorization: API_KEY } }
    );

    const { status, renderParams } = response.data.data;

    if (status === "completed") {
      console.log(`Found ${renderParams.elements.length} elements:\n`);

      // Log each element's id, type, and key info
      renderParams.elements.forEach(el => {
        if (el.elementType === "backgroundElement") {
          console.log(`  [Background] id: ${el.id}`);
          console.log(`    type: ${el.type}, startTime: ${el.startTime}, duration: ${el.duration}`);
          console.log(`    url: ${el.visualUrl}\n`);
        } else if (el.elementType === "SceneText") {
          console.log(`  [Scene Text] id: ${el.id}`);
          console.log(`    text: ${el.text.substring(0, 60)}...`);
          console.log(`    startTime: ${el.startTime}, duration: ${el.duration}\n`);
        } else if (el.elementType === "layerItem" && el.type === "text") {
          console.log(`  [Title Text] id: ${el.id}`);
          console.log(`    text: ${el.text}`);
          console.log(`    startTime: ${el.startTime}, duration: ${el.duration}\n`);
        } else if (el.elementType === "audioElement") {
          console.log(`  [Audio] id: ${el.id}`);
          console.log(`    url: ${el.url.substring(0, 60)}...\n`);
        }
      });

      return renderParams;
    } else if (status === "failed") {
      throw new Error("Preview generation failed");
    }

    await new Promise(resolve => setTimeout(resolve, 3000));
  }
}

const jobId = await createPreview();
const renderParams = await getPreviewElements(jobId);
Example output:
Found 16 elements:

  [Audio] id: voiceOver
    url: https://audios-prod.pictorycontent.com/polly/production/p...

  [Audio] id: bgMusic
    url: https://tracks.melod.ie/track_versions/23517/MEL565_15_1...

  [Background] id: backgroundElement_20260306230202544742ecd40c041449dac21bb737c799201
    type: video, startTime: 0, duration: 7.6
    url: https://media.gettyimages.com/id/2262113992/video/professor-lectures...

  [Title Text] id: 2026030623020497002befca37e444fb880a281abadc0c756
    text: AI's Impact on Educators and Creators
    startTime: 0, duration: 7.6

  [Background] id: backgroundElement_202603062302035443dd4953bf1294b4283d4b8ba0259fd1a
    type: video, startTime: 7.6, duration: 11.1
    url: https://media.gettyimages.com/id/2253469260/video/businesses-are-using...

  [Scene Text] id: SceneText_202603062302035443dd4953bf1294b4283d4b8ba0259fd1a
    text: By <strong>automating</strong> <strong>tasks</strong> like <strong>...
    startTime: 7.6, duration: 11.1

  ...more elements

Step 3: Update Elements

Now update the elements you want to modify. You can update one or many elements in a single request.
When updating background visuals, url is used for final rendering and visualUrl is used for preview playback. visualUrl can be a lower-resolution video or even a thumbnail image for faster preview loading — set visualType to match (e.g., "image" for a thumbnail). type always reflects the media type of url. If you use the same URL for both, set type and visualType to the same value.

Replace a Background Visual

async function updateBackgroundVisual(jobId, renderParams, sceneStartTime, newUrl, mediaType = "video") {
  // Find the background element for the specified scene
  const bgElement = renderParams.elements.find(
    el => el.elementType === "backgroundElement" && el.startTime === sceneStartTime
  );

  if (!bgElement) {
    throw new Error(`No background element found at startTime ${sceneStartTime}`);
  }

  const response = await axios.put(
    `${API_BASE_URL}/v2/video/storyboard/${jobId}/elements`,
    [
      {
        id: bgElement.id,
        elementType: "backgroundElement",
        type: mediaType,
        startTime: bgElement.startTime,
        duration: bgElement.duration,
        url: newUrl,
        visualUrl: newUrl,
        visualType: mediaType
      }
    ],
    {
      headers: {
        "Content-Type": "application/json",
        Authorization: API_KEY
      }
    }
  );

  console.log("Background updated:", response.status === 204 ? "Success" : "Failed");
}

// Replace scene 1 background (startTime: 0) with a custom video
await updateBackgroundVisual(
  jobId, renderParams, 0,
  "https://your-cdn.com/product-demo.mp4",
  "video"
);

// Replace scene 2 background (startTime: 7.6) with an image
await updateBackgroundVisual(
  jobId, renderParams, 7.6,
  "https://your-cdn.com/team-photo.jpg",
  "image"
);

Update Scene Text

Scene text elements support <strong> tags for keyword highlighting. The highlighted words will be rendered using the element’s keywordColor.
When updating the text property, you must set textLines to an empty array []. The textLines array contains pre-calculated line break positions and character coordinates for the original text. Keeping stale textLines after changing the text will cause incorrect line breaks and character positioning. Setting it to [] ensures the system recalculates the layout during preview and render.
async function updateSceneText(jobId, renderParams, sceneStartTime, newText) {
  const textElement = renderParams.elements.find(
    el => el.elementType === "SceneText" && el.startTime === sceneStartTime
  );

  if (!textElement) {
    throw new Error(`No scene text element found at startTime ${sceneStartTime}`);
  }

  const response = await axios.put(
    `${API_BASE_URL}/v2/video/storyboard/${jobId}/elements`,
    [
      {
        id: textElement.id,
        elementType: "SceneText",
        type: "text",
        startTime: textElement.startTime,
        duration: textElement.duration,
        text: newText,
        textLines: [] // Clear textLines so line breaks are recalculated for the new text
      }
    ],
    {
      headers: {
        "Content-Type": "application/json",
        Authorization: API_KEY
      }
    }
  );

  console.log("Scene text updated:", response.status === 204 ? "Success" : "Failed");
}

// Update scene text with keyword highlighting
await updateSceneText(
  jobId, renderParams, 7.6,
  "By <strong>automating</strong> repetitive <strong>tasks</strong>, AI frees up time for <strong>creative work</strong> and <strong>strategic thinking</strong>."
);

Change Background Music

async function updateBackgroundMusic(jobId, newMusicUrl) {
  const response = await axios.put(
    `${API_BASE_URL}/v2/video/storyboard/${jobId}/elements`,
    [
      {
        id: "bgMusic",
        elementType: "audioElement",
        type: "audio",
        startTime: 0,
        duration: 100000,
        url: newMusicUrl
      }
    ],
    {
      headers: {
        "Content-Type": "application/json",
        Authorization: API_KEY
      }
    }
  );

  console.log("Music updated:", response.status === 204 ? "Success" : "Failed");
}

// Replace background music with a new track
await updateBackgroundMusic(jobId, "https://your-cdn.com/upbeat-track.mp3");

Update Multiple Elements at Once

async function updateMultipleElements(jobId, renderParams) {
  const elements = renderParams.elements;

  // Find elements to update
  const bg1 = elements.find(el => el.elementType === "backgroundElement" && el.startTime === 0);
  const bg2 = elements.find(el => el.elementType === "backgroundElement" && el.startTime === 7.6);
  const sceneText = elements.find(el => el.elementType === "SceneText" && el.startTime === 7.6);

  const response = await axios.put(
    `${API_BASE_URL}/v2/video/storyboard/${jobId}/elements`,
    [
      // Update scene 1 background
      {
        id: bg1.id,
        elementType: "backgroundElement",
        type: "video",
        startTime: bg1.startTime,
        duration: bg1.duration,
        url: "https://your-cdn.com/intro-video.mp4",
        visualUrl: "https://your-cdn.com/intro-video.mp4",
        visualType: "video"
      },
      // Update scene 2 background
      {
        id: bg2.id,
        elementType: "backgroundElement",
        type: "image",
        startTime: bg2.startTime,
        duration: bg2.duration,
        url: "https://your-cdn.com/product-shot.jpg",
        visualUrl: "https://your-cdn.com/product-shot.jpg",
        visualType: "image"
      },
      // Update scene text — set textLines to null so line breaks are recalculated
      {
        id: sceneText.id,
        elementType: "SceneText",
        type: "text",
        startTime: sceneText.startTime,
        duration: sceneText.duration,
        text: "<strong>AI automation</strong> enables creators to focus on what matters most.",
        textLines: []
      },
      // Update background music
      {
        id: "bgMusic",
        elementType: "audioElement",
        type: "audio",
        startTime: 0,
        duration: 100000,
        url: "https://your-cdn.com/corporate-music.mp3"
      }
    ],
    {
      headers: {
        "Content-Type": "application/json",
        Authorization: API_KEY
      }
    }
  );

  console.log("All elements updated:", response.status === 204 ? "Success" : "Failed");
}

await updateMultipleElements(jobId, renderParams);

Step 4: Render the Final Video

After updating elements, render the final video using the Render from Preview API:
async function renderVideo(jobId) {
  // Trigger render from the updated preview
  const response = await axios.put(
    `${API_BASE_URL}/v2/video/render/${jobId}`,
    {},
    {
      headers: {
        "Content-Type": "application/json",
        Authorization: API_KEY
      }
    }
  );

  const renderJobId = response.data.data.jobId;
  console.log("Render job started:", renderJobId);

  // Poll for render completion
  while (true) {
    const statusResponse = await axios.get(
      `${API_BASE_URL}/v1/jobs/${renderJobId}`,
      { headers: { Authorization: API_KEY } }
    );

    const { status, videoURL } = statusResponse.data.data;

    if (status === "completed") {
      console.log("Video ready:", videoURL);
      return videoURL;
    } else if (status === "failed") {
      throw new Error("Render failed");
    }

    console.log(`Render status: ${status}`);
    await new Promise(resolve => setTimeout(resolve, 10000));
  }
}

const videoUrl = await renderVideo(jobId);

Element Types Quick Reference

The following element types are returned in the renderParams.elements array from a completed storyboard preview job:
ElementelementTypetypeid PatternKey Updatable Properties
Voice-OveraudioElementaudiovoiceOverurl
Background MusicaudioElementaudiobgMusicurl, fade
Scene BackgroundbackgroundElementvideo / imagebackgroundElement_{uniqueId}url, visualUrl, visualType, type, loop, mute, objectMode, colorOverlay
Scene TextSceneTexttextSceneText_{uniqueId}text (supports <strong> for keyword highlighting), fontFamily, fontSize, fontColor, keywordColor, textBackgroundColor, textAlign
Title/Layer TextlayerItemtext{uniqueId}text, fontFamily, fontSize, fontColor, textAlign
Layer VisuallayerItemvideo / image{uniqueId}url, visualUrl, type, objectMode, opacity
Element IDs are auto-generated unique strings. Always retrieve the actual IDs from the preview job response — do not hardcode or construct them manually.
For a complete reference of every attribute on each element type — including styling, animation, position presets, and audio segments — see the Element Attributes Reference in the API documentation.

Next Steps