olegkoval / magento2-regenerate-url-rewrites

olegkoval/magento2-regenerate-url-rewrites

Add into Magento 2 a CLI feature which allow to regenerate a Url Rewrites of products and categories

  • Oleg Koval
magento2-module Compatibility: 2.4.7-2.4.9 Code Quality: Fail Tests: N/A Security: Pass OSL-3.0, AFL-3.0

Are you the maintainer of olegkoval?

Packagento pulls olegkoval's Composer packages from the public registry so buyers can find them here.

Claim the namespace to take ownership, publish new releases directly, and start charging for premium versions.

Claim this namespace →

"Regenerate Url Rewrites" extension

Latest Version
Total Downloads
License

A Magento 2 CLI extension that regenerates URL Rewrites for products and categories — across all
stores, a single store view, or a specific ID/range — with options to preserve old URLs, delete
orphaned rewrites, change the URL suffix, and more.

Extension homepage: https://github.com/olegkoval/magento2-regenerate_url_rewrites
Changelog: CHANGELOG.md

Table of Contents

REQUIREMENTS

Magento Open Source (CE) PHP
2.4.7 8.2, 8.3
2.4.8 8.3, 8.4
2.4.9 (latest) 8.4, 8.5

composer.json declares "php": ">=8.2". PHP compatibility otherwise follows whatever your
Magento version itself requires — the extension doesn't impose a narrower floor than Magento does.
2.4.6 and older are treated as legacy/best-effort only (no longer patched upstream by Adobe).

INSTALLATION

COMPOSER INSTALLATION

  • run composer command:

$> composer require olegkoval/magento2-regenerate-url-rewrites

MANUAL INSTALLATION

  • extract files from an archive

  • deploy files into Magento2 folder app/code/OlegKoval/RegenerateUrlRewrites

ENABLE EXTENSION

  • enable extension (use Magento 2 command line interface):

$> php bin/magento module:enable OlegKoval_RegenerateUrlRewrites

  • to make sure that the enabled module is properly registered, run 'setup:upgrade':

$> php bin/magento setup:upgrade

  • [if needed] re-compile code and re-deploy static view files:

$> php bin/magento setup:di:compile
$> php bin/magento setup:static-content:deploy

QUICK START

  • regenerate Url Rewrites for all products, in all stores (product is the default entity type, so it can be omitted):

$> php bin/magento ok:urlrewrites:regenerate

  • regenerate Url Rewrites for all categories, in all stores:

$> php bin/magento ok:urlrewrites:regenerate --entity-type=category

  • regenerate Url Rewrites for one product (ID 122), in one store (ID 2):

$> php bin/magento ok:urlrewrites:regenerate --product-id=122 --store-id=2

Keep reading for the full options reference, or jump to More Examples.

CLI OPTIONS REFERENCE

General

Option Description
--entity-type=<product|category> Entity type to regenerate. Default: product. Can be omitted when --category-id/--categories-range is given — the extension infers category automatically.
--store-id=<id> Regenerate only for the given store view. Omit to run for all stores.
--save-old-urls Keep old URLs working: when a product/category URL changes, its old URL becomes a 301 redirect to the new one instead of being discarded.
--regen-url-key Also regenerate url_key values for the selected entity type. Category runs preserve the URL keys of cascaded products. By default url_key is left untouched and only url_path/URL Rewrites are regenerated.
--no-reindex Skip the full reindex that normally runs at the end.
--no-cache-clean Skip cache:clean at the end.
--no-cache-flush Skip cache:flush at the end.
--no-progress Hide the console progress bar.
--dry-run Preview a run: do everything, report what would change, then roll it all back — nothing is saved, no reindex or cache refresh. See Notes & Caveats.
-v Print every change the run makes (URL rewrites added/removed/updated, url_key/url_path values) and a summary of them.
--no-messages Don't show messages from the extension author at the end of the run. See Messages From the Author.
--delete-orphaned-rewrites Delete url_rewrite rows (for the given --entity-type) whose product/category no longer exists.
--skip-existing Skip an entity entirely if it already has any URL Rewrite for the current store, instead of always deleting + regenerating.

Product targeting & options

Option Description
--product-id=<id> Regenerate for one specific product.
--products-range=<from>-<to> Regenerate for a range of product IDs (gaps in the range are handled automatically).
--include-not-visible Also process products with visibility "Not Visible Individually" (e.g. configurable child products) — excluded by default. See Notes & Caveats.
--add-sku-to-url Append the product's SKU as a URL segment, e.g. screws.html → screws-2244000004.html. Custom (admin-created) rewrites and kept old-URL redirects are left unchanged.
--set-product-suffix=<suffix> Set the product URL suffix (e.g. .html) before regenerating. See Notes & Caveats.

Category targeting & options

Option Description
--category-id=<id> Regenerate for one specific category.
--categories-range=<from>-<to> Regenerate for a range of category IDs (gaps in the range are handled automatically).
--exact-categories With --category-id/--categories-range: process only those categories, not all their subcategories. See Notes & Caveats.
--skip-products Skip regenerating associated product URLs when regenerating categories. See Notes & Caveats.
--set-category-suffix=<suffix> Set the category URL suffix (e.g. .html) before regenerating. See Notes & Caveats.

NOTES & CAVEATS

  • --skip-products only has an effect when the "Use Category Path for Product URLs" setting
    (Stores > Configuration > Catalog > Catalog > Search Engine Optimization, config path
    catalog/seo/product_use_categories) is enabled — that setting is what makes category
    regeneration cascade into product URLs in the first place. If it's disabled, category
    regeneration never touches product URLs, with or without --skip-products.

  • --set-product-suffix / --set-category-suffix are applied to Default Config and every
    store view, or only to the store given via --store-id, using the same write path Magento's own
    config:set CLI command uses — so validation (e.g. rejecting # or //) and Magento's own
    automatic suffix swap on existing URL Rewrites both run. A suffix locked in app/etc/config.php is
    reported before anything is saved; if either suffix value fails validation, the whole command aborts
    before any regeneration runs. Since the config is written before
    regeneration, in the same command, you get a clean
    /categorya/oldname.html -> /categorya/newname.html redirect instead of a two-step chain.

  • Failures and exit code: if any product/category (or a cleanup, reindex or cache step) fails, the run
    continues with the rest, then prints a [FAILURES] summary (counts per type plus up to 20 of the
    failures) and exits with code 1 — after reindex and cache refresh have still run. Earlier versions always exited
    0, so cron jobs or scripts that check the exit code may start reporting failures that used to be
    hidden.

  • --exact-categories: by default a category run also processes every subcategory of the given
    categories (a subcategory's URL path is built from its parents'). With this option only the given
    categories are processed — their subcategories only if a given category's URL path changed, and then
    without regenerating the subcategories' url_key (so --regen-url-key touches only the given
    categories). Products are regenerated only for the processed categories.

  • --include-not-visible makes the run process "Not Visible Individually" products too — e.g. their
    url_key is regenerated with --regen-url-key — but it can't give them URL rewrites: Magento's own
    generator creates none for a product that isn't visible in any store view. (Since Magento 2.4.7 a product
    hidden by default but visible in some store view does get rewrites for that store view — every run
    includes it there anyway; --store-id=0 runs need this option for it.)

  • Product updated_at is left alone by a regeneration run (incl. products processed by category runs), so
    sitemap lastmod, delta syncs and "recently updated" reports don't see the whole catalog as changed. With
    --regen-url-key, a product's updated_at changes only when its URL key actually changes.

  • --dry-run runs the whole regeneration in one database transaction and rolls it back at the end, so the
    preview is exact — even for cascades, like a parent category's new url_key changing its children's and
    products' URLs. It prints Changes: … (with -v every change) and Dry run: no changes were saved.; reindex
    and cache refresh are skipped. It can't be combined with --set-product-suffix/--set-category-suffix (Magento
    keeps a saved suffix in memory, so it couldn't be undone reliably). Rows it touches stay locked until the end, so
    admin saves of the same products/categories wait meanwhile (the storefront doesn't): preview targeted runs, or
    large ones off-peak. If a save fails inside one of Magento's own transactions (e.g. a category's url_key), the
    rest of that preview fails too — the failure list says so; fix the first failure and preview again.

  • --regen-url-key inverted its default behavior in 1.8.0: url_key is no longer regenerated
    automatically. Pass --regen-url-key explicitly whenever you want it regenerated too — see
    Deprecated Options if you're upgrading from an older version.

COMBINING OPTIONS

Most options combine freely, e.g.:

$> php bin/magento ok:urlrewrites:regenerate --store-id=2 --save-old-urls --regen-url-key --no-reindex

These combinations are not allowed:

  • --entity-type=product together with --category-id / --categories-range
  • --entity-type=category together with --product-id / --products-range
  • --category-id and/or --categories-range together with --product-id and/or --products-range

DEPRECATED OPTIONS

  • --check-use-category-in-product-url — obsolete. The extension now uses Magento's built-in URL
    Rewrite generator, which already checks the "Use Category Path for Product URLs" config on its
    own, so this manual flag is no longer needed.
  • --no-regen-url-key — removed in 1.8.0 (not just deprecated — passing it now causes an
    "unrecognized option" error). url_key regeneration behavior was inverted: it is now skipped by
    default and only runs when you explicitly pass --regen-url-key. If you relied on the old
    default (url_key regenerated automatically), add --regen-url-key to your existing
    commands/scripts/cron jobs after upgrading to 1.8.0.

MORE EXAMPLES

  • Regenerate Url Rewrites for product with ID 38 in store with ID 3:

$> php bin/magento ok:urlrewrites:regenerate --entity-type=product --store-id=3 --product-id=38

or

$> php bin/magento ok:urlrewrites:regenerate --store-id=3 --product-id=38

  • Regenerate Url Rewrites for products with IDs 5,6,7,8,9,10,11,12 in store with ID 2 and skip the full reindex:

$> php bin/magento ok:urlrewrites:regenerate --entity-type=product --store-id=2 --products-range=5-12 --no-reindex

  • Regenerate Url Rewrites for category with ID 22 in all stores and save current Url Rewrites:

$> php bin/magento ok:urlrewrites:regenerate --entity-type=category --category-id=22 --save-old-urls

  • Regenerate Url Rewrites for categories with IDs 21,22,23,24,25 in store with ID 2:

$> php bin/magento ok:urlrewrites:regenerate --entity-type=category --categories-range=21-25 --store-id=2

  • Set the category URL suffix to .html and regenerate all category URLs in one step:

$> php bin/magento ok:urlrewrites:regenerate --entity-type=category --set-category-suffix=.html

USE FROM CODE

The same run is available as a service (@api), e.g. for cron jobs, queue consumers or integrations —
inject OlegKoval\RegenerateUrlRewrites\Api\RegenerateServiceInterface:

$options = $this->runOptionsBuilder // OlegKoval\RegenerateUrlRewrites\Model\RunOptionsBuilder
    ->setEntityType('product')
    ->setStoreIds([2])          // empty (default): all stores
    ->setProductIds([38, 39])   // empty (default): all products
    ->setSaveOldUrls(true)
    ->setReindex(false)         // reindex and cache refresh are on by default, as in the CLI
    ->create();

$problems = $this->regenerateService->validate($options); // string[]; run() throws InputException for these
$result = $this->regenerateService->run($options);
if ($result->hasFailures()) {
    // $result->getFailureCounts(), $result->getFailures()
}

run() accepts an optional Api\ProgressReporterInterface to receive progress; without one nothing is
printed. To stop a run, throw from the reporter's advance() or message(): the run stops after the current
entity (whose rewrites stay saved), skips the remaining stores and the reindex/cache steps, restores the current
store and rethrows your exception unchanged. The same goes for an exception from the change listener below.

To track the run's changes to URL rewrites and url_key/url_path values (e.g. to log, audit or undo a run),
pass an Api\ChangeListenerInterface as the third argument of run(): its onChange() receives an Api\Data\ChangeInterface per saved change —
rewrite_added / rewrite_removed / rewrite_updated (with the full old/new url_rewrite rows) and
url_key_changed / url_path_changed (with the store of the attribute row written, 0 = default scope, and
hasOldRow()/hasNewRow(), since such a row can exist and hold NULL). A rolled-back save reports nothing; without a
listener no extra queries run. The reports are complete enough to undo a run — except URL suffix changes
(setProductUrlSuffix()/setCategoryUrlSuffix()) and the suffix swap Magento then applies to existing rewrites,
which aren't reported.

To preview a run, setDryRun(true): the run happens in one transaction that is always rolled back (no reindex
or cache refresh), and a change listener still receives every change. A dry run can't set a URL suffix, and run()
throws InputException if a database transaction is already open, since rolling back would undo the caller's work
too (e.g. in a data patch).

$result->getProcessedCounts() has the entities the run went through (entity type → store ID → count).
$options->toArray() / $builder->fromArray($array) log and replay a run; an unknown key or a value of the wrong
type makes fromArray() throw \InvalidArgumentException, so a typo can't widen a run to e.g. all products.

Backward compatibility

This extension follows Semantic Versioning. Within 1.x:

  • Nothing marked @api is removed or renamed, and neither is any public or protected member of the command
    classes (Console\Command\RegenerateUrlRewrites, RegenerateUrlRewritesAbstract), the command name or the
    INPUT_KEY_* constant values; constructors only gain optional trailing arguments.
  • Interfaces you implement (Api\ProgressReporterInterface, Api\ChangeListenerInterface) never gain methods —
    new hooks come as new interfaces. Interfaces only this extension implements (Api\RegenerateServiceInterface,
    Api\Data\RunOptionsInterface, Api\Data\RunResultInterface, Api\Data\ChangeInterface) may gain methods
    or optional parameters in minor releases, so don't implement them yourself (a plugin on the service is fine).
    The keys of the rewrite arrays in ChangeInterface stay the same.
  • Helper\Regenerate::sanitizeSkuForUrl() (how --add-sku-to-url turns a SKU into a URL segment) stays, and the
    classes the command's constructor takes (Helper\Regenerate, Model\RegenerateProductRewrites,
    Model\RegenerateCategoryRewrites) keep their names, so subclasses calling parent::__construct() keep working.
  • Otherwise the models (Model\Regenerate*Rewrites) are internal: use the service instead.

MESSAGES FROM THE AUTHOR

The extension can show short messages from its author, such as security notes or new releases:

  • Admin: in the notifications bell (System > Notifications). Only security issues use the critical
    severity that opens Magento's "Incoming Message" popup.
  • Console: at the end of an interactive ok:urlrewrites:regenerate run, before the summary. Runs without a
    terminal (cron, pipes), runs with -q and runs with --no-messages show nothing.

The messages come from
regenerate-url-rewrites.xml
in the magento-extension-notifications repository,
read at most once a day (admin and console separately) with a 2 s timeout. A failed read is ignored and retried the
next day. The request sends only a generic user agent: no shop URL, admin URL, Magento version or other data.

To turn the messages off, set Stores > Configuration > Advanced > System > Notifications > Regenerate URL Rewrites Messages to "No", or run
bin/magento config:set system/adminnotification/olegkoval_regenerate_url_rewrites 0.

SUPPORT ME

CONTACTS

LICENSE

Dual-licensed, same as Magento itself:

Enjoy!

Best regards,
Oleg Koval

"Regenerate Url Rewrites" Change Log

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

[1.12.0] - 2026-10-07

Added

  • messages from the extension author (security notes, releases) in the admin notifications bell and at the end of
    interactive console runs, read once a day from the
    magento-extension-notifications repository. Only a
    generic user agent is sent. Turn them off in Stores > Configuration > Advanced > System > Notifications, or per
    run with the new --no-messages option. See "Messages From the Author" in the README
  • --dry-run (#149): preview a run — everything runs in one database transaction that is rolled back at the end,
    so nothing is saved; prints a summary of what would change, and skips reindex and cache refresh. Can't be combined
    with --set-product-suffix/--set-category-suffix. From code: RunOptionsBuilder::setDryRun()
  • -v prints every change a run makes (#91): URL rewrites added, removed or updated, and url_key/url_path
    changes with their scope, followed by a Changes: … summary. Without -v nothing is tracked, as before

Fixed

  • a failure inside a caller's database transaction (e.g. a data patch running the service) no longer makes every
    later save of the run fail: the extension's own saves use savepoints there

Changed

  • the module now depends on Magento_AdminNotification (part of Magento Open Source)
  • the Composer package no longer contains the unit tests (Test/) or the CI configuration (.github/)

[1.11.1] - 2026-10-05

Fixed

  • ordinary product URL rewrite regeneration no longer changes updated_at, including products processed
    by category runs; explicit URL-key regeneration updates the timestamp only when the key changes
  • category runs with --regen-url-key now preserve cascaded products' URL keys; only category URL keys are
    regenerated, while the products' paths and rewrites are still refreshed

[1.11.0] - 2026-09-30

Added

  • a public service API to run the regeneration from code (cron jobs, queue consumers, integrations):
    Api\RegenerateServiceInterface (validate(), run()), run options built with Model\RunOptionsBuilder,
    a RunResultInterface with failure counts/details, and an optional ProgressReporterInterface. The CLI
    command now runs through it; its options and output are unchanged (except the new summary line below). See "Use
    From Code" in the README.
    Also: a documented way to stop a run (throw from the reporter), processed counts in the result,
    toArray()/RunOptionsBuilder::fromArray() to log and replay a run's options, and an
    Api\ChangeListenerInterface that receives a run's changes (rewrites added/removed/updated with full rows,
    url_key/url_path changes with their scope; not URL suffix config) — groundwork for a verbose output (#91)
  • new option --exact-categories: with --category-id/--categories-range, process only the given categories —
    their subcategories only when a given category's URL path changed, and then without regenerating their
    url_key, so --regen-url-key stays on the given categories. Useful for precise fixes on large category trees
  • the command prints a summary before "Finished", e.g. Processed: 38 category(ies), 2040 product(s) in 3 store(s)
  • a backward-compatibility policy (README "Backward compatibility"): semantic versioning, what stays stable within
    1.x and which interfaces may grow

Changed

  • reindex and cache refresh after a run now happen in the same PHP process through Magento's indexer and
    cache services instead of spawning bin/magento indexer:reindex / cache:clean / cache:flush: they
    use the run's own memory limit (e.g. php -d memory_limit=-1 bin/magento ...), which the spawned processes
    didn't inherit, and a failing or locked indexer is now reported in the [FAILURES] summary (exit code 1)
    instead of being ignored
  • --include-not-visible help and README now say what the option does: "Not Visible Individually" products are
    processed too (e.g. url_key regeneration), but Magento creates no URL rewrites for a product that isn't
    visible in any store view — the option never could add those
  • category runs that regenerate their products (Use Categories Path for Product URLs) now show a progress bar for
    that product phase too, instead of nothing until it ends
  • a --set-product-suffix/--set-category-suffix value locked in app/etc/config.php is now reported before
    anything is saved; before, the other suffix was still saved and then the run aborted
  • for code extending the command or the models: the command's protected _runReindexation(), _runClearCache()
    and _setSeoUrlSuffixes() moved into the service, and the models' _showProgress() / progressBarProgress /
    progressBarTotal were replaced by the progress reporter; the command's constructor gained an optional 9th
    argument (8-argument subclasses keep working). The backward-compatibility policy applies from this release on

Fixed

  • --regen-url-key for a store view left a stale store-level url_key in place when the regenerated key was
    the same as the default one (it only skipped writing a redundant copy, see #92): the rewrite used the new key
    but the product/category kept the old one, and the next run without --regen-url-key switched the URL back.
    The store-level value is now removed, so the store view inherits the default

[1.10.0] - 2026-09-26

Changed

  • the command now reports failures instead of silently skipping them: products/categories whose URL
    rewrites couldn't be generated or saved (and failed cleanup steps) are listed in an end-of-run summary —
    counts per entity type plus up to 20 of the failures — and the command exits with code 1. Reindex and
    cache refresh still run first. Previously it always exited 0, so cron jobs/scripts that check the exit
    code may now start alerting on failures that used to be hidden.
  • all-stores runs no longer regenerate every store view twice: the store 0 (default scope) pass now only
    updates default-scope url_key/url_path, and each store view is generated once — with identical
    results; on a two-store-view sample catalog full runs were ~15–30% faster, and the saving grows with the
    number of store views
  • category runs with "Use Categories Path for Product URLs" enabled regenerate each affected product once,
    after all categories are done, instead of once per ancestor category (plus a full scan of the
    product/category rewrite table after every category) — identical results; on the sample catalog a full
    category run went from ~22s to ~5s

Fixed

  • --store-id=0 (global scope) runs dropped the product rewrites of every store view after the first
    (identical paths in different store views were treated as duplicates) and didn't replace the store views'
    current rewrites, so old URLs kept serving pages and --save-old-urls redirects were lost
  • --save-old-urls now actually creates 301 redirects: when a URL changes, the old URL redirects to the
    new one. Before, the old URL kept serving the page as an ordinary rewrite (duplicate content) and the
    redirect Magento generated was silently dropped when saving (#174, #142). This includes switching to
    --add-sku-to-url with --save-old-urls: the old unsuffixed URL now redirects to the SKU URL
  • --add-sku-to-url (and path clean-up/de-duplication in general) no longer alters custom URL
    rewrites: admin-created rewrites, and old-URL redirects kept by --save-old-urls, keep their exact
    request path. Before, --add-sku-to-url appended the SKU to them too, so a custom URL like
    promo/sale became promo/sale-<sku> and the original URL stopped working.
  • with --add-sku-to-url, custom redirects (and --save-old-urls redirects) pointing at a product's URL
    now follow it to the SKU-suffixed URL; before, they kept pointing at the old unsuffixed URL, which no
    longer existed (redirect to a 404)
  • a product/category whose generated URL paths were all empty no longer has its existing rewrites
    touched (nothing to write, so the current rewrites are kept)

[1.9.1] - 2026-08-26

Added

  • new options --set-product-suffix / --set-category-suffix — set the product/category URL suffix
    (e.g. .html) before regenerating, via the same write path Magento's own config:set CLI command
    uses, so validation and the automatic suffix swap on existing url_rewrite rows both run. Applied to
    Default Config and every store view, or only the store given via --store-id. If either suffix value
    fails Magento's validation, the whole command aborts before any regeneration runs.

Fixed

  • category url_path was not recalculated per store view: when a category's url_path had only ever
    been saved at the default (store 0) scope, Magento's CategoryUrlPathGenerator short-circuited and
    handed back that inherited value instead of building a new one from the store-scoped url_key,
    because the category object's still-populated url_path made its dataHasChangedFor() checks look
    unchanged. Now url_path is cleared on the category object before generation, matching the pattern
    already used on the product side. (#184)

[1.9.0] - 2026-08-11

Added

  • new option --skip-products — skip regenerating associated product URLs when regenerating categories (useful on large catalogs when "Use Category Path for Product URLs" is enabled)
  • new option --skip-existing — skip an entity entirely if it already has any url_rewrite for the current store, instead of always deleting + regenerating
  • new option --include-not-visible — also regenerate URLs for products with visibility "Not Visible Individually" (e.g. configurable child products), excluded by default since 1.6.2
  • new option --add-sku-to-url — append the product's SKU as a URL segment in generated product Url Rewrites, e.g. screws.html -> screws-2244000004.html

Fixed

  • guard against writing a blank url_key when Magento's own transliteration can't handle the product/category title (e.g. Arabic/other non-Latin scripts)

[1.8.0] - 2026-08-10

Changed

  • BREAKING: inverted url_key regeneration default — url_key is no longer regenerated automatically; use the new --regen-url-key option to opt in. The old --no-regen-url-key option is removed entirely (not deprecated) — update any existing scripts/cron jobs that relied on the previous default.
  • fixed data loss risk in saveUrlRewrites(): deleting existing rewrites and inserting new ones now happen in a single transaction, so a failed insert can no longer leave an entity with its rewrites deleted and nothing to replace them
  • fixed category URL rewrite regeneration to process every category (top level down to the deepest child) instead of only top-level categories, so child categories' own url_key/url_path are regenerated too
  • skip writing a redundant per-store url_key override when the regenerated value is identical to the default-scope value

Added

  • new option --delete-orphaned-rewrites — deletes url_rewrite rows (for the given --entity-type) whose product/category no longer exists

[1.7.3] - 2026-08-09

Changed

  • fixed deprecated non-canonical (double) type cast for PHP 8.5 compatibility
  • use the current PHP executable (PHP_BINARY) instead of a hardcoded "php" command for reindex/cache calls
  • fixed category URL rewrite regeneration stopping entirely when a single broken/orphaned category is encountered
  • added missing type hint and defensive casting in a couple of helper methods
  • declared minimum supported PHP version (>=8.2) in composer.json

[1.7.2] - 2026-03-31

Changed

  • fixed deprecated functionality: ctype_digit()
  • improved SQL safety in URL rewrite regeneration queries
  • fixed SQL escaping in URL rewrite cleanup logic
  • fixed _clearRequestPath() to correctly handle multiple consecutive slashes
  • removed dead code from category URL rewrite model
  • display validation errors before command failure
  • display a completed progress bar for empty product/category collections
  • normalized line endings to LF

[1.7.1] - 2025-05-30

Changed

  • adapted for compatibility with PHP 8.4 (deprecated implicitly nullable types)

[1.7.0] - 2025-04-15

Changed

  • adapted for compatibility with Magento 2.4.7-p4
  • adapted the composer.json file to be compatible with Composer 2

[1.6.2] - 2023-10-10

Changed

  • fixed Symfony Command constant issue
  • exclude non visible products from url regeneration

[1.6.1] - 2023-08-24

Changed

  • fixed compatibility with Symfony Console 5 and Magento 2.4.6
  • updated contact email to Gmail email (my own domain olegkoval.com was stolen)

[1.6.0] - 2021-01-27

Changed

  • adapted to Magento 2.3.5
  • fixed incorrect generation when URL suffix is slash

[1.5.6] - 2020-04-13

Changed

  • updated logic of "cleaning" of Url Rewrites and duplications check

[1.5.5] - 2020-04-02

Changed

  • updated logic of Url Rewrite regeneration via category entity
  • fixed compilation issue in helper

[1.5.4] - 2020-03-21

Changed

  • fixed issue of non-empty/non-false "request_path" of product entity.
  • modified logic of Url Rewrite db table updates

[1.5.3] - 2020-03-20

Changed

  • updated Url Rewrite preparing function
  • updated logic of Url Rewrite regeneration via category entity
  • updated save logic

[1.5.2] - 2020-03-18

Changed

  • updated logic of Url Rewrite regeneration via category entity
  • CLI options logic optimized (for category entity)

[1.5.1] - 2020-03-08

Changed

  • fixed issue of url_key and url rewrites regeneration based on product name value

[1.5.0] - 2020-02-26

Changed

  • revised and restructured code
  • modified functional logic of extension
  • removed option "--check-use-category-in-product-url"

[1.4.3] - 2019-05-12

Added

  • new option "no-regen-url-key"

Changed

  • fixed a "typo" issue

[1.4.2] - 2019-04-04

Added

  • new option "--check-use-category-in-product-url"
  • info into log about conflicted URL Rewrites

Changed

  • fixed logical issues in url_key regeneration
  • a fix for category/products rewrites for multistore
  • fixed issue of division by zero in progress bar
  • update the url_key regeneration behavior to use UrlPathGenerators
  • modified logic of displaying console messages (notifications, errors, exceptions...)

[1.4.1] - 2019-02-20

Changed

  • fixed the issue of removing previously added URL rewrites of product when the same URL key exists;
  • modified progress bar

[1.4.0] - 2019-02-11

Added

  • new option "--entity-type"
  • new option "--products-range"
  • new option "--product-id"
  • new option "--category-range"
  • new option "--category-id"

Changed

  • revised and restructured code
  • modified logic of url rewrites regeneration
  • removed "--clean-url-key"

[1.3.1] - 2018-11-14

Changed

  • fixed issue of empty product URL keys
  • fixed double slashes issue
  • update category attributes via resource saveAttribute()
  • use proxy for CategoryUrlPathGenerator

[1.3.0] - 2018-10-29

Added

  • new option "--no-cache-clean"
  • new option "--no-cache-flush"
  • new option "--no-progress"
  • new option "--no-clean-url-key"

Changed

  • optimized code
  • modified logic of url rewrites regeneration
  • fixed issue of store filter in a category collection

[1.2.3] - 2018-10-03

Added

  • display additional debug information for "URL key for specified store already exists" error

Changed

  • modified logic of url rewrites regeneration

[1.2.2] - 2018-10-02

Changed

  • fixed setStoreId() on null error

[1.2.1] - 2018-09-25

Changed

  • fixed compilation issues

[1.2.0] - 2018-09-25

Changed

  • added proxies to CLI commands
  • modified logic of url rewrites regeneration
  • updated a composer file
  • fixed issue of a compatibility with new Magento Commerce versions

[1.1.1] - 2018-09-10

Changed

  • fix composer file format issue

[1.1.0] - 2018-09-09

Added

  • added feature to add a Pro features through a "Layer" class

Changed

  • fix issue when optional arguments require value
  • updated a code structure

[1.0.6] - 2018-07-26

Added

  • new option to run URL rewrite generation without running full reindex

Changed

  • update help notice to show INPUT_KEY_SAVE_REWRITES_HISTORY and INPUT_KEY_NO_REINDEX

[1.0.5] - 2018-05-13

Added

  • new option to save current URL rewrites

Changed

  • improve the store ID arguments workflow

[1.0.4] - 2017-11-13

Added

  • additional checks of storeId argument

[1.0.3] - 2017-10-25

Added

  • check if area code is set

[1.0.2] - 2017-10-20

Fixed

  • fix "Area code not set" issue

[1.0.1] - 2017-10-10

Fixed

  • fix store id issue in collection filter

[1.0.0] - 2017-09-29

Release of Magento 2 "Regenerate Url Rewrites" extension

Versions
Version Stability QA Status Compatibility Released
1.12.0 stable Fail Magento 2.4.7-2.4.9 Details 2026-10-07 10:01:17
1.11.1 stable Fail Magento 2.4.7-2.4.9 Details 2026-10-05 15:58:48
1.11.0 stable Fail Magento 2.4.7-2.4.9 Details 2026-09-30 07:47:49
1.10.0 stable Fail Magento 2.4.7-2.4.9 Details 2026-09-26 14:37:52
1.9.1 stable Fail Magento 2.4.7-2.4.9 Details 2026-08-26 16:26:43
1.9.0 stable Fail Magento 2.4.7-2.4.9 Details 2026-08-18 18:39:18
1.8.0 stable Fail Magento 2.4.7-2.4.9 Details 2026-08-09 22:44:50
1.7.3 stable Not tested Not yet tested Details 2026-08-09 19:50:58
1.7.2 stable Fail Not yet tested Details 2026-04-07 11:58:33
1.7.1 stable Not tested Not yet tested Details 2025-05-30 08:50:45
1.7.0 stable Not tested Not yet tested Details 2025-04-17 08:57:27
1.6.2 stable Not tested Not yet tested Details 2023-10-10 16:03:11
1.6.1 stable Not tested Not yet tested Details 2023-08-24 08:11:18
1.6.0 stable Not tested Not yet tested Details 2021-01-27 19:08:16
1.5.6 stable Not tested Not yet tested Details 2020-04-14 12:37:22
1.5.5 stable Not tested Not yet tested Details 2020-04-02 09:38:49
1.5.4 stable Not tested Not yet tested Details 2020-03-21 13:20:23
1.5.3 stable Not tested Not yet tested Details 2020-03-21 00:52:01
1.5.2 stable Not tested Not yet tested Details 2020-03-18 13:09:43
1.5.1 stable Not tested Not yet tested Details 2020-03-08 13:19:47
1.5.0 stable Not tested Not yet tested Details 2020-02-26 00:39:38
1.4.3 stable Not tested Not yet tested Details 2019-05-12 16:26:18
1.4.2 stable Not tested Not yet tested Details 2019-04-04 19:25:24
1.4.1 stable Not tested Not yet tested Details 2019-02-22 18:50:45
1.4.0 stable Not tested Not yet tested Details 2019-02-10 23:31:00
1.3.1 stable Not tested Not yet tested Details 2018-11-14 01:09:36
1.3.0 stable Not tested Not yet tested Details 2018-10-29 21:35:32
1.2.3 stable Not tested Not yet tested Details 2018-10-03 17:50:49
1.2.2 stable Not tested Not yet tested Details 2018-10-02 10:16:34
1.2.1 stable Not tested Not yet tested Details 2018-09-25 08:06:32
1.2.0 stable Not tested Not yet tested Details 2018-09-24 23:20:50
1.1.1 stable Not tested Not yet tested Details 2018-09-10 05:45:05
1.0.6 stable Not tested Not yet tested Details 2018-07-26 20:52:28
1.0.5 stable Not tested Not yet tested Details 2018-05-12 23:15:48
1.0.4 stable Not tested Not yet tested Details 2017-11-13 13:46:25
1.0.3 stable Not tested Not yet tested Details 2017-10-25 11:32:32
1.0.2 stable Not tested Not yet tested Details 2017-10-20 00:25:54
1.0.1 stable Not tested Not yet tested Details 2017-10-10 08:12:20
1.0.0 stable Not tested Not yet tested Details 2017-09-29 10:23:11

Requires 1

Package Constraint
php >=8.2

Compatibility

Each Magento release line is installed on its supported PHP versions, then the module is built (DI compilation + static-content deploy) and its unit and integration suites are run. The matrix shows the lines and PHP versions the module is confirmed to install and run on. Code-quality results further down (phpstan, phpcs, …) are reported separately and never affect compatibility.

Compatibility matrix (Magento × PHP)
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 Pass Pass
2.4.8 Pass Pass
2.4.9 Pass Pass

Code Quality

Advisory checks against the module's source. Static analysis runs once across the whole module; PHPStan re-runs per Magento + PHP version because resolvable symbols differ between releases. These NEVER affect the Compatibility badge. A phpcs finding can't make a module incompatible.

Static analysis

Coding standards (phpcs), mess detection (phpmd), copy-pasted code (cpd), PHP cross-version compatibility, composer.json validity. Each runs once for the whole module.

Static analysis results
Tool Status Findings Summary
PHPCS Fail 127 6 errors, 121 warnings (ruleset: Magento2), 52 auto-fixable with phpcbf
PHPMD Warning 125 125 rule violations (MissingImport:57, UnusedFormalParameter:30, ExcessiveMethodLength:8, CyclomaticComplexity:6, NPathComplexity:6)
Cpd Pass 0
Composer validate Pass 0

PHPStan

Type-checks the module's PHP against a real Magento install at the configured gate level. Re-runs per Magento and PHP version because resolvable symbols differ between releases.

PHPStan results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 1 1
2.4.8 1 1
2.4.9 1 1

Tests

Unit and integration suites, run for each applicable Magento and PHP version. A test failure speaks to the module's behaviour, not its compatibility with a Magento line, so it is reported here separately and never reddens the compatibility matrix.

Unit tests

Unit tests results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 N/A N/A
2.4.8 N/A N/A
2.4.9 N/A N/A

Integration tests

Integration tests results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 N/A N/A
2.4.8 N/A N/A
2.4.9 N/A N/A

Security

Security checks run directly against the module: an audit of its declared dependencies for known vulnerabilities (composer audit) and a scan of its source for malware and web-shell signatures. Each runs once. A malware detection fails the version outright.

Security results
Tool Status Findings Summary
Composer audit Pass 0
Malware scan Pass 0
License
OSL-3.0, AFL-3.0
Homepage
https://github.com/olegkoval/magento2-regenerate_url_rewrites
Authors
Make it pay

Turn an existing module into recurring revenue.

If you already maintain a Magento 2 module on GitHub or GitLab, listing it on Packagento takes about five minutes. We mirror your tags, handle distribution signing, and route paid licenses through Stripe Connect, so you can keep shipping the way you already do.