Autogenerate SKU for Craft Commerce products

A client has a relatively simple algorithm for their product SKUs.

  • 1st initial of Product Type Watch
  • 1st initial of Category Wrist
  • 1st initial of Brand Rolex
  • a dash -
  • a random 3-6 digit number

So a valid SKU is WWR-187530

They want one less thing to worry about when adding products. Can Craft Commerce auto the SKUs for them?

Craft Commerce Settings: Automatic SKU Format

Craft Commerce 3 has an option in the Product Type settings that might work.

Go to Commerce > System Settings > Product Type > Your Product Type and look for Automatic SKU Format.

https://cdn.eaglepeakweb.com/img/projects/blog/automatic-sku-format.png

đź’ˇ If Automatic SKU Format is empty then SKU becomes a required field when creating a product.

Let’s assume you have a category: Underwear

With this code snippet { product.category[0].title|slice(0, 1)}-{now|date('mdy') }, you can get the 1st initial of the main category + the current date.

But with this trivial approach you could run into errors 🚨 For example, not being able to create a new product because the SKU is not unique.

To ensure a unique SKU, we’d like to use Product ID as the random digit. But it’s not possible via this settings field because the ID is only available after the product has been created. So let’s try a more robust approach — a custom Craft plugin.

Using a custom Craft plugin to generate SKU

🔌 We’re not going to delve into how to create a Craft plugin. For detailed info, read Andrew Welch’s guide or watch the CraftQuest series.

With a plugin, you can use product->id to ensure your SKU is unique. And also create custom functions to get the initials from a category or title.

There are 2 scenarios:

  1. New product being created or
  2. Existing product being updated

First, we confirm the element being saved is a Product type. Then we check the parameter isNew inside the events.

If the element is NOT new, we can update the product using the Elements::EVENT_BEFORE_SAVE_ELEMENT event since the product->id already exists.

Event::on(Elements::class, Elements::EVENT_BEFORE_SAVE_ELEMENT, function(Event $event) {
    if ($event->element instanceof \craft\commerce\elements\Product) {
        if(!$event->isNew){
            $product = $event->element;
            $sku = $this->generateSKU($product);
        }
    }
});

If the element IS new, we’ll need to use Elements::EVENT_AFTER_SAVE_ELEMENT

Event::on(Elements::class, Elements::EVENT_AFTER_SAVE_ELEMENT, function(Event $event) {
    if ($event->element instanceof \craft\commerce\elements\Product) {
        if($event->isNew){
            $product = $event->element;
            $sku = $this->generateSKU($product);
            Craft::$app->getElements()->saveElement($product, false, false);
        }
    }
});

There are a few reasons to use the isNew validation:

  1. ✨ Handling new products
    If a product is new, we don’t know the product->id until after the product has been saved, so we couldn’t use EVENT_BEFORE_SAVE_ELEMENT.
  2. ♻️ Preventing a loop
    EVENT_AFTER_SAVE_ELEMENT needs to execute saveElement(), without the if($event->isNew) validation, saving would stay in a loop, re-saving after every save.
  3. 📦 Handling existing products
    They will not fulfill the isNew rule and the product->id is known, therefore they can be handled using the EVENT_BEFORE_SAVE_ELEMENT.

Keep in mind that SKU assignment is done at the record/​variation level. So first you need to get the record of the product that’s just been created.

Even if you’re not using multiple variants, Craft Commerce’s product structure still uses variants under the hood.1

$product = $event->element;
$records = $product->getVariants(); 

So if we have the following product data:

Product Type: Shoes
Category: Sneakers -> Men
Brand: New Balance

The SKU should be:SSNB-132

Where 132 is the Product ID. And where NB are the brand initials.

Here’s the code for generateSKU($product) function to autogenerate the SKU:

function generateSKU($product){
        $records = $product->getVariants();
        $brandInitials = null;
        $category = $product->category->level('>= 2')->one();
        $brand = $product->brand->one();

        $productType = substr($product->type->name,0,1);
        $categoryInitial = (isset($category->title)) ? substr($category->title,0,1) : '';
        $brand = (isset($brand->title)) ? explode(" ", $brand->title) : '';
        foreach ($brand as $b) { $brandInitials .= $b[0]; }
        
        $sku = $productType.$categoryInitial.$brandInitials.'-'.$product->id;

        foreach($records as $key => $record){ 
            $record->sku = $sku; 
            $records[$key] = $record; 
        }
        $product->setVariants($records);

        return $sku;
    }

Summary

Craft Commerce Settings: Automatic SKU Format

Pros Cons
No PHP programming skills needed Only manually entered data can be used
Attempting to get the initials of multiple fields gets messy quickly
If the category is new or the selected variable is empty, the SKU value will also be empty

Custom Craft plugin

Pros Cons
You can write functions to get parts of the title, category or field PHP programming skills required
Possible to add validations if the category is new or a field is empty
You can get as creative as you want with the SKU value

đź‘Ť đź‘Ž What do you think of either approach? Let me know @claus0618

Bonus Tip - Set default values

With a custom plugin you can also set default values for product fields.

For example, for the product type Watches we needed to specify if it had the original box & papers. Since most of the watches were pre-owned, most didn’t have the original packaging.

We assigned the default values in the custom plugin by using Elements::EVENT_BEFORE_SAVE_ELEMENT:

$event->element->setFieldValues([
    'boxPapers' => ($product->boxPapers != "") ? $product->boxPapers : "No. Only the watch.", 
    'condition' => ($product->condition != "") ? $product->condition : "Pre-owned."
]);


  1. Commerce 3 Documentation: Products.

Claudia Aguilar

Partner, Software Engineer