Skip to content

Pitcher Apps - Deep Dive ​

What Are Pitcher Apps? ​

Pitcher Apps are web-based plugins that extend the platform's functionality. They can be embedded in multiple contexts (called "modules") and interact with the platform via the Pitcher JavaScript SDK.

App Architecture ​

App Manifest (app.json) ​

json
{
  "name": "product-configurator",
  "display_name": "Product Configurator",
  "icon": "assets/icon.png",
  "version": "1.2.0",
  "description": "Interactive product configuration tool",
  "type": "web",
  "authors": ["John Doe <john@example.com>"],

  "provide": [
    "ui",
    "multimedia_selector"
  ],

  "require": ["crm"],

  "module": {
    "ui_app": {
      "enabled": true,
      "entry": "index.html",
      "headless": false,
      "auto_install": true,
      "defaults": {
        "dimensions": {
          "width": "100%",
          "height": "100vh"
        }
      }
    },

    "canvas": {
      "enabled": true,
      "entry": "canvas.html",
      "settings": {
        "sections": [
          {
            "label": "Configuration",
            "fields": [
              {
                "type": "text",
                "name": "api_key",
                "label": "API Key",
                "help_text": "Your product catalog API key",
                "is_required": true
              },
              {
                "type": "select",
                "name": "catalog",
                "label": "Product Catalog",
                "options": [
                  { "label": "Electronics", "value": "electronics" },
                  { "label": "Furniture", "value": "furniture" }
                ]
              },
              {
                "type": "checkbox",
                "name": "show_pricing",
                "label": "Show Real-time Pricing"
              }
            ]
          }
        ]
      },
      "defaults": {
        "dimensions": {
          "width": "800px",
          "height": "600px"
        },
        "settings": {
          "catalog": "electronics",
          "show_pricing": true
        }
      },
      "shortcuts": {
        "fullscreen": [
          {
            "id": "refresh",
            "icon": "refresh",
            "tooltip": "Refresh Product Data",
            "order_override": 1
          }
        ],
        "presentation": [
          {
            "id": "configure",
            "icon": "settings",
            "tooltip": "Configure Product"
          }
        ],
        "edit": [
          {
            "id": "preview",
            "icon": "eye",
            "tooltip": "Preview Configuration"
          }
        ]
      }
    },

    "overlay_app": {
      "enabled": true,
      "entry": "overlay.html"
    }
  }
}

App Module Types ​

1. UI App (ui_app) ​

Full-screen standalone application

Examples:

  • Account Browser - Browse and display CRM accounts (you build the UI)
  • Meeting Scheduler - Schedule events with calendar integration
  • Analytics Dashboard - View content performance metrics
  • Content Library - Browse and search all files
  • Custom Reports - Build custom reporting dashboards

Code Example:

html
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Account Browser</title>
  <script src="https://cdn.jsdelivr.net/npm/@pitcher/js-api"></script>
</head>
<body>
  <div id="app">
    <h1>Account Browser</h1>
    <div id="accounts"></div>
  </div>

  <script>
    const api = window.pitcher.useApi();

    async function loadAccounts() {
      // Query Salesforce accounts using CRM integration
      const result = await api.crmQuery({
        query: 'SELECT Id, Name FROM Account ORDER BY Name LIMIT 100'
      });

      renderAccounts(result.records);
    }

    function renderAccounts(accounts) {
      const container = document.getElementById('accounts');
      container.innerHTML = accounts.map(account => `
        <div class="account-card" onclick="showAccountDetails('${account.Id}', '${account.Name}')">
          <h3>${account.Name}</h3>
        </div>
      `).join('');
    }

    function showAccountDetails(accountId, accountName) {
      // Display account details in your app
      // (No built-in account view - you build this yourself)
      alert(`Selected: ${accountName} (${accountId})`);

      // You could load more details via another CRM query
      // or navigate to another page in your app
    }

    loadAccounts();
  </script>
</body>
</html>

2. Canvas App (canvas) ​

Embedded interactive component within presentations

Modes:

  • Edit Mode - Canvas author configures the app
  • Fullscreen Mode - User views canvas in fullscreen
  • Presentation Mode - Active presentation to customer

Toolbar Shortcuts: Apps can add toolbar icons for each mode. Clicking one navigates to (and scrolls to) the slide that holds the app's embeddable — the click is not delivered to the app as an event.

typescript
shortcuts: {
  fullscreen: [
    { id: 'refresh', icon: 'refresh', tooltip: 'Refresh Data' }
  ],
  presentation: [
    { id: 'save', icon: 'save', tooltip: 'Save Configuration' }
  ],
  edit: [
    { id: 'configure', icon: 'settings', tooltip: 'App Settings' }
  ]
}

Code Example:

html
<!-- canvas.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Product Configurator</title>
  <script src="https://cdn.jsdelivr.net/npm/@pitcher/js-api"></script>
  <style>
    .configurator {
      padding: 20px;
      font-family: Arial, sans-serif;
    }
    .product-option {
      margin: 10px 0;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
    .price {
      font-size: 24px;
      font-weight: bold;
      color: #2196F3;
    }
  </style>
</head>
<body>
  <div class="configurator">
    <h2>Configure Your Product</h2>

    <div class="product-option">
      <label>
        Color:
        <select id="color" onchange="updatePrice()">
          <option value="black">Black (+$0)</option>
          <option value="white">White (+$50)</option>
          <option value="red">Red (+$100)</option>
        </select>
      </label>
    </div>

    <div class="product-option">
      <label>
        Storage:
        <select id="storage" onchange="updatePrice()">
          <option value="128">128GB (+$0)</option>
          <option value="256">256GB (+$200)</option>
          <option value="512">512GB (+$400)</option>
        </select>
      </label>
    </div>

    <div class="product-option">
      <div class="price">Total: $<span id="price">999</span></div>
    </div>

    <button onclick="saveConfiguration()">Save Configuration</button>
  </div>

  <script>
    const api = window.pitcher.useApi();
    const basePrice = 999;

    function updatePrice() {
      const colorPrice = {
        'black': 0,
        'white': 50,
        'red': 100
      }[document.getElementById('color').value];

      const storagePrice = {
        '128': 0,
        '256': 200,
        '512': 400
      }[document.getElementById('storage').value];

      const total = basePrice + colorPrice + storagePrice;
      document.getElementById('price').textContent = total;
    }

    async function saveConfiguration() {
      const config = {
        color: document.getElementById('color').value,
        storage: document.getElementById('storage').value,
        price: document.getElementById('price').textContent
      };

      // Save to AppsDB
      await api.appsDbUpsertEntry({
        type: 'product-config',
        data: config
      });

      alert('Configuration saved!');
    }
  </script>
</body>
</html>

3. Overlay App (overlay_app) ​

Opened in a modal over the app shell when launched from a global search result

Placement Options (top-level app_options.placement, read only for popup app_types — canvas-popup and ui-global-popup; not an overlay_app setting):

  • top-left, top-center, top-right
  • left, center, right
  • bottom-left, bottom-center, bottom-right
  • bottom-sheet — canvas-popup apps only. A full-width sheet that slides up from the bottom of the running presentation and closes from a footer "Close" button, the same shell the presentation tools use. dimensions are ignored for this placement. A ui-global-popup app that sets it falls back to center.

Code Example:

html
<!-- overlay.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Speaker Notes</title>
  <script src="https://cdn.jsdelivr.net/npm/@pitcher/js-api"></script>
  <style>
    .notes-panel {
      background: white;
      border: 1px solid #ccc;
      border-radius: 8px;
      padding: 15px;
      box-shadow: 0 4px 6px rgba(0,0,0,0.1);
      max-height: 380px;
      overflow-y: auto;
    }
    .note {
      margin-bottom: 10px;
      padding: 8px;
      background: #f5f5f5;
      border-radius: 4px;
    }
  </style>
</head>
<body>
  <div class="notes-panel">
    <h3>Speaker Notes</h3>
    <div id="notes"></div>
  </div>

  <script>
    const api = window.pitcher.useApi();
    let currentPage = 0;

    const pageNotes = {
      0: "Welcome the customer. Introduce yourself and Pitcher.",
      1: "Highlight key product features. Ask about their needs.",
      2: "Show pricing. Be ready to discuss discounts.",
      3: "Wrap up. Next steps and follow-up meeting."
    };

    function showNotes(pageIndex) {
      const notesDiv = document.getElementById('notes');
      const note = pageNotes[pageIndex] || "No notes for this page.";
      notesDiv.innerHTML = `<div class="note">${note}</div>`;
    }

    // Listen for page navigation (next / previous buttons)
    ['canvas_next_button_clicked', 'canvas_previous_button_clicked'].forEach((type) => {
      api.on(type, (payload) => {
        currentPage = payload.current_page_index;
        showNotes(currentPage);
      });
    });

    // Initialize
    showNotes(0);
  </script>
</body>
</html>

4. Admin Instance App (admin_instance) ​

Extensions to admin panel

Use Cases:

  • Configure CRM connection settings
  • Manage custom metadata fields
  • View license usage
  • Configure third-party integrations

5. Section Execution App (canvas_section_execution) ​

Opened per section from a SectionList in collapsibles mode — a status button next to each section opens the app in a modal

Use Cases:

  • Load dynamic content when section appears
  • Track section-specific engagement
  • Section-level forms/interactions
  • A/B testing per section

Pitcher SDK API Reference ​

The Pitcher JavaScript SDK provides a unified API for interacting with the platform. Import it from @pitcher/js-api:

typescript
import { useApi } from '@pitcher/js-api'
const api = useApi() // Automatically detects your embed location

Or via CDN (UMD):

html
<script src="https://cdn.jsdelivr.net/npm/@pitcher/js-api"></script>
<script>
  const api = window.pitcher.useApi()
</script>

📚 Full API Reference: See the complete JS API documentation for all available methods.

Core Methods ​

typescript
// Get platform environment info
const env = await api.getEnv()
// Returns (abridged, see PitcherEnv):
// {
//   mode: 'WEB' | 'IOS' | 'MACOS' | 'WINDOWS' | 'ANDROID' // WEB = browser; every other mode is a native shell (use isNativeHost(env.mode))
//   pitcher: {
//     access_token: string
//     id_token: string
//     token_claims: { 'https://pitcher.com/claims/urls': { custom_domain, ... }, ... }
//     organization: { id, metadata, ... }
//     org_config: { environment, ... }
//     instance: { id, name, color, ... }
//     user: { id, first_name, last_name, connected_services, ... }
//   }
//   ui_locale?: string      // web only
//   device_locale?: string  // iOS only
// }

// Pick the app's UI language from the env (ui_locale, then device_locale; exact match,
// then base language, then 'en'). Never env.pitcher.user.language — that field is unused.
// npm (bundled):
import { resolveEnvLocale, supportedEnvLocales } from '@pitcher/js-api'
const locale = resolveEnvLocale(env) // one of supportedEnvLocales
const zhOrEn = resolveEnvLocale(env, ['en', 'zh']) // an app that bundles fewer locales

// CDN (window.pitcher, js-api >= 1.33.0): browsers and WebViews cache the SDK URL for up to
// 7 days, so an app can still be running an older SDK without it. Guard and fall back:
const cdnLocale =
  typeof window.pitcher.resolveEnvLocale === 'function' ? window.pitcher.resolveEnvLocale(env) : 'en'

Opening Files ​

typescript
// Open a file (Impact API - usePitcherApi)
await api.open({ fileId: 'file-123' })

// Open file at specific page
await api.open({
  fileId: 'file-789',
  pageIndex: 5
})

// Open video at specific timestamp
await api.open({
  fileId: 'video-123',
  startTime: 45 // seconds
})

// UI API ONLY - Open canvas in overlay modal
// Only available when using useUi() or useApi() in UI context
await api.openCanvasOverlay({
  id: 'canvas-456',
  edit_mode: false,
  fullscreen: true
})

// With custom positioning
await api.openCanvasOverlay({
  id: 'canvas-789',
  position: {
    top: '10px',
    left: '20px',
    right: '20px',
    bottom: '10px'
  }
})

CRM Integration (Optional) ​

Note: CRM features are only available if your organization has CRM integration enabled. This uses Salesforce's SOQL queries via jsforce.

typescript
// Query Salesforce accounts
const result = await api.crmQuery({
  query: 'SELECT Id, Name, Industry FROM Account LIMIT 10'
})
console.log(result.records) // Array of Account records

// Query with filters
const contacts = await api.crmQuery({
  query: "SELECT Id, Name, Email FROM Contact WHERE AccountId = '001xx000003DGb2AAG'"
})

// Query opportunities
const opportunities = await api.crmQuery({
  query: 'SELECT Id, Name, Amount, StageName FROM Opportunity WHERE CloseDate = THIS_YEAR'
})

📖 Learn more: See SOQL documentation for query syntax.

Data Persistence ​

See Data Persistence for the options. The two most common:

typescript
// Option 1: Use browser localStorage (client-side only)
localStorage.setItem('myapp-config', JSON.stringify({ theme: 'dark' }))
const config = JSON.parse(localStorage.getItem('myapp-config') || '{}')

// Option 2: Store data in Pitcher with AppsDB
const entry = await api.appsDbUpsertEntry({ type: 'myapp-config', data: { theme: 'dark' } })
const { entries } = await api.appsDbGetEntries({ type: 'myapp-config' })

Events & Broadcasting ​

typescript
// Listen to platform events
api.on('canvas_opened', (data) => {
  console.log('Canvas opened:', data.body.canvas_id)
})

api.on('canvas_closed', (data) => {
  console.log('Canvas closed:', data.body.canvas_id)
})

api.on('canvas_next_button_clicked', (data) => {
  console.log('Next clicked, page:', data.body.current_page_index)
})

api.on('canvas_previous_button_clicked', (data) => {
  console.log('Previous clicked, page:', data.body.current_page_index)
})

api.on('route_changed', (data) => {
  console.log('Route:', data.body.route_path)
})

// Broadcast custom events (iOS multipeer connectivity)
api.broadcast({
  type: 'CUSTOM_EVENT_NAME',
  body: {
    customData: 'value',
    timestamp: Date.now()
  }
})

REST API Access ​

Access the Pitcher REST API using the access token from getEnv(). Build the API origin from the token's custom_domain claim — never from window.location (installable apps run at file:// on iOS):

typescript
const env = await api.getEnv()
const apiOrigin = env.pitcher.token_claims['https://pitcher.com/claims/urls'].custom_domain

// Get canvases
const canvases = await fetch(
  `${apiOrigin}/api/v1/canvases/`,
  {
    headers: {
      'Authorization': `Bearer ${env.pitcher.access_token}`,
      'Content-Type': 'application/json'
    }
  }
)

// Get files
const files = await fetch(
  `${apiOrigin}/api/v1/files/`,
  {
    headers: { 'Authorization': `Bearer ${env.pitcher.access_token}` }
  }
)

// Create canvas
const newCanvas = await fetch(
  `${apiOrigin}/api/v1/canvases/`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${env.pitcher.access_token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'My New Canvas',
      instance_id: env.pitcher.instance.id,
      content: []
    })
  }
)

📖 REST API Documentation: https://pitcher.readme.io/

Note: CRM data (Accounts, Contacts, Opportunities) is accessed via api.crmQuery() using SOQL, not REST endpoints.


App Development Workflow ​

Development Steps ​

  1. Develop locally - Use any web development stack
  2. Test with SDK - Mock Pitcher SDK during development
  3. Create manifest - Define app.json with modules and settings
  4. Package - ZIP all files
  5. Upload - Upload ZIP as File in Pitcher
  6. Install - Install app to Instance
  7. Configure - Admin configures app settings
  8. Embed - Add app to canvases or access as UI app
  9. Test - Test in real scenarios
  10. Iterate - Update and re-upload as needed

Real-World App Examples ​

Example 1: ROI Calculator (Canvas App) ​

Purpose: Help sales reps show ROI to customers
Module: canvas
Features:
  - Input customer metrics
  - Calculate savings/value
  - Generate PDF report
  - Save to customer account

Example 2: Meeting Scheduler (UI App + CRM Integration) ​

Purpose: Schedule meetings with CRM sync
Module: ui_app
Features:
  - Calendar view
  - Sync with Salesforce events
  - Link to accounts
  - Attach canvases
  - Send invites

Example 3: Live Pricing Engine (Canvas App + External API) ​

Purpose: Show real-time pricing with discounts
Module: canvas
Features:
  - Connect to pricing API
  - Apply discounts dynamically
  - Show competitor pricing
  - Generate quotes
  - Save to CRM opportunity

Example 4: Customer Feedback Form (Overlay App) ​

Purpose: Collect feedback during presentation
Module: overlay_app
Features:
  - Quick rating (1-5 stars)
  - Comments field
  - Submit to CRM
  - Track per-page ratings

Example 5: Analytics Dashboard (UI App) ​

Purpose: View content performance
Module: ui_app
Features:
  - Top-performing files
  - User engagement
  - Canvas analytics
  - Export reports

Security & Permissions ​

Apps inherit the permissions of the authenticated user:

  • Access Token - OAuth token for API calls
  • Instance Scope - Only access current instance data
  • User Permissions - Respect user roles (Admin/Editor/Member)
  • CRM Access - Only available if organization has CRM integration enabled (optional feature)

Best Practices:

  • Never store access tokens client-side
  • Use HTTPS for all external API calls
  • Validate user input
  • Follow OAuth best practices for CRM integrations (when enabled)

Summary ​

Pitcher Apps transform the platform from a presentation tool into a full application ecosystem:

  • Flexible - Multiple embed locations for different use cases
  • Powerful - Full JavaScript SDK with rich API
  • Integrated - Deep CRM and platform integration
  • Customizable - Settings framework for configuration
  • Scalable - Distribute to entire organization

Apps make Pitcher infinitely extensible - limited only by imagination! 🚀