shrtnr 0.3.0 copy "shrtnr: ^0.3.0" to clipboard
shrtnr: ^0.3.0 copied to clipboard

Dart client for the shrtnr URL shortener API. Create short links, manage custom slugs, and read click analytics.

shrtnr: URL Shortener SDK for Dart and Flutter #

Dart client for creating short links, managing URLs, and reading click analytics from a shrtnr instance. Works on the Dart VM, Flutter (mobile, desktop, web), and anywhere package:http runs.

Install #

dart pub add shrtnr

or for Flutter:

flutter pub add shrtnr

Quick Start #

import 'package:shrtnr/shrtnr.dart';

final client = ShrtnrClient(
  baseUrl: 'https://your-shrtnr.example.com',
  auth: const ApiKeyAuth(apiKey: 'sk_your_api_key'),
);

// Shorten a URL
final link = await client.createLink(
  const CreateLinkOptions(
    url: 'https://example.com/long-page',
    label: 'Campaign landing page',
  ),
);

print(link.id);           // 1
print(link.slugs.first);  // primary slug

What This SDK Covers #

This package wraps the public link-management API:

  • Shorten URLs (create short links)
  • Add, disable, enable, and remove custom slugs
  • List, read, update, disable, enable, and delete links
  • List links by owner identity
  • Read click analytics (referrer, country, device, browser)
  • Group links into bundles and read combined engagement stats
  • Check service health

Administrative operations (API key management, settings, dashboard stats) are not part of this package. Those are accessible through the admin UI.

API Reference #

Shorten a URL. Returns a Link with a random slug.

final link = await client.createLink(
  CreateLinkOptions(
    url: 'https://example.com',
    label: 'My link to the example page',
    expiresAt: DateTime.now().toUtc().add(const Duration(days: 1)),
  ),
);

To add a custom slug, call addCustomSlug after creation:

final link = await client.createLink(
  const CreateLinkOptions(url: 'https://example.com'),
);
final slug = await client.addCustomSlug(link.id, 'my-campaign');

List all short links.

final links = await client.listLinks();

Get a single link by ID, including its slugs and click count.

final link = await client.getLink(123);

getLinkBySlug #

Get a single link by its short URL slug (including custom slugs).

final link = await client.getLinkBySlug('my-custom-slug');

Update a link's URL, label, or expiry. Pass clearLabel: true or clearExpiresAt: true to explicitly null a field on the server.

final updated = await client.updateLink(
  123,
  const UpdateLinkOptions(
    label: 'Updated label',
    clearExpiresAt: true,
  ),
);

Disable a link so it stops redirecting.

final disabled = await client.disableLink(123);

Re-enable a previously disabled link.

final link = await client.enableLink(123);

Permanently delete a link. Only succeeds if the link has zero clicks: disable it instead if it has traffic.

await client.deleteLink(123);

listLinksByOwner #

List all links created by a specific identity (typically an email address).

final links = await client.listLinksByOwner('user@example.com');

addCustomSlug #

Add a custom short URL slug to an existing link. Throws ShrtnrException with status 409 if the slug already exists, or 400 for invalid format.

final slug = await client.addCustomSlug(123, 'campaign');

disableSlug #

Disable a custom slug without affecting the parent link or its other slugs.

await client.disableSlug(123, 'campaign');

enableSlug #

Re-enable a disabled custom slug.

await client.enableSlug(123, 'campaign');

removeSlug #

Permanently remove a custom slug. Only succeeds if the slug has zero clicks.

await client.removeSlug(123, 'campaign');

getLinkQR #

Fetch the QR code SVG for a link as a string. Optionally specify which slug to encode.

final svg = await client.getLinkQR(123);
final svgForSlug = await client.getLinkQR(123, slug: 'my-campaign');

getLinkAnalytics #

Read click analytics for a link: referrer, country, device type, and browser breakdown. Defaults to all-time. Pass an optional range to scope results to a window.

final lifetime = await client.getLinkAnalytics(123);
final last7d = await client.getLinkAnalytics(123, range: '7d');
print(lifetime.totalClicks);
print(lifetime.countries);

health #

Check service health and version.

final health = await client.health();
print(health.version);

createBundle #

Create a bundle to group related links. Returns the new Bundle.

final bundle = await client.createBundle(
  const CreateBundleOptions(
    name: 'Spring campaign',
    description: 'Email, social, and paid drops',
    icon: 'sparkles',
    accent: BundleAccent.purple,
  ),
);

listBundles #

List bundles with summary stats: lifetime click total, 30-day sparkline, and top links. Archived bundles are hidden by default.

final bundles = await client.listBundles();
final withArchived = await client.listBundles(archived: true);

getBundle #

Fetch a single bundle's metadata by ID.

final bundle = await client.getBundle(42);

updateBundle #

Rename a bundle or change its description, icon, or accent. Pass clearDescription: true or clearIcon: true to explicitly null a field on the server.

final updated = await client.updateBundle(
  42,
  const UpdateBundleOptions(
    name: 'Spring 2026 campaign',
    accent: BundleAccent.green,
  ),
);

deleteBundle #

Permanently delete a bundle. Member links are preserved, only the grouping is discarded.

await client.deleteBundle(42);

archiveBundle #

Archive a bundle so it drops out of the default listBundles response. Member links keep working.

await client.archiveBundle(42);

unarchiveBundle #

Restore a previously archived bundle.

await client.unarchiveBundle(42);

getBundleAnalytics #

Read combined analytics across every link in the bundle: per-link breakdown, countries, devices, browsers. Defaults to all-time; pass range to scope to a window.

final lifetime = await client.getBundleAnalytics(42);
final last7d = await client.getBundleAnalytics(42, range: '7d');
print(lifetime.totalClicks);
print(lifetime.perLink);

List every link currently in a bundle.

final links = await client.listBundleLinks(42);

addLinkToBundle #

Attach a link to a bundle. Idempotent: re-adding an existing member is a no-op.

await client.addLinkToBundle(42, 123);

removeLinkFromBundle #

Detach a link from a bundle. The link itself stays, only the membership is removed.

await client.removeLinkFromBundle(42, 123);

List every bundle a given link belongs to.

final bundles = await client.listBundlesForLink(123);

Error Handling #

Non-2xx responses throw ShrtnrException with the status code, message, and raw response body.

import 'package:shrtnr/shrtnr.dart';

try {
  await client.getLink(99999);
} on ShrtnrException catch (e) {
  print(e.statusCode); // 404
  print(e.message);    // "Link not found"
  print(e.body);
}

Custom slug collisions and format errors from addCustomSlug throw ShrtnrException (status 409 or 400). Handle them per-call.

Differences from the TypeScript SDK #

The Dart SDK mirrors the TypeScript SDK method-for-method. A few surfaces differ where Dart idioms diverge:

  • Constructor takes named parameters (baseUrl:, auth:) instead of a single config object.
  • Error type is named ShrtnrException (Dart reserves Error for programmer errors).
  • Timestamp fields (createdAt, expiresAt, disabledAt, health timestamp) are DateTime in UTC rather than raw Unix seconds.
  • isCustom and isPrimary are bool rather than 0 | 1.
  • UpdateLinkOptions uses clearLabel / clearExpiresAt flags to distinguish "leave unchanged" from "clear on server", since Dart's null-default pattern collapses them otherwise.

License #

Apache-2.0. See the root LICENSE file.

1
likes
160
points
465
downloads

Documentation

API reference

Publisher

verified publisheroddbit.id

Weekly Downloads

Dart client for the shrtnr URL shortener API. Create short links, manage custom slugs, and read click analytics.

Homepage
Repository (GitHub)
View/report issues

Topics

#url-shortener #short-url #link-management #analytics

License

Apache-2.0 (license)

Dependencies

http, meta

More

Packages that depend on shrtnr