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
Component | Description | Function |
---|
Invocation Name | The name users say to start your skill | “Alexa, open [invocation name]” |
Intents | Actions the skill can perform | Represent user’s requests |
Utterances | Example phrases users might say | Map user’s speech to intents |
Slots | Variables in utterances | Capture specific data from user input |
Dialog Model | Conversation flow | Manages multi-turn conversations |
Backend Service | Server-side logic | Processes requests and returns responses |
Persistence | Data storage | Maintains user data between sessions |
Skill Types and Templates
Skill Type | Description | Use Case |
---|
Custom | Fully customizable skills | Games, utilities, unique experiences |
Smart Home | Control smart home devices | Lights, thermostats, locks control |
Video | Video content management | Video playback, searching |
Music | Audio content for listening | Music streaming services |
Flash Briefing | Short content updates | News, weather, daily quotes |
Skill Blueprints | Template-based skills | Trivia, facts, compliments (no coding) |
Development Environment Setup
Alexa Developer Console
- Create an account at developer.amazon.com
- Navigate to Alexa Skills Console
- Click “Create Skill”
- Choose a skill name, default language, and model
- 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
Intent | Purpose | Example |
---|
AMAZON.StopIntent | Stop the skill | “Stop” |
AMAZON.CancelIntent | Cancel current operation | “Cancel” |
AMAZON.HelpIntent | Request assistance | “Help” |
AMAZON.YesIntent | Affirmative response | “Yes” |
AMAZON.NoIntent | Negative response | “No” |
AMAZON.RepeatIntent | Repeat last response | “Repeat” |
AMAZON.FallbackIntent | Handle unmatched utterances | [unrecognized phrases] |
AMAZON.NavigateHomeIntent | Return to skill’s main functionality | “Start over” |
Slot Types
Slot Type | Description | Example Values |
---|
AMAZON.NUMBER | Numeric values | “five”, “42” |
AMAZON.DATE | Calendar dates | “tomorrow”, “January 15th” |
AMAZON.TIME | Clock times | “3 PM”, “15:30” |
AMAZON.DURATION | Time periods | “5 minutes”, “two hours” |
AMAZON.FOUR_DIGIT_NUMBER | 4-digit numbers | “1234”, “9876” |
AMAZON.CITY | City names | “Seattle”, “London” |
AMAZON.US_STATE | U.S. state names | “California”, “Florida” |
AMAZON.COLOR | Color names | “red”, “turquoise” |
Custom Slots | Your 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
Tag | Purpose | Example |
---|
<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
Directive | Purpose | Example |
---|
Dialog.Delegate | Let Alexa manage the dialog | Next appropriate question |
Dialog.ElicitSlot | Ask for a specific slot value | “What city would you like to visit?” |
Dialog.ConfirmSlot | Confirm a slot value | “Did you say Boston?” |
Dialog.ConfirmIntent | Confirm 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
Permission | Description | Scope ID |
---|
Name | User’s full name | alexa::profile:name:read |
Given Name | User’s first name | alexa::profile:given_name:read |
Email | User’s email address | alexa::profile:email:read |
Phone Number | User’s phone number | alexa::profile:mobile_number:read |
Address | User’s address | alexa::devices:all:address:full:read |
Location | User’s geo location | alexa::devices:all:geolocation:read |
Account Linking Setup
- Create OAuth2 or Auth Code system in your auth provider
- 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 Type | Description | Use Case |
---|
One-time purchase | Single payment | Premium content, upgrades |
Subscription | Recurring payment | Content updates, premium features |
Consumable | Can be purchased repeatedly | Game 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
Method | Description | Best For |
---|
Developer Console | Built-in testing tab | Quick tests, voice simulation |
Echo Device | Testing on physical device | Natural interaction testing |
ASK CLI | Command-line testing | Automated tests, CI integration |
Skill Beta Testing | Invite real users | Real-world usage patterns |
Alexa Simulator | Virtual Echo device | Testing across device types |
Common Error Types and Troubleshooting
Issue | Possible Causes | Solutions |
---|
Skill Won’t Launch | Invocation issues, backend errors | Check invocation name, test backend separately |
“I don’t know that” | Missing utterances | Add more sample utterances |
Wrong Intent Triggered | Similar utterances | Refine utterance patterns, add more samples |
Skill Crashes | Backend code errors | Check CloudWatch logs, add error handling |
Session Ends Unexpectedly | Missing reprompts, errors | Always 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
Invocation
- Clear, easy to pronounce and remember
- Not a single word unless branded
- Not misleading or infringing
Voice Experience
- Provides help and clear instructions
- Handles unexpected utterances
- Maintains conversation context
- Uses varied responses
Functionality
- Core functionality works correctly
- No critical errors or crashes
- Performs as expected on all devices
- Meets expected performance standards
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