Version and Changelog a QGIS Plugin

Once a plugin has users, its version number stops being a label and becomes a promise. QGIS compares it to decide whether to offer an update; users read it to decide whether to take one; and your support inbox depends on whether the changelog tells them what changed. None of that requires ceremony — a version, a compatibility range and five lines of changelog per release cover it.

This recipe belongs to Publishing to the QGIS Plugin Repository. It covers how QGIS compares versions, choosing minimum and maximum QGIS versions honestly, the experimental flag, writing a changelog that answers the only question users have, and keeping the tag, the metadata and the archive in agreement.

What each part of the number promisesThe major number changes when something users depend on is removed or behaves differently, so an update may require action. The minor number changes when features are added compatibly, so an update is safe and brings something new. The patch number changes for fixes only, so an update is safe and brings nothing to learn. Users read the number to decide how carefully to read the changelog.The number tells the user how carefully to read the rest2.0.0 — majoran algorithm id changeda setting was removedoutput format differsread before updatingsaved models may break1.4.0 — minora new algorithman optional parametera new translationsafe to updatesomething new to try1.3.2 — patcha crash fixeda wrong total correcteda typo in a labeljust updatenothing to learn

Prerequisites

  • QGIS 3.34 LTR (bundled Python 3.12) or newer for testing.
  • A plugin with a metadata.txt — see Write metadata.txt for a QGIS Plugin.
  • A repository with tags, so releases can be identified after the fact.

Set the version and compatibility range

[general]
name=Parcel Tools
version=1.4.0
qgisMinimumVersion=3.22
qgisMaximumVersion=3.99
experimental=False
deprecated=False

Breakdown: version is compared by the plugin manager to decide whether an update exists, and it compares numerically part by part — so 1.10.0 is correctly newer than 1.9.0, but 1.4 and 1.4.0 are treated as different strings in some tooling, which is a good reason to always use three parts. qgisMinimumVersion should be the oldest release you have actually tested on, not the oldest you hope works; users on that release will install it and report what happens. qgisMaximumVersion at 3.99 means "any 3.x", which is the right default — setting it to the current release means every QGIS upgrade hides your plugin from its own users, and that generates more support mail than any bug.

Use the experimental channel deliberately

version=2.0.0-rc1
experimental=True

Breakdown: An experimental release is only offered to users who have ticked "Show also experimental plugins", which makes it the right vehicle for a rewrite, a risky change or a version you want tested before it reaches everybody. The repository keeps the latest stable and the latest experimental separately, so shipping 2.0.0-rc1 as experimental does not disturb anybody running 1.4.0. Two rules make it work: never leave experimental=True on a release you intend as stable, and never use the experimental channel as a substitute for testing — a broken experimental release still reaches real users, just fewer of them.

deprecated=True is the other end of the same mechanism: it marks the plugin as no longer maintained, warns users, and keeps it installable for anybody who still depends on it. Setting it with a final release whose changelog names a replacement is the courteous way to retire something.

Write a changelog users read

changelog=
    1.4.0 - 2026-08-15
    - Added the Split by Ward algorithm to the Processing provider
    - Field picker now remembers the last field per layer
    - Fixed area totals being reported in the wrong units for geographic layers

    1.3.2 - 2026-06-02
    - Fixed a crash when the input layer had no features
    - German and French translations updated

Breakdown: The changelog lives in metadata.txt and is shown directly in the plugin manager, which is the only place most users will ever read it — so it has to answer their single question, which is whether this update affects them. Newest first, one line per change, phrased as what changed rather than which function was edited. Dates matter more than they look: a user deciding whether their problem is already fixed compares the date against when they last updated. Indentation continues the value in the INI format, so every line after the first must be indented; an unindented line silently ends the changelog.

Three things to avoid: commit messages pasted wholesale, entries like "various fixes and improvements" which tell nobody anything, and silence about behaviour changes. If an output column was renamed, say so — the user whose spreadsheet broke will find out anyway, and would rather find out here.

Two changelogs for the same releaseOne changelog lists internal commit messages about refactoring and dependency bumps, which does not help a user decide whether to update. The other lists what changed from the user's point of view: a new algorithm, a remembered setting, and a corrected unit conversion, which answers the question directly.Write for the person deciding whether to click Upgradecommit messagesrefactor summarise helperbump minimum versionvarious fixes and improvementsanswers nothinguser-visible changesnew Split by Ward algorithmfield picker remembers your choicearea totals corrected for lat-longthe third line is why they update

Keep the tag, the metadata and the archive in agreement

Three places record the version, and they drift the moment any of them is edited by hand.

# bump once, in metadata.txt, then:
git commit -am "Release 1.4.0"
git tag -a v1.4.0 -m "Release 1.4.0"
git push --follow-tags

Breakdown: metadata.txt is the single source of truth, because it is what QGIS reads. The tag exists so the exact source of a release can be recovered when a user reports a bug against it — without one, "which version is this?" has no answer three releases later. Building the archive from the metadata version, as the script in Package a QGIS Plugin as a Zip does, closes the loop: the file name, the metadata and the tag cannot disagree unless somebody edits the zip. A continuous-integration job that builds on tag and refuses when the tag and the metadata differ makes the guarantee mechanical.

One version, three recordsThe version in metadata.txt is the source of truth because it is what QGIS reads. The git tag exists so the source of a release can be recovered later, and the archive name is derived from the metadata so it cannot disagree. Editing any of the three by hand is what lets them drift apart.Derive the other two and they cannot driftmetadata.txtversion=1.4.0 — what QGIS readsgit tag v1.4.0recovers the exact source laterparcel tools-1.4.0.zipname read from the metadata

Decide what counts as breaking

For a QGIS plugin, the interface users depend on is wider than the code, and three things in particular are breaking changes even though nothing in Python signals it.

Algorithm identifiers. A saved model or a script referencing parceltools:summariseparcels breaks if you rename either half. If you must, keep the old name registered as an alias for a release or two.

Parameter names. Batch configurations and scripts pass parameters by name. Renaming MIN_AREA to MINIMUM_AREA silently breaks every stored run, since an unknown key is ignored rather than rejected.

Output shapes. A renamed field, a changed unit, an extra row per input — anything downstream that reads your output is affected, and none of it produces an error.

Settings keys are the near-miss in this list: they break only your own plugin, so a migration on startup, as described in Plugin Settings and Localization, is enough there. Everything else in the list needs a major version and a changelog line that names the change plainly.

QGIS version compatibility

QGIS versionPythonNotes
3.22 LTR3.9Version comparison, experimental and deprecated flags as described.
3.28 LTR3.9Identical.
3.34 LTR3.12Baseline for this page.
3.40 / 3.443.12Identical metadata handling; the plugin manager presents changelogs slightly more prominently.

Supporting a wide range of QGIS versions costs testing, not metadata. Where an API you need only exists on newer releases, guard it with hasattr() and raise the minimum only when the workaround becomes worse than dropping the old release.

Troubleshooting

  • Users are not offered the update. The version did not increase by the manager's comparison, or qgisMaximumVersion excludes their release.
  • The plugin disappeared after a QGIS upgrade. qgisMaximumVersion is too low. Set it to 3.99 unless you have a specific reason.
  • The changelog shows only the first line. Continuation lines are not indented in the INI file.
  • An experimental release reached everyone. experimental=True was left off, or was removed for a release that was not ready.
  • A saved model stopped working after an update. An algorithm or parameter identifier changed. That is a major version, and needs an alias or a clear note.
  • The tag and the shipped version disagree. The archive was built before the metadata was bumped. Derive the file name from the metadata.

Conclusion

Use three-part versions and increase the part that matches the kind of change: major for anything that breaks a saved model, script or output; minor for additions; patch for fixes. Keep qgisMinimumVersion honest and qgisMaximumVersion open. Write the changelog for the person deciding whether to upgrade, newest first, one line per user-visible change. Tag every release, and build the archive from the metadata so nothing can drift.

Frequently Asked Questions

Does QGIS require semantic versioning? No — it compares numbers part by part. Semantic versioning is a convention that makes the comparison meaningful to humans, which is why it is worth following.

Can I publish a release for old QGIS versions alongside a new one? Yes. The repository serves the newest release compatible with each user's QGIS, so an older release with a lower maximum version keeps serving those users.

Should the changelog live in metadata.txt or a separate file? In metadata.txt, because that is what the plugin manager shows. Keeping a fuller CHANGELOG.md in the repository as well is common and costs little.

How do I retire a plugin? Set deprecated=True in a final release whose changelog names the successor. It stays installable and warns anybody who finds it.

What version should a first release be?0.1.0 if you expect the interface to change, 1.0.0 if you are ready to treat it as stable. Going from 0.x to 1.0.0 is itself a useful signal.

Do translations need a version bump? Yes — anything shipped is a release. New or updated translations are a minor bump, and worth a changelog line so speakers of that language know.