
Why we build Shopify apps in Laravel
A Shopify app is mostly a backend. Processing webhooks, syncing data, running tasks on time. Why Laravel is strong at that, with the queue and the scheduler at its heart.
An app is more than a screen
What a merchant sees of an app is a few screens in the Shopify admin. Most of the work happens behind them. An order comes in and has to go to the ERP. Every night the stock of a thousand products has to be right. An invoice has to be ready as a PDF when the customer asks for it.
That work does not follow the rhythm of a visitor pressing a button. It comes in bursts, it has to be retried when a system does not answer for a moment, and it has to run at fixed times. Laravel is a PHP framework that ships everything for exactly that: a queue, a scheduler, a database layer and tests.
Shopify waits five seconds
Shopify reports events with webhooks: an order, a new customer, a removed app. Your app has to answer within five seconds. If it does not, Shopify tries again eight times over four hours. If the webhook fails eight times in a row, Shopify deletes a subscription created through the API, and you stop receiving notifications.
Sending an order to an ERP often takes longer than five seconds. So at that moment the app does only two things: check that the message really comes from Shopify, and put the work on the queue. Shopify recommends that approach itself.
// Check the signature, put the work on the queue, and answer at once.
Route::post('/webhooks/orders/create', function (Request $request) {
$hmac = base64_encode(hash_hmac('sha256', $request->getContent(), config('services.shopify.secret'), true));
abort_unless(hash_equals($hmac, (string) $request->header('X-Shopify-Hmac-Sha256')), 401);
ProcessOrder::dispatch($request->header('X-Shopify-Webhook-Id'), $request->json()->all());
return response()->noContent();
});A queue that recovers by itself
On the queue, a worker picks up the task and runs it, separately from Shopify's request. What Laravel adds as standard:
- Retrying. You set how often a task may try and how long it waits in between. If the ERP is down for five minutes, the order simply goes through afterwards.
- Keeping failed tasks. Whatever does not make it after every attempt goes into a failed jobs table, with the error message. One command puts them back on the queue.
- No double work. A unique task is not scheduled again while the same one is still waiting or running. Shopify may send a webhook more than once; the webhook ID tells you it is a repeat.
- Overview. Horizon shows which tasks are waiting, how long they take and what went wrong, for queues in Redis.
Within Shopify's limits
Shopify's GraphQL Admin API charges points per request. Your app has a budget that refills every second; ask too much at once and you get an error. In Laravel you give a task a limit. When the budget runs out, the task goes back on the queue and tries again later, instead of failing.
For large amounts of data, such as every order from a year, you use Shopify's bulk operations. Shopify runs the query itself and delivers a file with the result. The query itself does not count toward the limit. Laravel then processes that file in batches, each batch its own task on the queue.
One cron entry for every task
For tasks at fixed times, the server needs one cron entry, which starts Laravel's scheduler every minute. Which tasks run when lives in the code, so it is in version control with the rest of the app and goes through review.
A task no app with webhooks should be without: periodically fetching from Shopify what has changed, in case a webhook did not arrive. Shopify does not guarantee webhook delivery and recommends such a check itself.
// The server starts this every minute; Laravel decides what is due.
Schedule::job(new ReconcileOrders)->hourly()->withoutOverlapping();
Schedule::job(new SyncStock)->everyFifteenMinutes()->onOneServer();
Schedule::command('queue:prune-failed --hours=168')->daily();What about Shopify's template?
Shopify recommends its own template for most new apps, built on React Router in JavaScript. That is a good start for an app that consists mostly of screens. So Laravel is a choice, not a requirement.
We choose Laravel when the app mostly works behind the scenes: integrations with an ERP or accounting system, tasks that take minutes, and lots of data. Then you want a queue, a scheduler, database migrations and tests that already come with the framework, instead of assembling them yourself.
Our stance
A webhook we miss, we fetch ourselves. An app that relies on Shopify's delivery misses an order sooner or later.Brandbrand
Questions about this
Does a Shopify app have to be in Laravel?
No. Shopify recommends a JavaScript template for most apps. We choose Laravel for apps with a lot of work behind the scenes: integrations, long tasks and lots of data.
What happens if the app's server is down for a while?
Shopify retries a webhook eight times over four hours. If it still does not arrive, the periodic check fetches the missed changes from Shopify.
Can Laravel handle Shopify's API limits?
Yes. A task with a limit waits until there is budget again, and large amounts of data go through Shopify's bulk operations.
What exactly is a queue?
A list of tasks that a separate process runs one by one. The app does not have to wait for them, and a task that fails can run again.


