Get the FREE Ultimate OpenClaw Setup Guide →

flutter-versioning

Scanned
npx machina-cli add skill nylo-core/claude-code/flutter-versioning --openclaw
Files (1)
SKILL.md
11.6 KB

Flutter Versioning

Overview

Flutter uses a single version field in pubspec.yaml with the format x.y.z+build to define both the user-facing version string and the internal build number. This value propagates to iOS (CFBundleShortVersionString / CFBundleVersion) and Android (versionName / versionCode) during the build process.

When to Use

  • User needs to set or bump the app version before a release
  • User is confused about the version format in pubspec.yaml
  • User needs to understand how Flutter versions map to platform-specific values
  • User is setting up CI/CD and needs automated version bumping
  • App was rejected because the version or build number was not incremented
  • NOT for Dart package versioning on pub.dev (similar format but different context)

Quick Reference

Conceptpubspec.yamliOSAndroid
Version stringx.y.zCFBundleShortVersionStringversionName
Build number+buildCFBundleVersionversionCode
Full formatversion: 1.2.3+41.2.3 / 41.2.3 / 4
Where setpubspec.yamlAuto from pubspecAuto from pubspec
OverrideN/A--build-name / --build-number flagsSame flags

Version Format in pubspec.yaml

version: 1.2.3+4

Breaking this down:

  • 1Major version: Incremented for breaking changes or major milestones
  • 2Minor version: Incremented for new features (backwards compatible)
  • 3Patch version: Incremented for bug fixes
  • +4Build number: Incremented for every build submitted to stores

Rules

  • The version string (x.y.z) follows Semantic Versioning
  • The build number (+build) must be a positive integer
  • The build number is optional in pubspec.yaml but required for store submissions
  • If omitted, the build number defaults to 0

How Versions Map to Platforms

iOS

Flutter reads pubspec.yaml and sets these in the generated Xcode build:

pubspec.yamliOS KeyLocation
1.2.3 (before +)CFBundleShortVersionStringInfo.plist / Build Settings
4 (after +)CFBundleVersionInfo.plist / Build Settings

The mapping happens through ios/Flutter/Generated.xcconfig:

FLUTTER_BUILD_NAME=1.2.3
FLUTTER_BUILD_NUMBER=4

And ios/Runner/Info.plist references these:

<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>

iOS Version Rules:

  • CFBundleShortVersionString — user-visible version, must be x.y.z format
  • CFBundleVersion — build number, must be a string of period-separated positive integers (e.g., 4 or 1.2.3.4)
  • Each new upload to App Store Connect must have a higher CFBundleVersion than the previous upload for the same CFBundleShortVersionString
  • You can reset the build number when you increment the version string

Android

Flutter reads pubspec.yaml and sets these in the Gradle build:

pubspec.yamlAndroid PropertyLocation
1.2.3 (before +)versionNamebuild.gradle (via flutter config)
4 (after +)versionCodebuild.gradle (via flutter config)

The mapping happens through android/app/build.gradle:

android {
    defaultConfig {
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }
}

These values are read from android/local.properties (generated by Flutter):

flutter.versionName=1.2.3
flutter.versionCode=4

Android Version Rules:

  • versionName — user-visible version string, can be any format (but x.y.z is standard)
  • versionCode — must be a positive integer
  • Each new upload to Google Play must have a strictly higher versionCode than any previously uploaded version
  • versionCode can never be reused or decreased, even for different tracks (internal, alpha, beta, production)
  • Maximum versionCode value: 2100000000

Semantic Versioning (SemVer)

Follow semantic versioning for the x.y.z part:

MAJOR.MINOR.PATCH

1.0.0  → Initial release
1.0.1  → Bug fix
1.1.0  → New feature added
2.0.0  → Breaking change or major redesign

When to Increment What

Change TypeBumpExample
Bug fix, small tweakPatch1.2.31.2.4
New feature, UI changeMinor1.2.31.3.0
Redesign, breaking changeMajor1.2.32.0.0
Every store submissionBuild number1.2.3+41.2.3+5

Pre-Release Versions

For apps not yet at 1.0.0:

0.1.0  → Early development, API may change
0.9.0  → Feature-complete, pre-release
1.0.0  → First stable release

Build Number Strategies

Simple Sequential

Increment by 1 for every build submitted to any store:

1.0.0+1
1.0.0+2
1.0.1+3
1.1.0+4
2.0.0+5

Pros: Simple, always increasing. Cons: Build number has no intrinsic meaning.

Date-Based

Use the date as the build number (format: YYYYMMDD or YYMMDDhh):

1.2.3+20260224    → February 24, 2026
1.2.3+2602241400  → February 24, 2026, 2:00 PM

Pros: Build number encodes when it was built. Cons: Multiple builds per day need hour/minute precision. Value can exceed Android's max integer for very long formats.

Computed from Version

Encode the version in the build number:

# Format: MAJOR * 10000 + MINOR * 100 + PATCH
1.2.3  → build number 10203
2.1.0  → build number 20100
12.3.1 → build number 120301

Pros: Build number is predictable from version. Cons: Limits patch versions to 99, cannot submit multiple builds for same version.

Per-Version Reset (iOS-friendly)

Reset build number to 1 with each new version:

1.0.0+1
1.0.0+2  → Fix before release
1.0.1+1  → Reset for new version
1.1.0+1  → Reset for new version

Pros: Clean, easy to understand. Cons: Does NOT work for AndroidversionCode must always increase globally, never reset.

Overriding Version at Build Time

You can override the pubspec.yaml version when building:

# Override both version name and build number
flutter build apk --build-name=1.2.3 --build-number=42

flutter build ipa --build-name=1.2.3 --build-number=42

flutter build appbundle --build-name=1.2.3 --build-number=42

This is especially useful in CI/CD where the build number is determined by the pipeline.

CI/CD Version Bumping

Reading the Current Version

# Extract version from pubspec.yaml
grep '^version:' pubspec.yaml
# Output: version: 1.2.3+4

Automated Bumping Script (Shell)

#!/bin/bash
# bump_version.sh — Increment build number in pubspec.yaml

CURRENT=$(grep '^version:' pubspec.yaml | sed 's/version: //')
VERSION_NAME=$(echo "$CURRENT" | cut -d'+' -f1)
BUILD_NUMBER=$(echo "$CURRENT" | cut -d'+' -f2)

NEW_BUILD=$((BUILD_NUMBER + 1))
NEW_VERSION="${VERSION_NAME}+${NEW_BUILD}"

sed -i '' "s/^version: .*/version: ${NEW_VERSION}/" pubspec.yaml
echo "Bumped version to ${NEW_VERSION}"

Using cider Package

The cider package provides structured version bumping:

# Install
dart pub global activate cider

# Bump commands
cider bump patch       # 1.2.3+4 → 1.2.4+4
cider bump minor       # 1.2.3+4 → 1.3.0+4
cider bump major       # 1.2.3+4 → 2.0.0+4
cider bump build       # 1.2.3+4 → 1.2.3+5
cider bump patch+build # 1.2.3+4 → 1.2.4+5

# Set exact version
cider version 2.0.0+1

GitHub Actions Example

name: Build and Release
on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Extract version from tag
        id: version
        run: |
          TAG=${GITHUB_REF#refs/tags/v}
          echo "version=$TAG" >> $GITHUB_OUTPUT

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'

      - name: Build APK
        run: |
          flutter build apk \
            --build-name=${{ steps.version.outputs.version }} \
            --build-number=${{ github.run_number }}

      - name: Build IPA
        run: |
          flutter build ipa \
            --build-name=${{ steps.version.outputs.version }} \
            --build-number=${{ github.run_number }}

Using ${{ github.run_number }} as the build number guarantees a unique, always-increasing integer for each CI run.

Platform-Specific Overrides

Hardcoded iOS Version (Not Recommended)

If ios/Runner/Info.plist has hardcoded values instead of variables:

<!-- BAD: Hardcoded — won't sync with pubspec.yaml -->
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>

<!-- GOOD: Dynamic — reads from Flutter build config -->
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>

Ensure the Info.plist uses $(FLUTTER_BUILD_NAME) and $(FLUTTER_BUILD_NUMBER).

Hardcoded Android Version

If android/app/build.gradle has hardcoded values:

// BAD: Hardcoded — ignores pubspec.yaml
defaultConfig {
    versionCode 1
    versionName "1.0"
}

// GOOD: Dynamic — reads from Flutter local.properties
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
def flutterVersionName = localProperties.getProperty('flutter.versionName')

defaultConfig {
    versionCode flutterVersionCode.toInteger()
    versionName flutterVersionName
}

Common Mistakes

  1. Not incrementing Android versionCode — Google Play requires every upload to have a strictly higher versionCode than all previous uploads across all tracks. Uploading with the same or lower versionCode is rejected.

  2. Resetting build number on Android — Unlike iOS, Android's versionCode can never go backwards. If you shipped 1.0.0+10 and try to upload 1.1.0+1, Google Play will reject it. Always keep Android build numbers strictly increasing.

  3. Forgetting the build number entirely — If you omit the +build part (version: 1.2.3), the build number defaults to 0. This works for development but will cause store submission issues since 0 is a valid but low starting point that leaves no room to go lower.

  4. Exceeding Android versionCode max — The maximum versionCode is 2100000000. Date-based schemes like YYYYMMDDHHmm can exceed this limit. Use shorter date formats or simple sequential numbers.

  5. Hardcoded versions in Info.plist or build.gradle — If platform files have hardcoded versions instead of reading from Flutter's generated config, changes to pubspec.yaml are silently ignored. Verify that Info.plist uses $(FLUTTER_BUILD_NAME) and build.gradle reads from localProperties.

  6. Misunderstanding version vs build number — The version (1.2.3) is what users see in the store. The build number (+4) is the internal identifier. You can submit multiple builds with the same version (incrementing only the build number) for testing, but you cannot submit the same build number twice.

  7. Inconsistent versions across platforms — If you override versions via --build-name / --build-number flags for one platform but not the other, iOS and Android can end up with different version strings. Use the same flags or rely on pubspec.yaml for both.

  8. Not updating version before release builds — Running flutter build appbundle without bumping the version submits the same version as the last upload. Always check and increment before building for store submission.

Source

git clone https://github.com/nylo-core/claude-code/blob/main/skills/flutter-versioning/SKILL.mdView on GitHub

Overview

Flutter uses a single version field in pubspec.yaml with the format x.y.z+build to define both the user-facing version and the internal build number. This value propagates to iOS (CFBundleShortVersionString / CFBundleVersion) and Android (versionName / versionCode) during the build process, so you can manage releases and store submissions from one source of truth.

How This Skill Works

Flutter reads the pubspec.yaml version and splits it into versionName (x.y.z) and build (the +build part). The build system then writes these values into platform-specific locations: iOS uses CFBundleShortVersionString and CFBundleVersion via the Generated.xcconfig and Info.plist, while Android uses versionName and versionCode through Gradle configuration. This mapping ensures consistent versioning across Flutter, iOS, and Android builds.

When to Use It

  • You need to bump the user-visible version before a release
  • You're unclear about the pubspec.yaml version format and its parts
  • You want to understand how Flutter versions map to iOS and Android values
  • You're setting up CI/CD and want automated version bumps
  • Your app was rejected due to not incrementing the version or build number

Quick Start

  1. Step 1: In pubspec.yaml, set version: 1.2.3+4
  2. Step 2: Run flutter pub get and build (flutter build ios or flutter build apk)
  3. Step 3: Check Xcode and Gradle outputs to confirm FLUTTER_BUILD_NAME / FLUTTER_BUILD_NUMBER and the corresponding Info.plist / Gradle values

Best Practices

  • Follow semantic versioning for the x.y.z part in pubspec.yaml
  • Increment the build number (+build) for every store submission
  • Keep iOS and Android versionName/versionCode in sync with pubspec.yaml
  • Automate version bumps in CI/CD and gate deployments on tests
  • Reset the build number when you increment the version string

Example Use Cases

  • Increment 1.2.3+4 to 1.3.0+5 for a feature release
  • A CI script automatically bumps the minor version on PR merge
  • Each App Store/Play Store submission uses a higher CFBundleVersion/versionCode
  • Reset build number when increasing the major version (1.x.y+0 -> 2.0.0+1)
  • Verify flutter.versionName and flutter.versionCode in local.properties during audits

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers