Changelog¶
What changed in each release of Product Feed, in terms of what it means for you rather than which files moved.
1.6.1 — 3 October 2026¶
Corrections, two of which change what you see. No setting was added, and the API answers with exactly the fields it did.
Switching a feed off now stops its URL serving. A feed's Status off used
to stop the schedule and leave the last file handed to anybody with the token.
It now answers its URL with the same Not found as every other refusal, and
serves the file again once you switch it back on. If a channel is still
reading a feed you have switched off, it starts getting Not found after this
update: switch the feed on and turn Regenerate on schedule off instead, to
pause it while the channel keeps reading. The module's own switch still does not
stop any URL. See pause a feed.
A feed's file can no longer be fetched without its token. A stock OpenCart
keeps its storage directory inside the web root, and a feed's file was named by
its number, so on a server that does not read .htaccess (nginx, for one) the
file could be fetched directly by anybody who guessed the number. Each file is
now named with a long random key of the feed's own, New URL changes that key
along with the token, and on Apache the folder also refuses a direct fetch. The
update renames every feed's file as it installs, so feeds keep serving through it
and no URL you have pasted into a channel changes. See the feed
URL.
A run that reads nothing, or leaves out everything, no longer replaces a good feed. A run whose filters matched nothing, or that ran while a nightly import had every product switched off, renamed an empty file over the live one and delisted the catalogue. A run that left out every product it read did the same and reported itself generated. Both now fail with the reason on the feed list, and the previous file keeps serving. A feed that has never had any products may still publish empty. See this feed could not be written.
- One feed failing no longer stops the scheduled pass. The other feeds due in the same pass are still generated.
- Two runs on one feed can no longer build it at once, so two browser tabs pressing Regenerate now cannot interleave their slices.
- A feed name with
&in it is written as you typed it. Bell & Howell went out asBell & Howellin the Google and Meta title, as&in the API and on the Preview heading, and asbell-amp-howellin the download's file name. - The category picker finds every branch of Google's taxonomy. Branches
whose name holds
&(Arts & Entertainment, Health & Beauty and most of the others at the top) opened on Nothing here. - Runs kept per feed keeps that many. It kept one more than you set.
- Category, brand, stock-status and product names are escaped on the admin screens, so a name an import wrote with markup in it shows as text rather than running in your browser. Preview no longer shows a name escaped twice.
- An admin button that gets no answer says so. Saving, accepting a category suggestion or switching a feed on or off from the list used to fail silently when the store answered with an error page; it now says it may not have saved, and a switch that did not take flips back. The two personal-data screens under Customers say the same when an erasure or a removal gets no answer.
- A schedule's cycle is measured from the last run's start, in your store's time zone, as OpenCart's own scheduler measures it, so a monthly feed no longer drifts by an hour across a clock change.
- The API refuses an API user whose key has been emptied. OpenCart's own form will not save one; a row edited by hand to an empty key admitted a caller presenting no key at all.
- One new column, the file key on each feed, and every one of your feeds, mappings, filters, runs and category mappings is kept. What this holds about a person now lists fifty-six columns, and still classifies every one as holding nothing about anybody. The key is never returned by the API.
1.6.0 — 30 September 2026¶
Variant products can be grouped. Map item_group_id from the new Master
product ID (variants only) source and every variant of one master sends that
master's id, so Google and Meta show one product in several versions rather than
unrelated items. The master itself is in no group. A variant's colour, size or
any other option comes from the new Variant options group on the Fields tab,
one entry per option your store has. See the
guide.
A feed can send the price a shopper is charged. Two new sources, Price including tax and Special price including tax, add your store's own tax rates at the feed store's own address, and a new Convert prices into this currency tick on the General tab multiplies every price by your store's own exchange rate for the feed's currency. A feed converting into a currency your store has no rate for fails its run and the previous file keeps serving. See what is in the file and the guide.
Categories, brands and individual products can be left out of a feed. Each
category and brand row on the Filters tab has a Leave out tick beside the
one that includes it, and Leave out these products picks up to 1,000
products by name. Leaving out wins over every include-list. Picking products
needs Access Permission on catalog/product (install). See
the guide.
The Fields tab proposes Product ID for id, and Master product ID for
item_group_id. It used to propose Model for both. OpenCart copies a
master's model onto each variant it makes, so on a store with variants id
from Model sends one id for a master and all its variants, and a channel
reads that as one product sent several times. Proposals only: a mapping you
already saved is not touched. If your store has variants, open each feed's
Fields tab and check what id is mapped from.
- Nothing any feed sends changes on upgrade. The new sources are unmapped, the conversion tick starts off, and a feed with nothing left out leaves nothing out.
- One new column, the conversion tick on each feed, and every one of your feeds, mappings, filters, runs and category mappings is kept. What this holds about a person now lists fifty-five columns, and still classifies every one as holding nothing about anybody.
1.5.0 — 21 September 2026¶
- OpenCart 4.1.0.4 is a tested release. A full pass on it (install, create a feed, map its fields, place categories, generate, serve the file over its URL, the scheduled pass, the command line, an upgrade over itself and an uninstall) runs green, so it joins 4.0.2.0 and 4.1.0.3 as a release this is sold for rather than one it merely installs on.
- What that release broke is still broken, and Product Feed still says so. OpenCart 4.1.0.4 cannot run any scheduled task (the fault is in OpenCart's own code, before any extension is reached), so scheduled regeneration there is one crontab line rather than your store's own cron. The screen names the release and what it has stopped, and does not tell you to check a cron that is not the problem.
- Nothing about your feeds, their addresses, their schedules, your category mapping or your run history changes on upgrade.
1.4.0 — 21 September 2026¶
- One crontab line now runs the scheduled pass, for a store whose own
scheduler cannot.
php extension/product_feed/product_feed.php --cronruns the same hourly pass OpenCart's scheduler would have run. It is the same job, not a second copy of it, so the runs are recorded as scheduled rather than as yours, the module's own switch is honoured, and the pass line lands in the log on a quiet night as well as a busy one. That matters on a host that does not call OpenCart'scron.phpat all, and on OpenCart 4.1.0.4, wherecron.phpfails inside OpenCart's own code before any extension is reached. See running the scheduled pass yourself. --dueis unchanged and still the command for an exact time of your own: it does the same work but reports it as a person's run rather than as the schedule's.- A product's SKU, barcode and part number are read from wherever your release keeps them. OpenCart moved those six identifiers out of the product row and behind a table of their own partway through 4.1, and went on writing the old columns for a while without updating them. A feed built on a store upgraded across that change was sending whatever the old column held the day the store was installed; it now sends what your product form last saved, and falls back to the column only where there is no newer value to have. Nothing to do on your side, and nothing changes on a store that was never upgraded across it.
- On OpenCart 4.1.0.4 the feed list stops telling you to check your cron. Beside an overdue feed that advice was wrong: your cron is fine, and OpenCart is what is broken. The notice at the top of the screen already says which release it is and what it has stopped.
1.3.0 — 21 September 2026¶
Product Feed now publishes what it holds about a person, and the answer is nothing. All fifty-four columns of its six tables are written down on what this holds about a person, every one classified as holding nothing about anybody, with the reason it is there. A feed carries catalog data (products, prices, availability, the category they were mapped into), so the channels that fetch yours get no shopper data at all.
The page exists because the build keeps the claim true: a column added to any of those tables has to be classified or our build fails naming it. A list that only named the personal columns could not tell a new personal column from a new counter.
- Your feed secret is named on that page as a credential rather than as
personal data, because a
varchar(64)calledsecretis the sort of column a scan stops at. It is a bearer token in the feed's own URL, the same one for every channel that fetches that feed, and it identifies a feed rather than anybody. - Customers > Personal Data is new, and answers what this and every other extension on your store holds about one named person, from one screen. Product Feed's answer is zero, stated on the screen rather than left out.
- Remove everything held about a person, linked from the settings screen, is new and ships for the same reason. It opens on an empty plan here, and the day a column on one of these tables holds something it starts emptying it.
- The settings screen gains a standing card saying what is held and that uninstalling keeps it.
- Nothing about your feeds, your mappings, your run history or your stored data changes on upgrade.
1.2.0 — 13 September 2026¶
Product Feed now answers a JSON API, for a system that keeps your own records (a monitoring tool that would page somebody at six in the morning, an agency watching forty stores, an ERP reconciling what it expects a channel to see against what went out) rather than for a browser. Two things it can read:
feed: one feed you have configured: its channel, its store and language, the currency its document is generated in, whether it is switched on, and when it last generated. So is anything stale is arithmetic rather than forty admin logins.run: one generation attempt: what triggered it, how many products it wrote, how many it left out, how it ended and why, and (on a single run, never on a page of them) which products it dropped and on which field. So did last night's Google feed generate, and what did it drop is one call.
A run whose lease has expired reports as stalled, not as running. Product
Feed reclaims a dead run when the next pass comes round, and on a store whose
cron has stopped that pass never comes. The API works the state out from the
lease rather than reading the stored column, so a feed that quietly stopped
generating is visible on the first poll instead of never. That is the reason a
monitoring system would ask.
It is off until you switch it on, and while it is off every route answers as though the extension had no API at all. The switch is in a new API section of the settings card at the top of Product Feed's screen, beside the base address, the versions being answered, a link to System → Users → API and a warning if there is no credential that could call it yet.
Your feed's URL token is in no answer, under any name. That token is the whole of what makes a hosted feed's address work, so a credential that could read it would be a way to hand out your catalogue. It is withheld deliberately, and a test fails if a later release ever lets it through. Your category mappings, the suggestion inbox and Product Feed's own settings are not readable either: the credential reads what the extension produced, never how you configured it.
A failed run's reason no longer carries your server's paths. The message on a run reaches the admin screen, your error log and now the API, and five of the places Product Feed writes one were naming a file by its full path on disk. They name the file and what a directory is for instead. Nothing you could act on has been lost, and if a database goes away mid-run, what its driver says still arrives as the driver phrased it.
Version 1 is the whole promise from its first release: no beta and no v0. What
will not change while it answers, and how long it keeps answering once a later
version replaces it, is on
the API promise.
Limits and guarantees is what a
credential reaches and what it deliberately does not.
Nothing about a feed's contents, its address, its schedule or when it regenerates changed.
A feed is called overdue on its own cycle and a day, rather than on two of its cycles. The feed list turned a row amber once a feed had gone twice its own cycle without generating successfully. Doubling the cycle was wrong at both ends. Product Feed asks OpenCart to wake it hourly, but what wakes OpenCart is your host's own cron, and plenty of those run once a day, so an Hourly feed on such a store was amber on every page load for ever, while regenerating as often as the store could regenerate it. At the other end a Monthly feed had two months of feeding a sales channel a stale catalogue before anything on the screen said so.
The threshold is now the feed's own cycle plus one day. A daily feed is called out at two days, which is where it already was. An hourly feed is called out at twenty-five hours, so a store on a daily cron sees green; a weekly feed at eight days rather than a fortnight, and a monthly feed at about a month and a day rather than two months. If your cron runs at least daily and your feeds are generating, you will see no change. If you have been living with a permanently amber hourly feed, it should go quiet. If it does not, the cron really has stopped and A feed did not regenerate is the list to work through. Weekly and monthly feeds are now called out sooner than before, which is intended.
Nothing about a feed's contents, its address, its schedule or when it regenerates changed. Only the point at which the list flags a feed's silence changed.
1.1.0 — 11 September 2026¶
On a store with one language nothing you can see changed. Product Feed still ships English and only English, so every screen reads as it did in 1.0.0: no new setting, no language to pick, no row on the form.
What it gains is the machinery for reading a translation of its screens once one exists: the extension now works out which of the languages it ships your admin should be served, rather than assuming your language code is spelled the way our directories are. With one language shipped that question has one answer, so a store running in one language is not asked anything and pays nothing.
On a store with more than one language, the settings screen now says so. A new read-only Language section lists every language your store has (including any that are switched off) and, for each, which of Product Feed's own translations it is served from. Today that is English for every language but English, and the section says which: not translated. There is nothing to set there and nothing to save.
The Google product taxonomy is not part of that count, on purpose. The category tree you map against is a copy of Google's own rather than wording of ours, so it sits outside the check that holds our translations to each other. Nothing guesses which language of that tree your store wants, and no release waits on a tree we do not own being re-read. A language whose screens are fully translated may still have no taxonomy of its own, and the picker falls back to the English tree. The category codes you assign are the ones Google expects either way; what changes is only the language you read them in while choosing.
There is nothing here a shopper reads, and there never will be: a feed is read by a sales channel. See Limits and guarantees for what that means for the translation figures published for this extension.
1.0.0 — 4 September 2026¶
The first public release. There is no previous version, so nothing below is a change. This entry is the baseline every later one is measured against.
What ships: feeds for Google Shopping, Meta and Microsoft Advertising, each built from that channel's own published field set and written in that channel's own format: RSS 2.0 XML for the first two, CSV for the third. A feed is one channel, one store, one language. You say which of your own values go into each field the channel asks for, with a fallback literal for the products where that value is empty; you place your categories against Google's taxonomy once, in a mapping every feed on that store and language reads; and you choose how often it regenerates. Google and Meta fetch the file from a URL Product Feed serves. The Microsoft Advertising file you download and upload yourself. Product Feed never holds a channel credential of yours.
The main feature is the checking. Every field shows what it would produce
for your own products, resolved by the same code that writes the feed, so
a missing barcode, a title that will be cut short or a price the format refuses
is something on the screen rather than in a Merchant Center report three days
later. Preview shows a feed as the channel's own fields before you generate it,
and marks the failing cell. The mapping Product Feed can work out for itself (a
barcode column into gtin, your category path into product_type) is proposed
with its reasoning and applies nothing until you accept it, and so is a Google
category for each unplaced one of yours: local guesswork, with no API key, no
account and no network call.
Around that: filters by category, brand, stock status and price range; unplaced
categories inheriting the nearest placed one above them, so mapping your top
level produces a valid feed immediately; a retired Google category sent as the
nearest live one above it rather than one Google no longer has; generation in
slices, so a catalogue of any size finishes instead of running into your host's
time limit, with a second Regenerate now joining the run already going rather
than starting another; a failed run leaving the previous feed serving byte for
byte, so a bad night does not delist your catalogue; a product that cannot be
represented left out and counted rather than failing the whole run; and each feed
served over its own tokenised URL from outside the web root, answering a
channel's conditional request with a 304 when nothing has changed, and taking a
new URL on one click.
What it does not do is the shorter list. Read it first. In particular there are three channels and no way to add a fourth, nothing is pushed to any channel, and the price is your catalogue price: no tax added, no currency converted.
It runs on OpenCart 4.0.2.0 and 4.1.0.3, the releases a full install-to-uninstall pass had been run against. Below 4.0.2.0 it declines to install and says which version you need, rather than half-installing: that is the first release with a scheduler for a feed to regenerate on. A release newer than 4.1.0.3 installs and works, and says on its own screen that it is untested. On 4.1.0.4, drive the schedule from the command line: that release cannot run any scheduled task at all, for any extension on the store, and Product Feed's screen says so.
A schedule needs your store's own cron to be calling OpenCart's cron.php, and
nothing else. Product Feed registers one hourly job on OpenCart's own scheduler
and works out from your feeds which are due. Hourly is the finest cycle there is,
because those are the four cycles that scheduler understands. For an exact time,
for a deploy pipeline, or for a store whose OpenCart cannot run a scheduled task,
there is the command line.
What an update keeps¶
Nothing you have configured is lost. Your feeds, their tokens and your category mapping stay. They are records in Product Feed's own tables, and nothing in the extension ever drops a table. So an update is not a round of dashboard edits, and the URLs you have already given Google and Meta keep working. What an update and a removal leave behind is on the limits page.
Your settings come back, with the exceptions below. An update is an uninstall and then an install, and OpenCart deletes an extension's settings at the uninstall step. Product Feed keeps its own copy of them and puts them back afterwards. They are one set for the whole installation rather than one per store.
These are asked for again rather than put back:
module_product_feed_status— Whether the module is enabled is OpenCart's own record rather than ours, and Product Feed deliberately keeps no copy of it. Switching it back on from Extensions > Modules, or from the Status switch on Product Feed's own screen, is what starts the scheduler again.module_product_feed_diary_verbose_until— Detailed logging comes back off after an update, the way a fresh install does. An upgrade is an uninstall and a reinstall, and putting a diagnostic window back is how a store ends up verbose months after the support exchange that asked for it ended.
A setting a release adds does not change what your store does. Its default is what Product Feed did before the setting existed, so an update never asks you to go and set something to get back the behaviour you already had.
What this cannot tell you is whether a release changed how a setting behaves, as against whether it survives. Nothing derives that from the code, so it is written by hand: a release that changes an answer you relied on says so in its own entry above, as its own paragraph and never among what is new.