Upload a presentation programmatically

Build a Vite presentation, zip the dist directory, and upload it to Mobile Locker with a shell script.

Upload a built presentation to Mobile Locker from a shell script.

Before you start

Complete these setup steps before you run the script:

📘

Set ML_API_TOKEN and MOBILE_LOCKER_BASE_URL before you run the script if you want it to run without prompts, including from CI.

Set your token in the current shell:

export ML_API_TOKEN="your_token_here"

Keep your API token secret. Do not commit it to a Git repository or share it with other people.

Optional: Load the token from a local file

If you prefer, save your token in $HOME/.mobilelocker and let the script load it when ML_API_TOKEN is not already set.

echo 'ML_API_TOKEN="your_token_here"' >> "$HOME/.mobilelocker"

Upload a presentation

  1. Save this script to your project root as deploy-prod.sh.
#!/bin/bash

# Stop on errors, unset variables, and failed piped commands so the script does not continue after a bad state.
set -euo pipefail

# If the token is not already in the environment, try a local fallback file for repeated manual uploads.
if [ -z "${ML_API_TOKEN:-}" ] && [ -f "$HOME/.mobilelocker" ]; then
  source "$HOME/.mobilelocker"
fi

# Set values you may need to change for your project or Mobile Locker region.
MOBILE_LOCKER_BASE_URL="${MOBILE_LOCKER_BASE_URL:-}"
PRESENTATION_ID="YOUR_PRESENTATION_ID"
BUILD_DIR="dist"
ZIP_FILE="presentation.zip"
RESPONSE_FILE="$(mktemp)"

# Prompt for the API token only when it was not provided by the environment or local file.
if [ -z "${ML_API_TOKEN:-}" ]; then
  read -rsp "Enter your Mobile Locker API token: " ML_API_TOKEN
  echo ""
fi

if [ -z "${ML_API_TOKEN:-}" ]; then
  echo "Error: ML_API_TOKEN cannot be empty."
  exit 1
fi

# Prompt for the Mobile Locker region only when it was not already set.
if [ -z "${MOBILE_LOCKER_BASE_URL:-}" ]; then
  while true; do
    read -r -p "Enter Mobile Locker base URL (https://app.mobilelocker.com or https://eu.mobilelocker.com): " MOBILE_LOCKER_BASE_URL

    # Only accept the two supported regions so uploads do not go to the wrong place.
    if [ "$MOBILE_LOCKER_BASE_URL" = "https://app.mobilelocker.com" ] || [ "$MOBILE_LOCKER_BASE_URL" = "https://eu.mobilelocker.com" ]; then
      break
    fi

    echo "Error: Set MOBILE_LOCKER_BASE_URL to https://app.mobilelocker.com or https://eu.mobilelocker.com"
  done
fi

# Build the upload endpoint from the region and presentation ID.
API_URL="$MOBILE_LOCKER_BASE_URL/api/presentations/$PRESENTATION_ID/upload"

if [ "$PRESENTATION_ID" = "YOUR_PRESENTATION_ID" ]; then
  echo "Error: Replace YOUR_PRESENTATION_ID with your presentation ID."
  exit 1
fi

# Validate the region again in case the value came from the environment instead of the prompt.
if [ "$MOBILE_LOCKER_BASE_URL" != "https://app.mobilelocker.com" ] && [ "$MOBILE_LOCKER_BASE_URL" != "https://eu.mobilelocker.com" ]; then
  echo "Error: Set MOBILE_LOCKER_BASE_URL to https://app.mobilelocker.com or https://eu.mobilelocker.com"
  exit 1
fi

# Vite builds into dist/ by default. Change BUILD_DIR if your output directory is different.
npx vite build

if [ ! -d "$BUILD_DIR" ]; then
  echo "Error: Build directory '$BUILD_DIR' was not created."
  echo "Check your build output or update BUILD_DIR in this script."
  exit 1
fi

# Remove any previous archive so you always upload a fresh build.
rm -f "$ZIP_FILE"
(
  cd "$BUILD_DIR"
  # Create the archive from the built files so Mobile Locker receives only deployable assets.
  zip -r --exclude=.gitignore "../$ZIP_FILE" .
)

# Send the ZIP file to Mobile Locker, save the response body to a temporary file, and capture the HTTP status code separately.
HTTP_STATUS=$(curl --silent --show-error --output "$RESPONSE_FILE" --write-out "%{http_code}" \
  --header "Authorization: Bearer $ML_API_TOKEN" \
  --form "file=@$ZIP_FILE" \
  "$API_URL")

if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 300 ]; then
  echo "Upload complete."
  echo "You will receive an email when the new version is ready."
else
  echo "Upload failed with status $HTTP_STATUS."
  # Print the API response so you can see the error returned by Mobile Locker.
  cat "$RESPONSE_FILE"
  rm -f "$RESPONSE_FILE"
  exit 1
fi

# Delete the temporary response file after the request finishes.
rm -f "$RESPONSE_FILE"
  1. Replace YOUR_PRESENTATION_ID with your presentation ID.

  2. Optional: set MOBILE_LOCKER_BASE_URL before you run the script if you want to skip the region prompt.

    • Global region: https://app.mobilelocker.com
    • European region: https://eu.mobilelocker.com
  3. Make the script executable.

chmod +x deploy-prod.sh
  1. Run the script.
./deploy-prod.sh

Expected output:

Upload complete.
You will receive an email when the new version is ready.

Optional: Customize the script

  • If you do not use Vite, replace npx vite build with your build command.
  • If your build output is not dist, update BUILD_DIR.
  • If MOBILE_LOCKER_BASE_URL is already set in your shell or CI environment, the script uses that value and does not prompt.
  • If your presentation is stored in the European region, set MOBILE_LOCKER_BASE_URL to https://eu.mobilelocker.com.
  • If ML_API_TOKEN is already set in your shell or CI environment, the script uses that value first and does not prompt.
  • If ML_API_TOKEN is stored in $HOME/.mobilelocker, the script loads that value before prompting.
  • If you want SMS notifications, add your mobile number to your profile.

Troubleshooting

  • ML_API_TOKEN cannot be empty

    • Enter your token when prompted, export ML_API_TOKEN in your shell, or add it to $HOME/.mobilelocker.
  • Replace YOUR_PRESENTATION_ID with your presentation ID

    • Copy the presentation ID from the admin portal and update PRESENTATION_ID in the script.
  • Set MOBILE_LOCKER_BASE_URL to https://app.mobilelocker.com or https://eu.mobilelocker.com

    • Enter one of the two valid region URLs when prompted, or set MOBILE_LOCKER_BASE_URL before you run the script.
  • Build directory 'dist' was not created

    • Confirm that your build completed successfully. If your tool writes files to a different directory, change BUILD_DIR.
  • Upload failed with status ...

    • Confirm that your API token is valid, the presentation ID is correct, the ZIP file was created before upload, and the script is using the correct Mobile Locker region.

What happens next

Mobile Locker sends you an email when the new version is ready.

You can also refresh the presentations page in the correct admin portal region to check when the upload has finished processing.