diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml
index 3ca3c8b099..40cf09d19b 100644
--- a/.github/workflows/native.yaml
+++ b/.github/workflows/native.yaml
@@ -108,6 +108,12 @@ jobs:
       - run: dart pub get -C test_data/native_dynamic_linking/
         if: ${{ matrix.package == 'native_assets_builder' }}
 
+      - run: dart pub get -C test_data/reusable_dynamic_library/
+        if: ${{ matrix.package == 'native_assets_builder' }}
+
+      - run: dart pub get -C test_data/reuse_dynamic_library/
+        if: ${{ matrix.package == 'native_assets_builder' }}
+
       - run: dart pub get -C test_data/no_hook/
         if: ${{ matrix.package == 'native_assets_builder' }}
 
diff --git a/pkgs/native_assets_builder/CHANGELOG.md b/pkgs/native_assets_builder/CHANGELOG.md
index fadb99469d..31a504a2d8 100644
--- a/pkgs/native_assets_builder/CHANGELOG.md
+++ b/pkgs/native_assets_builder/CHANGELOG.md
@@ -1,6 +1,7 @@
-## 0.13.1-wip
+## 0.14.0-wip
 
-- Nothing yet.
+- Bump `package:native_assets_cli` to 0.14.0.
+- Route assets from build hook to build hook with `ToBuild` `Routing`.
 
 ## 0.13.0
 
diff --git a/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart b/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart
index 8deb0009a4..4797b13623 100644
--- a/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart
+++ b/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart
@@ -91,14 +91,25 @@ class NativeAssetsBuildRunner {
     if (buildPlan == null) return null;
 
     var hookResult = HookResult();
+
+    /// Key is packageName.
     final globalMetadata = <String, Metadata>{};
+
+    /// Key is packageName.
+    final globalAssetsForBuild = <String, List<EncodedAsset>>{};
     for (final package in buildPlan) {
-      final metadata = <String, Metadata>{};
-      _metadataForPackage(
-        packageGraph: packageGraph!,
+      final metadata =
+          _metadataForPackage(
+            packageGraph: packageGraph!,
+            packageName: package.name,
+            targetMetadata: globalMetadata,
+          ) ??
+          {};
+      final assetsForBuild = _assetsForBuildForPackage(
+        packageGraph: packageGraph,
         packageName: package.name,
-        targetMetadata: globalMetadata,
-      )?.forEach((key, value) => metadata[key] = value);
+        globalAssetsForBuild: globalAssetsForBuild,
+      );
 
       final inputBuilder = BuildInputBuilder();
 
@@ -106,7 +117,7 @@ class NativeAssetsBuildRunner {
         e.setupBuildInput(inputBuilder);
       }
       inputBuilder.config.setupBuild(linkingEnabled: linkingEnabled);
-      inputBuilder.setupBuildInput(metadata: metadata);
+      inputBuilder.setupBuildInput(metadata: metadata, assets: assetsForBuild);
 
       final (buildDirUri, outDirUri, outDirSharedUri) = await _setupDirectories(
         Hook.build,
@@ -152,6 +163,8 @@ class NativeAssetsBuildRunner {
       final (hookOutput, hookDeps) = result;
       hookResult = hookResult.copyAdd(hookOutput, hookDeps);
       globalMetadata[package.name] = (hookOutput as BuildOutput).metadata;
+      globalAssetsForBuild[package.name] =
+          hookOutput.assets.encodedAssetsForBuild;
     }
 
     // We only perform application wide validation in the final result of
@@ -732,6 +745,23 @@ ${compileResult.stdout}
     };
   }
 
+  /// Returns only the assets output as assetForBuild by the packages that are
+  /// the direct dependencies of [packageName].
+  Map<String, List<EncodedAsset>>? _assetsForBuildForPackage({
+    required PackageGraph packageGraph,
+    required String packageName,
+    Map<String, List<EncodedAsset>>? globalAssetsForBuild,
+  }) {
+    if (globalAssetsForBuild == null) {
+      return null;
+    }
+    final dependencies = packageGraph.neighborsOf(packageName).toSet();
+    return {
+      for (final entry in globalAssetsForBuild.entries)
+        if (dependencies.contains(entry.key)) entry.key: entry.value,
+    };
+  }
+
   Future<ValidationErrors> _validate(
     HookInput input,
     HookOutput output,
diff --git a/pkgs/native_assets_builder/pubspec.yaml b/pkgs/native_assets_builder/pubspec.yaml
index bd6854aa3d..a6b3b912f9 100644
--- a/pkgs/native_assets_builder/pubspec.yaml
+++ b/pkgs/native_assets_builder/pubspec.yaml
@@ -1,7 +1,7 @@
 name: native_assets_builder
 description: >-
   This package is the backend that invokes build hooks.
-version: 0.13.1-wip
+version: 0.14.0-wip
 repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_builder
 
 publish_to: none
diff --git a/pkgs/native_assets_builder/test/test_data/reusable_dynamic_library_test.dart b/pkgs/native_assets_builder/test/test_data/reusable_dynamic_library_test.dart
new file mode 100644
index 0000000000..6a7cc589bb
--- /dev/null
+++ b/pkgs/native_assets_builder/test/test_data/reusable_dynamic_library_test.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@OnPlatform({'mac-os': Timeout.factor(2), 'windows': Timeout.factor(10)})
+library;
+
+import 'package:test/test.dart';
+
+import '../build_runner/helpers.dart';
+import '../helpers.dart';
+
+void main() async {
+  const name = 'reuse_dynamic_library';
+
+  test(
+    '$name build',
+    () => inTempDir((tempUri) async {
+      await copyTestProjects(targetUri: tempUri);
+      final packageUri = tempUri.resolve('$name/');
+
+      await runPubGet(workingDirectory: packageUri, logger: logger);
+
+      final logMessages = <String>[];
+      final result =
+          (await build(
+            packageUri,
+            logger,
+            dartExecutable,
+            capturedLogs: logMessages,
+            buildAssetTypes: [BuildAssetType.code],
+          ))!;
+
+      expect(result.encodedAssets.length, 2);
+    }),
+  );
+}
diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart b/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart
index e2811a8f37..c1ca18abc4 100644
--- a/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart
@@ -26,7 +26,7 @@ void main(List<String> arguments) async {
       input: input,
       output: output,
       logger: logger,
-      linkInPackage: 'add_asset_link',
+      routing: const [ToLinkHook('add_asset_link')],
     );
   });
 }
diff --git a/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart b/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart
index 6bfff46ac1..1f3012b043 100644
--- a/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart
@@ -27,7 +27,10 @@ void main(List<String> args) async {
 
       output.assets.data.add(
         DataAsset(package: packageName, name: name, file: dataAsset.uri),
-        linkInPackage: input.config.linkingEnabled ? packageName : null,
+        routing:
+            input.config.linkingEnabled
+                ? ToLinkHook(packageName)
+                : const ToAppBundle(),
       );
       // TODO(https://github.com/dart-lang/native/issues/1208): Report
       // dependency on asset.
diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart b/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart
index 033d9329e8..17ad3e52fd 100644
--- a/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart
@@ -28,8 +28,10 @@ void main(List<String> args) async {
       final forLinking = name.contains('2') || name.contains('3');
       output.assets.data.add(
         DataAsset(package: packageName, name: name, file: dataAsset.uri),
-        linkInPackage:
-            forLinking && input.config.linkingEnabled ? 'complex_link' : null,
+        routing:
+            forLinking && input.config.linkingEnabled
+                ? const ToLinkHook('complex_link')
+                : const ToAppBundle(),
       );
       // TODO(https://github.com/dart-lang/native/issues/1208): Report
       // dependency on asset.
diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart
index 69228e97d2..dc465fe3eb 100644
--- a/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart
@@ -14,30 +14,22 @@ void main(List<String> arguments) async {
           ..onRecord.listen((record) {
             print('${record.level.name}: ${record.time}: ${record.message}');
           });
-    final linkInPackage =
-        input.config.linkingEnabled ? input.packageName : null;
+    final routing =
+        input.config.linkingEnabled
+            ? [ToLinkHook(input.packageName)]
+            : [const ToAppBundle()];
     await CBuilder.library(
       name: 'add',
       assetName: 'dylib_add',
       sources: ['src/native_add.c'],
       linkModePreference: LinkModePreference.dynamic,
-    ).run(
-      input: input,
-      output: output,
-      logger: logger,
-      linkInPackage: linkInPackage,
-    );
+    ).run(input: input, output: output, logger: logger, routing: routing);
 
     await CBuilder.library(
       name: 'multiply',
       assetName: 'dylib_multiply',
       sources: ['src/native_multiply.c'],
       linkModePreference: LinkModePreference.dynamic,
-    ).run(
-      input: input,
-      output: output,
-      logger: logger,
-      linkInPackage: linkInPackage,
-    );
+    ).run(input: input, output: output, logger: logger, routing: routing);
   });
 }
diff --git a/pkgs/native_assets_builder/test_data/fail_on_os_sdk_version_link/hook/build.dart b/pkgs/native_assets_builder/test_data/fail_on_os_sdk_version_link/hook/build.dart
index 8cfdb1ef4b..723bfd507d 100644
--- a/pkgs/native_assets_builder/test_data/fail_on_os_sdk_version_link/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/fail_on_os_sdk_version_link/hook/build.dart
@@ -12,8 +12,10 @@ void main(List<String> arguments) async {
         file: input.packageRoot.resolve('assets/data.json'),
         package: input.packageName,
       ),
-      linkInPackage:
-          input.config.linkingEnabled ? 'fail_on_os_sdk_version_linker' : null,
+      routing:
+          input.config.linkingEnabled
+              ? const ToLinkHook('fail_on_os_sdk_version_linker')
+              : const ToAppBundle(),
     );
   });
 }
diff --git a/pkgs/native_assets_builder/test_data/manifest.yaml b/pkgs/native_assets_builder/test_data/manifest.yaml
index 819718c56c..387f53315b 100644
--- a/pkgs/native_assets_builder/test_data/manifest.yaml
+++ b/pkgs/native_assets_builder/test_data/manifest.yaml
@@ -112,6 +112,21 @@
 - relative_path/assets/test_asset.txt
 - relative_path/hook/build.dart
 - relative_path/pubspec.yaml
+- reusable_dynamic_library/ffigen.yaml
+- reusable_dynamic_library/hook/build.dart
+- reusable_dynamic_library/lib/add.dart
+- reusable_dynamic_library/lib/hook.dart
+- reusable_dynamic_library/pubspec.yaml
+- reusable_dynamic_library/src/add.c
+- reusable_dynamic_library/src/add.h
+- reusable_dynamic_library/test/add_test.dart
+- reuse_dynamic_library/ffigen.yaml
+- reuse_dynamic_library/hook/build.dart
+- reuse_dynamic_library/lib/my_add.dart
+- reuse_dynamic_library/pubspec.yaml
+- reuse_dynamic_library/src/my_add.c
+- reuse_dynamic_library/src/my_add.h
+- reuse_dynamic_library/test/add_test.dart
 - simple_data_asset/assets/test_asset.txt
 - simple_data_asset/bin/deep_modify_data_asset.dart.debug
 - simple_data_asset/bin/modify_data_asset.dart.debug
diff --git a/pkgs/native_assets_builder/test_data/native_add_duplicate/hook/build.dart b/pkgs/native_assets_builder/test_data/native_add_duplicate/hook/build.dart
index 509fbbaed1..c9bf9e0b6a 100644
--- a/pkgs/native_assets_builder/test_data/native_add_duplicate/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/native_add_duplicate/hook/build.dart
@@ -32,7 +32,10 @@ void main(List<String> arguments) async {
     output.assets.code.add(
       tempBuildOutput.assets.code.single,
       // Send dylib to linking if linking is enabled.
-      linkInPackage: input.config.linkingEnabled ? packageName : null,
+      routing:
+          input.config.linkingEnabled
+              ? ToLinkHook(packageName)
+              : const ToAppBundle(),
     );
     output.addDependencies(tempBuildOutput.dependencies);
   });
diff --git a/pkgs/native_assets_builder/test_data/package_reading_metadata/hook/build.dart b/pkgs/native_assets_builder/test_data/package_reading_metadata/hook/build.dart
index 9a4c8d7c33..d85f93e033 100644
--- a/pkgs/native_assets_builder/test_data/package_reading_metadata/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/package_reading_metadata/hook/build.dart
@@ -2,16 +2,21 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// ignore_for_file: deprecated_member_use
-
 import 'package:native_assets_cli/native_assets_cli.dart';
 
 void main(List<String> args) async {
   await build(args, (input, _) async {
-    final someValue = input.metadatum('package_with_metadata', 'some_key');
+    final someValue = input.metadata['package_with_metadata']['some_key'];
     assert(someValue != null);
-    final someInt = input.metadatum('package_with_metadata', 'some_int');
+    final someInt = input.metadata['package_with_metadata']['some_int'];
     assert(someInt != null);
     print({'some_int': someInt, 'some_key': someValue});
+
+    // ignore: deprecated_member_use
+    final someValueOld = input.metadatum('package_with_metadata', 'some_key');
+    assert(someValueOld != null);
+    // ignore: deprecated_member_use
+    final someIntOld = input.metadatum('package_with_metadata', 'some_int');
+    assert(someIntOld != null);
   });
 }
diff --git a/pkgs/native_assets_builder/test_data/package_with_metadata/hook/build.dart b/pkgs/native_assets_builder/test_data/package_with_metadata/hook/build.dart
index f933efab6a..d38a47a393 100644
--- a/pkgs/native_assets_builder/test_data/package_with_metadata/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/package_with_metadata/hook/build.dart
@@ -6,6 +6,7 @@ import 'package:native_assets_cli/native_assets_cli.dart';
 
 void main(List<String> arguments) async {
   await build(arguments, (input, output) async {
+    output.metadata.addAll({'some_key': 'some_value', 'some_int': 3});
     // ignore: deprecated_member_use
     output.addMetadata({'some_key': 'some_value', 'some_int': 3});
   });
diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/README.md b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/README.md
new file mode 100644
index 0000000000..d2dc5759eb
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/README.md
@@ -0,0 +1,7 @@
+An example of a package with a dynamic library that can be linked against in
+a dependent package.
+
+## Usage
+
+Run tests with `dart --enable-experiment=native-assets test`.
+
diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/ffigen.yaml b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/ffigen.yaml
new file mode 100644
index 0000000000..c8aba3eaa2
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/ffigen.yaml
@@ -0,0 +1,20 @@
+# Run with `flutter pub run ffigen --config ffigen.yaml`.
+name: AddBindings
+description: |
+  Bindings for `src/add.h`.
+
+  Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.
+output: 'lib/add.dart'
+headers:
+  entry-points:
+    - 'src/add.h'
+  include-directives:
+    - 'src/add.h'
+preamble: |
+  // Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+  // for details. All rights reserved. Use of this source code is governed by a
+  // BSD-style license that can be found in the LICENSE file.
+comments:
+  style: any
+  length: full
+ffi-native:
diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/hook/build.dart b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/hook/build.dart
new file mode 100644
index 0000000000..6e72c6c18b
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/hook/build.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:logging/logging.dart';
+import 'package:native_assets_cli/native_assets_cli.dart';
+import 'package:native_toolchain_c/native_toolchain_c.dart';
+
+void main(List<String> args) async {
+  await build(args, (input, output) async {
+    final logger =
+        Logger('')
+          ..level = Level.ALL
+          ..onRecord.listen((record) => print(record.message));
+
+    final builder = CBuilder.library(
+      name: 'add',
+      assetName: 'add.dart',
+      sources: ['src/add.c'],
+      buildMode: BuildMode.debug,
+    );
+
+    await builder.run(
+      input: input,
+      output: output,
+      logger: logger,
+      routing: const [
+        // Bundle the dylib in the app, someone might use it.
+        ToAppBundle(),
+        // Enable other packages to link to the dylib.
+        ToBuildHooks(),
+      ],
+    );
+
+    // Enable other packages to find the headers.
+    output.metadata['include'] = input.packageRoot.resolve('src/').toFilePath();
+  });
+}
diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/lib/add.dart b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/lib/add.dart
new file mode 100644
index 0000000000..c471412290
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/lib/add.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+// ignore_for_file: type=lint
+import 'dart:ffi' as ffi;
+
+@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(symbol: 'add')
+external int add(int a, int b);
diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/lib/hook.dart b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/lib/hook.dart
new file mode 100644
index 0000000000..00effbe1c2
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/lib/hook.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This library is meant to be used in hooks of packages depending on this
+/// package.
+library;
+
+import 'dart:io';
+
+import 'package:native_assets_cli/code_assets.dart';
+import 'package:native_toolchain_c/native_toolchain_c.dart';
+
+/// Helper class for populating a [CBuilder.library] call with the fields of
+/// this class to link against the `add` dynamic library.
+class AddLibrary {
+  final BuildInput input;
+
+  /// Librarie names for [CBuilder].
+  final List<String> libraries = ['add'];
+
+  /// Library directories for [CBuilder].
+  late final List<String> libraryDirectories;
+
+  /// Include directories for [CBuilder].
+  late final List<String> includes;
+
+  AddLibrary(this.input) {
+    const packageName = 'reusable_dynamic_library';
+    final buildAssetsFromPackage = input.assets[packageName];
+    final codeAssetsFromPackage =
+        buildAssetsFromPackage
+            .where((a) => a.isCodeAsset)
+            .map((a) => a.asCodeAsset)
+            .toList();
+    if (codeAssetsFromPackage.length != 1) {
+      throw Exception(
+        'Did not find a build asset from $packageName:'
+        ' $codeAssetsFromPackage',
+      );
+    }
+    final codeAsset = codeAssetsFromPackage.first;
+    final dylibFile = File.fromUri(codeAsset.file!);
+    if (!dylibFile.existsSync()) {
+      throw Exception('Dylib file does not exist: $dylibFile');
+    }
+    final libraryDirectory = Directory.fromUri(dylibFile.uri.resolve('.'));
+    libraryDirectories = [libraryDirectory.path];
+    final includeDirectory = input.metadata[packageName]['include'];
+    if (includeDirectory is! String) {
+      throw Exception('Include directory is not a string: $includeDirectory');
+    }
+    includes = [includeDirectory];
+  }
+}
diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/src/add.c b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/src/add.c
new file mode 100644
index 0000000000..41cd96a2ae
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/src/add.c
@@ -0,0 +1,7 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "add.h"
+
+int32_t add(int32_t a, int32_t b) { return a + b; }
diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/src/add.h b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/src/add.h
new file mode 100644
index 0000000000..275878fe85
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/src/add.h
@@ -0,0 +1,13 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include <stdint.h>
+
+#if _WIN32
+#define MYLIB_EXPORT __declspec(dllexport)
+#else
+#define MYLIB_EXPORT
+#endif
+
+MYLIB_EXPORT int32_t add(int32_t a, int32_t b);
diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/test/add_test.dart b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/test/add_test.dart
new file mode 100644
index 0000000000..0b3b5e9f35
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/test/add_test.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:reusable_dynamic_library/add.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test('invoke native function', () {
+    expect(add(24, 18), 42);
+  });
+}
diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/README.md b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/README.md
new file mode 100644
index 0000000000..fbc12b69b5
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/README.md
@@ -0,0 +1,7 @@
+An example of a native library which is dynamically linked against a dynamic
+library from another package.
+
+## Usage
+
+Run tests with `dart --enable-experiment=native-assets test`.
+
diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/ffigen.yaml b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/ffigen.yaml
new file mode 100644
index 0000000000..f056629b49
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/ffigen.yaml
@@ -0,0 +1,20 @@
+# Run with `flutter pub run ffigen --config ffigen.yaml`.
+name: AddBindings
+description: |
+  Bindings for `src/my_add.h`.
+
+  Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.
+output: 'lib/my_add.dart'
+headers:
+  entry-points:
+    - 'src/my_add.h'
+  include-directives:
+    - 'src/my_add.h'
+preamble: |
+  // Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+  // for details. All rights reserved. Use of this source code is governed by a
+  // BSD-style license that can be found in the LICENSE file.
+comments:
+  style: any
+  length: full
+ffi-native:
diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/hook/build.dart b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/hook/build.dart
new file mode 100644
index 0000000000..49bbb402ef
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/hook/build.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:logging/logging.dart';
+import 'package:native_assets_cli/native_assets_cli.dart';
+import 'package:native_toolchain_c/native_toolchain_c.dart';
+import 'package:reusable_dynamic_library/hook.dart';
+
+void main(List<String> args) async {
+  await build(args, (input, output) async {
+    final logger =
+        Logger('')
+          ..level = Level.ALL
+          ..onRecord.listen((record) => print(record.message));
+
+    final addLibrary = AddLibrary(input);
+    final builder = CBuilder.library(
+      name: 'my_add',
+      assetName: 'my_add.dart',
+      sources: ['src/my_add.c'],
+      libraries: [...addLibrary.libraries],
+      libraryDirectories: [...addLibrary.libraryDirectories],
+      includes: [...addLibrary.includes],
+      buildMode: BuildMode.debug,
+    );
+
+    await builder.run(
+      input: input,
+      output: output,
+      logger: logger,
+      routing: const [ToAppBundle()],
+    );
+  });
+}
diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/lib/my_add.dart b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/lib/my_add.dart
new file mode 100644
index 0000000000..357fc359c7
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/lib/my_add.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+// ignore_for_file: type=lint
+import 'dart:ffi' as ffi;
+
+@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(symbol: 'my_add')
+external int my_add(int a, int b);
diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/src/my_add.c b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/src/my_add.c
new file mode 100644
index 0000000000..ec0313cd0e
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/src/my_add.c
@@ -0,0 +1,11 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "my_add.h"
+#include "add.h"
+
+int32_t my_add(int32_t a, int32_t b) {
+  // Here we are calling a function from a dynamically linked library.
+  return add(a, b);
+}
diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/src/my_add.h b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/src/my_add.h
new file mode 100644
index 0000000000..85c40cf1e6
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/src/my_add.h
@@ -0,0 +1,13 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include <stdint.h>
+
+#if _WIN32
+#define MYLIB_EXPORT __declspec(dllexport)
+#else
+#define MYLIB_EXPORT
+#endif
+
+MYLIB_EXPORT int32_t my_add(int32_t a, int32_t b);
diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/test/add_test.dart b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/test/add_test.dart
new file mode 100644
index 0000000000..b63ce88e7c
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/test/add_test.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:reuse_dynamic_library/my_add.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test('invoke native function', () {
+    expect(my_add(24, 18), 42);
+  });
+}
diff --git a/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart b/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart
index 6bfff46ac1..1f3012b043 100644
--- a/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart
@@ -27,7 +27,10 @@ void main(List<String> args) async {
 
       output.assets.data.add(
         DataAsset(package: packageName, name: name, file: dataAsset.uri),
-        linkInPackage: input.config.linkingEnabled ? packageName : null,
+        routing:
+            input.config.linkingEnabled
+                ? ToLinkHook(packageName)
+                : const ToAppBundle(),
       );
       // TODO(https://github.com/dart-lang/native/issues/1208): Report
       // dependency on asset.
diff --git a/pkgs/native_assets_builder/test_data/treeshaking_native_libs/hook/build.dart b/pkgs/native_assets_builder/test_data/treeshaking_native_libs/hook/build.dart
index b28738411d..5c1f9e5d68 100644
--- a/pkgs/native_assets_builder/test_data/treeshaking_native_libs/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/treeshaking_native_libs/hook/build.dart
@@ -20,7 +20,10 @@ void main(List<String> arguments) async {
     await cbuilder.run(
       input: input,
       output: output,
-      linkInPackage: input.config.linkingEnabled ? input.packageName : null,
+      routing:
+          input.config.linkingEnabled
+              ? [ToLinkHook(input.packageName)]
+              : [const ToAppBundle()],
       logger:
           Logger('')
             ..level = Level.ALL
diff --git a/pkgs/native_assets_builder/test_data/use_all_api/hook/build.dart b/pkgs/native_assets_builder/test_data/use_all_api/hook/build.dart
index ac8ae9fda8..7474410dc0 100644
--- a/pkgs/native_assets_builder/test_data/use_all_api/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/use_all_api/hook/build.dart
@@ -48,7 +48,10 @@ void main(List<String> args) async {
         name: 'name',
         package: 'package',
       ),
-      linkInPackage: 'foo',
+      routing:
+          input.config.linkingEnabled
+              ? const ToLinkHook('foo')
+              : const ToAppBundle(),
     );
     output.addDependency(input.packageRoot.resolve('x.txt'));
   });
diff --git a/pkgs/native_assets_builder/test_data/wrong_linker/hook/build.dart b/pkgs/native_assets_builder/test_data/wrong_linker/hook/build.dart
index 6eeab74451..afe6948817 100644
--- a/pkgs/native_assets_builder/test_data/wrong_linker/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/wrong_linker/hook/build.dart
@@ -23,7 +23,7 @@ void main(List<String> arguments) async {
         os: OS.current,
         architecture: Architecture.current,
       ),
-      linkInPackage: 'a_package_that_does_not_exist',
+      routing: const ToLinkHook('a_package_that_does_not_exist'),
     );
   });
 }
diff --git a/pkgs/native_assets_cli/CHANGELOG.md b/pkgs/native_assets_cli/CHANGELOG.md
index bca4550557..916df2eef8 100644
--- a/pkgs/native_assets_cli/CHANGELOG.md
+++ b/pkgs/native_assets_cli/CHANGELOG.md
@@ -1,6 +1,7 @@
-## 0.13.1-wip
+## 0.14.0-wip
 
-- Nothing yet.
+- Added support for sending assets between build hooks via the `ToBuild`
+  `Routing`. This replaces the deprecated `Metadata`.
 
 ## 0.13.0
 
diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart b/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart
index 6bfff46ac1..1f3012b043 100644
--- a/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart
+++ b/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart
@@ -27,7 +27,10 @@ void main(List<String> args) async {
 
       output.assets.data.add(
         DataAsset(package: packageName, name: name, file: dataAsset.uri),
-        linkInPackage: input.config.linkingEnabled ? packageName : null,
+        routing:
+            input.config.linkingEnabled
+                ? ToLinkHook(packageName)
+                : const ToAppBundle(),
       );
       // TODO(https://github.com/dart-lang/native/issues/1208): Report
       // dependency on asset.
diff --git a/pkgs/native_assets_cli/lib/native_assets_cli.dart b/pkgs/native_assets_cli/lib/native_assets_cli.dart
index 4dae32f44b..b0f16c6d96 100644
--- a/pkgs/native_assets_cli/lib/native_assets_cli.dart
+++ b/pkgs/native_assets_cli/lib/native_assets_cli.dart
@@ -12,6 +12,7 @@ export 'src/api/link.dart' show link;
 export 'src/api/linker.dart' show Linker;
 export 'src/config.dart'
     show
+        AssetRouting,
         BuildConfig,
         BuildConfigBuilder,
         BuildInput,
@@ -22,6 +23,9 @@ export 'src/config.dart'
         HookConfigBuilder,
         HookInput,
         LinkInput,
-        LinkOutputBuilder;
+        LinkOutputBuilder,
+        ToAppBundle,
+        ToBuildHooks,
+        ToLinkHook;
 export 'src/encoded_asset.dart' show EncodedAsset;
-export 'src/metadata.dart';
+export 'src/metadata.dart' show Metadata;
diff --git a/pkgs/native_assets_cli/lib/src/code_assets/config.dart b/pkgs/native_assets_cli/lib/src/code_assets/config.dart
index 1f0dbad75d..d7d732a50d 100644
--- a/pkgs/native_assets_cli/lib/src/code_assets/config.dart
+++ b/pkgs/native_assets_cli/lib/src/code_assets/config.dart
@@ -151,16 +151,17 @@ extension CodeAssetBuildOutputBuilder on EncodedAssetBuildOutputBuilder {
 extension type CodeAssetBuildOutputBuilderAdd._(
   EncodedAssetBuildOutputBuilder _output
 ) {
-  /// Adds the given [asset] to the hook output (or send to [linkInPackage]
-  /// for linking if provided).
-  void add(CodeAsset asset, {String? linkInPackage}) =>
-      _output.addEncodedAsset(asset.encode(), linkInPackage: linkInPackage);
-
-  /// Adds the given [assets] to the hook output (or send to [linkInPackage]
-  /// for linking if provided).
-  void addAll(Iterable<CodeAsset> assets, {String? linkInPackage}) {
+  /// Adds the given [asset] to the hook output with [routing].
+  void add(CodeAsset asset, {AssetRouting routing = const ToAppBundle()}) =>
+      _output.addEncodedAsset(asset.encode(), routing: routing);
+
+  /// Adds the given [assets] to the hook output with [routing].
+  void addAll(
+    Iterable<CodeAsset> assets, {
+    AssetRouting routing = const ToAppBundle(),
+  }) {
     for (final asset in assets) {
-      add(asset, linkInPackage: linkInPackage);
+      add(asset, routing: routing);
     }
   }
 }
diff --git a/pkgs/native_assets_cli/lib/src/code_assets/validation.dart b/pkgs/native_assets_cli/lib/src/code_assets/validation.dart
index 96b179624d..3fb1c16a0e 100644
--- a/pkgs/native_assets_cli/lib/src/code_assets/validation.dart
+++ b/pkgs/native_assets_cli/lib/src/code_assets/validation.dart
@@ -15,11 +15,16 @@ import 'os.dart';
 import 'syntax.g.dart' as syntax;
 
 Future<ValidationErrors> validateCodeAssetBuildInput(BuildInput input) async =>
-    _validateConfig('BuildInput.config.code', input.config);
+    [
+      ..._validateConfig('BuildInput.config.code', input.config),
+      ...await _validateCodeAssetHookInput([
+        for (final assets in input.assets.encodedAssets.values) ...assets,
+      ]),
+    ];
 
 Future<ValidationErrors> validateCodeAssetLinkInput(LinkInput input) async => [
   ..._validateConfig('LinkInput.config.code', input.config),
-  ...await _validateCodeAssetLinkInput(input.assets.encodedAssets),
+  ...await _validateCodeAssetHookInput(input.assets.encodedAssets),
 ];
 
 ValidationErrors _validateConfig(String inputName, HookConfig config) {
@@ -59,7 +64,7 @@ ValidationErrors _validateConfigSyntax(HookConfig config) {
   return [...syntaxErrors, _semanticValidationSkippedMessage(syntaxNode.path)];
 }
 
-Future<ValidationErrors> _validateCodeAssetLinkInput(
+Future<ValidationErrors> _validateCodeAssetHookInput(
   List<EncodedAsset> encodedAssets,
 ) async {
   final errors = <String>[];
@@ -83,6 +88,7 @@ Future<ValidationErrors> validateCodeAssetBuildOutput(
   input.config.code,
   output.assets.encodedAssets,
   [
+    ...output.assets.encodedAssetsForBuild,
     for (final assetList in output.assets.encodedAssetsForLinking.values)
       ...assetList,
   ],
@@ -126,16 +132,16 @@ Future<ValidationErrors> validateCodeAssetInApplication(
 Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput(
   HookInput input,
   CodeConfig codeConfig,
-  List<EncodedAsset> encodedAssets,
-  List<EncodedAsset> encodedAssetsForLinking,
+  List<EncodedAsset> encodedAssetsBundled,
+  List<EncodedAsset> encodedAssetsNotBundled,
   HookOutput output,
   bool isBuild,
 ) async {
   final errors = <String>[];
-  final ids = <String>{};
+  final idsBundled = <String>{};
   final fileNameToEncodedAssetId = <String, Set<String>>{};
 
-  for (final asset in encodedAssets) {
+  for (final asset in encodedAssetsBundled) {
     if (!asset.isCodeAsset) continue;
     final syntaxErrors = _validateCodeAssetSyntax(asset);
     if (syntaxErrors.isNotEmpty) {
@@ -147,7 +153,7 @@ Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput(
       codeConfig,
       CodeAsset.fromEncoded(asset),
       errors,
-      ids,
+      idsBundled,
       isBuild,
       true,
     );
@@ -157,7 +163,8 @@ Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput(
     );
   }
 
-  for (final asset in encodedAssetsForLinking) {
+  final idsNotBundled = <String>{};
+  for (final asset in encodedAssetsNotBundled) {
     if (!asset.isCodeAsset) continue;
     final syntaxErrors = _validateCodeAssetSyntax(asset);
     if (syntaxErrors.isNotEmpty) {
@@ -169,7 +176,7 @@ Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput(
       codeConfig,
       CodeAsset.fromEncoded(asset),
       errors,
-      ids,
+      idsNotBundled,
       isBuild,
       false,
     );
diff --git a/pkgs/native_assets_cli/lib/src/config.dart b/pkgs/native_assets_cli/lib/src/config.dart
index d1e6e256dc..58b853a320 100644
--- a/pkgs/native_assets_cli/lib/src/config.dart
+++ b/pkgs/native_assets_cli/lib/src/config.dart
@@ -5,6 +5,7 @@
 import 'dart:convert' hide json;
 import 'dart:io';
 
+import 'package:collection/collection.dart';
 import 'package:crypto/crypto.dart' show sha256;
 import 'package:pub_semver/pub_semver.dart';
 
@@ -146,7 +147,8 @@ String _jsonChecksum(Map<String, Object?> json) {
 }
 
 final class BuildInput extends HookInput {
-  Map<String, Metadata> get metadata => {
+  @Deprecated('Use metadata backed by MetadataAsset instead.')
+  Map<String, Metadata> get metadataOld => {
     for (final entry in (_syntaxBuildInput.dependencyMetadata ?? {}).entries)
       entry.key: Metadata(entry.value),
   };
@@ -160,14 +162,49 @@ final class BuildInput extends HookInput {
   BuildInput(super.json)
     : _syntaxBuildInput = syntax.BuildInput.fromJson(json) {
     // Run validation.
-    metadata;
+    // ignore: deprecated_member_use_from_same_package
+    metadataOld;
   }
 
+  @Deprecated('Use metadata backed by MetadataAsset instead.')
   Object? metadatum(String packageName, String key) =>
-      metadata[packageName]?.metadata[key];
+      metadataOld[packageName]?.metadata[key];
 
   @override
   BuildConfig get config => BuildConfig._(this);
+
+  BuildInputAssets get assets => BuildInputAssets._(this);
+
+  BuildInputMetadata get metadata => BuildInputMetadata._(this);
+}
+
+extension type BuildInputMetadata._(BuildInput _input) {
+  PackageMetadata operator [](String packageName) => PackageMetadata(
+    (_input.assets.encodedAssets[packageName] ?? [])
+        .where((e) => e.isMetadataAsset)
+        .map((e) => e.asMetadataAsset)
+        .toList(),
+  );
+}
+
+class PackageMetadata {
+  PackageMetadata(this._metadata);
+
+  final List<MetadataAsset> _metadata;
+
+  Object? operator [](String key) =>
+      _metadata.firstWhereOrNull((e) => e.key == key)?.value;
+}
+
+extension type BuildInputAssets._(BuildInput _input) {
+  Map<String, List<EncodedAsset>> get encodedAssets => {
+    for (final MapEntry(:key, :value)
+        in (_input._syntaxBuildInput.assets ?? {}).entries)
+      key: _parseAssets(value),
+  };
+
+  List<EncodedAsset> operator [](String packageName) =>
+      encodedAssets[packageName] ?? [];
 }
 
 final class BuildInputBuilder extends HookInputBuilder {
@@ -175,7 +212,10 @@ final class BuildInputBuilder extends HookInputBuilder {
   syntax.BuildInput get _syntax =>
       syntax.BuildInput.fromJson(super._syntax.json);
 
-  void setupBuildInput({Map<String, Metadata> metadata = const {}}) {
+  void setupBuildInput({
+    Map<String, Metadata> metadata = const {},
+    Map<String, List<EncodedAsset>>? assets,
+  }) {
     if (metadata.isEmpty) {
       return;
     }
@@ -183,7 +223,16 @@ final class BuildInputBuilder extends HookInputBuilder {
       dependencyMetadata: {
         for (final key in metadata.keys) key: metadata[key]!.toJson(),
       },
-      assets: null, // TODO: Implement this.
+      assets:
+          assets == null
+              ? null
+              : {
+                for (final MapEntry(:key, :value) in assets.entries)
+                  key: [
+                    for (final asset in value)
+                      syntax.Asset.fromJson(asset.toJson()),
+                  ],
+              },
     );
   }
 
@@ -356,6 +405,9 @@ class BuildOutput extends HookOutput {
       key: _parseAssets(value),
   };
 
+  List<EncodedAsset> get _encodedAssetsForBuild =>
+      _parseAssets(_syntax.assetsForBuild ?? []);
+
   /// Metadata passed to dependent build hook invocations.
   Metadata get metadata => Metadata(_syntax.metadata?.json ?? {});
 
@@ -381,6 +433,9 @@ extension type BuildOutputAssets._(BuildOutput _output) {
   /// specified in the key, which can decide if they are bundled or not.
   Map<String, List<EncodedAsset>> get encodedAssetsForLinking =>
       _output._encodedAssetsForLinking;
+
+  List<EncodedAsset> get encodedAssetsForBuild =>
+      _output._encodedAssetsForBuild;
 }
 
 /// Builder to produce the output of a build hook.
@@ -418,6 +473,8 @@ class BuildOutputBuilder extends HookOutputBuilder {
     _syntax.metadata = syntax.JsonObject.fromJson(value);
   }
 
+  MetadataOutputBuilder get metadata => MetadataOutputBuilder._(this);
+
   EncodedAssetBuildOutputBuilder get assets =>
       EncodedAssetBuildOutputBuilder._(this);
 
@@ -426,12 +483,68 @@ class BuildOutputBuilder extends HookOutputBuilder {
       syntax.BuildOutput.fromJson(super._syntax.json);
 }
 
+extension type MetadataOutputBuilder._(BuildOutputBuilder _output) {
+  void operator []=(String key, Object value) {
+    _output.assets.addEncodedAsset(
+      MetadataAsset(key: key, value: value).encode(),
+      routing: const ToBuildHooks(),
+    );
+  }
+
+  void addAll(Map<String, Object> metadata) {
+    for (final MapEntry(:key, :value) in metadata.entries) {
+      this[key] = value;
+    }
+  }
+}
+
+/// The destination for assets output in a build hook.
+sealed class AssetRouting {
+  const AssetRouting();
+}
+
+/// Assets with this routing will be sent to the SDK to be bundled with the app.
+final class ToAppBundle extends AssetRouting {
+  const ToAppBundle();
+}
+
+/// Assets with this routing will be sent to build hooks.
+///
+/// The assets are only available for build hooks of packages that have a direct
+/// dependency on the package emitting the asset with this routing.
+///
+/// The assets will not be bundled in the final application unless also added
+/// with [ToAppBundle]. Prefer bundling the asset in the sending hook, otherwise
+/// multiple receivers might try to bundle the asset leading to duplicate assets
+/// in the app bundle.
+///
+/// The receiver will know about sender package (it must be a direct
+/// dependency), the sender does not know about the receiver. Hence this routing
+/// is a broadcast with 0-N receivers.
+final class ToBuildHooks extends AssetRouting {
+  const ToBuildHooks();
+}
+
+/// Assets with this routing will be sent to the link hook of [packageName].
+///
+/// The assets are only available to the link hook of [packageName].
+///
+/// The assets will not be bundled in the final application unless added with
+/// [ToAppBundle] in the link hook of [packageName].
+///
+/// The receiver will not know about the sender package. The sender knows about
+/// the receiver package. Hence, the receiver must be specified and there is
+/// exactly one receiver.
+final class ToLinkHook extends AssetRouting {
+  final String packageName;
+
+  const ToLinkHook(this.packageName);
+}
+
 extension type EncodedAssetBuildOutputBuilder._(BuildOutputBuilder _output) {
   /// Adds [EncodedAsset]s produced by this build.
   ///
-  /// If the [linkInPackage] argument is specified, the asset will not be
-  /// bundled during the build step, but sent as input to the link hook of the
-  /// specified package, where it can be further processed and possibly bundled.
+  /// The asset is routed according to [routing].
   ///
   /// Note to hook writers. Prefer using the `.add` method on the extension for
   /// the specific asset type being added:
@@ -444,28 +557,34 @@ extension type EncodedAssetBuildOutputBuilder._(BuildOutputBuilder _output) {
   ///   });
   /// }
   /// ```
-  void addEncodedAsset(EncodedAsset asset, {String? linkInPackage}) {
-    if (linkInPackage != null) {
-      final assetsForLinking = _syntax.assetsForLinking ?? {};
-      assetsForLinking[linkInPackage] ??= [];
-      assetsForLinking[linkInPackage]!.add(
-        syntax.Asset.fromJson(asset.toJson()),
-      );
-      _syntax.assetsForLinking = assetsForLinking;
-      _syntax.assetsForLinkingOld = assetsForLinking;
-    } else {
-      final assets = _syntax.assets ?? [];
-      assets.add(syntax.Asset.fromJson(asset.toJson()));
-      _syntax.assets = assets;
+  void addEncodedAsset(
+    EncodedAsset asset, {
+    AssetRouting routing = const ToAppBundle(),
+  }) {
+    switch (routing) {
+      case ToAppBundle():
+        final assets = _syntax.assets ?? [];
+        assets.add(syntax.Asset.fromJson(asset.toJson()));
+        _syntax.assets = assets;
+      case ToBuildHooks():
+        final assets = _syntax.assetsForBuild ?? [];
+        assets.add(syntax.Asset.fromJson(asset.toJson()));
+        _syntax.assetsForBuild = assets;
+      case ToLinkHook():
+        final packageName = routing.packageName;
+        final assetsForLinking = _syntax.assetsForLinking ?? {};
+        assetsForLinking[packageName] ??= [];
+        assetsForLinking[packageName]!.add(
+          syntax.Asset.fromJson(asset.toJson()),
+        );
+        _syntax.assetsForLinking = assetsForLinking;
+        _syntax.assetsForLinkingOld = assetsForLinking;
     }
   }
 
   /// Adds [EncodedAsset]s produced by this build.
   ///
-  /// If the [linkInPackage] argument is specified, the assets will not be
-  /// bundled during the build step, but sent as input to the link hook of the
-  /// specified package, where they can be further processed and possibly
-  /// bundled.
+  /// The asset is routed according to [routing].
   ///
   /// Note to hook writers. Prefer using the `.addAll` method on the extension
   /// for the specific asset type being added:
@@ -480,23 +599,31 @@ extension type EncodedAssetBuildOutputBuilder._(BuildOutputBuilder _output) {
   /// ```
   void addEncodedAssets(
     Iterable<EncodedAsset> assets, {
-    String? linkInPackage,
+    AssetRouting routing = const ToAppBundle(),
   }) {
-    if (linkInPackage != null) {
-      final assetsForLinking =
-          _syntax.assetsForLinking ?? _syntax.assetsForLinkingOld ?? {};
-      final list = assetsForLinking[linkInPackage] ??= [];
-      for (final asset in assets) {
-        list.add(syntax.Asset.fromJson(asset.toJson()));
-      }
-      _syntax.assetsForLinking = assetsForLinking;
-      _syntax.assetsForLinkingOld = assetsForLinking;
-    } else {
-      final list = _syntax.assets ?? [];
-      for (final asset in assets) {
-        list.add(syntax.Asset.fromJson(asset.toJson()));
-      }
-      _syntax.assets = list;
+    switch (routing) {
+      case ToAppBundle():
+        final list = _syntax.assets ?? [];
+        for (final asset in assets) {
+          list.add(syntax.Asset.fromJson(asset.toJson()));
+        }
+        _syntax.assets = list;
+      case ToBuildHooks():
+        final list = _syntax.assetsForBuild ?? [];
+        for (final asset in assets) {
+          list.add(syntax.Asset.fromJson(asset.toJson()));
+        }
+        _syntax.assetsForBuild = list;
+      case ToLinkHook():
+        final linkInPackage = routing.packageName;
+        final assetsForLinking =
+            _syntax.assetsForLinking ?? _syntax.assetsForLinkingOld ?? {};
+        final list = assetsForLinking[linkInPackage] ??= [];
+        for (final asset in assets) {
+          list.add(syntax.Asset.fromJson(asset.toJson()));
+        }
+        _syntax.assetsForLinking = assetsForLinking;
+        _syntax.assetsForLinkingOld = assetsForLinking;
     }
   }
 
diff --git a/pkgs/native_assets_cli/lib/src/data_assets/config.dart b/pkgs/native_assets_cli/lib/src/data_assets/config.dart
index 86b8632d6b..978ceba0c3 100644
--- a/pkgs/native_assets_cli/lib/src/data_assets/config.dart
+++ b/pkgs/native_assets_cli/lib/src/data_assets/config.dart
@@ -114,16 +114,17 @@ extension DataAssetBuildOutputBuilder on EncodedAssetBuildOutputBuilder {
 extension type DataAssetBuildOutputBuilderAdd._(
   EncodedAssetBuildOutputBuilder _output
 ) {
-  /// Adds the given [asset] to the hook output (or send to [linkInPackage]
-  /// for linking if provided).
-  void add(DataAsset asset, {String? linkInPackage}) =>
-      _output.addEncodedAsset(asset.encode(), linkInPackage: linkInPackage);
-
-  /// Adds the given [assets] to the hook output (or send to [linkInPackage]
-  /// for linking if provided).
-  void addAll(Iterable<DataAsset> assets, {String? linkInPackage}) {
+  /// Adds the given [asset] to the hook output with [routing].
+  void add(DataAsset asset, {AssetRouting routing = const ToAppBundle()}) =>
+      _output.addEncodedAsset(asset.encode(), routing: routing);
+
+  /// Adds the given [assets] to the hook output with [routing].
+  void addAll(
+    Iterable<DataAsset> assets, {
+    AssetRouting routing = const ToAppBundle(),
+  }) {
     for (final asset in assets) {
-      add(asset, linkInPackage: linkInPackage);
+      add(asset, routing: routing);
     }
   }
 }
diff --git a/pkgs/native_assets_cli/lib/src/data_assets/validation.dart b/pkgs/native_assets_cli/lib/src/data_assets/validation.dart
index 95f60e1ab3..15b9fbb9ed 100644
--- a/pkgs/native_assets_cli/lib/src/data_assets/validation.dart
+++ b/pkgs/native_assets_cli/lib/src/data_assets/validation.dart
@@ -8,11 +8,18 @@ import '../../data_assets_builder.dart';
 import 'syntax.g.dart' as syntax;
 
 Future<ValidationErrors> validateDataAssetBuildInput(BuildInput input) async =>
-    const [];
+    [
+      ..._validateHookInput([
+        for (final assets in input.assets.encodedAssets.values) ...assets,
+      ]),
+    ];
 
-Future<ValidationErrors> validateDataAssetLinkInput(LinkInput input) async {
+Future<ValidationErrors> validateDataAssetLinkInput(LinkInput input) async =>
+    _validateHookInput(input.assets.encodedAssets);
+
+List<String> _validateHookInput(List<EncodedAsset> assets) {
   final errors = <String>[];
-  for (final asset in input.assets.encodedAssets) {
+  for (final asset in assets) {
     final syntaxErrors = _validateDataAssetSyntax(asset);
     if (!asset.isDataAsset) continue;
     if (syntaxErrors.isNotEmpty) {
@@ -35,6 +42,7 @@ Future<ValidationErrors> validateDataAssetBuildOutput(
   BuildOutput output,
 ) => _validateDataAssetBuildOrLinkOutput(input, [
   ...output.assets.encodedAssets,
+  ...output.assets.encodedAssetsForBuild,
   for (final assetList in output.assets.encodedAssetsForLinking.values)
     ...assetList,
 ], true);
diff --git a/pkgs/native_assets_cli/lib/src/metadata.dart b/pkgs/native_assets_cli/lib/src/metadata.dart
index 19255ead3f..48d94899d5 100644
--- a/pkgs/native_assets_cli/lib/src/metadata.dart
+++ b/pkgs/native_assets_cli/lib/src/metadata.dart
@@ -4,6 +4,10 @@
 
 import 'package:collection/collection.dart';
 
+import 'config.dart';
+import 'encoded_asset.dart';
+import 'hooks/syntax.g.dart' as syntax;
+
 class Metadata {
   final UnmodifiableMapView<String, Object?> metadata;
 
@@ -30,3 +34,51 @@ class Metadata {
   @override
   String toString() => 'Metadata(${toJson()})';
 }
+
+/// An asset that contains metadata.
+///
+/// Should only be used with [ToLinkHook] and [ToBuildHooks].
+//
+// Note: not exported to public API. The public API only contains a way to read
+// and write metadata, it doesn't expose the underlying mechanism.
+final class MetadataAsset {
+  final String key;
+
+  final Object? value;
+
+  MetadataAsset({required this.key, required this.value});
+
+  factory MetadataAsset.fromEncoded(EncodedAsset asset) {
+    assert(asset.type == _type);
+    final syntaxNode = syntax.MetadataAssetEncoding.fromJson(
+      asset.encoding,
+      path: asset.jsonPath ?? [],
+    );
+    return MetadataAsset(key: syntaxNode.key, value: syntaxNode.value);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other is! MetadataAsset) {
+      return false;
+    }
+    return other.key == key &&
+        const DeepCollectionEquality().equals(value, other.value);
+  }
+
+  @override
+  int get hashCode =>
+      Object.hash(key, const DeepCollectionEquality().hash(value));
+
+  EncodedAsset encode() => EncodedAsset(_type, {'key': key, 'value': value});
+
+  @override
+  String toString() => 'MetadataAsset(${encode().encoding})';
+
+  static const _type = 'hooks/metadata';
+}
+
+extension EncodedMetadataAsset on EncodedAsset {
+  bool get isMetadataAsset => type == MetadataAsset._type;
+  MetadataAsset get asMetadataAsset => MetadataAsset.fromEncoded(this);
+}
diff --git a/pkgs/native_assets_cli/pubspec.yaml b/pkgs/native_assets_cli/pubspec.yaml
index e42a689b1e..200eb7916e 100644
--- a/pkgs/native_assets_cli/pubspec.yaml
+++ b/pkgs/native_assets_cli/pubspec.yaml
@@ -3,7 +3,7 @@ description: >-
   A library that contains the argument and file formats for implementing a
   native assets CLI.
 
-version: 0.13.1-wip
+version: 0.14.0-wip
 repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli
 
 publish_to: none
diff --git a/pkgs/native_assets_cli/test/build_input_test.dart b/pkgs/native_assets_cli/test/build_input_test.dart
index 78b91bec2f..6afde591d9 100644
--- a/pkgs/native_assets_cli/test/build_input_test.dart
+++ b/pkgs/native_assets_cli/test/build_input_test.dart
@@ -20,6 +20,7 @@ void main() async {
   late String packageName;
   late Uri packageRootUri;
   late Map<String, Metadata> metadata;
+  late Map<String, List<EncodedAsset>> assets;
   late Map<String, Object?> inputJson;
 
   setUp(() async {
@@ -36,8 +37,33 @@ void main() async {
       }),
       'foo': Metadata({'key': 321}),
     };
+    assets = {
+      'my_package': [
+        for (int i = 0; i < 3; i++)
+          EncodedAsset('my-asset-type', {'a-$i': 'v-$i'}),
+      ],
+    };
 
     inputJson = {
+      'assets': {
+        'my_package': [
+          {
+            'a-0': 'v-0',
+            'encoding': {'a-0': 'v-0'},
+            'type': 'my-asset-type',
+          },
+          {
+            'a-1': 'v-1',
+            'encoding': {'a-1': 'v-1'},
+            'type': 'my-asset-type',
+          },
+          {
+            'a-2': 'v-2',
+            'encoding': {'a-2': 'v-2'},
+            'type': 'my-asset-type',
+          },
+        ],
+      },
       'config': {
         'build_asset_types': ['my-asset-type'],
         'linking_enabled': false,
@@ -70,7 +96,7 @@ void main() async {
           )
           ..config.addBuildAssetTypes(['my-asset-type'])
           ..config.setupBuild(linkingEnabled: false)
-          ..setupBuildInput(metadata: metadata);
+          ..setupBuildInput(metadata: metadata, assets: assets);
     final input = BuildInput(inputBuilder.json);
 
     expect(input.json, inputJson);
@@ -87,7 +113,8 @@ void main() async {
     expect(input.config.buildAssetTypes, ['my-asset-type']);
 
     expect(input.config.linkingEnabled, false);
-    expect(input.metadata, metadata);
+    expect(input.metadataOld, metadata);
+    expect(input.assets.encodedAssets, assets);
   });
 
   group('BuildInput format issues', () {
diff --git a/pkgs/native_assets_cli/test/build_output_test.dart b/pkgs/native_assets_cli/test/build_output_test.dart
index 0c2bf54c74..9708a178e0 100644
--- a/pkgs/native_assets_cli/test/build_output_test.dart
+++ b/pkgs/native_assets_cli/test/build_output_test.dart
@@ -21,20 +21,28 @@ void main() {
     final builder = BuildOutputBuilder();
     final after = DateTime.now().roundDownToSeconds();
 
-    builder.addDependency(uris.take(1).single);
+    builder.addDependency(uris.first);
     builder.addDependencies(uris.skip(1).toList());
     builder.addMetadatum(metadata0.keys.single, metadata0.values.single);
     builder.addMetadata(metadata1);
 
-    builder.assets.addEncodedAsset(assets.take(1).single);
+    builder.assets.addEncodedAsset(assets.first);
+    builder.assets.addEncodedAsset(
+      assets.skip(2).first,
+      routing: const ToBuildHooks(),
+    );
     builder.assets.addEncodedAsset(
       assets.skip(1).first,
-      linkInPackage: 'package:linker1',
+      routing: const ToLinkHook('package:linker1'),
     );
     builder.assets.addEncodedAssets(assets.skip(2).take(2).toList());
+    builder.assets.addEncodedAssets(
+      assets.take(2),
+      routing: const ToBuildHooks(),
+    );
     builder.assets.addEncodedAssets(
       assets.skip(4).toList(),
-      linkInPackage: 'package:linker2',
+      routing: const ToLinkHook('package:linker2'),
     );
 
     final input = BuildOutput(builder.json);
@@ -46,7 +54,7 @@ void main() {
     );
 
     // The JSON format of the build output.
-    <String, Object?>{
+    final expectedJson = <String, Object?>{
       'version': '1.9.0',
       'dependencies': ['path0', 'path1', 'path2'],
       'metadata': {
@@ -66,6 +74,23 @@ void main() {
           'type': 'my-asset-type',
         },
       ],
+      'assets_for_build': [
+        {
+          'a-2': 'v-2',
+          'encoding': {'a-2': 'v-2'},
+          'type': 'my-asset-type',
+        },
+        {
+          'a-0': 'v-0',
+          'encoding': {'a-0': 'v-0'},
+          'type': 'my-asset-type',
+        },
+        {
+          'a-1': 'v-1',
+          'encoding': {'a-1': 'v-1'},
+          'type': 'my-asset-type',
+        },
+      ],
       'assetsForLinking': {
         'package:linker1': [
           {
@@ -86,9 +111,10 @@ void main() {
         ],
         'package:linker2': <Object?>[],
       },
-    }.forEach((k, v) {
-      expect(input.json[k], equals(v));
-    });
+      'timestamp': input.timestamp.toString(),
+    };
+
+    expect(input.json, equals(expectedJson));
   });
 
   for (final version in ['9001.0.0', '0.0.1']) {
diff --git a/pkgs/native_assets_cli/test/model/metadata_asset_test.dart b/pkgs/native_assets_cli/test/model/metadata_asset_test.dart
new file mode 100644
index 0000000000..247dcaaae2
--- /dev/null
+++ b/pkgs/native_assets_cli/test/model/metadata_asset_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:native_assets_cli/src/encoded_asset.dart';
+import 'package:native_assets_cli/src/metadata.dart';
+import 'package:test/test.dart';
+
+void main() async {
+  final jsonEncoding = {
+    'encoding': {
+      'key': 'some_key',
+      'value': {'foo': 'bar'},
+    },
+    'key': 'some_key',
+    'type': 'hooks/metadata',
+    'value': {'foo': 'bar'},
+  };
+  final metadataAsset = MetadataAsset(key: 'some_key', value: {'foo': 'bar'});
+
+  test('CodeAsset toJson', () {
+    expect(metadataAsset.encode().toJson(), equals(jsonEncoding));
+  });
+
+  test('CodeAsset fromJson', () {
+    final encodedAsset = EncodedAsset.fromJson(jsonEncoding);
+    expect(encodedAsset.isMetadataAsset, isTrue);
+    expect(MetadataAsset.fromEncoded(encodedAsset), metadataAsset);
+  });
+}
diff --git a/pkgs/native_assets_cli/test/validation_test.dart b/pkgs/native_assets_cli/test/validation_test.dart
index 810bc91216..130f8c4b92 100644
--- a/pkgs/native_assets_cli/test/validation_test.dart
+++ b/pkgs/native_assets_cli/test/validation_test.dart
@@ -50,7 +50,7 @@ void main() {
     await assetFile.writeAsBytes([1, 2, 3]);
     outputBuilder.assets.addEncodedAsset(
       EncodedAsset('my-asset-type', {}),
-      linkInPackage: 'bar',
+      routing: const ToLinkHook('bar'),
     );
     final errors = await validateBuildOutput(
       input,
diff --git a/pkgs/native_toolchain_c/CHANGELOG.md b/pkgs/native_toolchain_c/CHANGELOG.md
index 93ee66ce10..222615ffdf 100644
--- a/pkgs/native_toolchain_c/CHANGELOG.md
+++ b/pkgs/native_toolchain_c/CHANGELOG.md
@@ -1,6 +1,7 @@
-## 0.10.1-wip
+## 0.11.0-wip
 
-- Nothing yet.
+- Replace `linkInPackage` with `Routing`.
+- Bump `package:native_assets_cli` to 0.14.0.
 
 ## 0.10.0
 
diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart
index 561210bc37..d18d9e67d9 100644
--- a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart
+++ b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart
@@ -117,7 +117,7 @@ class CBuilder extends CTool implements Builder {
     required BuildInput input,
     required BuildOutputBuilder output,
     required Logger? logger,
-    String? linkInPackage,
+    List<AssetRouting> routing = const [ToAppBundle()],
   }) async {
     if (!input.config.buildCodeAssets) {
       logger?.info(
@@ -127,8 +127,8 @@ class CBuilder extends CTool implements Builder {
       return;
     }
     assert(
-      input.config.linkingEnabled || linkInPackage == null,
-      'linkInPackage can only be provided if input.config.linkingEnabled'
+      input.config.linkingEnabled || routing.whereType<ToLinkHook>().isEmpty,
+      'ToLinker can only be provided if input.config.linkingEnabled'
       ' is true.',
     );
     final outDir = input.outputDirectory;
@@ -200,17 +200,19 @@ class CBuilder extends CTool implements Builder {
     await task.run();
 
     if (assetName != null) {
-      output.assets.code.add(
-        CodeAsset(
-          package: input.packageName,
-          name: assetName!,
-          file: libUri,
-          linkMode: linkMode,
-          os: input.config.code.targetOS,
-          architecture: input.config.code.targetArchitecture,
-        ),
-        linkInPackage: linkInPackage,
-      );
+      for (final route in routing) {
+        output.assets.code.add(
+          CodeAsset(
+            package: input.packageName,
+            name: assetName!,
+            file: libUri,
+            linkMode: linkMode,
+            os: input.config.code.targetOS,
+            architecture: input.config.code.targetArchitecture,
+          ),
+          routing: route,
+        );
+      }
     }
 
     final includeFiles =
diff --git a/pkgs/native_toolchain_c/pubspec.yaml b/pkgs/native_toolchain_c/pubspec.yaml
index 845c87a78b..5148fd09d4 100644
--- a/pkgs/native_toolchain_c/pubspec.yaml
+++ b/pkgs/native_toolchain_c/pubspec.yaml
@@ -1,7 +1,7 @@
 name: native_toolchain_c
 description: >-
   A library to invoke the native C compiler installed on the host machine.
-version: 0.10.1-wip
+version: 0.11.0-wip
 repository: https://github.com/dart-lang/native/tree/main/pkgs/native_toolchain_c
 
 publish_to: none