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 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