← Articles

Building your own Flutter package from scratch

By John · 1 June 2025

Publishing your own Flutter package lets you share code between your own apps, contribute to the community, or distribute internal tooling. The process from idea to pub.dev takes about an hour for a simple package.

Package vs plugin

  • Package: pure Dart/Flutter code, no platform-specific code.
  • Plugin: includes native code (Kotlin/Swift). Needed for camera, Bluetooth, file system, etc.

This article covers pure Dart/Flutter packages.

Creating the scaffold

flutter create --template=package my_awesome_package
cd my_awesome_package

Structure:

my_awesome_package/
├── lib/
│   ├── my_awesome_package.dart  # Public API barrel file
│   └── src/                    # Private implementation
├── test/
├── example/lib/main.dart
├── pubspec.yaml
├── README.md
├── CHANGELOG.md
└── LICENSE

pubspec.yaml

name: my_awesome_package
description: A concise description of what this package does.
version: 0.1.0
repository: https://github.com/yourname/my_awesome_package

environment:
  sdk: '>=3.3.0 <4.0.0'
  flutter: '>=3.19.0'

dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^4.0.0

Public API barrel file

Only export what users should use:

// lib/my_awesome_package.dart
library my_awesome_package;

export 'src/widgets/animated_counter.dart';
export 'src/models/counter_config.dart';
// Do NOT export internal helpers

Writing quality documentation

/// A counter widget that animates number changes.
///
/// ```dart
/// AnimatedCounter(
///   value: 42,
///   duration: Duration(milliseconds: 400),
/// )
/// ```
class AnimatedCounter extends StatefulWidget {
  const AnimatedCounter({
    super.key,
    required this.value,
    this.duration = const Duration(milliseconds: 300),
  });

  /// The integer value to display.
  final int value;

  /// Duration of the rolling animation.
  final Duration duration;

  @override
  State<AnimatedCounter> createState() => _AnimatedCounterState();
}

Writing tests

Pub.dev shows a coverage indicator. Tests improve search ranking.

testWidgets('displays the value', (tester) async {
  await tester.pumpWidget(
    const MaterialApp(
      home: Scaffold(body: AnimatedCounter(value: 42)),
    ),
  );
  expect(find.text('42'), findsOneWidget);
});

testWidgets('updates on value change', (tester) async {
  int value = 0;
  await tester.pumpWidget(
    StatefulBuilder(
      builder: (_, setState) => MaterialApp(
        home: Scaffold(
          body: AnimatedCounter(value: value),
          floatingActionButton: FloatingActionButton(
            onPressed: () => setState(() => value = 1),
            child: const Icon(Icons.add),
          ),
        ),
      ),
    ),
  );
  await tester.tap(find.byIcon(Icons.add));
  await tester.pumpAndSettle();
  expect(find.text('1'), findsOneWidget);
});

CHANGELOG.md

## 0.1.0

### Added
- Initial release
- `AnimatedCounter` widget with configurable duration

Publishing

# Check what will be published
flutter pub publish --dry-run

# Publish
flutter pub publish

Pub.dev awards points for: README.md, CHANGELOG.md, API docs on all public members, analysis_options.yaml with lints, tests, and up-to-date SDK constraints.

Semantic versioning

  • 0.0.1 → 0.0.2: bug fix
  • 0.0.2 → 0.1.0: new feature (backwards compatible)
  • 0.1.0 → 1.0.0: first stable release
  • 1.0.0 → 2.0.0: breaking change

CI with GitHub Actions

name: Test
on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.22.0'
          cache: true
      - run: flutter pub get
      - run: flutter analyze
      - run: flutter test --coverage

Common pitfalls

Exporting internal types. Exported types become public API. Users will depend on them. Only export what you intend to support.

Forgetting the example app. Pub.dev awards points for an example. Show real usage, not just main() {}.

Overly tight SDK constraints. sdk: '>=3.3.0 <3.4.0' blocks users on newer Flutter. Use <4.0.0 unless you have a specific reason.

Publishing a breaking change without a major version bump. Renaming a public class is a breaking change. Bump the major version to signal this.

Sign in to like, dislike, or report.

Building your own Flutter package from scratch — ANN Tech