Shopify does not print. The admin can show you an order and your browser can send that page to a printer, but nothing in Shopify pushes a ticket to a machine in your kitchen or warehouse on its own. The official Order Printer app is a template renderer with a Print button: it still needs a person.
The short answer: an order becomes a printed ticket when something listens to Shopify's orders/create or orders/paid webhook and forwards it to a printer that is reachable from the internet. You can get there with a ready-made connector, with Shopify Flow, or with fifteen lines of your own code. This guide covers all three and, more usefully, when each one breaks.
The three approaches
| Approach | Setup time | Needs code | Custom layout | Best for |
|---|---|---|---|---|
| Connector app | Minutes | No | Template-based | Merchants, agencies delivering quickly |
| Shopify Flow | ~30 min | No | Limited to the payload you build | Stores already using Flow, conditional rules |
| Your own webhook | An hour | Yes | Total | Custom logic, multi-location routing |
None of them require a computer in the shop, provided the printer holds its own connection to the internet: either a cloud printer with 4G/Wi-Fi/Ethernet, or an existing USB printer behind a Raspberry Pi adapter.
1. The connector route
The fastest path is the Shopify connector: you authorise the app on your store, pick which printer receives which orders, and adjust the ticket template. Orders print as they are paid. Step-by-step instructions live in the Shopify integration docs.
This is the right answer for most merchants and for agencies who need a store live this week. Its limit is layout: you get a good default ticket and template controls, not arbitrary logic.
2. Shopify Flow
If your store already uses Flow, you can trigger printing without leaving it:
- Trigger: Order created (or Order paid: see below).
- Optional condition: only print orders above a value, from a location, or containing a given product.
- Action: Send HTTP request to the print endpoint, with the ticket text built from Flow variables.
Flow is worth the detour when the decision to print is conditional: one printer for pickup, another for delivery, none for digital-only orders. It is not worth it when you simply want every order printed; the connector does that with less to maintain.
3. Your own webhook
Full control, and less code than expected. Register a webhook on orders/paid, then in your handler:
// POST /webhooks/shopify/orders-paid
export default async function handler(req, res) {
const order = req.body;
const lines = [
`ORDER ${order.name}`,
new Date(order.created_at).toLocaleString(),
'',
...order.line_items.map(i => `${i.quantity}x ${i.title}`),
'',
`TOTAL: ${order.total_price} ${order.currency}`,
];
await fetch(
`https://www.expedy.fr/api/v2/printers/${process.env.PRINTER_UID}/print`,
{
method: 'POST',
headers: {
Authorization: `${process.env.API_SID}:${process.env.API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
printer_msg: lines.join('\n'),
origin: `shopify-${order.id}`,
}),
},
);
res.status(200).end();
}
Two details in that snippet matter more than the rest.
orders/paid, not orders/create. orders/create fires before payment is captured. Print on it and you will print orders that fail authorisation, plus every abandoned draft order your staff creates. Unless you deliberately want unpaid orders on paper, orders/paid is the correct trigger.
origin carries the order id. Shopify retries webhooks, and a retry after a timeout means the same order arrives twice. Passing a stable value in origin gives you a key to detect and discard the duplicate before printing it.
The failure modes nobody mentions
Duplicate tickets. Shopify considers a webhook failed if you do not answer within five seconds. If you print then respond, a slow print call turns into two tickets. Respond 200 first, print asynchronously.
Emoji and special characters. Thermal printers speak ESC/POS, not Unicode. A product title containing an emoji or an unusual glyph can produce garbage or, on some models, drop the connection. Strip or transliterate before sending: this is the single most common cause of "the printer stopped working" reports.
Paper width. An 80 mm roll fits about 42 characters per line at standard size. Long product titles wrap in ways that make the ticket hard to read in a rush. Truncate deliberately rather than letting the printer decide.
The offline printer. Someone unplugs it, the roll runs out, the router reboots. Decide in advance whether an undeliverable job should be queued, retried or alerted on. Checking the delivery state and alerting is a ten-minute addition that prevents an entire class of "we never got the order" incidents.
Formatting the ticket
Bold headers, a logo, a QR code linking to the order, a barcode, an automatic cut: all of it is markup inside printer_msg. The receipt layout reference lists the tags; image and logo printing and QR codes cover the two most requested additions.
Where to start
If you run one store and want orders on paper today, install the connector and stop reading. If you route orders between locations or need custom logic, write the webhook: it is an afternoon, not a project. Either way, create a free account and print a test ticket before you touch production: the free plan covers enough requests to validate the whole chain.
FAQ
Can Shopify print orders automatically without an app?
Only if you write the integration yourself: register an orders/paid webhook and forward the order to a print endpoint. Shopify itself never pushes to a printer, and the Order Printer app still requires someone to click Print.
Do I need a computer in the shop?
No, provided the printer holds its own internet connection. A cloud printer with 4G, Wi-Fi or Ethernet connects on its own; an existing USB printer can do the same behind a Raspberry Pi adapter.
Should I use orders/create or orders/paid?
orders/paid in almost every case. orders/create fires before payment is captured, so it also prints failed authorisations and draft orders created by staff.
Why do some orders print twice?
Shopify retries a webhook when your endpoint does not answer within five seconds. Respond 200 immediately and print asynchronously, and pass the order id in the origin field so duplicates can be detected.
Can I print to different printers per location?
Yes. Each printer has its own printer_uid, so routing is a matter of choosing the uid from the order's location, shipping method or line items before making the call.