
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
react-escpos
Advanced tools
Convert React components (especially @react-pdf/renderer) to ESC/POS thermal printer commands
Convert React components (especially @react-pdf/renderer components) directly to ESC/POS thermal printer commands.
Traditional workflow for thermal printing with React:
React → PDF → Print PDF → Thermal Printer
With react-escpos:
React → ESC/POS → Thermal Printer ✨
Benefits:
npm install react-escpos
# or
yarn add react-escpos
npm install react @react-pdf/renderer
import { convertToESCPOS } from "react-escpos";
import { Document, Page, View, Text } from "@react-pdf/renderer";
import fs from "fs";
// Define your receipt component
const Receipt = ({ data }) => (
<Document>
<Page size="A4">
<View>
<Text style={{ textAlign: "center", fontWeight: "bold" }}>
My Store
</Text>
<Text>Receipt #12345</Text>
<View style={{ borderBottom: "1px solid black" }} />
<Text>Total: ${data.total}</Text>
</View>
</Page>
</Document>
);
// Convert to ESC/POS
const buffer = await convertToESCPOS(<Receipt data={{ total: 99.99 }} />, {
paperWidth: 48, // 80mm thermal (48 chars)
encoding: "utf-8",
cut: "full",
feedBeforeCut: 3,
});
// Save for testing or send to printer
fs.writeFileSync("receipt.bin", buffer);
convertToESCPOS(component, options?)Converts a React component to ESC/POS commands.
Parameters:
component (ReactElement) - React component to convert (typically @react-pdf/renderer component)options (ConversionOptions) - Optional configurationOptions:
interface ConversionOptions {
paperWidth?: number; // Width in characters (default: 48 for 80mm)
encoding?: string; // Character encoding (default: 'utf-8')
debug?: boolean; // Enable debug output (default: false)
cut?: boolean | "full" | "partial"; // Paper cutting (default: 'full')
feedBeforeCut?: number; // Lines to feed before cut (default: 3)
adapter?: RendererAdapter | ComponentMapping; // Custom adapter or component mapping (default: ReactPDFAdapter)
}
Returns: Promise<Buffer> - ESC/POS command buffer ready to send to printer
react-escpos now supports a flexible adapter system that allows you to use custom component names instead of being tied to @react-pdf/renderer components. This makes it easier to create thermal printer-specific React components while maintaining the same ESC/POS output.
By default, react-escpos uses the ReactPDFAdapter which supports @react-pdf/renderer components:
// No adapter needed - uses ReactPDFAdapter by default
const buffer = await convertToESCPOS(
<Document>
<Page>
<Text>Hello World</Text>
</Page>
</Document>
);
You can map your own component names to standard element types using a simple configuration object:
// Define your custom components
const Receipt = ({ children }) => <div>{children}</div>;
const Header = ({ children }) => <div>{children}</div>;
const ItemRow = ({ children }) => <div>{children}</div>;
const Label = ({ children }) => <span>{children}</span>;
const Price = ({ children }) => <span>{children}</span>;
// Create your receipt
const MyReceipt = (
<Receipt>
<Header>
<Label>My Store</Label>
</Header>
<ItemRow>
<Label>Product A</Label>
<Price>$10.00</Price>
</ItemRow>
</Receipt>
);
// Convert with custom component mapping
const buffer = await convertToESCPOS(MyReceipt, {
paperWidth: 48,
adapter: {
Receipt: 'document',
Header: 'view',
ItemRow: 'view',
Label: 'text',
Price: 'text',
}
});
Your custom components must map to one of these standard types:
document - Top-level container (like <Document>)page - Page container (like <Page>)view - Layout container (like <View>)text - Text content (like <Text>)image - Image element (like <Image>)For more control, you can create a custom adapter class:
import { CustomAdapter } from 'react-escpos';
const myAdapter = new CustomAdapter({
Receipt: 'document',
Header: 'view',
Item: 'text',
// ... more mappings
});
const buffer = await convertToESCPOS(<MyComponent />, {
adapter: myAdapter
});
<Receipt> instead of <Document>, <ItemRow> instead of <View>@react-pdf/rendererimport { convertToESCPOS } from 'react-escpos';
// Custom thermal printer components
const Receipt = ({ children }) => <div>{children}</div>;
const Store = ({ name, children }) => (
<div style={{ textAlign: 'center', fontWeight: 'bold', fontSize: 20 }}>
{name}
{children}
</div>
);
const Divider = () => <div style={{ borderBottom: '1px solid black' }} />;
const Item = ({ label, price }) => (
<div style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<span>{label}</span>
<span>${price.toFixed(2)}</span>
</div>
);
const Total = ({ amount }) => (
<div style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<span style={{ fontWeight: 'bold' }}>TOTAL</span>
<span style={{ fontWeight: 'bold' }}>${amount.toFixed(2)}</span>
</div>
);
// Build your receipt
const MyReceipt = (
<Receipt>
<Store name="Coffee Shop">
<p>123 Main St</p>
</Store>
<Divider />
<Item label="Espresso" price={3.50} />
<Item label="Croissant" price={4.00} />
<Divider />
<Total amount={7.50} />
</Receipt>
);
// Convert with component mapping
const buffer = await convertToESCPOS(MyReceipt, {
adapter: {
Receipt: 'document',
Store: 'text',
Divider: 'view',
Item: 'view',
Total: 'view',
p: 'text',
div: 'view',
span: 'text',
}
});
Text content with styling (bold, size, alignment):
<Text
style={{
fontWeight: "bold",
fontSize: 20,
textAlign: "center",
}}
>
Hello World
</Text>
Container with flexbox-like layout:
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
borderBottom: "1px solid black",
}}
>
<Text>Item</Text>
<Text>$10.00</Text>
</View>
Display logos, graphics, or QR codes using base64-encoded images:
<Image
src="data:image/png;base64,iVBORw0KGgoAAAANS..."
style={{ width: 200, textAlign: "center" }}
/>
Features:
textAlign style (left, center, right)Base64 Image Example:
import { Image } from "@react-pdf/renderer";
const LOGO_BASE64 = "iVBORw0KGgoAAAANSUhEUgAA..."; // Your base64 string
const Receipt = () => (
<Document>
<Page>
<View>
{/* Centered logo */}
<Image
src={`data:image/png;base64,${LOGO_BASE64}`}
style={{ width: 150, textAlign: "center" }}
/>
<Text style={{ textAlign: "center" }}>My Store</Text>
</View>
</Page>
</Document>
);
How it works:
Top-level containers (automatically handled).
fontSize: Mapped to ESC/POS text sizes (normal, double-width, double-height, quad)fontWeight: 'bold' → ESC/POS boldtextAlign: 'left' | 'center' | 'right'flexDirection: 'row' | 'column' (affects layout)justifyContent: 'space-between' (special handling for receipts)borderBottom / borderTop: Divider lines (solid/dashed)padding / margin: Spacing (converted to line feeds)width: Column width (percentage or fixed)const Receipt = ({ data }) => (
<Document>
<Page>
<View>
<Text style={{ textAlign: "center", fontWeight: "bold", fontSize: 20 }}>
{data.storeName}
</Text>
<Text style={{ textAlign: "center" }}>{data.address}</Text>
<View style={{ borderBottom: "1px solid black" }} />
{data.items.map((item) => (
<View
style={{ flexDirection: "row", justifyContent: "space-between" }}
>
<Text>{item.name}</Text>
<Text>${item.price.toFixed(2)}</Text>
</View>
))}
<View style={{ borderTop: "1px solid black" }} />
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
<Text style={{ fontWeight: "bold" }}>TOTAL</Text>
<Text style={{ fontWeight: "bold" }}>${data.total.toFixed(2)}</Text>
</View>
</View>
</Page>
</Document>
);
<View style={{ flexDirection: "row" }}>
<View style={{ width: "50%" }}>
<Text>Product A</Text>
</View>
<View style={{ width: "25%" }}>
<Text style={{ textAlign: "center" }}>2</Text>
</View>
<View style={{ width: "25%" }}>
<Text style={{ textAlign: "right" }}>$20.00</Text>
</View>
</View>
Common thermal printer widths:
| Printer Width | Characters | paperWidth |
|---|---|---|
| 58mm | 32 chars | 32 |
| 80mm | 48 chars | 48 ✓ |
| 112mm | 64 chars | 64 |
The library uses CP860 encoding by default, which supports Brazilian Portuguese special characters:
Common characters are automatically handled. Unsupported characters are replaced with ?.
Control paper cutting behavior:
// Full cut
await convertToESCPOS(<Receipt />, { cut: "full" });
// Partial cut (perforated)
await convertToESCPOS(<Receipt />, { cut: "partial" });
// No cut
await convertToESCPOS(<Receipt />, { cut: false });
// Custom feed before cut
await convertToESCPOS(<Receipt />, {
cut: "full",
feedBeforeCut: 5, // 5 lines
});
import { ESCPOSGenerator } from "react-escpos";
const generator = new ESCPOSGenerator(48, "utf-8", false);
generator.initialize();
generator.setAlign("center");
generator.setBold(true);
generator.addText("Hello World");
generator.addNewline();
generator.addDivider(false);
generator.cutFullWithFeed(3);
const buffer = generator.getBuffer();
generator.addQRCode("https://example.com", 6);
After generating the ESC/POS buffer, send it to your printer:
import { SerialPort } from "serialport";
const port = new SerialPort({ path: "/dev/usb/lp0", baudRate: 9600 });
port.write(buffer);
// Main process
ipcMain.handle("print", async (event, buffer) => {
// Send to printer
});
// Renderer
const buffer = await convertToESCPOS(<Receipt />);
ipcRenderer.invoke("print", buffer);
import net from "net";
const socket = net.connect({ host: "192.168.1.100", port: 9100 });
socket.write(buffer);
socket.end();
You can convert any image to base64 for use in receipts:
import fs from 'fs';
// From file
const imageBuffer = fs.readFileSync('logo.png');
const base64 = imageBuffer.toString('base64');
// Use in component
<Image src={`data:image/png;base64,${base64}`} />
textAlign: 'center' for centered logosconst ReceiptWithLogo = ({ data, logoBase64 }) => (
<Document>
<Page>
<View>
{/* Centered logo at top */}
<Image
src={`data:image/png;base64,${logoBase64}`}
style={{ width: 150, textAlign: "center" }}
/>
{/* Store info */}
<Text style={{ textAlign: "center", fontWeight: "bold" }}>
{data.storeName}
</Text>
<Text style={{ textAlign: "center" }}>{data.address}</Text>
<View style={{ borderBottom: "1px solid black" }} />
{/* Items */}
{data.items.map((item, i) => (
<View key={i} style={{ flexDirection: "row", justifyContent: "space-between" }}>
<Text>{item.name}</Text>
<Text>${item.price.toFixed(2)}</Text>
</View>
))}
<View style={{ borderTop: "1px solid black" }} />
{/* Total */}
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
<Text style={{ fontWeight: "bold" }}>TOTAL:</Text>
<Text style={{ fontWeight: "bold" }}>${data.total.toFixed(2)}</Text>
</View>
<Text style={{ textAlign: "center" }}>Thank you!</Text>
</View>
</Page>
</Document>
);
// Usage
const buffer = await convertToESCPOS(
<ReceiptWithLogo data={receiptData} logoBase64={myLogo} />,
{ paperWidth: 48, cut: "full" }
);
Fully typed for TypeScript projects:
import {
convertToESCPOS,
ConversionOptions,
ESCPOSGenerator,
} from "react-escpos";
Contributions are welcome! Please feel free to submit a Pull Request.
This package is automatically published to npm and GitHub Packages when changes are pushed to the main branch.
For maintainers:
package.json and push to mainSee PUBLISHING.md for detailed publishing instructions and setup.
MIT © gmartinu
Made with ❤️ by gmartinu
FAQs
Convert React components (especially @react-pdf/renderer) to ESC/POS thermal printer commands
We found that react-escpos demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.