Skip to main content
This guide covers an advanced use-case where you design a project with well-defined layouts, subtitles, header text, and layered images or videos in the Pictory App, then use the API to create a reusable template. You can dynamically replace text, images, and videos at runtime to create new videos based on your project’s design.

What You’ll Learn

Create Templates

Convert Pictory projects into reusable API templates

Template Variables

Use placeholder variables for personalization at scale

Override Scenes

Modify existing scene content including subtitles and layers

Add New Scenes

Insert, replace, or append new scenes to templates

Dynamic Content

Replace text, images, and videos programmatically

Combine Approaches

Use variables and overrides together for maximum flexibility

Before You Begin

Make sure you have:
  • A Pictory account (create one here)
  • A Pictory API key (get one here)
  • A project created in Pictory with your desired design and layout
  • Node.js or Python installed on your machine
npm install axios

Workflow Overview

Step-by-Step Guide

Step 1: Create a Project in Pictory App

Design your video project in the Pictory App with all the elements you want to reuse:
  1. Open app.pictory.ai and create a new project
  2. Design your scenes with:
    • Background visuals (images or videos)
    • Text layers (headers, titles, captions)
    • Subtitles for voice-over narration
    • Layouts that define visual composition
  3. Add template variables using {{variableName}} syntax for dynamic content
  4. Preview and finalize your project design
Pictory storyboard editor with scenes showing layered text, background visuals, and subtitles

Pictory App storyboard editor showing a multi-scene project with text layers, background videos, and subtitles

The example project “art of mindfulness” contains 4 scenes with:
  • Background videos and images
  • Header text layers (“Present Moment Awareness”, “The Art of Mindfulness”)
  • Subtitles that will be narrated as voice-over
  • Custom layouts and styling
Design Tips for Templates:
  • Use consistent layouts across scenes for professional results
  • Add placeholder text using {{variables}} for dynamic personalization
  • Test your design with different content lengths to ensure proper fitting

Step 2: Download the Project File

Once your project is ready, download it as a .pictai file:
  1. Go to your Projects Page
  2. Find your project and click on it to open details
  3. Click the download icon next to the project file
  4. Save the .pictai file to your computer
Pictory projects page with download button highlighted for the project file

Pictory Projects page showing the download option for a project file

The .pictai file contains your complete project configuration including scenes, layers, audio settings, and visual assets.

Step 3: Create a Template from the Project

Upload your .pictai file to the Template API to create a reusable template:
POST https://api.pictory.ai/pictoryapis/v1/templates
Content-Type: application/octet-stream
Authorization: YOUR_API_KEY
import axios from "axios";
import fs from "fs";

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

async function createTemplateFromProject(filePath) {
  console.log("Creating template from project file...");

  const fileBuffer = fs.readFileSync(filePath);

  const response = await axios.post(
    `${API_BASE_URL}/v1/templates`,
    fileBuffer,
    {
      headers: {
        "Content-Type": "application/octet-stream",
        Authorization: API_KEY,
      },
    }
  );

  const template = response.data;
  console.log("Template created successfully!");
  console.log("Template ID:", template.templateId);
  console.log("Template Name:", template.name);
  console.log("Scenes:", template.scenes.length);

  return template;
}

// Upload your .pictai file
createTemplateFromProject("art of mindfulness.pictai")
  .then(template => console.log("\nTemplate ready for use!"))
  .catch(error => console.error("Error:", error.message));

Step 4: Get Template Details

Retrieve your template details to see the structure, scene IDs, layer IDs, and available customization points:
GET https://api.pictory.ai/pictoryapis/v1/templates/{templateId}
Authorization: YOUR_API_KEY
async function getTemplateDetails(templateId) {
  const response = await axios.get(
    `${API_BASE_URL}/v1/templates/${templateId}`,
    {
      headers: { Authorization: API_KEY },
    }
  );

  const template = response.data;

  console.log("Template Details:");
  console.log("Name:", template.name);
  console.log("Language:", template.language);
  console.log("\nScenes:");

  template.scenes.forEach((scene, index) => {
    console.log(`\n  Scene ${index + 1} (ID: ${scene.sceneId})`);

    // Show subtitles
    scene.subtitles?.forEach(sub => {
      console.log(`    Subtitle: "${sub.text.substring(0, 50)}..."`);
    });

    // Show layers
    scene.layers?.flat().forEach(layer => {
      console.log(`    Layer (${layer.type}): ${layer.layerId}`);
      if (layer.text) console.log(`      Text: "${layer.text}"`);
    });
  });

  return template;
}

getTemplateDetails("20260109004915031f181efb6b65749d7b5eae230cdc9c5df");
Example Template Response:
{
  "templateId": "20260109004915031f181efb6b65749d7b5eae230cdc9c5df",
  "name": "art of mindfulness",
  "language": "en",
  "published": true,
  "backgroundMusic": {
    "enabled": true,
    "musicUrl": "https://tracks.melod.ie/track_versions/...",
    "volume": 1
  },
  "scenes": [
    {
      "sceneId": "20260109004556808c640d883dc9a4bfe983cba4eadb7a0f1",
      "subtitles": [
        {
          "text": "The art of mindfulness is the practice of paying full attention to the present moment with calmness and without judgment."
        }
      ],
      "layers": [
        [
          {
            "layerId": "20260109004557054ef9462b6ae5f4d81afdafeb85ed369f1",
            "type": "video",
            "url": "https://media.gettyimages.com/..."
          },
          {
            "layerId": "202601090045570540d50b1852611498aa9f51b69131648ad",
            "type": "text",
            "text": "Present Moment Awareness"
          },
          {
            "layerId": "2026010900455705400270d33493b471d9b7e722bd804f06a",
            "type": "text",
            "text": "The Art of Mindfulness"
          }
        ]
      ]
    },
    {
      "sceneId": "20260109004556808ff7ed6167ea1471e8bc034c4844bb56d",
      "subtitles": [
        {
          "text": "It helps you notice your thoughts, feelings, and surroundings..."
        }
      ]
    }
  ]
}

Using Template Variables

Template variables provide a simple way to personalize videos without modifying the underlying structure. Variables defined in your project using {{variableName}} syntax are automatically detected and can be replaced when rendering videos.

How Template Variables Work

When you create a template from a project that contains {{variableName}} placeholders, these become customizable variables. The template response includes a variables object showing all detected placeholders.

Variable Syntax

SyntaxDescription
{{Name}}Simple variable for personalization
{{CompanyName}}Variable for company-specific content
{{ProductName}}Variable for product names
{{Date}}Variable for dynamic dates
Variables can be placed in:
  • Scene subtitles/narration text
  • Text layers (headers, titles, captions)
  • Any text element in your project

Example: Template with Variables

If your template contains text like:
Scene 1: "Welcome {{Name}} to {{CompanyName}}!"
Scene 2: "Your personalized journey starts on {{Date}}."
The template response will include:
{
  "templateId": "your_template_id",
  "name": "Welcome Template",
  "variables": {
    "Name": "NAME",
    "CompanyName": "COMPANY_NAME",
    "Date": "DATE"
  }
}

Render Video with Variable Substitution

Use the variables object to replace placeholders with actual values:
const response = await axios.post(
  `${API_BASE_URL}/v2/video/storyboard/render`,
  {
    templateId: "your_template_id",
    videoName: "welcome_john_acme",
    variables: {
      Name: "John Smith",
      CompanyName: "Acme Corporation",
      Date: "January 15, 2026"
    }
  },
  {
    headers: {
      "Content-Type": "application/json",
      Authorization: API_KEY,
    },
  }
);

console.log("Job ID:", response.data.data.jobId);
This transforms:
  • {{Name}}John Smith
  • {{CompanyName}}Acme Corporation
  • {{Date}}January 15, 2026

Generating Personalized Videos at Scale

The real power of template variables is generating personalized videos for many recipients:
const recipients = [
  { name: "John Smith", company: "Acme Corp", date: "January 15" },
  { name: "Sarah Wilson", company: "Tech Inc", date: "January 16" },
  { name: "Michael Brown", company: "Global Ltd", date: "January 17" },
];

async function createPersonalizedVideos() {
  const jobs = [];

  for (const recipient of recipients) {
    console.log(`Creating video for ${recipient.name}...`);

    const response = await axios.post(
      `${API_BASE_URL}/v2/video/storyboard/render`,
      {
        templateId: TEMPLATE_ID,
        videoName: `welcome_${recipient.name.toLowerCase().replace(" ", "_")}`,
        variables: {
          Name: recipient.name,
          CompanyName: recipient.company,
          Date: recipient.date
        }
      },
      { headers: { "Content-Type": "application/json", Authorization: API_KEY } }
    );

    jobs.push({
      recipient: recipient.name,
      jobId: response.data.data.jobId
    });
    console.log(`Started job: ${response.data.data.jobId}`);
  }

  return jobs;
}

createPersonalizedVideos();

Common Variable Use Cases

Personalized Greetings:
{
  "templateId": "greeting_template_id",
  "videoName": "birthday_greeting",
  "variables": {
    "RecipientName": "John",
    "Occasion": "Birthday",
    "SenderName": "The Marketing Team"
  }
}
Product Promotions:
{
  "templateId": "promo_template_id",
  "videoName": "winter_sale",
  "variables": {
    "ProductName": "Winter Collection",
    "Discount": "25%",
    "PromoCode": "WINTER25"
  }
}
Customer Onboarding:
{
  "templateId": "onboarding_template_id",
  "videoName": "onboarding_acme",
  "variables": {
    "CompanyName": "Acme Corp",
    "AccountManager": "Mike Wilson",
    "TrialDays": "14"
  }
}
Variable Naming Tips:
  • Use descriptive names like {{CustomerName}} instead of {{n}}
  • Variables are case-sensitive: {{Name}} and {{name}} are different
  • Avoid spaces in variable names: use {{FirstName}} not {{First Name}}

Combining Variables with Template Overrides

You can use both variables for simple text substitution and templateOverride for structural changes in the same request:
{
  "templateId": "your_template_id",
  "videoName": "combined_example",
  "variables": {
    "Name": "John Smith",
    "CompanyName": "Acme Corp"
  },
  "scenes": [
    {
      "templateOverride": {
        "scenePosition": 1,
        "layers": [
          [
            {
              "layerId": "header_layer_id",
              "type": "image",
              "url": "https://your-cdn.com/acme-logo.png"
            }
          ]
        ]
      }
    }
  ]
}
This approach allows you to:
  • Replace text variables globally with the variables object
  • Override specific layers (like logos or background videos) with templateOverride
  • Add, delete, or modify scenes while maintaining personalization

Using Template Overrides

The templateOverride object allows you to customize existing scenes, add new scenes, or modify layers when creating videos from your template.

Understanding Template Override Structure

{
  "templateId": "your_template_id",
  "scenes": [
    {
      "templateOverride": {
        // Reference existing scene by ID or position
        "sceneId": "existing_scene_id",      // OR
        "scenePosition": 1,                   // 1-based position

        // Modify existing scene content
        "subtitles": [...],                   // Override subtitles
        "layers": [...],                      // Override layers

        // Scene operations
        "deleteScene": true,                  // Delete this scene
        "copyScene": true                     // Duplicate this scene
      }
    }
  ]
}

Override Existing Scene Subtitles

Replace the subtitle text of an existing scene:
const response = await axios.post(
  `${API_BASE_URL}/v2/video/storyboard/render`,
  {
    templateId: "20260109004915031f181efb6b65749d7b5eae230cdc9c5df",
    videoName: "mindfulness_customized",
    scenes: [
      {
        templateOverride: {
          sceneId: "20260109004556808c640d883dc9a4bfe983cba4eadb7a0f1",
          subtitles: [
            {
              text: "Welcome to the journey of self-awareness and inner peace through mindfulness meditation."
            }
          ]
        }
      }
    ]
  },
  { headers: { Authorization: API_KEY, "Content-Type": "application/json" } }
);

Override Scene Layers

Replace text, images, or videos in scene layers:
{
  "templateId": "your_template_id",
  "scenes": [
    {
      "templateOverride": {
        "sceneId": "20260109004556808c640d883dc9a4bfe983cba4eadb7a0f1",
        "layers": [
          [
            {
              "layerId": "20260109004557054ef9462b6ae5f4d81afdafeb85ed369f1",
              "type": "video",
              "url": "https://your-cdn.com/new-background-video.mp4"
            },
            {
              "layerId": "202601090045570540d50b1852611498aa9f51b69131648ad",
              "type": "text",
              "text": "New Header Text"
            },
            {
              "layerId": "2026010900455705400270d33493b471d9b7e722bd804f06a",
              "type": "text",
              "text": "Updated Title"
            }
          ]
        ]
      }
    }
  ]
}

Layer Override Options

PropertyTypeDescription
layerIdstringID of the layer to modify
typestringLayer type: text, image, or video
textstringNew text content (for text layers)
urlstringNew media URL (for image/video layers)
styleobjectOverride text styling (font, color, size, etc.)
styleIdstringApply a predefined text style
styleNamestringApply a text style by name
deleteLayerbooleanRemove this layer from the scene

Override with Custom Text Styles

{
  "templateOverride": {
    "sceneId": "your_scene_id",
    "layers": [
      [
        {
          "layerId": "your_layer_id",
          "type": "text",
          "text": "Styled Header",
          "style": {
            "fontFamily": "Montserrat",
            "fontSize": 48,
            "color": "#FF5722",
            "position": "top-center",
            "alignment": "center",
            "decorations": ["bold"]
          }
        }
      ]
    ]
  }
}

Delete or Copy Scenes

Delete a scene:
{
  "templateOverride": {
    "sceneId": "scene_to_delete",
    "deleteScene": true
  }
}
Duplicate a scene:
{
  "templateOverride": {
    "scenePosition": 2,
    "copyScene": true
  }
}

Adding New Scenes to Templates

You can insert new scenes into your template at specific positions. New scenes require content from one of these sources: story, storyCoPilot, blogUrl, pptUrl, audioUrl, or videoUrl.

Scene Positioning Options

PropertyDescription
newScenePositionInsert at specific position (1-based)
insertAfterSceneIdInsert after a specific scene ID
insertBeforeSceneIdInsert before a specific scene ID
replaceSceneIdReplace an existing scene by ID
replaceScenePositionReplace scene at position (1-based)

Inherit Style from Existing Scene

When adding new scenes, you can inherit the layout and styling from an existing scene:
PropertyDescription
baseSceneIdUse this scene’s style as the base
baseScenePositionUse scene at this position as style base

Example: Insert a New Scene After Scene 1

{
  "templateId": "20260109004915031f181efb6b65749d7b5eae230cdc9c5df",
  "videoName": "mindfulness_with_new_scene",
  "scenes": [
    {
      "templateOverride": {
        "insertAfterSceneId": "20260109004556808c640d883dc9a4bfe983cba4eadb7a0f1",
        "baseSceneId": "20260109004556808c640d883dc9a4bfe983cba4eadb7a0f1"
      },
      "story": "Mindfulness helps us connect with our inner self and find peace in everyday moments."
    }
  ]
}

Example: Replace Scene 3 with New Content

const response = await axios.post(
  `${API_BASE_URL}/v2/video/storyboard/render`,
  {
    templateId: "20260109004915031f181efb6b65749d7b5eae230cdc9c5df",
    videoName: "mindfulness_replaced_scene",
    scenes: [
      {
        templateOverride: {
          replaceScenePosition: 3,
          baseScenePosition: 1  // Use Scene 1's style
        },
        story: "Practice deep breathing: inhale for 4 counts, hold for 4, exhale for 4. This simple technique calms your nervous system instantly."
      }
    ]
  },
  { headers: { Authorization: API_KEY, "Content-Type": "application/json" } }
);

Example: Add Scene at Specific Position

{
  "templateId": "your_template_id",
  "scenes": [
    {
      "templateOverride": {
        "newScenePosition": 2,
        "baseSceneId": "existing_scene_id"
      },
      "story": "This new scene will appear at position 2, pushing existing scenes down."
    }
  ]
}

New Scene Content Sources

When adding new scenes, provide content using one of these properties:
PropertyDescription
storyPlain text content for the scene narration
storyCoPilotAI-generated content based on a prompt
blogUrlURL to a blog article to extract content from
pptUrlURL to a PowerPoint presentation
audioUrlURL to an audio file for transcription
videoUrlURL to a video file for repurposing
Example with Story CoPilot:
{
  "templateOverride": {
    "insertAfterSceneId": "scene_123",
    "baseSceneId": "scene_123"
  },
  "storyCoPilot": {
    "videoType": "Explainer",
    "prompt": "Explain the benefits of morning meditation for productivity",
    "tone": "friendly",
    "duration": 30
  }
}
Example with Blog URL:
{
  "templateOverride": {
    "newScenePosition": 3,
    "baseScenePosition": 1
  },
  "blogUrl": "https://example.com/article-about-mindfulness"
}

Complete Example: Dynamic Video Generation

Here’s a complete example that demonstrates overriding existing content and adding new scenes:
import axios from "axios";

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

async function createCustomizedVideo() {
  console.log("Creating customized video from template...");

  const response = await axios.post(
    `${API_BASE_URL}/v2/video/storyboard/render`,
    {
      templateId: TEMPLATE_ID,
      videoName: "mindfulness_personalized",

      // Override voice settings
      voiceOver: {
        enabled: true,
        aiVoices: [{ speaker: "Brian", speed: 100 }]
      },

      scenes: [
        // Override Scene 1: Update subtitles and text layers
        {
          templateOverride: {
            scenePosition: 1,
            subtitles: [
              {
                text: "Welcome to your personal mindfulness journey. Let's explore the art of being present."
              }
            ],
            layers: [
              [
                {
                  layerId: "202601090045570540d50b1852611498aa9f51b69131648ad",
                  type: "text",
                  text: "Your Journey Begins"
                },
                {
                  layerId: "2026010900455705400270d33493b471d9b7e722bd804f06a",
                  type: "text",
                  text: "Mindfulness for Beginners"
                }
              ]
            ]
          }
        },

        // Add new scene after Scene 1 using AI-generated content
        {
          templateOverride: {
            insertAfterSceneId: "20260109004556808c640d883dc9a4bfe983cba4eadb7a0f1",
            baseSceneId: "20260109004556808c640d883dc9a4bfe983cba4eadb7a0f1"
          },
          storyCoPilot: {
            videoType: "Explainer",
            prompt: "Describe a simple 1-minute breathing exercise for stress relief",
            tone: "calm",
            duration: 15
          }
        },

        // Override Scene 2: Change subtitle text
        {
          templateOverride: {
            scenePosition: 2,
            subtitles: [
              {
                text: "Notice your breath. Feel the air flowing in and out. This simple act anchors you to the present."
              }
            ]
          }
        },

        // Replace Scene 4 with new story content
        {
          templateOverride: {
            replaceScenePosition: 4,
            baseScenePosition: 1
          },
          story: "As you continue this practice daily, you'll discover a deeper sense of calm and clarity in everything you do."
        }
      ]
    },
    {
      headers: {
        "Content-Type": "application/json",
        Authorization: API_KEY,
      },
    }
  );

  const jobId = response.data.data.jobId;
  console.log("Video creation started!");
  console.log("Job ID:", jobId);

  return jobId;
}

async function waitForVideo(jobId) {
  console.log("\nMonitoring video creation...");

  while (true) {
    const response = await axios.get(
      `${API_BASE_URL}/v1/jobs/${jobId}`,
      { headers: { Authorization: API_KEY } }
    );

    const status = response.data.data.status;
    console.log("Status:", status);

    if (status === "completed") {
      console.log("\nVideo is ready!");
      console.log("Video URL:", response.data.data.videoURL);
      return response.data;
    } else if (status === "failed") {
      throw new Error("Video creation failed");
    }

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

// Run the workflow
createCustomizedVideo()
  .then(jobId => waitForVideo(jobId))
  .catch(error => console.error("Error:", error));

Template Override Reference

Referencing Existing Scenes

PropertyTypeDescription
sceneIdstringReference scene by unique ID
scenePositionnumberReference scene by position (1-based)
Use either sceneId or scenePosition, not both. Scene IDs are more reliable as positions can change.

Adding New Scenes

PropertyTypeDescription
newScenePositionnumberInsert at this position (1-based)
insertAfterSceneIdstringInsert after this scene
insertBeforeSceneIdstringInsert before this scene
replaceSceneIdstringReplace this scene entirely
replaceScenePositionnumberReplace scene at this position

Styling New Scenes

PropertyTypeDescription
baseSceneIdstringInherit style from this scene
baseScenePositionnumberInherit style from scene at position

Scene Operations

PropertyTypeDescription
deleteScenebooleanRemove this scene from output
copyScenebooleanDuplicate this scene

Best Practices

  • Create modular scenes that work independently
  • Use consistent layer naming for easy identification
  • Design with various content lengths in mind
  • Test templates with different override combinations
  • Scene IDs are stable identifiers that don’t change
  • Positions shift when scenes are added or removed
  • Store and reference scene IDs from the Get Template response
  • Use positions only when the template structure is fixed
  • Fetch template details to verify scene and layer IDs exist
  • Check that media URLs are accessible before submission
  • Test with small changes before complex overrides
  • Monitor job status and handle failures gracefully
  • Use optimized image and video formats
  • Host media on reliable CDNs with good latency
  • Match video dimensions to template aspect ratio
  • Keep file sizes reasonable for faster processing

Troubleshooting

Problem: API returns error about invalid scene or layer ID.Solution:
  • Fetch fresh template details using Get Template API
  • Verify the ID matches exactly (case-sensitive)
  • Check that the scene/layer hasn’t been removed
  • Use scenePosition if scene IDs have changed
Problem: Added scene doesn’t show in rendered video.Solution:
  • Ensure you’ve provided a content source (story, blogUrl, etc.)
  • Verify positioning parameters are correct
  • Check that baseSceneId or baseScenePosition exists
  • Review job status for any processing errors
Problem: Text or media changes not reflected in output.Solution:
  • Confirm the layerId matches template exactly
  • Ensure type matches the layer type in template
  • Check that media URLs are publicly accessible
  • Verify the layer isn’t being deleted elsewhere
Problem: New scene doesn’t match expected styling.Solution:
  • Verify baseSceneId or baseScenePosition is valid
  • Check that the base scene has the styling you expect
  • Only one of baseSceneId or baseScenePosition should be used
  • Ensure the base scene isn’t being deleted in the same request

Next Steps

Explore more advanced features to enhance your template-based workflows:

API Reference