HowTo: Create a Cron Job in Magento 2


A while ago I needed to run a task automatically every night to clean up some old rows from a custom table. The obvious tool for the job is a cron job, but if you come from Magento 1 the syntax changed a bit, so let me show you how I set it up.

This post assumes that there is already a custom module created in app/code/Vendor/Module. If you need to know how to create a custom module, look at How to create a custom module for Magento 2.

The crontab.xml

Magento reads the jobs of every module from its etc/crontab.xml file, so that is the first thing we need to create.

xml
<?xml version="1.0"?>
<!-- app/code/Vendor/Module/etc/crontab.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="default">
        <job name="vendor_module_cleanup" instance="Vendor\Module\Cron\Cleanup" method="execute">
            <schedule>0 2 * * *</schedule>
        </job>
    </group>
</config>

There are a couple of things worth explaining here:

  1. The instance and method tell Magento which class and method to run, in this case Vendor\Module\Cron\Cleanup::execute().
  2. The <schedule> uses the standard cron expression. 0 2 * * * means every day at 2:00 AM.
  3. The group id lets you run your jobs in a separate group if you need to, but for most cases the default group is fine.
Notice: If you want the merchant to be able to change the schedule from the admin, replace <schedule> with <config_path>vendor_module/cron/schedule</config_path> and expose that value in system.xml.

The Cron Class

Now we create the class we referenced in the instance attribute. Keep it thin, the cron is just an entry point, the real work should live in a service.

php
<?php
/** app/code/Vendor/Module/Cron/Cleanup.php */
namespace Vendor\Module\Cron;

use Psr\Log\LoggerInterface;

class Cleanup
{
    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @param LoggerInterface $logger
     */
    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    /**
     * @return $this
     */
    public function execute()
    {
        $this->logger->info('Vendor\Module cleanup cron started');

        // Your logic here, e.g. delete old rows, send a report, warm a cache...

        return $this;
    }
}

Testing it

You don’t have to wait until 2 AM to know if it works. You can trigger the group manually:

bash
bin/magento cron:run --group=default

Then check var/log/system.log (or your custom logger) and you should see the message we logged.

Remember that on a real server the system crontab needs to call Magento’s cron every minute, otherwise nothing will ever run: * * * * * php /path/to/magento/bin/magento cron:run. Running bin/magento cron:install sets this up for you.

Hope this helps.

If you have any question or know a better way to achieve this, feel free to share in the comments below.

Related links: