{"id":50706333,"url":"https://github.com/bamlab/go_router_url_params","last_synced_at":"2026-06-09T12:02:26.391Z","repository":{"id":362150115,"uuid":"1131240070","full_name":"bamlab/go_router_url_params","owner":"bamlab","description":"Easily keep your state in the url when using go_router in Flutter","archived":false,"fork":false,"pushed_at":"2026-06-02T21:55:51.000Z","size":5261,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-02T23:11:27.570Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bamlab.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-09T17:26:42.000Z","updated_at":"2026-06-02T21:55:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bamlab/go_router_url_params","commit_stats":null,"previous_names":["bamlab/go_router_url_params"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/bamlab/go_router_url_params","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bamlab%2Fgo_router_url_params","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bamlab%2Fgo_router_url_params/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bamlab%2Fgo_router_url_params/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bamlab%2Fgo_router_url_params/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bamlab","download_url":"https://codeload.github.com/bamlab/go_router_url_params/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bamlab%2Fgo_router_url_params/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34105565,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-09T12:02:25.558Z","updated_at":"2026-06-09T12:02:26.385Z","avatar_url":"https://github.com/bamlab.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go_router_url_params\n\nType the path and query parameters of your [go_router](https://pub.dev/packages/go_router) URLs, read them as plain Dart objects, and write them back — without keeping a second copy of that state anywhere else.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/bamlab/go_router_url_params/main/doc/demo_counter.gif\" width=\"45%\" /\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/bamlab/go_router_url_params/main/doc/demo_tab_view.gif\" width=\"45%\" /\u003e\n\u003c/p\u003e\n\n## Motivation\n\nThe URL already holds part of your application state: the current route, its path parameters, and its query string.\n\n- First problem: this state is often awkward to read in a typed way, because a URL is entirely a string, and dart doesn't support reflection at runtime for performance reasons, which makes it hard to parse any string into a typed object like javascript does.\n- Second problem: this state doesn't belong to the state management library that you probably use (riverpod, bloc, provider, etc.), so manipulating the URL as state management requires its own project standards, which are often complex yet implicit.\n\nFor these reasons, it is tempting to replicate it into your state management and keep the two in sync by hand. That duplication introduces boilerplate, imperativeness (one variable \"name\" in your state management, one query param \"initialName\", side effects between the two), and unnecessary complexity.\n\nIn short, bugs.\n\n**go_router_url_params** fills that gap by reusing the `fromMap`/`toMap` methods that a data class often exposes, and defining a simple, performant interface to read and write the URL as typed objects. The URL becomes the single source of truth, and your widgets read it as typed objects. No boilerplate, no duplicated state.\n\n## Installation\n\nUse `flutter pub add go_router_url_params`, or add `go_router_url_params` to the `dependencies` of your `pubspec.yaml` manually. You also need [go_router](https://pub.dev/packages/go_router) itself.\n\n---\n\n# Basic usage\n\n## 1. Make your data classes serializable\n\nA class participates by mixing in `UrlParamsData` and providing `toMap()` plus a `fromMap` factory:\n\n```dart\nclass Person with UrlParamsData {\n  const Person({this.name, this.age = 0, PersonStatus? status})\n      : status = status ?? const PersonStatus();\n\n  final String? name;\n  final int age;\n  final PersonStatus status;\n\n  factory Person.fromMap(Map\u003cString, dynamic\u003e map) =\u003e Person(\n        name: map['name'] as String?,\n        age: int.tryParse(map['age']?.toString() ?? '') ?? 0,\n        status: map['status'] != null\n            ? PersonStatus.fromMap(map['status'] as Map\u003cString, dynamic\u003e)\n            : null,\n      );\n\n  @override\n  Map\u003cString, dynamic\u003e toMap() =\u003e {\n        'name': name,\n        'age': age,\n        'status': status.toMap(),\n      };\n\n  Person copyWith({String? name, int? age, PersonStatus? status}) =\u003e Person(\n        name: name ?? this.name,\n        age: age ?? this.age,\n        status: status ?? this.status,\n      );\n}\n\nclass PersonStatus with UrlParamsData {\n  const PersonStatus({this.isActive = true, this.labels = const []});\n\n  final bool isActive;\n  final List\u003cString\u003e labels;\n\n  factory PersonStatus.fromMap(Map\u003cString, dynamic\u003e map) =\u003e PersonStatus(\n        isActive: bool.tryParse(map['isActive']?.toString() ?? '') ?? true,\n        labels: (map['labels'] as List?)?.cast\u003cString\u003e() ?? const [],\n      );\n\n  @override\n  Map\u003cString, dynamic\u003e toMap() =\u003e {'isActive': isActive, 'labels': labels};\n}\n```\n\nSounds complex? Ok that's fair.\n\nBut `toMap`/`fromMap`/`copyWith` are exactly the methods code generators produce, so you don't have to write them by hand. The `example/lib/model_examples/` folder shows the same model generated with `dart_mappable`, `freezed`, and `json_serializable`.\n\n## 2. Add the scope below `MaterialApp.router`\n\n`UrlParamsScope` subscribes to the router and republishes its parameters. Register one `UrlParamBuilder` per type any widget will read:\n\n```dart\nMaterialApp.router(\n  routerDelegate: router.routerDelegate,\n  routeInformationParser: router.routeInformationParser,\n  routeInformationProvider: router.routeInformationProvider,\n  builder: (context, child) =\u003e UrlParamsScope(\n    router: router,\n    builders: [\n      UrlParamBuilder\u003cPerson\u003e(Person.fromMap),\n    ],\n    child: child!,\n  ),\n)\n```\n\n## 3. Read and write typed params\n\n```dart\n// Read. Rebuilds only when the Person slice of the URL changes.\nfinal person = context.watchUrlParams\u003cPerson\u003e();\n\n// Write. Updates the URL, which rebuilds the widgets that read it.\ncontext.setUrlParams(person.copyWith(age: person.age + 1));\n```\n\n`watchUrlParams\u003cT\u003e()` returns `null` when no `Person` can be parsed from the current URL, so you should pay attention to that case.\n\nGlobally, you can consider that anything can be in the URL (especially in web). Be really careful with nullability and default values.\n\n## How rebuilds are scoped\n\nA widget that uses `watchUrlParams\u003cT\u003e()` to read a type `T` rebuilds only when the parsed `T` changes; it does not rebuild when an unrelated field changes, even though both live in the same URL. Parsing is cached per type for the lifetime of a URL, so a registered builder runs at most once per URL change regardless of how many widgets read that type.\n\n---\n\n# Advanced usage\n\n## Building a URL to navigate to\n\n`UrlParamsData.toQueryParamsString` turns a `UrlParamsData` into a query string for `context.go`. Use `keysToIgnore`/`keysToInclude` to control which fields are serialized (for instance, to drop a field that is already a path parameter), and pass `currentUri` to merge with the query params already present, if you don't want the rest of your state to be lost:\n\n```dart\ncontext.go(\n  '/counter/${person.name}'\n  '${person.toQueryParamsString(keysToIgnore: [\"name\"], currentUri: context.uri)}',\n);\n```\n\n`context.uri` is a shortcut for the router's current `Uri`.\n\n## One limitation: avoid using the same name for a path param and a query param\n\nPath parameters and query parameters share one flat namespace when written. Do not give a path parameter the same name as a query parameter: if both exist, the value is assigned to the path parameter and the query parameter stays empty. If you genuinely need overlapping names, drop down to the lower-level API below.\n\n## Disambiguating types with `prefixKey`\n\nIf two registered types expose colliding field names, or if you want to register to a sub-slice of a type, you can give a `prefixKey` in the `UrlParamBuilder`.\n\nExample:\n\n```dart\nUrlParamsScope(\n    router: router,\n    builders: [\n      UrlParamBuilder\u003cPerson\u003e(Person.fromMap),\n      UrlParamBuilder\u003cPersonStatus\u003e(PersonStatus.fromMap, prefixKey: \"status\"),\n    ],\n    child: child!,\n  ),\n\n```\n\nWith `prefixKey: \"status\"`, `PersonStatus.isActive` is written as `?status.isActive=true` instead of `?isActive=true`.\n\n## Lower level: Reading a single key\n\nWhen you do not want to model a whole object, read one parameter at a time. These also require a `UrlParamsScope` ancestor and rebuild only when that specific key changes:\n\n```dart\nfinal age = context.watchQueryParamFromKey\u003cint\u003e('age');\nfinal personName = context.watchPathParamFromKey\u003cString\u003e('name');\nfinal favoriteFlavor = context.watchQueryParamFromKey\u003cFlavorEnum\u003e('flavor', parseFromString: (value) =\u003e Flavor.values.byName(value));\n```\n\nSupported types by default: `String`, `int`, `double`, `bool`, `DateTime`, and (for query params) a `List` of these. For other types, you can provide a `parseFromString` function to parse the value from String to the requested type.\n\nA value that cannot be parsed to the requested type returns `null` rather than throwing — reading `age` as a `bool` gives `null`.\n\n### Lists (query params only)\n\ngo_router exposes repeated query keys (`?tag=a\u0026tag=b`) as a list. Read them with a `List` type to get every value; reading a scalar type keeps the last value:\n\n```dart\nfinal tags = context.watchQueryParamFromKey\u003cList\u003cString\u003e\u003e('tag'); // ['a', 'b']\nfinal ids  = context.watchQueryParamFromKey\u003cList\u003cint\u003e\u003e('id');     // null if any value isn't an int\n```\n\n## Lower-level: writes\n\n`setUrlParamsFromMap` writes raw query and path maps, bypassing the typed object layer. Pair it with the single-key readers when you need full control:\n\n```dart\ncontext.setUrlParamsFromMap(\n  queryParams: {'tag': ['a', 'b']},\n  pathParams: {'name': 'Rico'},\n);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbamlab%2Fgo_router_url_params","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbamlab%2Fgo_router_url_params","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbamlab%2Fgo_router_url_params/lists"}