Packaging & Deployment
Packaging for Deployment
When your app is ready, you need to package it into a .zip file for upload. The ZIP file must follow specific structure conventions and include a mandatory manifest.json file.
The manifest.json File (Required)
All apps must include a manifest.json file in the assets/ directory. This file contains your app's metadata:
{
"app": {
"type": "lua",
"name": "my-app",
"title": "My App",
"version": "1.0.0",
"description": "A brief description of what your app does",
"dependencies": [
{
"name": "other-app",
"version": ">=1.0.0"
}
]
},
"contact_fields": [
{
"type": "STRING",
"name": "customer_id",
"display": "Customer ID",
"private": false
}
],
"journeys": [
{
"name": "Welcome Journey",
"file": "journeys/welcome.md",
"description": "Welcomes new users"
}
],
"media_assets": [
{
"asset_path": "images/logo.png",
"filename": "logo.png",
"content_type": "image/png",
"description": "Company logo"
}
],
"skills": [
{
"name": "get_weather",
"file": "my-app/skills/get_weather.lua"
}
]
}
App Metadata (Required)
Required fields:
app.type: The type of app (e.g.,"lua"for Lua apps)app.name: The name of your app (should match the main Lua file name without extension)app.title: The display title for your app (shown in the Turn UI)app.version: Semantic version string (e.g.,1.0.0,2.1.3,1.0.0-beta)app.description: A brief description of your app's functionality
Optional fields:
app.dependencies: An array of apps that must be installed for your app to function. Each dependency must specify:name: The exact name of the required appversion: A version constraint (e.g.,>=1.0.0,~>2.1,1.0.0)
Dependency behavior:
- If
dependenciesis not specified or is an empty array[], the platform assumes your app has no dependencies and can be installed independently - When dependencies are specified, the platform validates that all required apps are installed with compatible versions before allowing installation
- Invalid dependency entries (missing
nameorversion) are silently ignored
Contact Fields (Optional)
The contact_fields array defines custom fields that your app will add to contacts. These fields are automatically created when the app is installed using turn.manifest.install().
"contact_fields": [
{
"type": "STRING",
"name": "customer_id",
"display": "Customer ID",
"private": false
},
{
"type": "BOOLEAN",
"name": "is_premium",
"display": "Premium Member",
"private": false
},
{
"type": "NUMBER",
"name": "loyalty_points",
"display": "Loyalty Points",
"private": true
}
]
Field properties:
type(required): Field type -"STRING","BOOLEAN","NUMBER", or"DATE"name(required): Internal field name (lowercase, underscores allowed)display(required): Human-readable label shown in the UIprivate(required):trueto hide from non-admin users,falseto show to all
Notes:
- Fields are created automatically during app installation
- Fields can be subscribed to for change notifications using
turn.app.set_contact_subscriptions() - Use
private: truefor sensitive data like patient records, financial info, or personal identifiers
Journeys (Optional)
The journeys array defines journey templates that your app provides. These are markdown files in your assets/ directory that define conversation flows.
"journeys": [
{
"name": "Welcome Journey",
"file": "journeys/welcome.md",
"description": "Welcomes new users and collects basic information"
},
{
"name": "Payment Flow",
"file": "journeys/payment.md",
"description": "Handles payment processing with Stripe integration"
}
]
Journey properties:
name(required): Display name for the journey in the Turn UIfile(required): Path to the markdown file within theassets/directorydescription(required): Brief description of what the journey does
Notes:
- Journey files are automatically imported during installation when using
turn.manifest.install() - Journeys can call back to your app using the
app()DSL function - Journey markdown files support the full Turn.io journey DSL syntax
Media Assets (Optional)
The media_assets array defines images and other media files included with your app. These are uploaded and made available for use in journeys and messages.
"media_assets": [
{
"asset_path": "images/welcome-banner.jpg",
"filename": "welcome-banner.jpg",
"content_type": "image/jpeg",
"description": "Welcome screen banner image"
},
{
"asset_path": "images/logo.png",
"filename": "logo.png",
"content_type": "image/png",
"description": "Company logo"
},
{
"asset_path": "documents/terms.pdf",
"filename": "terms-of-service.pdf",
"content_type": "application/pdf",
"description": "Terms of service document"
}
]
Media asset properties:
asset_path(required): Path to the file within your app'sassets/directoryfilename(required): Name to use when uploading (can be different from asset_path)content_type(required): MIME type (e.g.,"image/jpeg","image/png","application/pdf")description(required): Description of the asset's purpose
Supported content types:
- Images:
image/jpeg,image/png,image/gif,image/webp - Documents:
application/pdf - Videos:
video/mp4,video/3gpp - Audio:
audio/mpeg,audio/ogg,audio/aac
Notes:
- Media files are uploaded to Turn's media storage during installation
- Uploaded files get unique URLs that can be used in messages and journeys
- Use
turn.assets.read()to access the binary content of assets for uploading
Skills (Optional)
The skills array defines AI agent skills that your app provides. Skills are Lua functions that AI agents can call during conversations.
"skills": [
{
"name": "get_weather",
"file": "my-app/skills/get_weather.lua"
},
{
"name": "lookup_order",
"file": "my-app/skills/lookup_order.lua"
}
]
Skill properties:
name(required): The skill name used when attaching to AI agentsfile(required): Path to the Lua skill file within your app package
Notes:
- Skills are referenced in AI agents as
app:app_name:skill_name - Each skill file must include LDoc annotations defining parameters and return types
- See AI Agent Skills for full documentation on writing skills
Without the manifest.json file, your app upload will fail with a :manifest_not_found error.
ZIP File Structure
Your app must follow this structure:
my-app.zip
├── my-app.lua # Main file (required, must match app name)
├── my-app/ # Optional: Modules directory
│ ├── utils.lua
│ ├── handlers.lua
│ └── api/
│ └── client.lua
└── assets/ # Required: Contains manifest and static files
├── manifest.json # REQUIRED: App metadata
├── templates/
│ └── welcome.md
└── images/
└── logo.png
Important: The assets/ directory is now required (not optional) since it must contain the manifest.json file.
Using the Docker SDK (Recommended)
If you used the Docker SDK to scaffold your app, packaging is simple:
make build
# This creates my_app-0.0.1.zip (version from manifest.json)
The build command automatically:
- Includes your main
.luafile - Includes the
assets/directory withmanifest.json - Includes any modules in the
lib/directory - Excludes test files and development files
Using the Zip-It Script
The traditional template includes a zip-it.sh script that automates packaging:
./zip-it.sh
# The script will remove any old zip files,
# create a new zip file with your latest code
Manual Packaging
If you need more control over packaging:
# Simple app (single file)
zip payment-processor-1.0.0.zip payment-processor.lua
# App with modules
zip -r my-app-1.0.0.zip my-app.lua my-app/
# App with assets
zip -r my-app-1.0.0.zip my-app.lua my-app/ assets/
# Exclude test files
zip -r my-app-1.0.0.zip my-app.lua my-app/ assets/ -x "*/spec/*" "*/turn.lua" "*.rockspec"
# Verify contents
unzip -l my-app-1.0.0.zip
Important Notes
DO NOT include in your ZIP:
- Test files (
spec/directory) - Mock Turn API (
turn.lua) - Development files (
*.rockspec,.git/, etc.) - Documentation files (
README.md, unless in assets/)
DO include:
- Main app file (must match app name)
assets/manifest.jsonfile (REQUIRED)- All required Lua modules
- Assets folder with manifest.json and any templates/images
- Any configuration files your app needs
Upload Process
- Go to Turn.io platform
- Navigate to Settings → Apps
- Click "Upload New App"
- Select your ZIP file
- The platform will validate:
- Presence of
assets/manifest.json - Valid JSON structure in manifest
- Required fields (name, version, description)
- Semantic versioning format
- Presence of
- Upon successful validation, an app definition will be created
Providing App Documentation in the UI
To ensure a great user experience, you can provide documentation that will appear directly in the Turn UI. Handle the get_app_info_markdown event and return a Markdown string.
This is the perfect place to explain what your app does, list its features, and provide configuration instructions or API endpoint examples.
elseif event == "get_app_info_markdown" then
return [[
# My Awesome App
This app integrates with the external `XYZ` service.
## Configuration
To use this app, please provide your `XYZ_API_KEY` in the configuration below.
## Webhook Endpoint
Send `POST` requests from your service to the following URL:
`/apps/]] .. app.uuid .. [[/webhook`
]]
end