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": "boolean",
                "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",
      "defaults": {
        "dimensions": {
          "width": "300px",
          "height": "400px"
        }
      },
      "app_options": {
        "placement": "bottom-right",
        "mode_config": {
          "fullscreen": { "show": true },
          "presentation": { "show": true },
          "edit": { "show": false }
        }
      }
    }
  }
}

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 custom toolbar buttons for each mode.

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.setGlobalEntry(
        'product-config',
        config
      );

      alert('Configuration saved!');
    }

    // Listen for shortcut button clicks
    api.on('shortcut_clicked', (event) => {
      if (event.shortcut_id === 'refresh') {
        location.reload();
      } else if (event.shortcut_id === 'save') {
        saveConfiguration();
      }
    });
  </script>
</body>
</html>

3. Overlay App (overlay_app)

Floating panel on top of canvas

Placement Options:

  • top-left, top-center, top-right
  • left, center, right
  • bottom-left, bottom-center, bottom-right

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 changes
    api.on('canvas_page_changed', (event) => {
      currentPage = event.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)

Runs when a section is displayed

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: {
//   mode: 'IOS' | 'ANDROID' | 'WEB'
//   platform: 'impact' | 'admin'
//   pitcher: {
//     accessToken: string
//     instanceId: string
//     apiOrigin: string
//     user: { id, first_name, last_name, ... }
//   }
// }

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

Apps need to implement their own data persistence strategy:

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 using the REST API
const env = await api.getEnv()
const response = await fetch(`${env.pitcher.apiOrigin}/api/v1/your-storage/`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${env.pitcher.access_token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ key: 'config', value: { theme: 'dark' } })
})

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():

typescript
const env = await api.getEnv()

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

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

// Create canvas
const newCanvas = await fetch(
  `${env.pitcher.apiOrigin}/api/v1/canvases/`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${env.pitcher.accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'My New Canvas',
      instance_id: env.pitcher.instanceId,
      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! 🚀