Alexa Skills Development: The Ultimate Cheat Sheet

Introduction to Alexa Skills

Alexa Skills are voice-driven capabilities that enhance the functionality of Amazon Alexa-enabled devices (like Echo, Echo Dot, Fire TV). Skills allow users to perform tasks, access content, play games, control smart home devices, and more through voice commands. Skills consist of two main components: a voice user interface (VUI) that processes voice commands and a cloud-based service that provides the logic and functionality.

Core Components of an Alexa Skill

ComponentDescriptionFunction
Invocation NameThe name users say to start your skill“Alexa, open [invocation name]”
IntentsActions the skill can performRepresent user’s requests
UtterancesExample phrases users might sayMap user’s speech to intents
SlotsVariables in utterancesCapture specific data from user input
Dialog ModelConversation flowManages multi-turn conversations
Backend ServiceServer-side logicProcesses requests and returns responses
PersistenceData storageMaintains user data between sessions

Skill Types and Templates

Skill TypeDescriptionUse Case
CustomFully customizable skillsGames, utilities, unique experiences
Smart HomeControl smart home devicesLights, thermostats, locks control
VideoVideo content managementVideo playback, searching
MusicAudio content for listeningMusic streaming services
Flash BriefingShort content updatesNews, weather, daily quotes
Skill BlueprintsTemplate-based skillsTrivia, facts, compliments (no coding)

Development Environment Setup

Alexa Developer Console

  1. Create an account at developer.amazon.com
  2. Navigate to Alexa Skills Console
  3. Click “Create Skill”
  4. Choose a skill name, default language, and model
  5. Select a hosting method for your backend

Backend Hosting Options

  • Alexa-Hosted (Node.js/Python): Amazon hosts your backend code
  • AWS Lambda: Serverless functions (best for most skills)
  • Custom Endpoint: Your own HTTPS service

Development Tools

  • ASK CLI: Command-line interface for Alexa Skills Kit
  • ASK SDK: Software Development Kit (Node.js, Python, Java)
  • VS Code Extension: Alexa Skills Kit extension for Visual Studio Code

Voice User Interface (VUI) Design

Interaction Model Components

{
  "interactionModel": {
    "languageModel": {
      "invocationName": "space facts",
      "intents": [
        {
          "name": "GetFactIntent",
          "slots": [],
          "samples": [
            "tell me a fact",
            "give me a space fact",
            "share a fact"
          ]
        },
        {
          "name": "AMAZON.HelpIntent",
          "samples": []
        }
      ],
      "types": []
    }
  }
}

Built-in Intents

IntentPurposeExample
AMAZON.StopIntentStop the skill“Stop”
AMAZON.CancelIntentCancel current operation“Cancel”
AMAZON.HelpIntentRequest assistance“Help”
AMAZON.YesIntentAffirmative response“Yes”
AMAZON.NoIntentNegative response“No”
AMAZON.RepeatIntentRepeat last response“Repeat”
AMAZON.FallbackIntentHandle unmatched utterances[unrecognized phrases]
AMAZON.NavigateHomeIntentReturn to skill’s main functionality“Start over”

Slot Types

Slot TypeDescriptionExample Values
AMAZON.NUMBERNumeric values“five”, “42”
AMAZON.DATECalendar dates“tomorrow”, “January 15th”
AMAZON.TIMEClock times“3 PM”, “15:30”
AMAZON.DURATIONTime periods“5 minutes”, “two hours”
AMAZON.FOUR_DIGIT_NUMBER4-digit numbers“1234”, “9876”
AMAZON.CITYCity names“Seattle”, “London”
AMAZON.US_STATEU.S. state names“California”, “Florida”
AMAZON.COLORColor names“red”, “turquoise”
Custom SlotsYour defined values[custom values]

Backend Development with ASK SDK

Node.js SDK Installation

npm install ask-sdk
# For Lambda Optimized version
npm install ask-sdk-core

Python SDK Installation

pip install ask-sdk
# For Lambda Optimized version
pip install ask-sdk-core

Basic Skill Structure (Node.js)

const Alexa = require('ask-sdk-core');

// Intent handlers
const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  handle(handlerInput) {
    const speechText = 'Welcome to my Alexa skill!';
    
    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .withSimpleCard('Welcome', speechText)
      .getResponse();
  }
};

// Error handler
const ErrorHandler = {
  canHandle() {
    return true;
  },
  handle(handlerInput, error) {
    console.log(`Error handled: ${error.message}`);
    
    return handlerInput.responseBuilder
      .speak('Sorry, I had trouble doing what you asked. Please try again.')
      .reprompt('Please try again.')
      .getResponse();
  }
};

// Skill builder
exports.handler = Alexa.SkillBuilders.custom()
  .addRequestHandlers(
    LaunchRequestHandler,
    // Add other handlers here
  )
  .addErrorHandlers(ErrorHandler)
  .lambda();

Basic Skill Structure (Python)

from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.dispatch_components import AbstractExceptionHandler
from ask_sdk_core.utils import is_request_type, is_intent_name
from ask_sdk_model import Response

# Intent handlers
class LaunchRequestHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        return is_request_type("LaunchRequest")(handler_input)
    
    def handle(self, handler_input):
        speech_text = "Welcome to my Alexa skill!"
        
        return (
            handler_input.response_builder
                .speak(speech_text)
                .ask(speech_text)
                .set_card(SimpleCard("Welcome", speech_text))
                .response
        )

# Error handler
class CatchAllExceptionHandler(AbstractExceptionHandler):
    def can_handle(self, handler_input, exception):
        return True
    
    def handle(self, handler_input, exception):
        print(f"Exception caught: {exception}")
        
        speech = "Sorry, I had trouble doing what you asked. Please try again."
        
        return (
            handler_input.response_builder
                .speak(speech)
                .ask(speech)
                .response
        )

# Skill builder
sb = SkillBuilder()
sb.add_request_handler(LaunchRequestHandler())
# Add other handlers here
sb.add_exception_handler(CatchAllExceptionHandler())

def handler(event, context):
    return sb.lambda_handler()(event, context)

Response Building

SSML (Speech Synthesis Markup Language)

// Basic SSML example
const speechText = '<speak>I can speak with <emphasis level="strong">emphasis</emphasis> and add <break time="1s"/> pauses.</speak>';

return handlerInput.responseBuilder
  .speak(speechText)
  .getResponse();

Common SSML Tags

TagPurposeExample
<speak>Root element<speak>...</speak>
<break>Pause speech<break time="1s"/>
<emphasis>Add emphasis<emphasis level="strong">important</emphasis>
<prosody>Control volume, pitch, rate<prosody rate="slow">slow speech</prosody>
<phoneme>Pronunciation<phoneme alphabet="ipa" ph="təˈmɑːtoʊ">tomato</phoneme>
<say-as>Interpret content<say-as interpret-as="date">2023-05-15</say-as>
<audio>Play audio file<audio src="https://example.com/sound.mp3"/>
<amazon:effect>Special effects<amazon:effect name="whispered">secret</amazon:effect>

Response Cards

// Simple card
return handlerInput.responseBuilder
  .speak(speechText)
  .withSimpleCard('Card Title', 'Card content text')
  .getResponse();

// Standard card with image
return handlerInput.responseBuilder
  .speak(speechText)
  .withStandardCard(
    'Card Title',
    'Card content text',
    'https://example.com/small-image.jpg',
    'https://example.com/large-image.jpg'
  )
  .getResponse();

Session Management

Session Attributes (Node.js)

// Set session attributes
const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
sessionAttributes.key = 'value';
handlerInput.attributesManager.setSessionAttributes(sessionAttributes);

// Get session attributes
const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
const value = sessionAttributes.key;

Session Attributes (Python)

# Set session attributes
session_attr = handler_input.attributes_manager.session_attributes
session_attr["key"] = "value"

# Get session attributes
session_attr = handler_input.attributes_manager.session_attributes
value = session_attr["key"]

Persistent Attributes with S3 (Node.js)

const { S3PersistenceAdapter } = require('ask-sdk-s3-persistence-adapter');

const persistenceAdapter = new S3PersistenceAdapter({
  bucketName: 'YOUR_S3_BUCKET_NAME'
});

// Save attributes
const PersistAttributesHandler = {
  handle(handlerInput) {
    const attributesManager = handlerInput.attributesManager;
    const persistentAttributes = {
      user_data: {
        name: 'John',
        score: 100
      }
    };
    
    attributesManager.setPersistentAttributes(persistentAttributes);
    return attributesManager.savePersistentAttributes();
  }
};

// Load attributes
const LoadAttributesHandler = {
  async handle(handlerInput) {
    const attributesManager = handlerInput.attributesManager;
    const persistentAttributes = await attributesManager.getPersistentAttributes() || {};
    
    // Use the attributes
    const userData = persistentAttributes.user_data || {};
    console.log(`User name: ${userData.name}, score: ${userData.score}`);
  }
};

// Add persistence adapter to skill builder
exports.handler = Alexa.SkillBuilders.custom()
  .addRequestHandlers(
    // your handlers
  )
  .withPersistenceAdapter(persistenceAdapter)
  .lambda();

Dialog Management

Dialog Delegation

// Check if dialog is in progress
const isDialogInProgress = handlerInput.requestEnvelope.request.dialogState !== 'COMPLETED';

// Delegate dialog to Alexa
if (isDialogInProgress) {
  return handlerInput.responseBuilder
    .addDelegateDirective()
    .getResponse();
}

Dialog Directive Types

DirectivePurposeExample
Dialog.DelegateLet Alexa manage the dialogNext appropriate question
Dialog.ElicitSlotAsk for a specific slot value“What city would you like to visit?”
Dialog.ConfirmSlotConfirm a slot value“Did you say Boston?”
Dialog.ConfirmIntentConfirm the entire intent“You want to book a trip to Boston on Friday?”

Elicit Slot Example

return handlerInput.responseBuilder
  .speak("What city would you like to visit?")
  .reprompt("Please tell me the city you want to visit.")
  .addElicitSlotDirective('cityName')
  .getResponse();

Account Linking and Permissions

Request User Permission

// Request permission to access user's name
return handlerInput.responseBuilder
  .speak("To personalize your experience, I need access to your name.")
  .withAskForPermissionsConsentCard(['alexa::profile:given_name:read'])
  .getResponse();

Available Permissions

PermissionDescriptionScope ID
NameUser’s full namealexa::profile:name:read
Given NameUser’s first namealexa::profile:given_name:read
EmailUser’s email addressalexa::profile:email:read
Phone NumberUser’s phone numberalexa::profile:mobile_number:read
AddressUser’s addressalexa::devices:all:address:full:read
LocationUser’s geo locationalexa::devices:all:geolocation:read

Account Linking Setup

  1. Create OAuth2 or Auth Code system in your auth provider
  2. In the Alexa Developer Console:
    • Go to “Account Linking” tab
    • Set “Auth Code Grant” or “Implicit Grant”
    • Enter Authorization URI, Access Token URI, Client ID, Client Secret
    • Define Scopes
    • Save and Test

In-Skill Purchasing (ISP)

Types of Products

Product TypeDescriptionUse Case
One-time purchaseSingle paymentPremium content, upgrades
SubscriptionRecurring paymentContent updates, premium features
ConsumableCan be purchased repeatedlyGame tokens, extra lives

ISP Flow Example (Node.js)

const { getMonetizationServiceClient } = require('ask-sdk-monetization');

// Check if product is entitled
const isEntitled = async (handlerInput, productId) => {
  const monetizationClient = getMonetizationServiceClient(handlerInput);
  const locale = handlerInput.requestEnvelope.request.locale;
  const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient();
  
  const result = await ms.getInSkillProducts(locale);
  const product = result.inSkillProducts.find(record => record.productId === productId);
  
  return product && product.entitled === 'ENTITLED';
};

// Make a purchase request
const BuyProductHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.intent.name === 'BuyProductIntent';
  },
  handle(handlerInput) {
    const productId = 'amzn1.adg.product.YOUR_PRODUCT_ID';
    
    return handlerInput.responseBuilder
      .addDirective({
        type: 'Connections.SendRequest',
        name: 'Buy',
        payload: {
          InSkillProduct: {
            productId: productId
          }
        },
        token: 'correlationToken'
      })
      .getResponse();
  }
};

// Handle purchase response
const BuyResponseHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'Connections.Response' &&
           handlerInput.requestEnvelope.request.name === 'Buy';
  },
  handle(handlerInput) {
    const purchaseResult = handlerInput.requestEnvelope.request.payload.purchaseResult;
    
    if (purchaseResult === 'ACCEPTED') {
      return handlerInput.responseBuilder
        .speak("Thank you for your purchase! You now have access to premium content.")
        .getResponse();
    } else if (purchaseResult === 'DECLINED') {
      return handlerInput.responseBuilder
        .speak("No problem. You can purchase this anytime.")
        .getResponse();
    } else {
      return handlerInput.responseBuilder
        .speak("There was an issue with the purchase. Please try again later.")
        .getResponse();
    }
  }
};

Testing and Debugging

Test Methods

MethodDescriptionBest For
Developer ConsoleBuilt-in testing tabQuick tests, voice simulation
Echo DeviceTesting on physical deviceNatural interaction testing
ASK CLICommand-line testingAutomated tests, CI integration
Skill Beta TestingInvite real usersReal-world usage patterns
Alexa SimulatorVirtual Echo deviceTesting across device types

Common Error Types and Troubleshooting

IssuePossible CausesSolutions
Skill Won’t LaunchInvocation issues, backend errorsCheck invocation name, test backend separately
“I don’t know that”Missing utterancesAdd more sample utterances
Wrong Intent TriggeredSimilar utterancesRefine utterance patterns, add more samples
Skill CrashesBackend code errorsCheck CloudWatch logs, add error handling
Session Ends UnexpectedlyMissing reprompts, errorsAlways include reprompt text, add fallbacks

Debugging with CloudWatch Logs (AWS Lambda)

// Add comprehensive logging
const MyIntentHandler = {
  canHandle(handlerInput) {
    console.log('Checking if handler can handle request', JSON.stringify(handlerInput.requestEnvelope));
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'MyIntent';
  },
  handle(handlerInput) {
    console.log('Handling the MyIntent');
    try {
      // Your logic here
      console.log('Operation completed successfully');
      return handlerInput.responseBuilder
        .speak('Success message')
        .getResponse();
    } catch (error) {
      console.error('Error in MyIntent handler:', error);
      throw error; // Let error handler catch it
    }
  }
};

Certification Requirements & Best Practices

Certification Checklist

  1. Invocation

    • Clear, easy to pronounce and remember
    • Not a single word unless branded
    • Not misleading or infringing
  2. Voice Experience

    • Provides help and clear instructions
    • Handles unexpected utterances
    • Maintains conversation context
    • Uses varied responses
  3. Functionality

    • Core functionality works correctly
    • No critical errors or crashes
    • Performs as expected on all devices
    • Meets expected performance standards
  4. Policy Compliance

    • No offensive, dangerous, or illegal content
    • Appropriate age rating
    • Clear privacy policy
    • No prohibited use cases (emergency services, health diagnosis)

Design Best Practices

  • Keep it conversational: Design for voice, not text
  • Be concise: Keep responses brief and informative
  • Use variety: Provide multiple response variations
  • Progressive disclosure: Reveal information gradually
  • Give users control: Clear navigation options
  • Error recovery: Make error recovery intuitive
  • Mind the context: Keep conversational context clear
  • Test with real users: Real user feedback is invaluable

Common Alexa Conversation Patterns

Welcome Flow

User: "Alexa, open [skill name]"
Alexa: "Welcome to [skill name]. I can help you with [key functions]. What would you like to do?"
User: "I want to [action]"

Help Flow

User: "Help"
Alexa: "I can help you [list top 3 functions]. For example, try saying '[example command]'. What would you like to do?"
User: [states request or "cancel"]

Error Recovery

User: [unrecognized command]
Alexa: "I'm not sure about that. You can say '[example 1]' or '[example 2]'. What would you like to do?"
User: [tries again or asks for help]

Exit Flow

User: "Exit" or "Stop"
Alexa: "Thanks for using [skill name]. Goodbye!"

New and Advanced Features

APL (Alexa Presentation Language)

// Add visual component with APL
if (Alexa.getSupportedInterfaces(handlerInput.requestEnvelope)['Alexa.Presentation.APL']) {
  const aplDocument = require('./apl_template.json');
  
  handlerInput.responseBuilder.addDirective({
    type: 'Alexa.Presentation.APL.RenderDocument',
    token: 'token',
    document: aplDocument,
    datasources: {
      "data": {
        "title": "My Skill",
        "text": "This is APL content",
        "imageUrl": "https://example.com/image.jpg"
      }
    }
  });
}

Alexa Conversations

{
  "dialogs": [
    {
      "name": "OrderPizza",
      "slots": [
        {
          "name": "size",
          "type": "PizzaSize"
        },
        {
          "name": "topping",
          "type": "PizzaTopping"
        }
      ],
      "utteranceTemplates": [
        "I'd like to order a {size} pizza with {topping}",
        "Order me a {size} {topping} pizza"
      ]
    }
  ]
}

Skill Connections

// Send a request to another skill
return handlerInput.responseBuilder
  .addDirective({
    type: 'Connections.StartConnection',
    uri: 'connection://AMAZON.PrintPDF/1',
    input: {
      '@type': 'PrintPDFRequest',
      '@version': '1',
      'title': 'My Document',
      'url': 'https://example.com/document.pdf'
    },
    token: 'token'
  })
  .getResponse();

Resources for Further Learning

Official Documentation

Tools and Repositories

Tutorials and Courses

Communities

Scroll to Top