Publishing Your First Package
This guide walks through the complete workflow of creating a Dart package, publishing it to your club server, and consuming it as a dependency in another project.
Prerequisites
- A running club server (see Installation)
- The Dart SDK installed
- The club CLI installed and logged in (see steps below)
Set up authentication
Before you can publish, you need to authenticate with your club server.
-
Install the club CLI
Terminal window curl -fsSL https://club.birju.dev/install.sh | bashSee the CLI installation guide for Windows and alternative install methods.
-
Login to your server
Terminal window club login https://packages.example.comBy default,
club loginopens your browser and runs an OAuth 2.0 Authorization Code flow with PKCE. Approve the request in the browser and the CLI receives a scoped personal access token automatically. On a headless machine, use--no-browserto fall back to a terminal email/password prompt, or pass an existing personal access token with--key club_pat_.... -
Configure dart pub (optional)
Terminal window club setupclub loginalready registers the token withdart pub token addautomatically. Runclub setupif you need to switch to an--env-varconfiguration (for example, on a CI machine where you would rather pull the token from a secret). -
Verify the token is registered
Terminal window dart pub token listYou should see
https://packages.example.comin the output.
Create a package
-
Scaffold a new package
Terminal window mkdir -p ~/my_utils && cd ~/my_utils -
Create
pubspec.yamlThe key setting is
publish_to, which tellsdart pub publishwhere to send the package:name: my_utilsversion: 1.0.0description: Shared utility functions for our team.publish_to: https://packages.example.comenvironment:sdk: ^3.0.0 -
Add library code
Create
lib/my_utils.dart:/// Formats a duration as a human-readable string.String formatDuration(Duration duration) {final hours = duration.inHours;final minutes = duration.inMinutes.remainder(60);final seconds = duration.inSeconds.remainder(60);if (hours > 0) return '${hours}h ${minutes}m ${seconds}s';if (minutes > 0) return '${minutes}m ${seconds}s';return '${seconds}s';}/// Capitalizes the first letter of a string.String capitalize(String input) {if (input.isEmpty) return input;return input[0].toUpperCase() + input.substring(1);} -
Add a README
Create
README.md:# my_utilsShared utility functions for our team.## Usage```dartimport 'package:my_utils/my_utils.dart';void main() {print(formatDuration(Duration(hours: 1, minutes: 30)));// Output: 1h 30m 0sprint(capitalize('hello'));// Output: Hello} -
Add a CHANGELOG
Create
CHANGELOG.md:## 1.0.0- Initial release- Added `formatDuration` function- Added `capitalize` function
Publish the package
-
Dry run (optional)
Preview what will be published without actually uploading:
Terminal window dart pub publish --dry-runThis validates the package structure and shows any warnings.
-
Publish
Terminal window dart pub publishYou will be prompted to confirm. Type
yto proceed. Alternatively, use--forceto skip the prompt:Terminal window dart pub publish --force -
Verify on the web UI
Open
https://packages.example.comin your browser and navigate to the packages page. You should seemy_utilslisted with version1.0.0. Click it to see the rendered README, changelog, version history, and installation instructions.
Use the package in another project
-
Create or open a Dart project
Terminal window mkdir -p ~/my_app && cd ~/my_appdart create -t console . -
Add the dependency
Edit
pubspec.yamland addmy_utilsunderdependencies:dependencies:my_utils:hosted: https://packages.example.comversion: ^1.0.0 -
Fetch dependencies
Terminal window dart pub get -
Use the package
Edit
bin/my_app.dart:import 'package:my_utils/my_utils.dart';void main() {final elapsed = Duration(minutes: 42, seconds: 7);print('Build took ${formatDuration(elapsed)}');print(capitalize('club works!'));}Run it:
Terminal window dart runExpected output:
Build took 42m 7sClub works!
Publishing updates
To publish a new version, update the version field in pubspec.yaml and run dart pub publish again:
version: 1.1.0dart pub publish --forceConsumers upgrade by running:
dart pub upgrade my_utilsCI/CD publishing
For automated publishing from CI/CD pipelines, create a personal access token in the web dashboard at Settings → API keys with read,write scope. The raw token (format club_pat_...) is shown once at creation — copy it immediately. Then in your CI configuration (for example, GitHub Actions):
- name: Configure club run: dart pub token add https://packages.example.com --env-var CLUB_TOKEN env: CLUB_TOKEN: ${{ secrets.CLUB_TOKEN }}
- name: Publish run: dart pub publish --forceAlternatively, use club publish --force directly and provide the token via the CLUB_TOKEN environment variable, which the CLI honours as a bearer token override.
Troubleshooting
dart pub publish returns 401 Unauthorized
Your token is missing or invalid. Re-register it:
dart pub token remove https://packages.example.comclub setupThen verify with dart pub token list.
dart pub publish returns 403 Forbidden
You do not have permission to publish this package. This happens when:
- The package is owned by a publisher and you are not a member
- Another user published the first version and you are not an uploader
- Your API token does not have the
writescope
Contact your club admin to be added as an uploader or publisher member.
”publish_to URL does not match”
The publish_to value in your pubspec.yaml must exactly match the server URL, including the scheme (https:// vs http://) and the absence of a trailing slash. Compare your publish_to with your server’s SERVER_URL.
dart pub get fails with “Could not resolve”
Make sure:
- Your token is registered:
dart pub token list - The
hostedURL in yourpubspec.yamlmatches exactly - The package name and version exist on the server
- The server is reachable:
curl https://packages.example.com/api/v1/health
Package published but not showing in search
The search index updates immediately on publish. If the package does not appear:
- Verify it exists via the API:
curl -H "Authorization: Bearer $TOKEN" https://packages.example.com/api/packages/my_utils - Try a direct URL:
https://packages.example.com/packages/my_utils - Check the server logs for errors:
docker compose logs club