What You Can Do
With Typecast and Google Sheets, you can:
- Batch generate TTS - Convert multiple texts to speech with one click
- Automate workflows - Process data and generate audio automatically
- No coding required - Use the simple custom menu interface
- Team collaboration - Share sheets with team members for collaborative audio production
- Auto-save to Drive - Generated audio files are automatically stored in Google Drive
Prerequisites
Before you start, make sure you have:
- Google Account - Access to Google Sheets
- Typecast API Key - Get yours here
Setup Guide
Step 1: Create Your Spreadsheet
- Open Google Sheets
- Create a new blank spreadsheet
- Set up your columns with headers in the first row:
- Column A:
Text- The text you want to convert to speech - Column B:
Voice- Voice ID (e.g.,tc_66aca22c7d31e45ff05ff418) or Voice Name from the Voice Library - Column C:
Language- Language code (eng,kor,jpn,cmn, etc.) - Column D:
Audio URL- Leave empty (will be auto-filled)
- Column A:
- Add your data starting from the second row (row 1 is reserved for headers)
Step 2: Open Apps Script Editor
- Click Extensions in the menu bar
- Select Apps Script
- A new tab will open with the Apps Script editor
Step 3: Add the Typecast Integration Code
- Delete the default
myFunction()code - Copy and paste the following code:
📋 Click to view the complete Apps Script code
// Typecast API Configuration
const TYPECAST_API_KEY = "YOUR_API_KEY_HERE"; // Replace with your actual API key
const TYPECAST_API_URL = "https://api.typecast.ai/v1/text-to-speech";
/**
* Creates custom menu when spreadsheet opens
*/
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("🎙️ Typecast TTS")
.addItem("Generate All Audio", "generateAllAudio")
.addItem("Generate Selected Rows", "generateSelectedAudio")
.addSeparator()
.addItem("Clear Audio URLs", "clearAudioUrls")
.addToUi();
}
/**
* Generates audio for all rows with text
*/
function generateAllAudio() {
const sheet = SpreadsheetApp.getActiveSheet();
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
SpreadsheetApp.getUi().alert("No data to process!");
return;
}
// Get all data at once for better performance
const dataRange = sheet.getRange(2, 1, lastRow - 1, 4);
const data = dataRange.getValues();
let successCount = 0;
let errorCount = 0;
// Process each row
data.forEach((row, index) => {
const text = row[0];
const voiceNameOrId = row[1];
const language = row[2] || "eng"; // Default to English if not specified
const rowNumber = index + 2;
// Skip if text or voice name/ID is empty
if (!text || !voiceNameOrId) {
return;
}
// Skip if audio URL already exists
if (row[3]) {
return;
}
try {
const audioUrl = callTypecastAPI(text, voiceNameOrId, language);
sheet.getRange(rowNumber, 4).setValue(audioUrl);
successCount++;
// Add a small delay to avoid rate limiting
Utilities.sleep(500);
} catch (error) {
sheet.getRange(rowNumber, 4).setValue("Error: " + error.message);
errorCount++;
}
});
SpreadsheetApp.getUi().alert(
`Generation complete!\n\nSuccess: ${successCount}\nErrors: ${errorCount}`,
);
}
/**
* Generates audio for selected rows only
*/
function generateSelectedAudio() {
const sheet = SpreadsheetApp.getActiveSheet();
const selection = sheet.getActiveRange();
const startRow = selection.getRow();
const numRows = selection.getNumRows();
if (startRow === 1) {
SpreadsheetApp.getUi().alert("Please select data rows (not the header)");
return;
}
let successCount = 0;
let errorCount = 0;
for (let i = 0; i < numRows; i++) {
const rowNumber = startRow + i;
const text = sheet.getRange(rowNumber, 1).getValue();
const voiceNameOrId = sheet.getRange(rowNumber, 2).getValue();
const language = sheet.getRange(rowNumber, 3).getValue() || "eng";
if (!text || !voiceNameOrId) {
continue;
}
try {
const audioUrl = callTypecastAPI(text, voiceNameOrId, language);
sheet.getRange(rowNumber, 4).setValue(audioUrl);
successCount++;
Utilities.sleep(500);
} catch (error) {
sheet.getRange(rowNumber, 4).setValue("Error: " + error.message);
errorCount++;
}
}
SpreadsheetApp.getUi().alert(
`Generation complete!\n\nSuccess: ${successCount}\nErrors: ${errorCount}`,
);
}
/**
* Clears all audio URLs from column D
*/
function clearAudioUrls() {
const sheet = SpreadsheetApp.getActiveSheet();
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
return;
}
const response = SpreadsheetApp.getUi().alert(
"Clear Audio URLs",
"Are you sure you want to clear all audio URLs?",
SpreadsheetApp.getUi().ButtonSet.YES_NO,
);
if (response === SpreadsheetApp.getUi().Button.YES) {
sheet.getRange(2, 4, lastRow - 1, 1).clearContent();
SpreadsheetApp.getUi().alert("Audio URLs cleared!");
}
}
/**
* Gets voice ID from voice name by calling the Voices API
* @param {string} voiceName - Voice name to look up
* @returns {string} Voice ID (e.g., tc_66aca22c7d31e45ff05ff418)
*/
function getVoiceIdByName(voiceName) {
const url = "https://api.typecast.ai/v2/voices";
const options = {
method: "get",
headers: {
"X-API-KEY": TYPECAST_API_KEY,
},
muteHttpExceptions: true,
};
const response = UrlFetchApp.fetch(url, options);
const responseCode = response.getResponseCode();
if (responseCode !== 200) {
throw new Error(`Failed to fetch voices: ${responseCode}`);
}
const voices = JSON.parse(response.getContentText());
// Search for voice by name (case-insensitive)
const searchName = voiceName.toLowerCase().trim();
const voice = voices.find((v) => v.voice_name.toLowerCase() === searchName);
if (!voice) {
throw new Error(
`Voice "${voiceName}" not found. Please check the voice name or use voice ID instead.`,
);
}
return voice.voice_id;
}
/**
* Calls the Typecast API to generate speech
* @param {string} text - Text to convert to speech
* @param {string} voiceNameOrId - Voice name (e.g., "Emily") or Voice ID (e.g., "tc_66aca22c7d31e45ff05ff418")
* @param {string} language - Language code (eng, kor, jpn, cmn, etc.)
* @returns {string} URL to the generated audio file
*/
function callTypecastAPI(text, voiceNameOrId, language) {
// Trim and validate inputs
text = String(text).trim();
voiceNameOrId = String(voiceNameOrId).trim();
language = String(language || "eng").trim();
if (!text || !voiceNameOrId) {
throw new Error("Text and Voice Name/ID are required");
}
// Determine if input is voice ID or name
// Voice IDs start with "tc_"
let voiceId;
if (voiceNameOrId.startsWith("tc_")) {
voiceId = voiceNameOrId;
Logger.log("Using voice ID: " + voiceId);
} else {
// It's a voice name, look up the ID
Logger.log("Looking up voice name: " + voiceNameOrId);
voiceId = getVoiceIdByName(voiceNameOrId);
Logger.log("Found voice ID: " + voiceId);
}
// Log for debugging
Logger.log("Calling API with text: " + text);
Logger.log("Language: " + language);
const payload = {
voice_id: voiceId,
text: text,
model: "ssfm-v30",
language: language,
prompt: {
emotion_type: "smart", // Enable Smart Emotion for natural-sounding speech
},
output: {
audio_format: "mp3",
},
};
// Log payload for debugging
Logger.log("Payload: " + JSON.stringify(payload));
const options = {
method: "post",
contentType: "application/json",
headers: {
"X-API-KEY": TYPECAST_API_KEY,
},
payload: JSON.stringify(payload),
muteHttpExceptions: true,
};
const response = UrlFetchApp.fetch(TYPECAST_API_URL, options);
const responseCode = response.getResponseCode();
if (responseCode !== 200) {
const errorBody = response.getContentText();
Logger.log("Error response: " + errorBody);
throw new Error(`API Error: ${responseCode} - ${errorBody}`);
}
// Get the audio blob
const audioBlob = response.getBlob();
// Upload to Google Drive
const folder = DriveApp.getRootFolder(); // Or specify a folder
const fileName = `typecast_${new Date().getTime()}.mp3`;
const file = folder.createFile(audioBlob.setName(fileName));
// Set file to be accessible with link
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
// Return the file URL
return file.getUrl();
}
- Click the Save icon (💾) or press
⌘+S(Mac) /Ctrl+S(Windows) - Give your project a name (e.g., "Typecast TTS Integration")
Step 4: Authorize the Script
The first time you run the script, Google will ask for permissions:
- Close the Apps Script tab and return to your spreadsheet
- Refresh the page (F5 or
⌘+R) - You should see a new menu: 🎙️ Typecast TTS appear in the menu bar
- Click 🎙️ Typecast TTS → Generate All Audio
- Google will prompt you to authorize the script:
- Click Continue
- Select your Google account
- Click Advanced → Go to [Project Name] (unsafe)
- Click Allow
Usage
Generate Audio for All Rows
- Click 🎙️ Typecast TTS → Generate All Audio
- The script will process all rows with text and voice ID
- When complete, you'll see a success message
- Audio URLs will appear in Column D
- Generated audio files are saved to your Google Drive
Generate Audio for Selected Rows
- Select the rows you want to process (click and drag on row numbers)
- Click 🎙️ Typecast TTS → Generate Selected Rows
- Only the selected rows will be processed
Clear Audio URLs
To remove all generated URLs (doesn't delete audio files from Drive):
- Click 🎙️ Typecast TTS → Clear Audio URLs
- Confirm the action
- All URLs in Column D will be cleared
Advanced Configuration
Smart Emotion (Default)
The script uses Smart Emotion by default, which automatically analyzes your text and applies appropriate emotions:
prompt: {
emotion_type: "smart"; // Automatically detects best emotion for your text
}
Use Preset Emotions Instead
If you want to manually control emotions, change emotion_type to preset:
function callTypecastAPI(text, voiceId, language) {
const payload = {
voice_id: voiceId,
text: text,
model: "ssfm-v30",
language: language,
prompt: {
emotion_type: "preset",
emotion_preset: "happy", // Options: happy, sad, angry, whisper, normal, toneup, or tonedown.
emotion_intensity: 1.0, // 0.0 to 1.0
},
output: {
audio_format: "mp3",
audio_tempo: 1.0, // Speed: 0.5 (slow) to 2.0 (fast)
audio_pitch: 0, // Pitch: -12 to +12 semitones
volume: 100, // Volume: 0 to 200
},
};
// ... rest of the code
}
Supported Languages
The script supports 37 languages with the ssfm-v30 model (used by default):
| Code | Language | Code | Language | Code | Language |
|---|---|---|---|---|---|
ara | Arabic | ind | Indonesian | por | Portuguese |
ben | Bengali | ita | Italian | ron | Romanian |
bul | Bulgarian | jpn | Japanese | rus | Russian |
ces | Czech | kor | Korean | slk | Slovak |
dan | Danish | msa | Malay | spa | Spanish |
deu | German | nan | Min Nan | swe | Swedish |
ell | Greek | nld | Dutch | tam | Tamil |
eng | English | nor | Norwegian | tgl | Tagalog |
fin | Finnish | pan | Punjabi | tha | Thai |
fra | French | pol | Polish | tur | Turkish |
hin | Hindi | ukr | Ukrainian | vie | Vietnamese |
hrv | Croatian | yue | Cantonese | zho | Chinese |
hun | Hungarian |
Simply change the value in Column C to use different languages! Language codes are case-insensitive (ENG and eng both work).
Save to Specific Drive Folder
To save audio files to a specific folder instead of root:
// Replace this line:
const folder = DriveApp.getRootFolder();
// With this (using folder ID):
const folder = DriveApp.getFolderById("YOUR_FOLDER_ID_HERE");
// Or create a new folder:
const folder = DriveApp.createFolder("Typecast Audio Files");
Add More Columns
You can extend the spreadsheet with additional columns for even more control:
- Column E: Emotion Preset (happy, sad, angry, normal)
- Column F: Audio Tempo (0.5 to 2.0)
- Column G: Audio Pitch (-12 to +12)
- Column H: Status (Processing, Done, Error)
Then modify the script to read these values from the sheet.
Why Use Google Sheets with Typecast?
Simple copy-paste setup. Non-developers can easily generate professional voiceovers in just 5 minutes.
Set it up once and use forever. Ideal for repetitive TTS tasks and macro-style workflows.
Generate hundreds of audio files with a single click. Process entire content calendars at once.
Share spreadsheets with your team. Everyone can contribute text and generate audio together.
Use Cases
E-learning Content Creation
Create course narrations from lesson scripts. Add text in Column A, generate audio, and download for your videos.
Podcast Production
Generate intro/outro segments, ad reads, and announcements from a shared Google Sheet.
Product Descriptions
Convert e-commerce product descriptions into audio for accessibility or marketing videos.
Social Media Content
Batch-generate voiceovers for Instagram Reels, TikTok, or YouTube Shorts from your content calendar.
Multilingual Content
Translate text in your sheet and generate audio in multiple languages for global audiences.
Troubleshooting
Menu doesn't appear after refresh
- Make sure you saved the script in Apps Script editor - Try running the
onOpenfunction manually: 1. Go back to Apps Script editor 2. SelectonOpenfrom the function dropdown 3. Click the Run button (▶️) - Check the browser console for errors (F12)
Authorization error
- Make sure you clicked Allow when prompted - Try clearing authorization and re-authorizing: 1. Apps Script editor → Run → Clear authorization 2. Save and close 3. Refresh your spreadsheet 4. Try the menu again
API Error: 401 - Invalid API key
- Check that you replaced
YOUR_API_KEY_HEREwith your actual API key - Verify your API key at Typecast API console - Make sure there are no extra spaces around the key
API Error: 402 - Insufficient credits
- Check your credit balance in the Typecast API console - Each character costs credits - make sure you have enough
Script times out on large datasets
- Process in smaller batches using "Generate Selected Rows" - Apps Script has a 6-minute execution limit - For very large datasets (500+ rows), split into multiple sheets
Audio files not appearing in Drive
- Check your Google Drive root folder - Audio files are named
typecast_[timestamp].mp3- Make sure the script has Drive permissions (authorized correctly)