Skip to main content
The Bridge Plugin is an optional enhancement to Vue Print It that enables direct communication with physical printers, bypassing the browser’s print dialog entirely. This is essential for POS systems, kiosks, receipt printers, and automated workflows.

Why Bridge Printing?

Silent Printing

Print directly to configured printers without user interaction or print dialogs.

Printer Selection

Programmatically choose which printer to use, perfect for multi-printer environments.

Print Job Control

Set copies, paper size, and other print options programmatically.

Receipt Printers

Direct support for thermal and ESC/POS receipt printers.

Architecture Overview

Bridge printing uses a client-server architecture:
  1. Vue App: Your application with the bridge plugin installed
  2. Bridge Plugin: Client-side code that communicates with the bridge service
  3. Bridge Service: Local HTTP server that interfaces with system printers
  4. System Printers: OS-level printer drivers
  5. Physical Printer: The actual printing hardware

Installation

The bridge plugin is separate from the core Vue Print It plugin:
import { createVuePrintIt, createVuePrintItBridge } from 'vue-print-it'

const app = createApp(App)

// Install core plugin
app.use(createVuePrintIt({
  windowTitle: 'My App',
  preserveStyles: true
}))

// Install bridge plugin
app.use(createVuePrintItBridge({
  baseUrl: 'http://localhost:8765',
  autoConnect: true,
  autoSelectDefault: true
}))

app.mount('#app')

Bridge Plugin Internals

The bridge plugin (bridge-plugin.ts:29) creates a reactive state management system:
export function createVuePrintItBridge(options: BridgePluginOptions = {}) {
  const bridgeOptions = {
    baseUrl: 'http://localhost:8765',
    autoConnect: false,
    autoSelectDefault: true,
    timeout: 2000,
    retryAttempts: 3,
    ...options
  };

  // Create reactive bridge state
  const bridgeState = reactive<BridgeState>({
    availablePrinters: [],
    defaultPrinter: bridgeOptions.defaultPrinter || null,
    isConnected: false,
    lastUpdated: null
  });

  return {
    install(app: App) {
      const bridgeClient = new BridgeClient(bridgeOptions.baseUrl);
      
      // Enhanced bridge client with state management
      const enhancedBridgeClient = {
        ...bridgeClient,
        updatePrinters,
        setDefaultPrinter,
        getDefaultPrinter,
        getState: () => ({ ...bridgeState })
      };
      
      // Provide bridge client and state globally
      app.provide('vuePrintItBridge', enhancedBridgeClient);
      app.provide('vuePrintItBridgeState', bridgeState);
      
      // Add to global properties
      app.config.globalProperties.$printBridge = enhancedBridgeClient;
      app.config.globalProperties.$printBridgeState = bridgeState;
      
      // Auto-connect if enabled
      if (bridgeOptions.autoConnect) {
        updatePrinters().catch(() => {
          console.debug('Bridge auto-connect failed - will retry on first use');
        });
      }
    }
  };
}

Reactive State

The bridge maintains a reactive state that components can monitor:
interface BridgeState {
  availablePrinters: BridgePrinter[];  // List of discovered printers
  defaultPrinter: string | null;        // Currently selected default
  isConnected: boolean;                 // Bridge service availability
  lastUpdated: Date | null;             // Last printer list refresh
}

Bridge Client

The BridgeClient class (bridge-client.ts:11) handles HTTP communication:
export class BridgeClient {
  private baseUrl: string;
  private isAvailable: boolean | null = null;
  
  constructor(baseUrl: string = 'http://localhost:8765') {
    this.baseUrl = baseUrl;
  }
  
  // Check if bridge service is responding
  async checkAvailability(): Promise<boolean> {
    try {
      const response = await fetch(`${this.baseUrl}/health`, {
        method: 'GET',
        signal: AbortSignal.timeout(2000)
      });
      
      if (response.ok) {
        this.isAvailable = true;
        return true;
      }
    } catch (error) {
      console.debug('Bridge not available:', error);
    }
    
    this.isAvailable = false;
    return false;
  }
  
  // Get list of available printers
  async getPrinters(): Promise<BridgePrinter[]> {
    const response = await fetch(`${this.baseUrl}/api/printers`);
    return response.ok ? await response.json() : [];
  }
  
  // Send print job to bridge
  async print(request: BridgePrintRequest): Promise<BridgePrintResponse> {
    const response = await fetch(`${this.baseUrl}/api/print`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(request)
    });
    
    if (response.ok) {
      return await response.json();
    } else {
      const error = await response.text();
      throw new Error(`Bridge error: ${error}`);
    }
  }
  
  // Convert HTML to Base64 for transmission
  htmlToBase64(html: string): string {
    return btoa(unescape(encodeURIComponent(html)));
  }
}

Automatic Bridge Detection

The core print function automatically detects and uses the bridge when appropriate:
// From usePrint.ts:334-347
async function print(element: HTMLElement | string, localOptions: Partial<PrintOptions> = {}) {
  const options = { ...defaultOptions, ...globalOptions, ...localOptions };
  
  try {
    await options.onBeforePrint();
    
    // Check if bridge should be used and is available
    const bridgeClient = getBridgeClient();
    const shouldUseBridge = options.useBridge && 
                           bridgeClient && 
                           await bridgeClient.checkAvailability();
    
    if (shouldUseBridge) {
      await printWithBridge(element, options, globalOptions, bridgeClient);
    } else {
      await printWithBrowser(element, options, globalOptions);
    }
    
    await options.onAfterPrint();
  } catch (error) {
    options.onPrintError(error as Error);
    throw error;
  }
}

Bridge Print Flow

When using bridge printing, the complete HTML with styles is sent to the bridge service:
// From usePrint.ts:175-217
async function printWithBridge(
  element: HTMLElement | string, 
  options: any, 
  globalOptions: Partial<GlobalPrintOptions>,
  bridgeClient: any
): Promise<void> {
  const targetElement = resolveElement(element);
  const htmlContent = buildPrintHtml(targetElement, options, globalOptions);
  
  // Convert to Base64
  const base64Content = bridgeClient.htmlToBase64(htmlContent);
  
  // Determine printer name with fallback logic
  let printerName = options.printerName;
  
  if (!printerName) {
    // Try to get default printer from bridge state
    printerName = bridgeClient.getDefaultPrinter ? bridgeClient.getDefaultPrinter() : null;
    
    // If still no printer, refresh and try again
    if (!printerName && bridgeClient.updatePrinters) {
      await bridgeClient.updatePrinters();
      printerName = bridgeClient.getDefaultPrinter();
    }
  }
  
  // Prepare request for bridge
  const printRequest: BridgePrintRequest = {
    printer_name: printerName,
    content: base64Content,
    content_type: options.contentType || 'html',
    copies: options.copies || 1
  };
  
  // Send to bridge
  const result = await bridgeClient.print(printRequest);
  
  if (!result.success) {
    throw new Error(`Print error: ${result.message}`);
  }
  
  console.log(`Print successful. Job ID: ${result.job_id}`);
}

Printer Selection Logic

  1. Use explicitly specified printerName if provided
  2. Fall back to bridge’s default printer
  3. If no default, refresh printer list and try again
  4. If still no printer, the request will fail with an error

Using Bridge Printing

Option 1: Automatic Detection

Set useBridge: true in print options:
await print('receipt-content', {
  useBridge: true,
  printerName: 'EPSON TM-T88V',
  copies: 2,
  contentType: 'html'
})

Option 2: Direct Bridge Method

Use the printDirect method for full control:
import { usePrint } from 'vue-print-it'

const { printDirect } = usePrint()

const htmlContent = `
  <!DOCTYPE html>
  <html>
    <head><title>Receipt</title></head>
    <body>
      <h1>Order #12345</h1>
      <p>Total: $99.99</p>
    </body>
  </html>
`

await printDirect(htmlContent, {
  printer_name: 'Receipt Printer',
  content_type: 'html',
  copies: 1
})

Option 3: Using the Composable

The usePrintBridge composable provides reactive state and methods:
<script setup>
import { usePrintBridge } from 'vue-print-it'

const { 
  bridgeState, 
  refreshPrinters, 
  setDefaultPrinter,
  getDefaultPrinter,
  checkConnection 
} = usePrintBridge()

// Check connection on mount
onMounted(async () => {
  const connected = await checkConnection()
  if (connected) {
    await refreshPrinters()
  }
})

// Change default printer
const selectPrinter = (printerName) => {
  setDefaultPrinter(printerName)
}
</script>

<template>
  <div>
    <h2>Bridge Status</h2>
    <p>Connected: {{ bridgeState.isConnected }}</p>
    <p>Default Printer: {{ bridgeState.defaultPrinter }}</p>
    
    <h3>Available Printers</h3>
    <ul>
      <li v-for="printer in bridgeState.availablePrinters" :key="printer.name">
        {{ printer.name }}
        <span v-if="printer.is_default">(Default)</span>
        <button @click="selectPrinter(printer.name)">Set Default</button>
      </li>
    </ul>
    
    <button @click="refreshPrinters">Refresh Printers</button>
  </div>
</template>

Bridge API Reference

Endpoints

The bridge service exposes these HTTP endpoints:
GET /health
endpoint
Check if bridge service is runningResponse:
{
  "status": "ok",
  "version": "1.0.0",
  "uptime": 3600
}
GET /api/printers
endpoint
List all available printersResponse:
[
  {
    "name": "HP LaserJet Pro",
    "is_default": true,
    "status": "idle"
  },
  {
    "name": "EPSON TM-T88V",
    "is_default": false,
    "status": "idle"
  }
]
POST /api/print
endpoint
Send a print jobRequest Body:
{
  "printer_name": "EPSON TM-T88V",
  "content": "<base64-encoded-html>",
  "content_type": "html",
  "copies": 2,
  "options": {}
}
Response:
{
  "success": true,
  "job_id": "print-job-12345",
  "message": "Print job sent successfully"
}

Configuration Options

baseUrl
string
default:"http://localhost:8765"
URL where the bridge service is running. Can be a remote server.
autoConnect
boolean
default:"false"
Automatically connect and discover printers when plugin is installed.
autoSelectDefault
boolean
default:"true"
Automatically select the system’s default printer if no printer is specified.
timeout
number
default:"2000"
Connection timeout in milliseconds for bridge requests.
retryAttempts
number
default:"3"
Number of times to retry failed connections before giving up.
defaultPrinter
string
Explicitly set a default printer by name. Takes precedence over system default.
debug
boolean
default:"false"
Enable detailed logging for bridge operations.

When to Use Bridge Printing

Print receipts automatically when transactions complete without requiring user interaction.
async function printReceipt(order) {
  await print('receipt-template', {
    useBridge: true,
    printerName: 'Receipt Printer',
    autoClose: true
  })
}
Self-service kiosks need to print tickets, receipts, or labels without showing print dialogs.
await printDirect(ticketHtml, {
  printer_name: 'Label Printer',
  content_type: 'html',
  copies: 1
})
Warehouses or offices with multiple printers where specific printers must be used for different document types.
// Labels go to label printer
await print('label', { 
  useBridge: true, 
  printerName: 'Zebra ZP450' 
})

// Documents go to laser printer
await print('document', { 
  useBridge: true, 
  printerName: 'HP LaserJet' 
})
Background processes or scheduled tasks that need to print without user presence.
// Nightly report generation
cron.schedule('0 0 * * *', async () => {
  const report = await generateReport()
  await printDirect(report, {
    printer_name: 'Office Printer',
    copies: 3
  })
})

Fallback Behavior

If bridge is unavailable, the plugin automatically falls back to browser printing:
const shouldUseBridge = options.useBridge && 
                       bridgeClient && 
                       await bridgeClient.checkAvailability();

if (shouldUseBridge) {
  await printWithBridge(element, options, globalOptions, bridgeClient);
} else {
  // Fallback to browser print dialog
  await printWithBrowser(element, options, globalOptions);
}
This ensures your application continues to work even if:
  • Bridge service is down
  • Network connection is lost
  • Bridge plugin isn’t installed
  • User is on a machine without the bridge

Troubleshooting

Symptoms: isConnected always false, print operations failSolutions:
  1. Verify bridge service is running: curl http://localhost:8765/health
  2. Check firewall isn’t blocking port 8765
  3. Confirm correct baseUrl in plugin options
  4. Enable debug logging: debug: true
Symptoms: availablePrinters array is emptySolutions:
  1. Verify printers are installed and online in OS settings
  2. Restart bridge service
  3. Manually call refreshPrinters()
  4. Check bridge service logs for errors
Symptoms: Fetch errors in browser consoleSolutions:
  1. Bridge service must send proper CORS headers
  2. If bridge is on different domain, configure CORS
  3. Use proxy in development if needed

Security Considerations

The bridge service has direct access to system printers. Always:
  • Run bridge service on trusted networks only
  • Use authentication if bridge is exposed
  • Validate all print content on server side
  • Never expose bridge to public internet without security

Next Steps

Plugin System

Learn how the plugin architecture works

Style Injection

Understand how styles are preserved

Build docs developers (and LLMs) love