> ## Documentation Index
> Fetch the complete documentation index at: https://heygen-1fa696a7.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Building a Studio Template

> Create a reusable template in HeyGen Studio, mark its script, voice, avatar, and background as variables, then fill those variables and render videos over the API.

## Overview

A template is a video you design once in HeyGen Studio and reuse by swapping in different values. You mark the parts you want to change per video, the script, the voice, the avatar, the background, as **variables**, give each one a name, then use the API to fill those variables and generate a new video for every set of values.

One idea ties Studio and the API together: **the name you give a variable in Studio is the exact key you send in the API.** Name a variable `name` in the editor, and you fill it with `"name": { ... }` in your request.

This guide walks the full loop: build the template in Studio, then drive it from the API.

<Note>
  For the complete variable-type schema (text, image, video, audio, voice, character), asset inputs, scenes, and every generate option, see the [Studio Template reference](/templates).
</Note>

## Prerequisites

* A HeyGen account with **Studio** access ([app.heygen.com](https://app.heygen.com/avatar/studio)).
* An **API key**.

## Part 1 — Build the template in Studio

You'll build a one-scene welcome video with four variables: the viewer's **name** (text), the **voiceover**, the **presenter** (avatar), and the **background**.

<Steps>
  <Step title="Open the template editor">
    In HeyGen Studio, create a new video (or open an existing one) and lay out your avatar, script, and background as usual. This layout is what every generated video will share.

    <Frame>
      <img src="https://mintcdn.com/heygen-1fa696a7/4WbqL3058bMfOsMy/images/templates/02-editor-overview.png?fit=max&auto=format&n=4WbqL3058bMfOsMy&q=85&s=7a7b865dc2d9b5c53438d1c0859a0e06" alt="HeyGen Studio editor with an avatar on the canvas" width="2548" height="1258" data-path="images/templates/02-editor-overview.png" />
    </Frame>
  </Step>

  <Step title="Turn script text into a variable">
    In the **Script** pane, wrap any word you want to change per video in double braces. Here the script is `Hi {{name}}, Welcome to HeyGen`, so `name` becomes a text variable you fill at generate time.

    <Frame>
      <img src="https://mintcdn.com/heygen-1fa696a7/4WbqL3058bMfOsMy/images/templates/03-script-variable.png?fit=max&auto=format&n=4WbqL3058bMfOsMy&q=85&s=f9da573bc4f8bb14f41c08fcede984ad" alt="Script with the name token typed as a variable" width="1540" height="912" data-path="images/templates/03-script-variable.png" />
    </Frame>

    <Tip>The text between the braces is the variable name, and it is the exact key you send in the API.</Tip>
  </Step>

  <Step title="Make the voice a variable">
    Open the three-dots menu on the script and choose **API Variable**. Name it `voiceover` and click **Save**. The voice for the scene can now be set per request.

    <Frame>
      <img src="https://mintcdn.com/heygen-1fa696a7/4WbqL3058bMfOsMy/images/templates/05-voice-name-popover.png?fit=max&auto=format&n=4WbqL3058bMfOsMy&q=85&s=72ba4dfef66d11b5a7dbf00c0a326e05" alt="Three-dots menu with API Variable, naming the voice variable voiceover" width="1889" height="925" data-path="images/templates/05-voice-name-popover.png" />
    </Frame>
  </Step>

  <Step title="Make the avatar a variable">
    Select the avatar on the canvas, then click the **API** button in the hover toolbar. Name it `presenter` and **Save**. You can now swap the avatar per video.

    <Frame>
      <img src="https://mintcdn.com/heygen-1fa696a7/4WbqL3058bMfOsMy/images/templates/07-avatar-name-popover.png?fit=max&auto=format&n=4WbqL3058bMfOsMy&q=85&s=7e0e0b88f9887b1c76af3e83c2758f52" alt="Avatar selected with the API button, naming the character variable presenter" width="2522" height="1256" data-path="images/templates/07-avatar-name-popover.png" />
    </Frame>
  </Step>

  <Step title="Make the background a variable">
    Click the background directly (not the avatar), then click the **API** button in its toolbar. Name it `background` and **Save**. The scene background can now be set per request.

    <Frame>
      <img src="https://mintcdn.com/heygen-1fa696a7/4WbqL3058bMfOsMy/images/templates/09-background-name-popover.png?fit=max&auto=format&n=4WbqL3058bMfOsMy&q=85&s=659c47ea2637509de1d02de4ac17ebd9" alt="Background selected with the API button, naming the image variable background" width="2512" height="1262" data-path="images/templates/09-background-name-popover.png" />
    </Frame>
  </Step>

  <Step title="Save the template">
    Click **Done Editing**. Your template is now API-ready and will appear in the templates list.

    <Frame>
      <img src="https://mintcdn.com/heygen-1fa696a7/4WbqL3058bMfOsMy/images/templates/10-save-template.png?fit=max&auto=format&n=4WbqL3058bMfOsMy&q=85&s=5527f4e7298d6b97b1cee2594c0e9384" alt="Template saved successfully" width="2512" height="1247" data-path="images/templates/10-save-template.png" />
    </Frame>
  </Step>
</Steps>

You now have a template with four named variables, `name`, `voiceover`, `presenter`, and `background`. Those names are exactly the keys you use next.

## Part 2 — Generate videos from the API

The flow is four calls: find the template, read its variables, fill them, then poll for the finished video.

<Steps>
  <Step title="Find your template">
    List the API-ready templates in your workspace and grab the `id` of the one you built.

    ```bash theme={null}
    curl -X GET "https://api.heygen.com/v3/templates?limit=10" \
      -H "x-api-key: YOUR_API_KEY"
    ```

    ```json Response theme={null}
    {
      "data": [
        {
          "id": "8ce3768236cd4e8a9ee8d88aad3537ae",
          "name": "DemoTemp",
          "thumbnail_url": "https://resource2.heygen.ai/image/.../original.png",
          "aspect_ratio": "16:9",
          "created_at": 1784323047,
          "updated_at": 1784323073
        }
      ],
      "has_more": false,
      "next_token": null
    }
    ```
  </Step>

  <Step title="Read the variable schema">
    Fetch the template to see the variables you named in Studio, with their types and current defaults.

    ```bash theme={null}
    curl -X GET "https://api.heygen.com/v3/templates/8ce3768236cd4e8a9ee8d88aad3537ae" \
      -H "x-api-key: YOUR_API_KEY"
    ```

    ```json Response theme={null}
    {
      "data": {
        "id": "8ce3768236cd4e8a9ee8d88aad3537ae",
        "name": "DemoTemp",
        "aspect_ratio": "16:9",
        "variables": {
          "name":       { "type": "text",      "content": "" },
          "voiceover":  { "type": "voice",     "voice_id": "330290724a1b470fb63153f34d4c0183" },
          "presenter":  { "type": "character", "character_id": "Annie_expressive5_public", "character_type": "avatar" },
          "background": { "type": "image",     "asset": { "type": "url", "url": "https://..." }, "fit": "none" }
        },
        "scenes": [
          {
            "scene_id": "BSueuHJY",
            "script": "Hi {{name}}, Welcome to HeyGen",
            "variables": [
              { "name": "name",       "variable_type": "text" },
              { "name": "voiceover",  "variable_type": "voice" },
              { "name": "presenter",  "variable_type": "character" },
              { "name": "background", "variable_type": "image" }
            ]
          }
        ]
      }
    }
    ```

    <Tip>Each variable comes back **in the same shape a generate request accepts**, so you can take this object, swap the values, and send it straight back.</Tip>
  </Step>

  <Step title="Fill the variables and generate">
    Send the variables keyed by the names you set in Studio. Here we personalize `name` to "Alex" and set a new background.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST "https://api.heygen.com/v3/templates/8ce3768236cd4e8a9ee8d88aad3537ae" \
        -H "x-api-key: YOUR_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "title": "Welcome video for Alex",
          "variables": {
            "name":       { "type": "text",  "content": "Alex" },
            "voiceover":  { "type": "voice", "voice_id": "330290724a1b470fb63153f34d4c0183" },
            "presenter":  { "type": "character", "character_id": "Annie_expressive5_public", "character_type": "avatar" },
            "background": { "type": "image", "asset": { "type": "url", "url": "https://example.com/office.jpg" }, "fit": "cover" }
          }
        }'
      ```

      ```python Python theme={null}
      import requests

      resp = requests.post(
          "https://api.heygen.com/v3/templates/8ce3768236cd4e8a9ee8d88aad3537ae",
          headers={"x-api-key": "YOUR_API_KEY"},
          json={
              "title": "Welcome video for Alex",
              "variables": {
                  "name":       {"type": "text", "content": "Alex"},
                  "voiceover":  {"type": "voice", "voice_id": "330290724a1b470fb63153f34d4c0183"},
                  "presenter":  {"type": "character", "character_id": "Annie_expressive5_public", "character_type": "avatar"},
                  "background": {"type": "image", "asset": {"type": "url", "url": "https://example.com/office.jpg"}, "fit": "cover"},
              },
          },
      )
      video_id = resp.json()["data"]["id"]
      print(video_id)
      ```

      ```javascript Node theme={null}
      const res = await fetch(
        "https://api.heygen.com/v3/templates/8ce3768236cd4e8a9ee8d88aad3537ae",
        {
          method: "POST",
          headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" },
          body: JSON.stringify({
            title: "Welcome video for Alex",
            variables: {
              name:       { type: "text", content: "Alex" },
              voiceover:  { type: "voice", voice_id: "330290724a1b470fb63153f34d4c0183" },
              presenter:  { type: "character", character_id: "Annie_expressive5_public", character_type: "avatar" },
              background: { type: "image", asset: { type: "url", url: "https://example.com/office.jpg" }, fit: "cover" },
            },
          }),
        }
      );
      const { data } = await res.json();
      console.log(data.id);
      ```
    </CodeGroup>

    ```json Response theme={null}
    {
      "data": {
        "id": "cb0009e7367b4a14b12981c4b6a85cb2",
        "status": "pending",
        "title": "Welcome video for Alex",
        "created_at": 1784325403
      }
    }
    ```

    <Note>Only include the variables you want to change, any you omit keep their template defaults. Add `"test": true` to render a lower-quality test video with no credit charge while you wire things up.</Note>
  </Step>

  <Step title="Poll for the finished video">
    Generation is asynchronous. Poll the video by the `id` returned above until its `status` is `completed`, then read `video_url`.

    ```bash theme={null}
    curl -X GET "https://api.heygen.com/v3/videos/cb0009e7367b4a14b12981c4b6a85cb2" \
      -H "x-api-key: YOUR_API_KEY"
    ```

    ```json Response theme={null}
    {
      "data": {
        "id": "cb0009e7367b4a14b12981c4b6a85cb2",
        "status": "completed",
        "video_url": "https://files2.heygen.ai/.../cb0009e7....mp4",
        "duration": 2.03
      }
    }
    ```

    <Tip>Instead of polling, set a `callback_url` on the generate request to be notified when the video is ready. See [Webhooks](/docs/webhooks).</Tip>
  </Step>
</Steps>

## Troubleshooting

<AccordionGroup>
  <Accordion title="My template isn't in the list">
    Only templates that have **at least one variable defined** appear in `GET /v3/templates`, and the detail endpoint supports templates built in the current editor (draft version 4). Make sure you marked and saved at least one variable in Studio.
  </Accordion>

  <Accordion title="I get an unknown-variable error">
    The keys in your `variables` object must match the variable names you set in Studio exactly (case-sensitive). Read `GET /v3/templates/{template_id}` to confirm the names.
  </Accordion>

  <Accordion title="A text variable comes back empty">
    A `text` variable can be empty in the schema (an unfilled default), but it must be non-empty when you generate.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Studio Template reference" href="/templates" icon="book">
    Every variable type, asset input, scenes, and generate options.
  </Card>

  <Card title="Webhooks" href="/docs/webhooks" icon="bell">
    Get notified when a video finishes instead of polling.
  </Card>
</CardGroup>
