diff --git a/pkgs/native_assets_builder/CHANGELOG.md b/pkgs/native_assets_builder/CHANGELOG.md
index 8909f3e54..50a1b629f 100644
--- a/pkgs/native_assets_builder/CHANGELOG.md
+++ b/pkgs/native_assets_builder/CHANGELOG.md
@@ -10,7 +10,10 @@
   dart-lang/native repository to make it clear those are not intended to be used
   by end-users.
 - Remove link-dry-run concept as it's unused by Flutter Tools & Dart SDK
-- Bump `native_assets_cli` to `0.9.0`
+- Bump `native_assets_cli` to `0.9.0`.
+- **Breaking change**: Remove asset-type specific logic from `package:native_assets_builder`.
+  Bundling tools have to now supply `supportedAssetTypes` and corresponding
+  validation routines.
 
 ## 0.8.3
 
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 c2e4df213..c17774f3b 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
@@ -23,6 +23,25 @@ import 'build_planner.dart';
 
 typedef DependencyMetadata = Map<String, Metadata>;
 
+typedef _HookValidator = Future<ValidationErrors> Function(
+    HookConfig config, HookOutputImpl output);
+
+// A callback that validates the output of a `hook/link.dart` invocation is
+// valid (it may valid asset-type specific information).
+typedef BuildValidator = Future<ValidationErrors> Function(
+    BuildConfig config, BuildOutput outup);
+
+// A callback that validates the output of a `hook/link.dart` invocation is
+// valid (it may valid asset-type specific information).
+typedef LinkValidator = Future<ValidationErrors> Function(
+    LinkConfig config, LinkOutput output);
+
+// A callback that validates assets emitted across all packages are valid / can
+// be used together (it may valid asset-type specific information - e.g. that
+// there are no classes in shared library filenames).
+typedef ApplicationAssetValidator = Future<ValidationErrors> Function(
+    List<EncodedAsset> assets);
+
 /// The programmatic API to be used by Dart launchers to invoke native builds.
 ///
 /// These methods are invoked by launchers such as dartdev (for `dart run`)
@@ -58,6 +77,8 @@ class NativeAssetsBuildRunner {
     required Target target,
     required Uri workingDirectory,
     required BuildMode buildMode,
+    required BuildValidator buildValidator,
+    required ApplicationAssetValidator applicationAssetValidator,
     CCompilerConfig? cCompilerConfig,
     IOSSdk? targetIOSSdk,
     int? targetIOSVersion,
@@ -66,10 +87,14 @@ class NativeAssetsBuildRunner {
     required bool includeParentEnvironment,
     PackageLayout? packageLayout,
     String? runPackageName,
-    Iterable<String>? supportedAssetTypes,
+    required Iterable<String> supportedAssetTypes,
     required bool linkingEnabled,
   }) async =>
       _run(
+        validator: (HookConfig config, HookOutputImpl output) =>
+            buildValidator(config as BuildConfig, output as BuildOutput),
+        applicationAssetValidator: (assets) async =>
+            linkingEnabled ? [] : applicationAssetValidator(assets),
         hook: Hook.build,
         linkModePreference: linkModePreference,
         target: target,
@@ -103,6 +128,8 @@ class NativeAssetsBuildRunner {
     required Target target,
     required Uri workingDirectory,
     required BuildMode buildMode,
+    required LinkValidator linkValidator,
+    required ApplicationAssetValidator applicationAssetValidator,
     CCompilerConfig? cCompilerConfig,
     IOSSdk? targetIOSSdk,
     int? targetIOSVersion,
@@ -112,10 +139,14 @@ class NativeAssetsBuildRunner {
     PackageLayout? packageLayout,
     Uri? resourceIdentifiers,
     String? runPackageName,
-    Iterable<String>? supportedAssetTypes,
+    required Iterable<String> supportedAssetTypes,
     required BuildResult buildResult,
   }) async =>
       _run(
+        validator: (HookConfig config, HookOutputImpl output) =>
+            linkValidator(config as LinkConfig, output as LinkOutput),
+        applicationAssetValidator: (assets) async =>
+            applicationAssetValidator(assets),
         hook: Hook.link,
         linkModePreference: linkModePreference,
         target: target,
@@ -141,6 +172,8 @@ class NativeAssetsBuildRunner {
     required Target target,
     required Uri workingDirectory,
     required BuildMode buildMode,
+    required _HookValidator validator,
+    required ApplicationAssetValidator applicationAssetValidator,
     CCompilerConfig? cCompilerConfig,
     IOSSdk? targetIOSSdk,
     int? targetIOSVersion,
@@ -150,7 +183,7 @@ class NativeAssetsBuildRunner {
     PackageLayout? packageLayout,
     Uri? resourceIdentifiers,
     String? runPackageName,
-    Iterable<String>? supportedAssetTypes,
+    required Iterable<String> supportedAssetTypes,
     BuildResult? buildResult,
     bool? linkingEnabled,
   }) async {
@@ -236,6 +269,7 @@ class NativeAssetsBuildRunner {
       final (hookOutput, packageSuccess) = await _runHookForPackageCached(
         hook,
         config,
+        validator,
         packageLayout.packageConfigUri,
         workingDirectory,
         includeParentEnvironment,
@@ -246,17 +280,14 @@ class NativeAssetsBuildRunner {
       metadata[config.packageName] = hookOutput.metadata;
     }
 
-    // Note the caller will need to check whether there are no duplicates
-    // between the build and link hook.
-    final validateResult = validateNoDuplicateDylibs(hookResult.assets);
-    if (validateResult.isNotEmpty) {
-      for (final error in validateResult) {
-        logger.severe(error);
-      }
-      hookResult = hookResult.copyAdd(HookOutputImpl(), false);
-    }
+    final errors = await applicationAssetValidator(hookResult.encodedAssets);
+    if (errors.isEmpty) return hookResult;
 
-    return hookResult;
+    logger.severe('Application asset verification failed:');
+    for (final error in errors) {
+      logger.severe('- $error');
+    }
+    return HookResult.failure();
   }
 
   static Future<HookConfigImpl> _cliConfig(
@@ -272,7 +303,7 @@ class NativeAssetsBuildRunner {
     int? targetAndroidNdkApi,
     int? targetIOSVersion,
     int? targetMacOSVersion,
-    Iterable<String>? supportedAssetTypes,
+    Iterable<String> supportedAssetTypes,
     Hook hook,
     Uri? resourceIdentifiers,
     BuildResult? buildResult,
@@ -331,7 +362,7 @@ class NativeAssetsBuildRunner {
         cCompiler: cCompilerConfig,
         targetAndroidNdkApi: targetAndroidNdkApi,
         recordedUsagesFile: resourcesFile?.uri,
-        assets: buildResult!.assetsForLinking[package.name] ?? [],
+        encodedAssets: buildResult!.encodedAssetsForLinking[package.name] ?? [],
         supportedAssetTypes: supportedAssetTypes,
         linkModePreference: linkModePreference,
       );
@@ -370,9 +401,10 @@ class NativeAssetsBuildRunner {
     required Uri workingDirectory,
     required bool includeParentEnvironment,
     required bool linkingEnabled,
+    required BuildValidator buildValidator,
     PackageLayout? packageLayout,
     String? runPackageName,
-    Iterable<String>? supportedAssetTypes,
+    required Iterable<String> supportedAssetTypes,
   }) async {
     const hook = Hook.build;
     packageLayout ??= await PackageLayout.fromRootPackageRoot(workingDirectory);
@@ -413,7 +445,7 @@ class NativeAssetsBuildRunner {
         continue;
       }
       // TODO(https://github.com/dart-lang/native/issues/1321): Should dry runs be cached?
-      var (buildOutput, packageSuccess) = await runUnderDirectoriesLock(
+      final (buildOutput, packageSuccess) = await runUnderDirectoriesLock(
         [
           Directory.fromUri(config.outputDirectoryShared.parent),
           Directory.fromUri(config.outputDirectory.parent),
@@ -423,6 +455,8 @@ class NativeAssetsBuildRunner {
         () => _runHookForPackage(
           hook,
           config,
+          (HookConfig config, HookOutputImpl output) =>
+              buildValidator(config as BuildConfig, output as BuildOutput),
           packageConfigUri,
           workingDirectory,
           includeParentEnvironment,
@@ -431,38 +465,15 @@ class NativeAssetsBuildRunner {
           packageLayout!,
         ),
       );
-      buildOutput = _expandArchsCodeAssets(buildOutput);
       hookResult = hookResult.copyAdd(buildOutput, packageSuccess);
     }
     return hookResult;
   }
 
-  HookOutputImpl _expandArchsCodeAssets(HookOutputImpl buildOutput) {
-    final assets = <Asset>[];
-    for (final asset in buildOutput.assets) {
-      switch (asset) {
-        case CodeAsset _:
-          if (asset.architecture != null) {
-            // Backwards compatibility, if an architecture is provided use it.
-            assets.add(asset);
-          } else {
-            // Dry run does not report architecture. Dart VM branches on OS
-            // and Target when looking up assets, so populate assets for all
-            // architectures.
-            for (final architecture in asset.os.architectures) {
-              assets.add(asset.copyWith(architecture: architecture));
-            }
-          }
-        case DataAsset _:
-          assets.add(asset);
-      }
-    }
-    return buildOutput.copyWith(assets: assets);
-  }
-
   Future<_PackageBuildRecord> _runHookForPackageCached(
     Hook hook,
     HookConfigImpl config,
+    _HookValidator validator,
     Uri packageConfigUri,
     Uri workingDirectory,
     bool includeParentEnvironment,
@@ -497,25 +508,13 @@ class NativeAssetsBuildRunner {
           final lastBuilt = hookOutput.timestamp.roundDownToSeconds();
           final dependenciesLastChange =
               await hookOutput.dependenciesModel.lastModified();
-          late final DateTime assetsLastChange;
-          if (hook == Hook.link) {
-            assetsLastChange = await (config as LinkConfigImpl)
-                .assets
-                .map((a) => a.file)
-                .whereType<Uri>()
-                .map((u) => u.fileSystemEntity)
-                .lastModified();
-          }
           if (lastBuilt.isAfter(dependenciesLastChange) &&
-              lastBuilt.isAfter(hookLastSourceChange) &&
-              (hook == Hook.build || lastBuilt.isAfter(assetsLastChange))) {
+              lastBuilt.isAfter(hookLastSourceChange)) {
             logger.info(
               [
                 'Skipping ${hook.name} for ${config.packageName} in $outDir.',
                 'Last build on $lastBuilt.',
                 'Last dependencies change on $dependenciesLastChange.',
-                if (hook == Hook.link)
-                  'Last assets for linking change on $assetsLastChange.',
                 'Last hook change on $hookLastSourceChange.',
               ].join(' '),
             );
@@ -528,6 +527,7 @@ class NativeAssetsBuildRunner {
         return await _runHookForPackage(
           hook,
           config,
+          validator,
           packageConfigUri,
           workingDirectory,
           includeParentEnvironment,
@@ -542,6 +542,7 @@ class NativeAssetsBuildRunner {
   Future<_PackageBuildRecord> _runHookForPackage(
     Hook hook,
     HookConfigImpl config,
+    _HookValidator validator,
     Uri packageConfigUri,
     Uri workingDirectory,
     bool includeParentEnvironment,
@@ -601,16 +602,12 @@ ${result.stdout}
       final output = HookOutputImpl.readFromFile(file: config.outputFile) ??
           HookOutputImpl();
 
-      final validateResult = await validate(
-        config,
-        output,
-        packageLayout,
-      );
-      success &= validateResult.success;
-      if (!validateResult.success) {
+      final errors = await _validate(config, output, packageLayout, validator);
+      success &= errors.isEmpty;
+      if (errors.isNotEmpty) {
         logger.severe('package:${config.packageName}` has invalid output.');
       }
-      for (final error in validateResult.errors) {
+      for (final error in errors) {
         logger.severe('- $error');
       }
       return (output, success);
@@ -758,7 +755,7 @@ ${compileResult.stdout}
     required OS targetOS,
     required LinkModePreference linkMode,
     required Uri buildParentDir,
-    Iterable<String>? supportedAssetTypes,
+    required Iterable<String> supportedAssetTypes,
     required bool? linkingEnabled,
   }) async {
     const hook = Hook.build;
@@ -814,32 +811,33 @@ ${compileResult.stdout}
     };
   }
 
-  Future<ValidateResult> validate(
+  Future<ValidationErrors> _validate(
     HookConfigImpl config,
     HookOutputImpl output,
     PackageLayout packageLayout,
+    _HookValidator validator,
   ) async {
-    var (errors: errors, success: success) = config is BuildConfigImpl
-        ? await validateBuild(config, output)
-        : await validateLink(config as LinkConfigImpl, output);
+    final errors = config is BuildConfigImpl
+        ? await validateBuildOutput(config, output)
+        : await validateLinkOutput(config as LinkConfig, output);
+    errors.addAll(await validator(config, output));
 
     if (config is BuildConfigImpl) {
       final packagesWithLink =
           (await packageLayout.packagesWithAssets(Hook.link))
               .map((p) => p.name);
-      for (final targetPackage in output.assetsForLinking.keys) {
+      for (final targetPackage in output.encodedAssetsForLinking.keys) {
         if (!packagesWithLink.contains(targetPackage)) {
-          for (final asset in output.assetsForLinking[targetPackage]!) {
-            success &= false;
+          for (final asset in output.encodedAssetsForLinking[targetPackage]!) {
             errors.add(
-              'Asset "${asset.id}" is sent to package "$targetPackage" for'
+              'Asset "$asset" is sent to package "$targetPackage" for'
               ' linking, but that package does not have a link hook.',
             );
           }
         }
       }
     }
-    return (errors: errors, success: success);
+    return errors;
   }
 
   Future<(List<Package> plan, PackageGraph? dependencyGraph, bool success)>
@@ -878,9 +876,9 @@ ${compileResult.stdout}
         // Link hooks are skipped if no assets for linking are provided.
         buildPlan = [];
         final skipped = <String>[];
-        final assetsForLinking = buildResult?.assetsForLinking;
+        final encodedAssetsForLinking = buildResult?.encodedAssetsForLinking;
         for (final package in packagesWithHook) {
-          if (assetsForLinking![package.name]?.isNotEmpty ?? false) {
+          if (encodedAssetsForLinking![package.name]?.isNotEmpty ?? false) {
             buildPlan.add(package);
           } else {
             skipped.add(package.name);
diff --git a/pkgs/native_assets_builder/lib/src/model/build_dry_run_result.dart b/pkgs/native_assets_builder/lib/src/model/build_dry_run_result.dart
index 1379cf244..6a37448c5 100644
--- a/pkgs/native_assets_builder/lib/src/model/build_dry_run_result.dart
+++ b/pkgs/native_assets_builder/lib/src/model/build_dry_run_result.dart
@@ -10,7 +10,7 @@ import '../../native_assets_builder.dart';
 /// the dependency tree of the entry point application.
 abstract interface class BuildDryRunResult {
   /// The native assets produced by the hooks, which should be bundled.
-  List<Asset> get assets;
+  List<EncodedAsset> get encodedAssets;
 
   /// Whether all hooks completed without errors.
   ///
@@ -18,5 +18,5 @@ abstract interface class BuildDryRunResult {
   bool get success;
 
   /// The native assets produced by the hooks, which should be linked.
-  Map<String, List<Asset>> get assetsForLinking;
+  Map<String, List<EncodedAsset>> get encodedAssetsForLinking;
 }
diff --git a/pkgs/native_assets_builder/lib/src/model/build_result.dart b/pkgs/native_assets_builder/lib/src/model/build_result.dart
index b38680563..96b2aac2d 100644
--- a/pkgs/native_assets_builder/lib/src/model/build_result.dart
+++ b/pkgs/native_assets_builder/lib/src/model/build_result.dart
@@ -18,8 +18,8 @@ abstract class BuildResult {
   bool get success;
 
   /// The native assets produced by the hooks, which should be bundled.
-  List<Asset> get assets;
+  List<EncodedAsset> get encodedAssets;
 
   /// The native assets produced by the hooks, which should be linked.
-  Map<String, List<Asset>> get assetsForLinking;
+  Map<String, List<EncodedAsset>> get encodedAssetsForLinking;
 }
diff --git a/pkgs/native_assets_builder/lib/src/model/hook_result.dart b/pkgs/native_assets_builder/lib/src/model/hook_result.dart
index 3e0644991..52ec4d98f 100644
--- a/pkgs/native_assets_builder/lib/src/model/hook_result.dart
+++ b/pkgs/native_assets_builder/lib/src/model/hook_result.dart
@@ -10,13 +10,13 @@ import '../../native_assets_builder.dart';
 /// The result from a [NativeAssetsBuildRunner.build] or
 /// [NativeAssetsBuildRunner.link].
 final class HookResult implements BuildResult, BuildDryRunResult, LinkResult {
-  /// The native assets produced by the hooks, which should be bundled.
+  /// The native encodedAssets produced by the hooks, which should be bundled.
   @override
-  final List<Asset> assets;
+  final List<EncodedAsset> encodedAssets;
 
-  /// The assets produced by the hooks, which should be linked.
+  /// The encodedAssets produced by the hooks, which should be linked.
   @override
-  final Map<String, List<Asset>> assetsForLinking;
+  final Map<String, List<EncodedAsset>> encodedAssetsForLinking;
 
   /// The files used by the hooks.
   @override
@@ -29,21 +29,21 @@ final class HookResult implements BuildResult, BuildDryRunResult, LinkResult {
   final bool success;
 
   HookResult._({
-    required this.assets,
-    required this.assetsForLinking,
+    required this.encodedAssets,
+    required this.encodedAssetsForLinking,
     required this.dependencies,
     required this.success,
   });
 
   factory HookResult({
-    List<Asset>? assets,
-    Map<String, List<Asset>>? assetsForLinking,
+    List<EncodedAsset>? encodedAssets,
+    Map<String, List<EncodedAsset>>? encodedAssetsForLinking,
     List<Uri>? dependencies,
     bool success = true,
   }) =>
       HookResult._(
-        assets: assets ?? [],
-        assetsForLinking: assetsForLinking ?? {},
+        encodedAssets: encodedAssets ?? [],
+        encodedAssetsForLinking: encodedAssetsForLinking ?? {},
         dependencies: dependencies ?? [],
         success: success,
       );
@@ -51,28 +51,18 @@ final class HookResult implements BuildResult, BuildDryRunResult, LinkResult {
   factory HookResult.failure() => HookResult(success: false);
 
   HookResult copyAdd(HookOutputImpl hookOutput, bool hookSuccess) {
-    final mergedMaps = mergeMaps(
-      assetsForLinking,
-      hookOutput.assetsForLinking,
-      value: (assets1, assets2) {
-        final twoInOne = assets1.where((asset) => assets2.contains(asset));
-        final oneInTwo = assets2.where((asset) => assets1.contains(asset));
-        if (twoInOne.isNotEmpty || oneInTwo.isNotEmpty) {
-          throw ArgumentError(
-              'Found duplicate IDs, ${oneInTwo.map((e) => e.id).toList()}');
-        }
-        return [
-          ...assets1,
-          ...assets2,
-        ];
-      },
-    );
+    final mergedMaps =
+        mergeMaps(encodedAssetsForLinking, hookOutput.encodedAssetsForLinking,
+            value: (encodedAssets1, encodedAssets2) => [
+                  ...encodedAssets1,
+                  ...encodedAssets2,
+                ]);
     return HookResult(
-      assets: [
-        ...assets,
-        ...hookOutput.assets,
+      encodedAssets: [
+        ...encodedAssets,
+        ...hookOutput.encodedAssets,
       ],
-      assetsForLinking: mergedMaps,
+      encodedAssetsForLinking: mergedMaps,
       dependencies: [
         ...dependencies,
         ...hookOutput.dependencies,
@@ -82,8 +72,8 @@ final class HookResult implements BuildResult, BuildDryRunResult, LinkResult {
   }
 
   HookResult withSuccess(bool success) => HookResult(
-        assets: assets,
-        assetsForLinking: assetsForLinking,
+        encodedAssets: encodedAssets,
+        encodedAssetsForLinking: encodedAssetsForLinking,
         dependencies: dependencies,
         success: success,
       );
diff --git a/pkgs/native_assets_builder/lib/src/model/link_result.dart b/pkgs/native_assets_builder/lib/src/model/link_result.dart
index e3f6877f7..a717f9618 100644
--- a/pkgs/native_assets_builder/lib/src/model/link_result.dart
+++ b/pkgs/native_assets_builder/lib/src/model/link_result.dart
@@ -10,7 +10,7 @@ import '../build_runner/build_runner.dart';
 /// the dependency tree of the entry point application.
 abstract interface class LinkResult {
   /// The native assets produced by the hooks, which should be bundled.
-  List<Asset> get assets;
+  List<EncodedAsset> get encodedAssets;
 
   /// The files used by the hooks.
   List<Uri> get dependencies;
diff --git a/pkgs/native_assets_builder/test/build_runner/build_dependencies_test.dart b/pkgs/native_assets_builder/test/build_runner/build_dependencies_test.dart
index 20e5b9509..e5616093b 100644
--- a/pkgs/native_assets_builder/test/build_runner/build_dependencies_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/build_dependencies_test.dart
@@ -26,8 +26,15 @@ void main() async {
       // Trigger a build, should invoke build for libraries with native assets.
       {
         final logMessages = <String>[];
-        final result = await build(packageUri, logger, dartExecutable,
-            capturedLogs: logMessages);
+        final result = await build(
+          packageUri,
+          logger,
+          dartExecutable,
+          capturedLogs: logMessages,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
+        );
         expect(
           logMessages.join('\n'),
           stringContainsInOrder(
@@ -39,7 +46,7 @@ void main() async {
             ],
           ),
         );
-        expect(result.assets.length, 2);
+        expect(result.encodedAssets.length, 2);
         expect(
           result.dependencies,
           [
diff --git a/pkgs/native_assets_builder/test/build_runner/build_planner_test.dart b/pkgs/native_assets_builder/test/build_runner/build_planner_test.dart
index b4a03a30d..4eddad787 100644
--- a/pkgs/native_assets_builder/test/build_runner/build_planner_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/build_planner_test.dart
@@ -6,7 +6,6 @@ import 'dart:io';
 
 import 'package:native_assets_builder/native_assets_builder.dart';
 import 'package:native_assets_builder/src/build_runner/build_planner.dart';
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
diff --git a/pkgs/native_assets_builder/test/build_runner/build_runner_asset_id_test.dart b/pkgs/native_assets_builder/test/build_runner/build_runner_asset_id_test.dart
index fdf8273ac..5f75a90ea 100644
--- a/pkgs/native_assets_builder/test/build_runner/build_runner_asset_id_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/build_runner_asset_id_test.dart
@@ -27,6 +27,9 @@ void main() async {
           packageUri,
           createCapturingLogger(logMessages, level: Level.SEVERE),
           dartExecutable,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
         );
         final fullLog = logMessages.join('\n');
         expect(result.success, false);
@@ -59,6 +62,9 @@ void main() async {
           packageUri,
           logger,
           dartExecutable,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
         );
         expect(result.success, true);
       }
diff --git a/pkgs/native_assets_builder/test/build_runner/build_runner_build_dry_run_test.dart b/pkgs/native_assets_builder/test/build_runner/build_runner_build_dry_run_test.dart
index d91cb8961..9e3fa7c4f 100644
--- a/pkgs/native_assets_builder/test/build_runner/build_runner_build_dry_run_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/build_runner_build_dry_run_test.dart
@@ -5,7 +5,6 @@
 import 'dart:io';
 
 import 'package:file_testing/file_testing.dart';
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
@@ -29,29 +28,39 @@ void main() async {
         logger,
         dartExecutable,
         linkingEnabled: false,
+        supportedAssetTypes: [CodeAsset.type],
+        buildValidator: validateCodeAssetBuildOutput,
       );
-      final dryRunAssets = dryRunResult.assets.toList();
-      final result = await build(
+      final buildResult = await build(
         packageUri,
         logger,
         dartExecutable,
+        supportedAssetTypes: [CodeAsset.type],
+        buildValidator: validateCodeAssetBuildOutput,
+        applicationAssetValidator: validateCodeAssetsInApplication,
       );
 
-      // Every OS has more than one architecture.
-      expect(dryRunAssets.length, greaterThan(result.assets.length));
-      for (var i = 0; i < dryRunAssets.length; i++) {
-        final dryRunAsset = dryRunAssets[i];
-        final buildAsset = result.assets[0];
-        expect(dryRunAsset.id, buildAsset.id);
-        // The build runner expands CodeAssets to all architectures.
-        expect(buildAsset.file, isNotNull);
-        if (dryRunAsset is CodeAsset && buildAsset is CodeAsset) {
-          expect(dryRunAsset.architecture, isNotNull);
-          expect(buildAsset.architecture, isNotNull);
-          expect(dryRunAsset.os, buildAsset.os);
-          expect(dryRunAsset.linkMode, buildAsset.linkMode);
-        }
-      }
+      expect(dryRunResult.encodedAssets.length, 1);
+      expect(buildResult.encodedAssets.length, 1);
+
+      final dryRunAsset = dryRunResult.encodedAssets[0];
+      expect(dryRunAsset.type, CodeAsset.type);
+      final dryRunCodeAsset = CodeAsset.fromEncoded(dryRunAsset);
+
+      final buildAsset = buildResult.encodedAssets[0];
+      final buildCodeAsset = CodeAsset.fromEncoded(buildAsset);
+      expect(buildAsset.type, CodeAsset.type);
+
+      // Common across dry-run & build
+      expect(dryRunCodeAsset.id, buildCodeAsset.id);
+      expect(dryRunCodeAsset.os, buildCodeAsset.os);
+      expect(dryRunCodeAsset.linkMode, buildCodeAsset.linkMode);
+      expect(dryRunCodeAsset.file?.pathSegments.last,
+          buildCodeAsset.file?.pathSegments.last);
+
+      // Different in dry-run: No architecture
+      expect(dryRunCodeAsset.architecture, isNull);
+      expect(buildCodeAsset.architecture, isNotNull);
 
       final nativeAssetsBuilderDirectory =
           packageUri.resolve('.dart_tool/native_assets_builder/');
diff --git a/pkgs/native_assets_builder/test/build_runner/build_runner_build_output_format_test.dart b/pkgs/native_assets_builder/test/build_runner/build_runner_build_output_format_test.dart
index e148f16de..827b9d0ea 100644
--- a/pkgs/native_assets_builder/test/build_runner/build_runner_build_output_format_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/build_runner_build_output_format_test.dart
@@ -33,6 +33,9 @@ void main() async {
             packageUri,
             createCapturingLogger(logMessages, level: Level.SEVERE),
             dartExecutable,
+            supportedAssetTypes: [],
+            buildValidator: (config, output) async => [],
+            applicationAssetValidator: validateCodeAssetsInApplication,
           );
           final fullLog = logMessages.join('\n');
           expect(result.success, false);
diff --git a/pkgs/native_assets_builder/test/build_runner/build_runner_caching_test.dart b/pkgs/native_assets_builder/test/build_runner/build_runner_caching_test.dart
index d20862d2a..c3ca4d278 100644
--- a/pkgs/native_assets_builder/test/build_runner/build_runner_caching_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/build_runner_caching_test.dart
@@ -4,7 +4,6 @@
 
 import 'dart:io';
 
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
@@ -29,8 +28,15 @@ void main() async {
 
       {
         final logMessages = <String>[];
-        final result = await build(packageUri, logger, dartExecutable,
-            capturedLogs: logMessages);
+        final result = await build(
+          packageUri,
+          logger,
+          dartExecutable,
+          capturedLogs: logMessages,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
+        );
         expect(
           logMessages.join('\n'),
           contains(
@@ -48,8 +54,15 @@ void main() async {
 
       {
         final logMessages = <String>[];
-        final result = await build(packageUri, logger, dartExecutable,
-            capturedLogs: logMessages);
+        final result = await build(
+          packageUri,
+          logger,
+          dartExecutable,
+          capturedLogs: logMessages,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
+        );
         expect(
           logMessages.join('\n'),
           contains('Skipping build for native_add'),
@@ -86,9 +99,17 @@ void main() async {
       await Future<void>.delayed(const Duration(seconds: 1));
 
       {
-        final result = await build(packageUri, logger, dartExecutable);
+        final result = await build(
+          packageUri,
+          logger,
+          dartExecutable,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
+        );
         await expectSymbols(
-            asset: result.assets.single as CodeAsset, symbols: ['add']);
+            asset: CodeAsset.fromEncoded(result.encodedAssets.single),
+            symbols: ['add']);
       }
 
       await copyTestProjects(
@@ -97,9 +118,16 @@ void main() async {
       );
 
       {
-        final result = await build(packageUri, logger, dartExecutable);
+        final result = await build(
+          packageUri,
+          logger,
+          dartExecutable,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
+        );
         await expectSymbols(
-          asset: result.assets.single as CodeAsset,
+          asset: CodeAsset.fromEncoded(result.encodedAssets.single),
           symbols: ['add', 'subtract'],
         );
       }
@@ -124,7 +152,14 @@ void main() async {
         // cached.
         await Future<void>.delayed(const Duration(seconds: 1));
 
-        final result = await build(packageUri, logger, dartExecutable);
+        final result = await build(
+          packageUri,
+          logger,
+          dartExecutable,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
+        );
         {
           final compiledHook = logMessages
               .where((m) =>
@@ -135,7 +170,7 @@ void main() async {
         }
         logMessages.clear();
         await expectSymbols(
-          asset: result.assets.single as CodeAsset,
+          asset: CodeAsset.fromEncoded(result.encodedAssets.single),
           symbols: ['add'],
         );
 
@@ -144,7 +179,14 @@ void main() async {
             targetUri: packageUri);
 
         {
-          final result = await build(packageUri, logger, dartExecutable);
+          final result = await build(
+            packageUri,
+            logger,
+            dartExecutable,
+            supportedAssetTypes: [CodeAsset.type],
+            buildValidator: validateCodeAssetBuildOutput,
+            applicationAssetValidator: validateCodeAssetsInApplication,
+          );
           {
             final compiledHook = logMessages
                 .where((m) =>
@@ -155,7 +197,7 @@ void main() async {
           }
           logMessages.clear();
           await expectSymbols(
-            asset: result.assets.single as CodeAsset,
+            asset: CodeAsset.fromEncoded(result.encodedAssets.single),
             symbols: ['add', 'multiply'],
           );
         }
diff --git a/pkgs/native_assets_builder/test/build_runner/build_runner_cycle_test.dart b/pkgs/native_assets_builder/test/build_runner/build_runner_cycle_test.dart
index f99251865..746cc2701 100644
--- a/pkgs/native_assets_builder/test/build_runner/build_runner_cycle_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/build_runner_cycle_test.dart
@@ -27,6 +27,8 @@ void main() async {
           createCapturingLogger(logMessages, level: Level.SEVERE),
           dartExecutable,
           linkingEnabled: false,
+          supportedAssetTypes: [],
+          buildValidator: (config, output) async => [],
         );
         final fullLog = logMessages.join('\n');
         expect(result.success, false);
@@ -45,6 +47,9 @@ void main() async {
           packageUri,
           createCapturingLogger(logMessages, level: Level.SEVERE),
           dartExecutable,
+          supportedAssetTypes: [],
+          buildValidator: (config, output) async => [],
+          applicationAssetValidator: (_) async => [],
         );
         final fullLog = logMessages.join('\n');
         expect(result.success, false);
diff --git a/pkgs/native_assets_builder/test/build_runner/build_runner_failure_test.dart b/pkgs/native_assets_builder/test/build_runner/build_runner_failure_test.dart
index d4939bec6..f973b9c90 100644
--- a/pkgs/native_assets_builder/test/build_runner/build_runner_failure_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/build_runner_failure_test.dart
@@ -5,7 +5,6 @@
 import 'dart:io';
 
 import 'package:logging/logging.dart';
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
@@ -25,10 +24,18 @@ void main() async {
       );
 
       {
-        final result = await build(packageUri, logger, dartExecutable);
-        expect(result.assets.length, 1);
+        final result = await build(
+          packageUri,
+          logger,
+          dartExecutable,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
+        );
+        expect(result.encodedAssets.length, 1);
         await expectSymbols(
-            asset: result.assets.single as CodeAsset, symbols: ['add']);
+            asset: CodeAsset.fromEncoded(result.encodedAssets.single),
+            symbols: ['add']);
         expect(
           result.dependencies,
           [
@@ -48,6 +55,9 @@ void main() async {
           packageUri,
           createCapturingLogger(logMessages, level: Level.SEVERE),
           dartExecutable,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
         );
         final fullLog = logMessages.join('\n');
         expect(result.success, false);
@@ -68,10 +78,18 @@ void main() async {
       );
 
       {
-        final result = await build(packageUri, logger, dartExecutable);
-        expect(result.assets.length, 1);
+        final result = await build(
+          packageUri,
+          logger,
+          dartExecutable,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
+        );
+        expect(result.encodedAssets.length, 1);
         await expectSymbols(
-            asset: result.assets.single as CodeAsset, symbols: ['add']);
+            asset: CodeAsset.fromEncoded(result.encodedAssets.single),
+            symbols: ['add']);
         expect(
           result.dependencies,
           [
diff --git a/pkgs/native_assets_builder/test/build_runner/build_runner_non_root_package_test.dart b/pkgs/native_assets_builder/test/build_runner/build_runner_non_root_package_test.dart
index 1df7626a3..c34013ae3 100644
--- a/pkgs/native_assets_builder/test/build_runner/build_runner_non_root_package_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/build_runner_non_root_package_test.dart
@@ -30,8 +30,11 @@ void main() async {
           dartExecutable,
           capturedLogs: logMessages,
           runPackageName: 'some_dev_dep',
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
         );
-        expect(result.assets, isEmpty);
+        expect(result.encodedAssets, isEmpty);
         expect(result.dependencies, isEmpty);
       }
 
@@ -43,8 +46,11 @@ void main() async {
           dartExecutable,
           capturedLogs: logMessages,
           runPackageName: 'native_add',
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
         );
-        expect(result.assets, isNotEmpty);
+        expect(result.encodedAssets, isNotEmpty);
         expect(
           result.dependencies,
           [
diff --git a/pkgs/native_assets_builder/test/build_runner/build_runner_reusability_test.dart b/pkgs/native_assets_builder/test/build_runner/build_runner_reusability_test.dart
index 6568332ab..a19cffb95 100644
--- a/pkgs/native_assets_builder/test/build_runner/build_runner_reusability_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/build_runner_reusability_test.dart
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:native_assets_builder/src/build_runner/build_runner.dart';
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
@@ -34,6 +33,8 @@ void main() async {
         workingDirectory: packageUri,
         includeParentEnvironment: true,
         linkingEnabled: false,
+        supportedAssetTypes: [],
+        buildValidator: (config, output) async => [],
       );
       await buildRunner.buildDryRun(
         targetOS: Target.current.os,
@@ -41,6 +42,8 @@ void main() async {
         workingDirectory: packageUri,
         includeParentEnvironment: true,
         linkingEnabled: false,
+        supportedAssetTypes: [],
+        buildValidator: (config, output) async => [],
       );
       await buildRunner.build(
         buildMode: BuildMode.release,
@@ -49,6 +52,9 @@ void main() async {
         workingDirectory: packageUri,
         includeParentEnvironment: true,
         linkingEnabled: false,
+        supportedAssetTypes: [],
+        buildValidator: (config, output) async => [],
+        applicationAssetValidator: (_) async => [],
       );
       await buildRunner.build(
         buildMode: BuildMode.release,
@@ -57,6 +63,9 @@ void main() async {
         workingDirectory: packageUri,
         includeParentEnvironment: true,
         linkingEnabled: false,
+        supportedAssetTypes: [],
+        buildValidator: (config, output) async => [],
+        applicationAssetValidator: (_) async => [],
       );
     });
   });
diff --git a/pkgs/native_assets_builder/test/build_runner/build_runner_run_in_isolation_test.dart b/pkgs/native_assets_builder/test/build_runner/build_runner_run_in_isolation_test.dart
index bfb9b738f..e5b0ecf49 100644
--- a/pkgs/native_assets_builder/test/build_runner/build_runner_run_in_isolation_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/build_runner_run_in_isolation_test.dart
@@ -4,7 +4,6 @@
 
 import 'dart:io';
 
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
@@ -61,8 +60,11 @@ void main() async {
         ),
         // Prevent any other environment variables.
         includeParentEnvironment: false,
+        supportedAssetTypes: [CodeAsset.type],
+        buildValidator: validateCodeAssetBuildOutput,
+        applicationAssetValidator: validateCodeAssetsInApplication,
       );
-      expect(result.assets.length, 1);
+      expect(result.encodedAssets.length, 1);
     });
   });
 }
diff --git a/pkgs/native_assets_builder/test/build_runner/build_runner_test.dart b/pkgs/native_assets_builder/test/build_runner/build_runner_test.dart
index cdd19fa95..9c635ac51 100644
--- a/pkgs/native_assets_builder/test/build_runner/build_runner_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/build_runner_test.dart
@@ -32,15 +32,22 @@ void main() async {
       // Trigger a build, should invoke build for libraries with native assets.
       {
         final logMessages = <String>[];
-        final result = await build(packageUri, logger, dartExecutable,
-            capturedLogs: logMessages);
+        final result = await build(
+          packageUri,
+          logger,
+          dartExecutable,
+          capturedLogs: logMessages,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
+        );
         expect(
             logMessages.join('\n'),
             stringContainsInOrder([
               'native_add${Platform.pathSeparator}hook'
                   '${Platform.pathSeparator}build.dart',
             ]));
-        expect(result.assets.length, 1);
+        expect(result.encodedAssets.length, 1);
       }
 
       // Trigger a build, should not invoke anything.
@@ -56,6 +63,9 @@ void main() async {
           dartExecutable,
           capturedLogs: logMessages,
           packageLayout: packageLayout,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
         );
         expect(
           false,
@@ -64,7 +74,7 @@ void main() async {
                 '${Platform.pathSeparator}build.dart',
               ),
         );
-        expect(result.assets.length, 1);
+        expect(result.encodedAssets.length, 1);
       }
     });
   });
diff --git a/pkgs/native_assets_builder/test/build_runner/concurrency_shared_test.dart b/pkgs/native_assets_builder/test/build_runner/concurrency_shared_test.dart
index 1b095e6dd..3dc75b307 100644
--- a/pkgs/native_assets_builder/test/build_runner/concurrency_shared_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/concurrency_shared_test.dart
@@ -6,7 +6,6 @@ import 'dart:async';
 
 import 'package:native_assets_builder/src/utils/run_process.dart'
     show RunProcessResult;
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
diff --git a/pkgs/native_assets_builder/test/build_runner/concurrency_shared_test_helper.dart b/pkgs/native_assets_builder/test/build_runner/concurrency_shared_test_helper.dart
index 1e127a21b..bafbc2d5e 100644
--- a/pkgs/native_assets_builder/test/build_runner/concurrency_shared_test_helper.dart
+++ b/pkgs/native_assets_builder/test/build_runner/concurrency_shared_test_helper.dart
@@ -4,7 +4,6 @@
 
 import 'package:logging/logging.dart';
 import 'package:native_assets_builder/native_assets_builder.dart';
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 
 import '../helpers.dart';
 
@@ -29,6 +28,9 @@ void main(List<String> args) async {
     linkingEnabled: false,
     supportedAssetTypes: [DataAsset.type],
     targetAndroidNdkApi: target.os == OS.android ? 30 : null,
+    buildValidator: (config, output) async =>
+        await validateDataAssetBuildOutput(config, output),
+    applicationAssetValidator: (_) async => [],
   );
   if (!result.success) {
     throw Error();
diff --git a/pkgs/native_assets_builder/test/build_runner/concurrency_test_helper.dart b/pkgs/native_assets_builder/test/build_runner/concurrency_test_helper.dart
index fe37397bb..32ac611a6 100644
--- a/pkgs/native_assets_builder/test/build_runner/concurrency_test_helper.dart
+++ b/pkgs/native_assets_builder/test/build_runner/concurrency_test_helper.dart
@@ -4,7 +4,6 @@
 
 import 'package:logging/logging.dart';
 import 'package:native_assets_builder/native_assets_builder.dart';
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 
 import '../helpers.dart';
 
@@ -31,6 +30,12 @@ void main(List<String> args) async {
     workingDirectory: packageUri,
     includeParentEnvironment: true,
     linkingEnabled: false,
+    supportedAssetTypes: [CodeAsset.type, DataAsset.type],
+    buildValidator: (config, output) async => [
+      ...await validateCodeAssetBuildOutput(config, output),
+      ...await validateDataAssetBuildOutput(config, output),
+    ],
+    applicationAssetValidator: validateCodeAssetsInApplication,
   );
   if (!result.success) {
     throw Error();
diff --git a/pkgs/native_assets_builder/test/build_runner/conflicting_dylib_test.dart b/pkgs/native_assets_builder/test/build_runner/conflicting_dylib_test.dart
index e3e13bc02..ddd48256a 100644
--- a/pkgs/native_assets_builder/test/build_runner/conflicting_dylib_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/conflicting_dylib_test.dart
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:logging/logging.dart';
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
@@ -28,6 +27,9 @@ void main() async {
           packageUri,
           createCapturingLogger(logMessages, level: Level.SEVERE),
           dartExecutable,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
         );
         final fullLog = logMessages.join('\n');
         expect(result.success, false);
@@ -55,6 +57,9 @@ void main() async {
         logger,
         linkingEnabled: true,
         dartExecutable,
+        supportedAssetTypes: [CodeAsset.type],
+        buildValidator: validateCodeAssetBuildOutput,
+        applicationAssetValidator: validateCodeAssetsInApplication,
       );
       expect(buildResult.success, isTrue);
 
@@ -63,11 +68,17 @@ void main() async {
         logger,
         dartExecutable,
         buildResult: buildResult,
+        supportedAssetTypes: [CodeAsset.type],
+        linkValidator: validateCodeAssetLinkOutput,
+        applicationAssetValidator: validateCodeAssetsInApplication,
       );
       expect(linkResult.success, isTrue);
 
-      final allAssets = [...buildResult.assets, ...linkResult.assets];
-      final validateResult = validateNoDuplicateDylibs(allAssets);
+      final allAssets = [
+        ...buildResult.encodedAssets,
+        ...linkResult.encodedAssets
+      ].where((e) => e.type == CodeAsset.type).toList();
+      final validateResult = await validateCodeAssetsInApplication(allAssets);
       expect(validateResult, isNotEmpty);
     });
   });
diff --git a/pkgs/native_assets_builder/test/build_runner/fail_on_os_sdk_version_test.dart b/pkgs/native_assets_builder/test/build_runner/fail_on_os_sdk_version_test.dart
index 0e2055130..021a3d2fd 100644
--- a/pkgs/native_assets_builder/test/build_runner/fail_on_os_sdk_version_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/fail_on_os_sdk_version_test.dart
@@ -3,9 +3,6 @@
 // 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' show OS;
-import 'package:native_assets_cli/native_assets_cli_internal.dart'
-    show IOSSdk, Target;
 import 'package:test/test.dart';
 
 import '../helpers.dart';
@@ -76,6 +73,16 @@ void main() async {
                 packageUri,
                 createCapturingLogger(logMessages, level: Level.SEVERE),
                 dartExecutable,
+                supportedAssetTypes: [CodeAsset.type, DataAsset.type],
+                buildValidator: (config, output) async => [
+                  ...await validateCodeAssetBuildOutput(config, output),
+                  ...await validateDataAssetBuildOutput(config, output),
+                ],
+                linkValidator: (config, output) async => [
+                  ...await validateCodeAssetLinkOutput(config, output),
+                  ...await validateDataAssetLinkOutput(config, output),
+                ],
+                applicationAssetValidator: validateCodeAssetsInApplication,
               );
               final fullLog = logMessages.join('\n');
               if (hook == 'build') {
diff --git a/pkgs/native_assets_builder/test/build_runner/helpers.dart b/pkgs/native_assets_builder/test/build_runner/helpers.dart
index ba809a893..daaa8f8b1 100644
--- a/pkgs/native_assets_builder/test/build_runner/helpers.dart
+++ b/pkgs/native_assets_builder/test/build_runner/helpers.dart
@@ -5,11 +5,9 @@
 import 'dart:async';
 import 'dart:io';
 
-import 'package:file_testing/file_testing.dart';
 import 'package:logging/logging.dart';
 import 'package:native_assets_builder/native_assets_builder.dart';
 import 'package:native_assets_builder/src/model/hook_result.dart';
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
@@ -35,6 +33,8 @@ Future<BuildResult> build(
   Uri packageUri,
   Logger logger,
   Uri dartExecutable, {
+  required BuildValidator buildValidator,
+  required ApplicationAssetValidator applicationAssetValidator,
   LinkModePreference linkModePreference = LinkModePreference.dynamic,
   CCompilerConfig? cCompilerConfig,
   bool includeParentEnvironment = true,
@@ -47,7 +47,7 @@ Future<BuildResult> build(
   int? targetAndroidNdkApi,
   Target? target,
   bool linkingEnabled = false,
-  Iterable<String>? supportedAssetTypes,
+  required Iterable<String> supportedAssetTypes,
 }) async =>
     await runWithLog(capturedLogs, () async {
       final result = await NativeAssetsBuildRunner(
@@ -68,12 +68,15 @@ Future<BuildResult> build(
         targetAndroidNdkApi: targetAndroidNdkApi,
         linkingEnabled: linkingEnabled,
         supportedAssetTypes: supportedAssetTypes,
+        buildValidator: buildValidator,
+        applicationAssetValidator: applicationAssetValidator,
       );
 
       if (result.success) {
-        await expectAssetsExist(result.assets);
-        for (final assetsForLinking in result.assetsForLinking.values) {
-          await expectAssetsExist(assetsForLinking);
+        expect(await result.encodedAssets.allExist(), true);
+        for (final encodedAssetsForLinking
+            in result.encodedAssetsForLinking.values) {
+          expect(await encodedAssetsForLinking.allExist(), true);
         }
       }
 
@@ -84,6 +87,8 @@ Future<LinkResult> link(
   Uri packageUri,
   Logger logger,
   Uri dartExecutable, {
+  required LinkValidator linkValidator,
+  required ApplicationAssetValidator applicationAssetValidator,
   LinkModePreference linkModePreference = LinkModePreference.dynamic,
   CCompilerConfig? cCompilerConfig,
   bool includeParentEnvironment = true,
@@ -96,7 +101,7 @@ Future<LinkResult> link(
   int? targetMacOSVersion,
   int? targetAndroidNdkApi,
   Target? target,
-  Iterable<String>? supportedAssetTypes,
+  required Iterable<String> supportedAssetTypes,
 }) async =>
     await runWithLog(capturedLogs, () async {
       final result = await NativeAssetsBuildRunner(
@@ -117,10 +122,12 @@ Future<LinkResult> link(
         targetMacOSVersion: targetMacOSVersion,
         targetAndroidNdkApi: targetAndroidNdkApi,
         supportedAssetTypes: supportedAssetTypes,
+        linkValidator: linkValidator,
+        applicationAssetValidator: applicationAssetValidator,
       );
 
       if (result.success) {
-        await expectAssetsExist(result.assets);
+        expect(await result.encodedAssets.allExist(), true);
       }
 
       return result;
@@ -132,6 +139,9 @@ Future<(BuildResult, LinkResult)> buildAndLink(
   Uri dartExecutable, {
   LinkModePreference linkModePreference = LinkModePreference.dynamic,
   CCompilerConfig? cCompilerConfig,
+  required LinkValidator linkValidator,
+  required BuildValidator buildValidator,
+  required ApplicationAssetValidator applicationAssetValidator,
   bool includeParentEnvironment = true,
   List<String>? capturedLogs,
   PackageLayout? packageLayout,
@@ -142,7 +152,7 @@ Future<(BuildResult, LinkResult)> buildAndLink(
   int? targetAndroidNdkApi,
   Target? target,
   Uri? resourceIdentifiers,
-  Iterable<String>? supportedAssetTypes,
+  required Iterable<String> supportedAssetTypes,
 }) async =>
     await runWithLog(capturedLogs, () async {
       final buildRunner = NativeAssetsBuildRunner(
@@ -164,15 +174,18 @@ Future<(BuildResult, LinkResult)> buildAndLink(
         targetAndroidNdkApi: targetAndroidNdkApi,
         linkingEnabled: true,
         supportedAssetTypes: supportedAssetTypes,
+        buildValidator: buildValidator,
+        applicationAssetValidator: applicationAssetValidator,
       );
 
       if (!buildResult.success) {
         return (buildResult, HookResult());
       }
 
-      await expectAssetsExist(buildResult.assets);
-      for (final assetsForLinking in buildResult.assetsForLinking.values) {
-        await expectAssetsExist(assetsForLinking);
+      expect(await buildResult.encodedAssets.allExist(), true);
+      for (final encodedAssetsForLinking
+          in buildResult.encodedAssetsForLinking.values) {
+        expect(await encodedAssetsForLinking.allExist(), true);
       }
 
       final linkResult = await buildRunner.link(
@@ -190,10 +203,12 @@ Future<(BuildResult, LinkResult)> buildAndLink(
         targetMacOSVersion: targetMacOSVersion,
         targetAndroidNdkApi: targetAndroidNdkApi,
         supportedAssetTypes: supportedAssetTypes,
+        linkValidator: linkValidator,
+        applicationAssetValidator: applicationAssetValidator,
       );
 
       if (linkResult.success) {
-        await expectAssetsExist(buildResult.assets);
+        expect(await buildResult.encodedAssets.allExist(), true);
       }
 
       return (buildResult, linkResult);
@@ -222,13 +237,14 @@ Future<BuildDryRunResult> buildDryRun(
   Uri packageUri,
   Logger logger,
   Uri dartExecutable, {
+  required BuildValidator buildValidator,
   LinkModePreference linkModePreference = LinkModePreference.dynamic,
   CCompilerConfig? cCompilerConfig,
   bool includeParentEnvironment = true,
   List<String>? capturedLogs,
   PackageLayout? packageLayout,
   required bool linkingEnabled,
-  Iterable<String>? supportedAssetTypes,
+  required Iterable<String> supportedAssetTypes,
 }) async =>
     runWithLog(capturedLogs, () async {
       final result = await NativeAssetsBuildRunner(
@@ -242,16 +258,11 @@ Future<BuildDryRunResult> buildDryRun(
         packageLayout: packageLayout,
         linkingEnabled: linkingEnabled,
         supportedAssetTypes: supportedAssetTypes,
+        buildValidator: buildValidator,
       );
       return result;
     });
 
-Future<void> expectAssetsExist(List<Asset> assets) async {
-  for (final asset in assets) {
-    expect(File.fromUri(asset.file!), exists);
-  }
-}
-
 Future<void> expectSymbols({
   required CodeAsset asset,
   required List<String> symbols,
diff --git a/pkgs/native_assets_builder/test/build_runner/link_caching_test.dart b/pkgs/native_assets_builder/test/build_runner/link_caching_test.dart
index 0104ed64c..f43429f8e 100644
--- a/pkgs/native_assets_builder/test/build_runner/link_caching_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/link_caching_test.dart
@@ -5,14 +5,12 @@
 import 'dart:io';
 
 import 'package:native_assets_builder/native_assets_builder.dart';
-import 'package:native_assets_cli/src/asset.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
 import 'helpers.dart';
 
 void main() async {
-  const supportedAssetTypes = [DataAsset.type];
   const packageName = 'simple_link';
 
   test('link hook caching', () async {
@@ -40,8 +38,10 @@ void main() async {
           logger,
           dartExecutable,
           linkingEnabled: true,
-          supportedAssetTypes: supportedAssetTypes,
+          supportedAssetTypes: [DataAsset.type],
           capturedLogs: logMessages,
+          buildValidator: validateDataAssetBuildOutput,
+          applicationAssetValidator: (_) async => [],
         );
       }
 
@@ -52,8 +52,10 @@ void main() async {
           logger,
           dartExecutable,
           buildResult: buildResult,
-          supportedAssetTypes: supportedAssetTypes,
+          supportedAssetTypes: [DataAsset.type],
           capturedLogs: logMessages,
+          linkValidator: validateDataAssetLinkOutput,
+          applicationAssetValidator: (_) async => [],
         );
       }
 
diff --git a/pkgs/native_assets_builder/test/build_runner/link_test.dart b/pkgs/native_assets_builder/test/build_runner/link_test.dart
index d7cc00e33..2a7d5d76f 100644
--- a/pkgs/native_assets_builder/test/build_runner/link_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/link_test.dart
@@ -4,7 +4,6 @@
 
 import 'dart:io';
 
-import 'package:native_assets_cli/src/asset.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
@@ -13,8 +12,6 @@ import 'helpers.dart';
 const Timeout longTimeout = Timeout(Duration(minutes: 5));
 
 void main() async {
-  const supportedAssetTypes = [DataAsset.type];
-
   test(
     'simple_link linking',
     timeout: longTimeout,
@@ -34,27 +31,33 @@ void main() async {
           logger,
           dartExecutable,
           linkingEnabled: true,
-          supportedAssetTypes: supportedAssetTypes,
+          supportedAssetTypes: [DataAsset.type],
+          buildValidator: validateDataAssetBuildOutput,
+          applicationAssetValidator: (_) async => [],
         );
-        expect(buildResult.assets.length, 0);
+        expect(buildResult.encodedAssets.length, 0);
 
         final linkResult = await link(
           packageUri,
           logger,
           dartExecutable,
           buildResult: buildResult,
-          supportedAssetTypes: supportedAssetTypes,
+          supportedAssetTypes: [DataAsset.type],
+          linkValidator: validateDataAssetLinkOutput,
+          applicationAssetValidator: (_) async => [],
         );
-        expect(linkResult.assets.length, 2);
+        expect(linkResult.encodedAssets.length, 2);
 
         final buildNoLinkResult = await build(
           packageUri,
           logger,
           dartExecutable,
           linkingEnabled: false,
-          supportedAssetTypes: supportedAssetTypes,
+          supportedAssetTypes: [DataAsset.type],
+          buildValidator: validateDataAssetBuildOutput,
+          applicationAssetValidator: (_) async => [],
         );
-        expect(buildNoLinkResult.assets.length, 4);
+        expect(buildNoLinkResult.encodedAssets.length, 4);
       });
     },
   );
@@ -78,11 +81,11 @@ void main() async {
           'assets/data_0.json',
           'assets/data_1.json',
         ];
-        final assetsForLinking = [
+        final encodedAssetsForLinking = [
           ...helperAssetsForLinking,
           ...mainAssetsForLinking,
         ];
-        final linkedAssets = assetsForLinking.skip(1);
+        final linkedAssets = encodedAssetsForLinking.skip(1);
 
         await copyTestProjects(targetUri: tempUri);
         final packageUri = tempUri.resolve('complex_link/');
@@ -95,14 +98,16 @@ void main() async {
           logger,
           dartExecutable,
           linkingEnabled: true,
-          supportedAssetTypes: supportedAssetTypes,
+          supportedAssetTypes: [DataAsset.type],
+          buildValidator: validateDataAssetBuildOutput,
+          applicationAssetValidator: (_) async => [],
         );
         expect(buildResult.success, true);
+        expect(_getNames(buildResult.encodedAssets),
+            unorderedEquals(builtHelperAssets));
         expect(
-            _getNames(buildResult.assets), unorderedEquals(builtHelperAssets));
-        expect(
-          _getNames(buildResult.assetsForLinking['complex_link']!),
-          unorderedEquals(assetsForLinking),
+          _getNames(buildResult.encodedAssetsForLinking['complex_link']!),
+          unorderedEquals(encodedAssetsForLinking),
         );
 
         final linkResult = await link(
@@ -110,11 +115,14 @@ void main() async {
           logger,
           dartExecutable,
           buildResult: buildResult,
-          supportedAssetTypes: supportedAssetTypes,
+          supportedAssetTypes: [DataAsset.type],
+          linkValidator: validateDataAssetLinkOutput,
+          applicationAssetValidator: (_) async => [],
         );
         expect(linkResult.success, true);
 
-        expect(_getNames(linkResult.assets), unorderedEquals(linkedAssets));
+        expect(
+            _getNames(linkResult.encodedAssets), unorderedEquals(linkedAssets));
       });
     },
   );
@@ -135,10 +143,12 @@ void main() async {
         logger,
         dartExecutable,
         linkingEnabled: true,
-        supportedAssetTypes: supportedAssetTypes,
+        supportedAssetTypes: [DataAsset.type],
+        buildValidator: validateDataAssetBuildOutput,
+        applicationAssetValidator: (_) async => [],
       );
-      expect(buildResult.assets.length, 0);
-      expect(buildResult.assetsForLinking.length, 0);
+      expect(buildResult.encodedAssets.length, 0);
+      expect(buildResult.encodedAssetsForLinking.length, 0);
 
       final logMessages = <String>[];
       final linkResult = await link(
@@ -147,9 +157,11 @@ void main() async {
         dartExecutable,
         buildResult: buildResult,
         capturedLogs: logMessages,
-        supportedAssetTypes: supportedAssetTypes,
+        supportedAssetTypes: [DataAsset.type],
+        linkValidator: validateDataAssetLinkOutput,
+        applicationAssetValidator: (_) async => [],
       );
-      expect(linkResult.assets.length, 0);
+      expect(linkResult.encodedAssets.length, 0);
       expect(
         logMessages,
         contains(
@@ -184,9 +196,12 @@ void main() async {
           logger,
           dartExecutable,
           linkingEnabled: true,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
         );
-        expect(buildResult.assets.length, 0);
-        expect(buildResult.assetsForLinking.length, 1);
+        expect(buildResult.encodedAssets.length, 0);
+        expect(buildResult.encodedAssetsForLinking.length, 1);
 
         final logMessages = <String>[];
         final linkResult = await link(
@@ -195,13 +210,17 @@ void main() async {
           dartExecutable,
           buildResult: buildResult,
           capturedLogs: logMessages,
+          supportedAssetTypes: [CodeAsset.type],
+          linkValidator: validateCodeAssetLinkOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
         );
-        expect(linkResult.assets.length, 1);
-        expect(linkResult.assets.first, isA<CodeAsset>());
+        expect(linkResult.encodedAssets.length, 1);
+        expect(linkResult.encodedAssets.first.type, CodeAsset.type);
       });
     },
   );
 }
 
-Iterable<String> _getNames(List<Asset> assets) =>
-    assets.whereType<DataAsset>().map((asset) => asset.name);
+Iterable<String> _getNames(List<EncodedAsset> assets) => assets
+    .where((e) => e.type == DataAsset.type)
+    .map((e) => DataAsset.fromEncoded(e).name);
diff --git a/pkgs/native_assets_builder/test/build_runner/metadata_test.dart b/pkgs/native_assets_builder/test/build_runner/metadata_test.dart
index d38863533..8fcb39afe 100644
--- a/pkgs/native_assets_builder/test/build_runner/metadata_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/metadata_test.dart
@@ -26,8 +26,15 @@ void main() async {
       // Trigger a build, should invoke build for libraries with native assets.
       {
         final logMessages = <String>[];
-        await build(packageUri, logger, dartExecutable,
-            capturedLogs: logMessages);
+        await build(
+          packageUri,
+          logger,
+          dartExecutable,
+          capturedLogs: logMessages,
+          supportedAssetTypes: ['foo'],
+          buildValidator: (config, output) async => [],
+          applicationAssetValidator: (_) async => [],
+        );
         expect(
             logMessages.join('\n'),
             stringContainsInOrder([
diff --git a/pkgs/native_assets_builder/test/build_runner/packaging_preference_test.dart b/pkgs/native_assets_builder/test/build_runner/packaging_preference_test.dart
index 2129b4ed5..55b20dba4 100644
--- a/pkgs/native_assets_builder/test/build_runner/packaging_preference_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/packaging_preference_test.dart
@@ -2,7 +2,6 @@
 // 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/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
@@ -27,6 +26,9 @@ void main() async {
         logger,
         dartExecutable,
         linkModePreference: LinkModePreference.dynamic,
+        supportedAssetTypes: [CodeAsset.type],
+        buildValidator: validateCodeAssetBuildOutput,
+        applicationAssetValidator: validateCodeAssetsInApplication,
       );
 
       final resultPreferDynamic = await build(
@@ -34,6 +36,9 @@ void main() async {
         logger,
         dartExecutable,
         linkModePreference: LinkModePreference.preferDynamic,
+        supportedAssetTypes: [CodeAsset.type],
+        buildValidator: validateCodeAssetBuildOutput,
+        applicationAssetValidator: validateCodeAssetsInApplication,
       );
 
       final resultStatic = await build(
@@ -41,6 +46,9 @@ void main() async {
         logger,
         dartExecutable,
         linkModePreference: LinkModePreference.static,
+        supportedAssetTypes: [CodeAsset.type],
+        buildValidator: validateCodeAssetBuildOutput,
+        applicationAssetValidator: validateCodeAssetsInApplication,
       );
 
       final resultPreferStatic = await build(
@@ -48,23 +56,27 @@ void main() async {
         logger,
         dartExecutable,
         linkModePreference: LinkModePreference.preferStatic,
+        supportedAssetTypes: [CodeAsset.type],
+        buildValidator: validateCodeAssetBuildOutput,
+        applicationAssetValidator: validateCodeAssetsInApplication,
       );
 
       // This package honors preferences.
       expect(
-        (resultDynamic.assets.single as CodeAsset).linkMode,
+        CodeAsset.fromEncoded(resultDynamic.encodedAssets.single).linkMode,
         DynamicLoadingBundled(),
       );
       expect(
-        (resultPreferDynamic.assets.single as CodeAsset).linkMode,
+        CodeAsset.fromEncoded(resultPreferDynamic.encodedAssets.single)
+            .linkMode,
         DynamicLoadingBundled(),
       );
       expect(
-        (resultStatic.assets.single as CodeAsset).linkMode,
+        CodeAsset.fromEncoded(resultStatic.encodedAssets.single).linkMode,
         StaticLinking(),
       );
       expect(
-        (resultPreferStatic.assets.single as CodeAsset).linkMode,
+        CodeAsset.fromEncoded(resultPreferStatic.encodedAssets.single).linkMode,
         StaticLinking(),
       );
     });
diff --git a/pkgs/native_assets_builder/test/build_runner/resources_test.dart b/pkgs/native_assets_builder/test/build_runner/resources_test.dart
index a46027f34..14b80af78 100644
--- a/pkgs/native_assets_builder/test/build_runner/resources_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/resources_test.dart
@@ -34,6 +34,9 @@ void main() async {
           logger,
           dartExecutable,
           linkingEnabled: true,
+          supportedAssetTypes: [DataAsset.type],
+          buildValidator: validateDataAssetBuildOutput,
+          applicationAssetValidator: (_) async => [],
         );
 
         Iterable<String> buildFiles() => Directory.fromUri(
@@ -49,6 +52,9 @@ void main() async {
           dartExecutable,
           buildResult: buildResult,
           resourceIdentifiers: resourcesUri,
+          supportedAssetTypes: [DataAsset.type],
+          linkValidator: validateDataAssetLinkOutput,
+          applicationAssetValidator: (_) async => [],
         );
         expect(buildFiles(), anyElement(endsWith('resources.json')));
       });
diff --git a/pkgs/native_assets_builder/test/build_runner/wrong_linker_test.dart b/pkgs/native_assets_builder/test/build_runner/wrong_linker_test.dart
index 1b7f98a6d..01e637f5a 100644
--- a/pkgs/native_assets_builder/test/build_runner/wrong_linker_test.dart
+++ b/pkgs/native_assets_builder/test/build_runner/wrong_linker_test.dart
@@ -27,6 +27,9 @@ void main() async {
           packageUri,
           createCapturingLogger(logMessages, level: Level.SEVERE),
           dartExecutable,
+          supportedAssetTypes: [CodeAsset.type],
+          buildValidator: validateCodeAssetBuildOutput,
+          applicationAssetValidator: validateCodeAssetsInApplication,
         );
         final fullLog = logMessages.join('\n');
         expect(result.success, false);
diff --git a/pkgs/native_assets_builder/test/helpers.dart b/pkgs/native_assets_builder/test/helpers.dart
index 21d22a538..85759c73b 100644
--- a/pkgs/native_assets_builder/test/helpers.dart
+++ b/pkgs/native_assets_builder/test/helpers.dart
@@ -13,6 +13,8 @@ import 'package:native_assets_cli/native_assets_cli_internal.dart' as internal;
 import 'package:test/test.dart';
 import 'package:yaml/yaml.dart';
 
+export 'package:native_assets_cli/native_assets_cli_internal.dart';
+
 extension UriExtension on Uri {
   String get name => pathSegments.where((e) => e != '').last;
 
@@ -170,21 +172,24 @@ extension on String {
   Uri asFileUri() => Uri.file(this);
 }
 
-extension AssetIterable on Iterable<Asset> {
+extension AssetIterable on Iterable<EncodedAsset> {
   Future<bool> allExist() async {
-    final allResults = await Future.wait(map((e) => e.exists()));
-    final missing = allResults.contains(false);
-    return !missing;
-  }
-}
-
-extension on Asset {
-  Future<bool> exists() async {
-    final path_ = file;
-    return switch (path_) {
-      null => true,
-      _ => await path_.fileSystemEntity.exists(),
-    };
+    for (final encodedAsset in this) {
+      if (encodedAsset.type == DataAsset.type) {
+        final dataAsset = DataAsset.fromEncoded(encodedAsset);
+        if (!await dataAsset.file.fileSystemEntity.exists()) {
+          return false;
+        }
+      } else if (encodedAsset.type == CodeAsset.type) {
+        final codeAsset = CodeAsset.fromEncoded(encodedAsset);
+        if (!await (codeAsset.file?.fileSystemEntity.exists() ?? true)) {
+          return false;
+        }
+      } else {
+        throw UnimplementedError('Unknown asset type ${encodedAsset.type}');
+      }
+    }
+    return true;
   }
 }
 
diff --git a/pkgs/native_assets_builder/test/test_data/native_dynamic_linking_test.dart b/pkgs/native_assets_builder/test/test_data/native_dynamic_linking_test.dart
index 165a76e44..fbf4947d2 100644
--- a/pkgs/native_assets_builder/test/test_data/native_dynamic_linking_test.dart
+++ b/pkgs/native_assets_builder/test/test_data/native_dynamic_linking_test.dart
@@ -11,7 +11,6 @@ library;
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 import '../helpers.dart';
@@ -46,6 +45,7 @@ void main() async {
           targetArchitecture: dryRun ? null : Architecture.current,
           buildMode: dryRun ? null : BuildMode.debug,
           cCompiler: dryRun ? null : cCompiler,
+          supportedAssetTypes: [CodeAsset.type],
         );
 
         final buildConfigUri = testTempUri.resolve('build_config.json');
@@ -70,7 +70,7 @@ void main() async {
         final buildOutputUri = outputDirectory.resolve('build_output.json');
         final buildOutput = HookOutputImpl.fromJsonString(
             await File.fromUri(buildOutputUri).readAsString());
-        final assets = buildOutput.assets;
+        final assets = buildOutput.encodedAssets;
         final dependencies = buildOutput.dependencies;
         if (dryRun) {
           expect(assets.length, greaterThanOrEqualTo(3));
@@ -88,6 +88,8 @@ void main() async {
           );
 
           final addLibraryPath = assets
+              .where((e) => e.type == CodeAsset.type)
+              .map(CodeAsset.fromEncoded)
               .firstWhere((asset) => asset.id.endsWith('add.dart'))
               .file!
               .toFilePath();
diff --git a/pkgs/native_assets_builder/test/test_data/transformer_test.dart b/pkgs/native_assets_builder/test/test_data/transformer_test.dart
index e26c8be5e..334fa8724 100644
--- a/pkgs/native_assets_builder/test/test_data/transformer_test.dart
+++ b/pkgs/native_assets_builder/test/test_data/transformer_test.dart
@@ -11,7 +11,6 @@ library;
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:native_assets_cli/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 import '../build_runner/helpers.dart';
@@ -43,7 +42,7 @@ void main() async {
       final dartUri = Uri.file(Platform.resolvedExecutable);
 
       late String stdout;
-      late HookOutputImpl output;
+      late BuildOutput output;
 
       Future<void> runBuild(Architecture architecture) async {
         final config = BuildConfigImpl(
@@ -95,7 +94,7 @@ void main() async {
         ]),
       );
       expect(
-        output.assets,
+        output.dataAssets.all,
         contains(
           DataAsset(
             file: outputDirectoryShared.resolve('data_transformed0.json'),
diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/hook/link.dart b/pkgs/native_assets_builder/test_data/add_asset_link/hook/link.dart
index 371081747..ccfbb9775 100644
--- a/pkgs/native_assets_builder/test_data/add_asset_link/hook/link.dart
+++ b/pkgs/native_assets_builder/test_data/add_asset_link/hook/link.dart
@@ -6,9 +6,9 @@ import 'package:native_assets_cli/native_assets_cli.dart';
 
 void main(List<String> arguments) async {
   await link(arguments, (config, output) async {
-    final builtDylib = config.assets.first as CodeAsset;
+    final builtDylib = config.codeAssets.all.first;
     output
-      ..addAsset(
+      ..codeAssets.add(
         CodeAsset(
           package: 'add_asset_link',
           name: 'dylib_add_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 08fa5e4e2..0ac49c85c 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
@@ -24,7 +24,7 @@ void main(List<String> args) async {
           .toFilePath(windows: false)
           .substring(config.packageRoot.toFilePath(windows: false).length);
 
-      output.addAsset(
+      output.dataAssets.add(
         DataAsset(
           package: packageName,
           name: name,
diff --git a/pkgs/native_assets_builder/test_data/complex_link/hook/link.dart b/pkgs/native_assets_builder/test_data/complex_link/hook/link.dart
index 8e393e7c2..113586781 100644
--- a/pkgs/native_assets_builder/test_data/complex_link/hook/link.dart
+++ b/pkgs/native_assets_builder/test_data/complex_link/hook/link.dart
@@ -7,9 +7,10 @@ import 'package:native_assets_cli/native_assets_cli.dart';
 void main(List<String> args) async {
   await link(
     args,
-    (config, output) async => output.addAssets(treeshake(config.assets)),
+    (config, output) async =>
+        output.dataAssets.addAll(treeshake(config.dataAssets.all)),
   );
 }
 
-Iterable<Asset> treeshake(Iterable<Asset> assets) =>
+Iterable<DataAsset> treeshake(Iterable<DataAsset> assets) =>
     assets.where((asset) => !asset.id.endsWith('assets/data_helper_2.json'));
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 a4c60e03a..79ff0dfdf 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
@@ -25,7 +25,7 @@ void main(List<String> args) async {
           .substring(config.packageRoot.toFilePath(windows: false).length);
 
       final forLinking = name.contains('2') || name.contains('3');
-      output.addAsset(
+      output.dataAssets.add(
         DataAsset(
           package: packageName,
           name: name,
diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/link.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/link.dart
index b2f15c0d9..b3e266d9b 100644
--- a/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/link.dart
+++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/link.dart
@@ -7,11 +7,12 @@ import 'package:native_assets_cli/native_assets_cli.dart';
 void main(List<String> arguments) async {
   await link(arguments, (config, output) async {
     print('''
-Received ${config.assets.length} assets: ${config.assets.map((e) => e.id)}.
+Received ${config.codeAssets.all.length} encodedAssets: ${config.codeAssets.all.map((e) => e.id)}.
 ''');
-    output.addAssets(config.assets.where((asset) => asset.id.endsWith('add')));
+    output.codeAssets.addAll(
+        config.codeAssets.all.where((asset) => asset.id.endsWith('add')));
     print('''
-Keeping only ${output.assets.map((e) => e.id)}.
+Keeping only ${output.codeAssets.all.map((e) => e.id)}.
 ''');
     output.addDependency(config.packageRoot.resolve('hook/link.dart'));
   });
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 3cece99b0..5ac16fef7 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
@@ -6,7 +6,7 @@ import 'package:native_assets_cli/native_assets_cli.dart';
 
 void main(List<String> arguments) async {
   await build(arguments, (config, output) async {
-    output.addAsset(
+    output.dataAssets.add(
       DataAsset(
         name: 'data',
         file: config.packageRoot.resolve('assets/data.json'),
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 dc2b2b4c6..aab673ecc 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
@@ -28,8 +28,8 @@ void main(List<String> arguments) async {
           print('${record.level.name}: ${record.time}: ${record.message}');
         }),
     );
-    output.addAsset(
-      tempBuildOutput.assets.single,
+    output.addEncodedAsset(
+      tempBuildOutput.encodedAssets.single,
       // Send dylib to linking if linking is enabled.
       linkInPackage: config.linkingEnabled ? packageName : null,
     );
diff --git a/pkgs/native_assets_builder/test_data/native_add_duplicate/hook/link.dart b/pkgs/native_assets_builder/test_data/native_add_duplicate/hook/link.dart
index 21d879ca6..b69973d47 100644
--- a/pkgs/native_assets_builder/test_data/native_add_duplicate/hook/link.dart
+++ b/pkgs/native_assets_builder/test_data/native_add_duplicate/hook/link.dart
@@ -7,6 +7,6 @@ import 'package:native_assets_cli/native_assets_cli.dart';
 void main(List<String> args) async {
   await link(args, (config, output) async {
     // Simply output the dylib in the link hook.
-    output.addAssets(config.assets);
+    output.codeAssets.addAll(config.codeAssets.all);
   });
 }
diff --git a/pkgs/native_assets_builder/test_data/no_asset_for_link/hook/link.dart b/pkgs/native_assets_builder/test_data/no_asset_for_link/hook/link.dart
index d617fa723..e73ad42ea 100644
--- a/pkgs/native_assets_builder/test_data/no_asset_for_link/hook/link.dart
+++ b/pkgs/native_assets_builder/test_data/no_asset_for_link/hook/link.dart
@@ -6,11 +6,7 @@ import 'package:native_assets_cli/native_assets_cli.dart';
 
 void main(List<String> arguments) async {
   await link(arguments, (config, output) async {
-    output.addAssets(
-      config.assets,
-    );
-    output.addDependency(
-      config.packageRoot.resolve('hook/link.dart'),
-    );
+    output.addEncodedAssets(config.encodedAssets);
+    output.addDependency(config.packageRoot.resolve('hook/link.dart'));
   });
 }
diff --git a/pkgs/native_assets_builder/test_data/simple_data_asset/hook/build.dart b/pkgs/native_assets_builder/test_data/simple_data_asset/hook/build.dart
index 3e18d4c11..e7aed2a3c 100644
--- a/pkgs/native_assets_builder/test_data/simple_data_asset/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/simple_data_asset/hook/build.dart
@@ -24,7 +24,7 @@ void main(List<String> args) async {
             .toFilePath(windows: false)
             .substring(config.packageRoot.toFilePath(windows: false).length);
 
-        output.addAsset(
+        output.dataAssets.add(
           DataAsset(
             package: config.packageName,
             name: name,
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 08fa5e4e2..0ac49c85c 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
@@ -24,7 +24,7 @@ void main(List<String> args) async {
           .toFilePath(windows: false)
           .substring(config.packageRoot.toFilePath(windows: false).length);
 
-      output.addAsset(
+      output.dataAssets.add(
         DataAsset(
           package: packageName,
           name: name,
diff --git a/pkgs/native_assets_builder/test_data/simple_link/hook/link.dart b/pkgs/native_assets_builder/test_data/simple_link/hook/link.dart
index 1c019fcee..9e4d9f2ee 100644
--- a/pkgs/native_assets_builder/test_data/simple_link/hook/link.dart
+++ b/pkgs/native_assets_builder/test_data/simple_link/hook/link.dart
@@ -5,10 +5,16 @@
 import 'package:native_assets_cli/native_assets_cli.dart';
 
 void main(List<String> args) async {
-  await link(
-    args,
-    (config, output) async => output.addAssets(shake(config.assets)),
-  );
+  await link(args, (config, output) async {
+    shake(output, config.dataAssets.all);
+  });
 }
 
-Iterable<Asset> shake(Iterable<Asset> assets) => assets.skip(2);
+void shake(LinkOutput output, Iterable<DataAsset> assets) {
+  for (final asset in assets.skip(2)) {
+    output.dataAssets.add(asset);
+
+    // If the file changes we'd like to re-run the linker.
+    output.addDependency(asset.file);
+  }
+}
diff --git a/pkgs/native_assets_builder/test_data/transformer/hook/build.dart b/pkgs/native_assets_builder/test_data/transformer/hook/build.dart
index 9b1f2308c..9f5956bed 100644
--- a/pkgs/native_assets_builder/test_data/transformer/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/transformer/hook/build.dart
@@ -40,7 +40,7 @@ void main(List<String> arguments) async {
         }
       }
 
-      output.addAsset(
+      output.dataAssets.add(
         DataAsset(
           package: config.packageName,
           name: name,
diff --git a/pkgs/native_assets_builder/test_data/treeshaking_native_libs/hook/link.dart b/pkgs/native_assets_builder/test_data/treeshaking_native_libs/hook/link.dart
index 22d7600f6..07c868d95 100644
--- a/pkgs/native_assets_builder/test_data/treeshaking_native_libs/hook/link.dart
+++ b/pkgs/native_assets_builder/test_data/treeshaking_native_libs/hook/link.dart
@@ -12,9 +12,9 @@ void main(List<String> arguments) async {
     (config, output) async {
       final linker = CLinker.library(
         name: config.packageName,
-        assetName: config.assets.single.id.split('/').skip(1).join('/'),
+        assetName: config.codeAssets.all.single.id.split('/').skip(1).join('/'),
         linkerOptions: LinkerOptions.treeshake(symbols: ['add']),
-        sources: [config.assets.single.file!.toFilePath()],
+        sources: [config.codeAssets.all.single.file!.toFilePath()],
       );
       await linker.run(
         config: config,
diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output/hook/build.dart b/pkgs/native_assets_builder/test_data/wrong_build_output/hook/build.dart
index 969f76335..cd8dac2c2 100644
--- a/pkgs/native_assets_builder/test_data/wrong_build_output/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/wrong_build_output/hook/build.dart
@@ -13,7 +13,7 @@ void main(List<String> args) async {
 
 const _wrongContents = '''
 timestamp: 2023-07-28 14:22:45.000
-assets: []
+encodedAssets: []
 dependencies: []
 metadata: {}
 version: 9001.0.0
diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output_3/hook/build.dart b/pkgs/native_assets_builder/test_data/wrong_build_output_3/hook/build.dart
index c0c95d799..cb50e5731 100644
--- a/pkgs/native_assets_builder/test_data/wrong_build_output_3/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/wrong_build_output_3/hook/build.dart
@@ -14,7 +14,7 @@ void main(List<String> args) async {
 
 const _rightContents = '''
 timestamp: 2023-07-28 14:22:45.000
-assets: []
+encodedAssets: []
 dependencies: []
 metadata: {}
 version: 1.0.0
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 b8730f013..8f3c9993c 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
@@ -15,7 +15,7 @@ void main(List<String> arguments) async {
       await File.fromUri(assetUri).writeAsBytes([1, 2, 3]);
     }
 
-    output.addAsset(
+    output.codeAssets.add(
       CodeAsset(
         package: config.packageName,
         name: 'foo',
diff --git a/pkgs/native_assets_builder/test_data/wrong_namespace_asset/hook/build.dart b/pkgs/native_assets_builder/test_data/wrong_namespace_asset/hook/build.dart
index 0cf220b41..66ad30529 100644
--- a/pkgs/native_assets_builder/test_data/wrong_namespace_asset/hook/build.dart
+++ b/pkgs/native_assets_builder/test_data/wrong_namespace_asset/hook/build.dart
@@ -15,7 +15,7 @@ void main(List<String> arguments) async {
       await File.fromUri(assetUri).writeAsBytes([1, 2, 3]);
     }
 
-    output.addAsset(
+    output.codeAssets.add(
       CodeAsset(
         package: 'other_package',
         name: 'foo',
diff --git a/pkgs/native_assets_cli/CHANGELOG.md b/pkgs/native_assets_cli/CHANGELOG.md
index 3805a1643..71aff12e5 100644
--- a/pkgs/native_assets_cli/CHANGELOG.md
+++ b/pkgs/native_assets_cli/CHANGELOG.md
@@ -20,6 +20,9 @@
 - **Breaking change** Moved some methods to be extension methods.
 - Some classes in the `BuildConfig` and `BuildOutput` now expose `fromJson` and
   `toJson`.
+- **Breaking change** Removed `Asset` class, removed `{Build,Link}Output.assets*`.
+   Hook writers should now use e.g. `output.dataAssets.add(DataAsset(...))`
+   instead of `output.addAsset(DataAsset(...))`.
 
 ## 0.8.0
 
diff --git a/pkgs/native_assets_cli/example/build/local_asset/hook/build.dart b/pkgs/native_assets_cli/example/build/local_asset/hook/build.dart
index 19fa40160..280945122 100644
--- a/pkgs/native_assets_cli/example/build/local_asset/hook/build.dart
+++ b/pkgs/native_assets_cli/example/build/local_asset/hook/build.dart
@@ -30,7 +30,7 @@ Future<void> main(List<String> args) async {
       ]);
     }
 
-    output.addAsset(
+    output.codeAssets.add(
       // TODO: Change to DataAsset once the Dart/Flutter SDK can consume it.
       CodeAsset(
         package: packageName,
diff --git a/pkgs/native_assets_cli/example/build/local_asset/test/build_test.dart b/pkgs/native_assets_cli/example/build/local_asset/test/build_test.dart
index 0befd2e4a..eda5bcd25 100644
--- a/pkgs/native_assets_cli/example/build/local_asset/test/build_test.dart
+++ b/pkgs/native_assets_cli/example/build/local_asset/test/build_test.dart
@@ -13,12 +13,12 @@ void main() {
     description: 'test my build hook',
     mainMethod: build.main,
     check: (_, output) {
-      expect(output.assets, isNotEmpty);
-      expect(output.assets.first, isA<CodeAsset>());
+      expect(output.codeAssets.all, isNotEmpty);
       expect(
-        (output.assets.first as CodeAsset).id,
+        output.codeAssets.all.first.id,
         'package:local_asset/asset.txt',
       );
     },
+    supportedAssetTypes: [CodeAsset.type],
   );
 }
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 08fa5e4e2..0ac49c85c 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
@@ -24,7 +24,7 @@ void main(List<String> args) async {
           .toFilePath(windows: false)
           .substring(config.packageRoot.toFilePath(windows: false).length);
 
-      output.addAsset(
+      output.dataAssets.add(
         DataAsset(
           package: packageName,
           name: name,
diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/hook/link.dart b/pkgs/native_assets_cli/example/link/package_with_assets/hook/link.dart
index a5cd73bfc..d77ea69bc 100644
--- a/pkgs/native_assets_cli/example/link/package_with_assets/hook/link.dart
+++ b/pkgs/native_assets_cli/example/link/package_with_assets/hook/link.dart
@@ -20,9 +20,8 @@ void main(List<String> args) async {
     final usedAssets = (usages.instancesOf(multiplyIdentifier) ?? []).map((e) =>
         (e.instanceConstant.fields.values.first as StringConstant).value);
 
-    output.addAssets(config.assets
-        .whereType<DataAsset>()
-        .where((asset) => usedAssets.contains(asset.name)));
+    output.dataAssets.addAll(config.dataAssets.all
+        .where((dataAsset) => usedAssets.contains(dataAsset.name)));
   });
 }
 
diff --git a/pkgs/native_assets_cli/lib/native_assets_cli.dart b/pkgs/native_assets_cli/lib/native_assets_cli.dart
index 21bc081b6..8bac2546a 100644
--- a/pkgs/native_assets_cli/lib/native_assets_cli.dart
+++ b/pkgs/native_assets_cli/lib/native_assets_cli.dart
@@ -15,9 +15,28 @@ export 'src/api/link.dart' show link;
 export 'src/api/link_config.dart' show LinkConfig;
 export 'src/api/linker.dart' show Linker;
 export 'src/architecture.dart' show Architecture;
-export 'src/asset.dart' show Asset, CodeAsset, DataAsset, OSLibraryNaming;
 export 'src/build_mode.dart' show BuildMode;
 export 'src/c_compiler_config.dart' show CCompilerConfig;
+export 'src/code_assets/code_asset.dart'
+    show
+        BuildOutputCodeAssets,
+        CodeAsset,
+        CodeAssetsBuildOutput,
+        CodeAssetsLinkConfig,
+        CodeAssetsLinkOutput,
+        LinkConfigCodeAssets,
+        LinkOutputCodeAssets,
+        OSLibraryNaming;
+export 'src/data_assets/data_asset.dart'
+    show
+        BuildOutputDataAssets,
+        DataAsset,
+        DataAssetsBuildOutput,
+        DataAssetsLinkConfig,
+        DataAssetsLinkOutput,
+        LinkConfigDataAssets,
+        LinkOutputDataAssets;
+export 'src/encoded_asset.dart' show EncodedAsset;
 export 'src/ios_sdk.dart' show IOSSdk;
 export 'src/link_mode.dart'
     show
diff --git a/pkgs/native_assets_cli/lib/native_assets_cli_internal.dart b/pkgs/native_assets_cli/lib/native_assets_cli_internal.dart
index 2d1b478c9..173e9086e 100644
--- a/pkgs/native_assets_cli/lib/native_assets_cli_internal.dart
+++ b/pkgs/native_assets_cli/lib/native_assets_cli_internal.dart
@@ -21,11 +21,18 @@ export 'src/api/build_config.dart' show BuildConfigImpl;
 export 'src/api/build_output.dart' show HookOutputImpl;
 export 'src/api/hook_config.dart' show HookConfigImpl;
 export 'src/api/link_config.dart' show LinkConfigImpl;
-export 'src/asset.dart' show Asset, CodeAsset, DataAsset;
+export 'src/code_assets/validation.dart'
+    show
+        validateCodeAssetBuildOutput,
+        validateCodeAssetLinkOutput,
+        validateCodeAssetsInApplication;
+export 'src/data_assets/validation.dart'
+    show validateDataAssetBuildOutput, validateDataAssetLinkOutput;
+export 'src/encoded_asset.dart' show EncodedAsset;
 export 'src/model/dependencies.dart';
 export 'src/model/hook.dart';
 export 'src/model/metadata.dart';
 export 'src/model/resource_identifiers.dart';
 export 'src/model/target.dart' show Target;
-export 'src/validator/validator.dart'
-    show ValidateResult, validateBuild, validateLink, validateNoDuplicateDylibs;
+export 'src/validation.dart'
+    show ValidationErrors, validateBuildOutput, validateLinkOutput;
diff --git a/pkgs/native_assets_cli/lib/src/api/build.dart b/pkgs/native_assets_cli/lib/src/api/build.dart
index ef7dfd3fa..b1a0eae93 100644
--- a/pkgs/native_assets_cli/lib/src/api/build.dart
+++ b/pkgs/native_assets_cli/lib/src/api/build.dart
@@ -2,7 +2,7 @@
 // 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 '../validator/validator.dart';
+import '../validation.dart';
 import 'build_config.dart';
 import 'build_output.dart';
 
@@ -70,7 +70,7 @@ import 'build_output.dart';
 ///       ]);
 ///     }
 ///
-///     output.addAsset(
+///     output.codeAssets.add(
 ///       // TODO: Change to DataAsset once the Dart/Flutter SDK can consume it.
 ///       CodeAsset(
 ///         package: packageName,
@@ -91,13 +91,13 @@ Future<void> build(
   final config = BuildConfigImpl.fromArguments(arguments);
   final output = HookOutputImpl();
   await builder(config, output);
-  final validateResult = await validateBuild(config, output);
-  if (validateResult.success) {
+  final errors = await validateBuildOutput(config, output);
+  if (errors.isEmpty) {
     await output.writeToFile(config: config);
   } else {
     final message = [
       'The output contained unsupported output:',
-      for (final error in validateResult.errors) '- $error',
+      for (final error in errors) '- $error',
     ].join('\n');
     throw UnsupportedError(message);
   }
diff --git a/pkgs/native_assets_cli/lib/src/api/build_config.dart b/pkgs/native_assets_cli/lib/src/api/build_config.dart
index c7d880cfb..08c03389e 100644
--- a/pkgs/native_assets_cli/lib/src/api/build_config.dart
+++ b/pkgs/native_assets_cli/lib/src/api/build_config.dart
@@ -10,7 +10,6 @@ import 'package:pub_semver/pub_semver.dart';
 
 import '../architecture.dart';
 import '../args_parser.dart';
-import '../asset.dart';
 import '../build_mode.dart';
 import '../c_compiler_config.dart';
 import '../ios_sdk.dart';
@@ -48,8 +47,9 @@ abstract final class BuildConfig implements HookConfig {
 
   /// Whether link hooks will be run after the build hooks.
   ///
-  /// If [linkingEnabled] is true, [BuildOutput.addAsset] may be called with the
-  /// `linkInPackage` parameter so that assets can be linked in a link hook.
+  /// If [linkingEnabled] is true, [BuildOutput.addEncodedAsset] may be called
+  /// with the`linkInPackage` parameter so that assets can be linked in a link
+  /// hook.
   /// Linking is enabled in Flutter release builds and Dart AOT configurations.
   /// These configurations are optimized for app size.
   /// - `flutter build`
@@ -127,7 +127,7 @@ abstract final class BuildConfig implements HookConfig {
     required LinkModePreference linkModePreference,
     @Deprecated(metadataDeprecation)
     Map<String, Map<String, Object>>? dependencyMetadata,
-    Iterable<String>? supportedAssetTypes,
+    required Iterable<String> supportedAssetTypes,
     required bool linkingEnabled,
   }) =>
       BuildConfigImpl(
@@ -169,7 +169,7 @@ abstract final class BuildConfig implements HookConfig {
     required OS targetOS,
     required LinkModePreference linkModePreference,
     required bool linkingEnabled,
-    Iterable<String>? supportedAssetTypes,
+    required Iterable<String> supportedAssetTypes,
   }) =>
       BuildConfigImpl.dryRun(
         outputDirectory: outputDirectory,
diff --git a/pkgs/native_assets_cli/lib/src/api/build_output.dart b/pkgs/native_assets_cli/lib/src/api/build_output.dart
index d15bc4b8e..a435bf066 100644
--- a/pkgs/native_assets_cli/lib/src/api/build_output.dart
+++ b/pkgs/native_assets_cli/lib/src/api/build_output.dart
@@ -9,7 +9,8 @@ import 'package:collection/collection.dart';
 import 'package:pub_semver/pub_semver.dart';
 
 import '../architecture.dart';
-import '../asset.dart';
+import '../encoded_asset.dart';
+import '../json_utils.dart';
 import '../model/dependencies.dart';
 import '../model/metadata.dart';
 import '../os.dart';
@@ -51,7 +52,7 @@ abstract final class BuildOutput {
   ///
   /// In dry runs, the assets for all [Architecture]s for the [OS] specified in
   /// the dry run must be provided.
-  Iterable<Asset> get assets;
+  Iterable<EncodedAsset> get encodedAssets;
 
   /// The assets produced by this build which should be linked.
   ///
@@ -61,7 +62,7 @@ abstract final class BuildOutput {
   ///
   /// In dry runs, the assets for all [Architecture]s for the [OS] specified in
   /// the dry run must be provided.
-  Map<String, Iterable<Asset>> get assetsForLinking;
+  Map<String, Iterable<EncodedAsset>> get encodedAssetsForLinking;
 
   /// The files used by this build.
   ///
@@ -83,10 +84,9 @@ abstract final class BuildOutput {
   /// because [File.lastModified] is rounded to whole seconds and caching logic
   /// compares these timestamps.
   ///
-  /// The [Asset]s produced by this build or dry-run can be provided to the
-  /// constructor as [assets], or can be added later using [addAsset] and
-  /// [addAssets]. In dry runs, the [Architecture] for [CodeAsset]s can be
-  /// omitted.
+  /// The [EncodedAsset]s produced by this build or dry-run can be provided to
+  /// the constructor as [encodedAssets], or can be added later using
+  /// [addEncodedAsset] and [addEncodedAssets].
   ///
   /// The files used by this build must be provided to the constructor as
   /// [dependencies], or can be added later with [addDependency] and
@@ -100,31 +100,55 @@ abstract final class BuildOutput {
   /// [addMetadatum] and [addMetadata].
   factory BuildOutput({
     DateTime? timestamp,
-    Iterable<Asset>? assets,
+    Iterable<EncodedAsset>? encodedAssets,
     Iterable<Uri>? dependencies,
     @Deprecated(metadataDeprecation) Map<String, Object>? metadata,
   }) =>
       HookOutputImpl(
         timestamp: timestamp,
-        assets: assets?.cast<Asset>().toList(),
+        encodedAssets: encodedAssets?.toList(),
         dependencies: Dependencies([...?dependencies]),
         metadata: Metadata({...?metadata}),
       );
 
-  /// Adds [Asset]s produced by this build or dry run.
+  /// Adds [EncodedAsset]s produced by this build or dry run.
   ///
   /// 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.
-  void addAsset(Asset asset, {String? linkInPackage});
-
-  /// Adds [Asset]s produced by this build or dry run.
+  ///
+  /// Note to hook writers. Prefer using the `.add` method on the extension for
+  /// the specific asset type being added:
+  ///
+  /// ```dart
+  /// main(List<String> arguments) async {
+  ///   await build((config, output) {
+  ///     output.codeAssets.add(CodeAsset(...));
+  ///     output.dataAssets.add(DataAsset(...));
+  ///   });
+  /// }
+  /// ```
+  void addEncodedAsset(EncodedAsset asset, {String? linkInPackage});
+
+  /// Adds [EncodedAsset]s produced by this build or dry run.
   ///
   /// 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.
-  void addAssets(Iterable<Asset> assets, {String? linkInPackage});
+  ///
+  /// Note to hook writers. Prefer using the `.addAll` method on the extension
+  /// for the specific asset type being added:
+  ///
+  /// ```dart
+  /// main(List<String> arguments) async {
+  ///   await build((config, output) {
+  ///     output.codeAssets.addAll([CodeAsset(...), ...]);
+  ///     output.dataAssets.addAll([DataAsset(...), ...]);
+  ///   });
+  /// }
+  /// ```
+  void addEncodedAssets(Iterable<EncodedAsset> assets, {String? linkInPackage});
 
   /// Adds file used by this build.
   ///
diff --git a/pkgs/native_assets_cli/lib/src/api/builder.dart b/pkgs/native_assets_cli/lib/src/api/builder.dart
index 11b8a6d18..99d21c716 100644
--- a/pkgs/native_assets_cli/lib/src/api/builder.dart
+++ b/pkgs/native_assets_cli/lib/src/api/builder.dart
@@ -59,11 +59,11 @@ import 'linker.dart';
 /// The builder is designed to immediately operate on [BuildOutput]. If a
 /// builder should output something else than standard, it should be
 /// configurable through a constructor parameter. For example to send an asset
-/// for linking to the output ([BuildOutput.addAsset] with `linkInPackage` set),
-/// the builder should have a constructor parameter. (Instead of capturing the
-/// BuildOutput as a return value and manually manipulating it in the build
-/// hook.) This ensures that builder is in control of what combination of build
-/// outputs are valid.
+/// for linking to the output ([BuildOutput.addEncodedAsset] with
+/// `linkInPackage` set), the builder should have a constructor parameter.
+/// (Instead of capturing the  BuildOutput as a return value and manually
+/// manipulating it in the build hook.) This ensures that builder is in control
+/// of what combination of build outputs are valid.
 abstract interface class Builder {
   /// Runs this build.
   ///
diff --git a/pkgs/native_assets_cli/lib/src/api/hook_config.dart b/pkgs/native_assets_cli/lib/src/api/hook_config.dart
index de31b34a1..f43e62e3a 100644
--- a/pkgs/native_assets_cli/lib/src/api/hook_config.dart
+++ b/pkgs/native_assets_cli/lib/src/api/hook_config.dart
@@ -10,9 +10,10 @@ import 'package:crypto/crypto.dart';
 import 'package:pub_semver/pub_semver.dart';
 
 import '../architecture.dart';
-import '../asset.dart';
 import '../build_mode.dart';
 import '../c_compiler_config.dart';
+import '../code_assets/code_asset.dart';
+import '../data_assets/data_asset.dart';
 import '../ios_sdk.dart';
 import '../json_utils.dart';
 import '../link_mode.dart';
diff --git a/pkgs/native_assets_cli/lib/src/api/link.dart b/pkgs/native_assets_cli/lib/src/api/link.dart
index 82ae3acc2..98edb9e65 100644
--- a/pkgs/native_assets_cli/lib/src/api/link.dart
+++ b/pkgs/native_assets_cli/lib/src/api/link.dart
@@ -2,8 +2,7 @@
 // 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 '../model/dependencies.dart';
-import '../validator/validator.dart';
+import '../validation.dart';
 import 'build_output.dart';
 import 'link_config.dart';
 
@@ -13,7 +12,7 @@ import 'link_config.dart';
 /// files. Each individual asset is assigned a unique asset ID.
 ///
 /// The linking script may receive assets from build scripts, which are accessed
-/// through [LinkConfig.assets]. They will only be bundled with the final
+/// through [LinkConfig.encodedAssets]. They will only be bundled with the final
 /// application if included in the [LinkOutput].
 ///
 ///
@@ -22,9 +21,9 @@ import 'link_config.dart';
 ///
 /// void main(List<String> args) async {
 ///   await link(args, (config, output) async {
-///     final dataAssets = config.assets
+///     final dataEncodedAssets = config.assets
 ///         .whereType<DataAsset>();
-///     output.addAssets(dataAssets);
+///     output.addEncodedAssets(dataEncodedAssets);
 ///   });
 /// }
 /// ```
@@ -34,21 +33,15 @@ Future<void> link(
 ) async {
   final config = LinkConfig.fromArguments(arguments) as LinkConfigImpl;
 
-  // The built assets are dependencies of linking, as the linking should be
-  // rerun if they change.
-  final builtAssetsFiles =
-      config.assets.map((asset) => asset.file).whereType<Uri>().toList();
-  final output = HookOutputImpl(
-    dependencies: Dependencies(builtAssetsFiles),
-  );
+  final output = HookOutputImpl();
   await linker(config, output);
-  final validateResult = await validateLink(config, output);
-  if (validateResult.success) {
+  final errors = await validateLinkOutput(config, output);
+  if (errors.isEmpty) {
     await output.writeToFile(config: config);
   } else {
     final message = [
       'The output contained unsupported output:',
-      for (final error in validateResult.errors) '- $error',
+      for (final error in errors) '- $error',
     ].join('\n');
     throw UnsupportedError(message);
   }
diff --git a/pkgs/native_assets_cli/lib/src/api/link_config.dart b/pkgs/native_assets_cli/lib/src/api/link_config.dart
index 0353d0512..a029a460d 100644
--- a/pkgs/native_assets_cli/lib/src/api/link_config.dart
+++ b/pkgs/native_assets_cli/lib/src/api/link_config.dart
@@ -10,9 +10,9 @@ import 'package:pub_semver/pub_semver.dart';
 
 import '../architecture.dart';
 import '../args_parser.dart';
-import '../asset.dart';
 import '../build_mode.dart';
 import '../c_compiler_config.dart';
+import '../encoded_asset.dart';
 import '../ios_sdk.dart';
 import '../json_utils.dart';
 import '../link_mode_preference.dart';
@@ -27,11 +27,11 @@ part '../model/link_config.dart';
 /// The configuration for a link hook (`hook/link.dart`) invocation.
 ///
 /// It consists of a subset of the fields from the [BuildConfig] already passed
-/// to the build hook and the [assets] from the build step.
+/// to the build hook and the [encodedAssets] from the build step.
 abstract class LinkConfig implements HookConfig {
   /// The list of assets to be linked. These are the assets generated by a
   /// `build.dart` script destined for this packages `link.dart`.
-  Iterable<Asset> get assets;
+  Iterable<EncodedAsset> get encodedAssets;
 
   /// The path to the file containing recorded uses after kernel tree-shaking.
   ///
@@ -55,15 +55,15 @@ abstract class LinkConfig implements HookConfig {
     int? targetMacOSVersion,
     CCompilerConfig? cCompiler,
     BuildMode? buildMode,
-    Iterable<String>? supportedAssetTypes,
+    required Iterable<String> supportedAssetTypes,
     int? targetAndroidNdkApi,
-    required Iterable<Asset> assets,
+    required Iterable<EncodedAsset> assets,
     required LinkModePreference linkModePreference,
     bool? dryRun,
     Version? version,
   }) =>
       LinkConfigImpl(
-        assets: assets.cast(),
+        encodedAssets: assets,
         outputDirectory: outputDirectory,
         outputDirectoryShared: outputDirectoryShared,
         packageName: packageName,
@@ -88,13 +88,13 @@ abstract class LinkConfig implements HookConfig {
     required String packageName,
     required Uri packageRoot,
     required OS targetOS,
-    Iterable<String>? supportedAssetTypes,
-    required Iterable<Asset> assets,
+    required Iterable<String> supportedAssetTypes,
+    required Iterable<EncodedAsset> assets,
     required LinkModePreference linkModePreference,
     Version? version,
   }) =>
       LinkConfigImpl.dryRun(
-        assets: assets.cast(),
+        encodedAssets: assets,
         outputDirectory: outputDirectory,
         outputDirectoryShared: outputDirectoryShared,
         packageName: packageName,
diff --git a/pkgs/native_assets_cli/lib/src/api/link_output.dart b/pkgs/native_assets_cli/lib/src/api/link_output.dart
index 77b7bb030..e90ace891 100644
--- a/pkgs/native_assets_cli/lib/src/api/link_output.dart
+++ b/pkgs/native_assets_cli/lib/src/api/link_output.dart
@@ -27,7 +27,7 @@ abstract final class LinkOutput {
   ///
   /// In dry runs, the assets for all [Architecture]s for the [OS] specified in
   /// the dry run must be provided.
-  Iterable<Asset> get assets;
+  Iterable<EncodedAsset> get encodedAssets;
 
   /// The files used by this link.
   ///
@@ -47,19 +47,19 @@ abstract final class LinkOutput {
   /// re-run.
   void addDependencies(Iterable<Uri> dependencies);
 
-  /// Adds [Asset]s produced by this link or dry run.
-  void addAsset(Asset asset);
+  /// Adds [EncodedAsset]s produced by this link or dry run.
+  void addEncodedAsset(EncodedAsset asset);
 
-  /// Adds [Asset]s produced by this link or dry run.
-  void addAssets(Iterable<Asset> assets);
+  /// Adds [EncodedAsset]s produced by this link or dry run.
+  void addEncodedAssets(Iterable<EncodedAsset> assets);
 
   factory LinkOutput({
-    Iterable<Asset>? assets,
+    Iterable<EncodedAsset>? encodedAssets,
     Dependencies? dependencies,
     DateTime? timestamp,
   }) =>
       HookOutputImpl(
-        assets: assets,
+        encodedAssets: encodedAssets,
         dependencies: dependencies,
         timestamp: timestamp,
       );
diff --git a/pkgs/native_assets_cli/lib/src/api/test.dart b/pkgs/native_assets_cli/lib/src/api/test.dart
index 4efd0dbd0..df1782c15 100644
--- a/pkgs/native_assets_cli/lib/src/api/test.dart
+++ b/pkgs/native_assets_cli/lib/src/api/test.dart
@@ -9,9 +9,10 @@ import 'package:test/test.dart';
 import 'package:yaml/yaml.dart';
 
 import '../architecture.dart';
-import '../asset.dart';
 import '../build_mode.dart';
 import '../c_compiler_config.dart';
+import '../code_assets/code_asset.dart';
+import '../data_assets/data_asset.dart';
 import '../ios_sdk.dart';
 import '../link_mode_preference.dart';
 import '../os.dart';
@@ -33,7 +34,7 @@ Future<void> testBuildHook({
   int? targetAndroidNdkApi,
   CCompilerConfig? cCompiler,
   LinkModePreference? linkModePreference,
-  Iterable<String>? supportedAssetTypes,
+  required Iterable<String> supportedAssetTypes,
   bool? linkingEnabled,
 }) async {
   test(
@@ -69,23 +70,27 @@ Future<void> testBuildHook({
 
       await mainMethod(['--config=${buildConfigUri.toFilePath()}']);
 
-      final hookOutput = await _readOutput(buildConfig);
+      final hookOutput = await _readOutput(buildConfig) as BuildOutput;
 
       check(buildConfig, hookOutput);
 
-      final allAssets = [
-        ...hookOutput.assets,
-        ...hookOutput.assetsForLinking.values.expand((e) => e)
+      final allEncodedAssets = [
+        ...hookOutput.encodedAssets,
+        ...hookOutput.encodedAssetsForLinking.values.expand((e) => e)
       ];
-      for (final asset in allAssets.where((asset) => asset.file != null)) {
-        final file = File.fromUri(asset.file!);
-        expect(await file.exists(), true);
+      for (final asset in allEncodedAssets) {
+        expect(buildConfig.supportedAssetTypes, contains(asset.type));
       }
-      if (allAssets.any((asset) => asset is CodeAsset)) {
-        expect(buildConfig.supportedAssetTypes, CodeAsset.type);
+
+      for (final asset in hookOutput.dataAssets.all) {
+        final file = File.fromUri(asset.file);
+        expect(await file.exists(), true);
       }
-      if (allAssets.any((asset) => asset is DataAsset)) {
-        expect(buildConfig.supportedAssetTypes, DataAsset.type);
+      for (final asset in hookOutput.codeAssets.all) {
+        if (asset.file != null) {
+          final file = File.fromUri(asset.file!);
+          expect(await file.exists(), true);
+        }
       }
     },
   );
diff --git a/pkgs/native_assets_cli/lib/src/asset.dart b/pkgs/native_assets_cli/lib/src/asset.dart
deleted file mode 100644
index ef2385bdc..000000000
--- a/pkgs/native_assets_cli/lib/src/asset.dart
+++ /dev/null
@@ -1,95 +0,0 @@
-// 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 'api/build_config.dart';
-import 'api/build_output.dart';
-import 'architecture.dart';
-import 'json_utils.dart';
-import 'link_mode.dart';
-import 'os.dart';
-import 'utils/map.dart';
-
-part 'code_asset.dart';
-part 'data_asset.dart';
-
-/// Data or code bundled with a Dart or Flutter application.
-///
-/// An asset is data or code which is accessible from a Dart or Flutter
-/// application. To access an asset at runtime, the asset [id] is used.
-abstract final class Asset {
-  /// The identifier for this asset.
-  ///
-  /// An [Asset] has a string identifier called "asset id". Dart code that uses
-  /// an asset references the asset using this asset id.
-  ///
-  /// An asset identifier consists of two elements, the `package` and `name`,
-  /// which together make a library uri `package:<package>/<name>`. The package
-  /// being part of the identifer prevents name collisions between assets of
-  /// different packages.
-  ///
-  /// The default asset id for an asset reference from `lib/src/foo.dart` is
-  /// `'package:foo/src/foo.dart'`. For example a [CodeAsset] can be accessed
-  /// via `@Native` with the `assetId` argument omitted:
-  ///
-  /// ```dart
-  /// // file package:foo/src/foo.dart
-  /// @Native<Int Function(Int, Int)>()
-  /// external int add(int a, int b);
-  /// ```
-  ///
-  /// This will be then automatically expanded to
-  ///
-  /// ```dart
-  /// // file package:foo/src/foo.dart
-  /// @Native<Int Function(Int, Int)>(assetId: 'package:foo/src/foo.dart')
-  /// external int add(int a, int b);
-  /// ```
-  String get id;
-
-  /// The file to be bundled with the Dart or Flutter application.
-  ///
-  /// How this file is bundled depends on the kind of asset, represented by a
-  /// concrete subtype of [Asset], and the SDK (Dart or Flutter).
-  ///
-  /// The file can be omitted in the [BuildOutput] for [BuildConfig.dryRun].
-  ///
-  /// The file can also be omitted for asset types which refer to an asset
-  /// already present on the target system or an asset already present in Dart
-  /// or Flutter.
-  Uri? get file;
-
-  /// A json representation of this [Asset].
-  Map<String, Object> toJson();
-
-  static List<Asset> listFromJson(List<Object?>? list) {
-    final assets = <Asset>[];
-    if (list == null) return assets;
-    for (var i = 0; i < list.length; ++i) {
-      final jsonMap = list.mapAt(i);
-      final type = jsonMap[_typeKey];
-      switch (type) {
-        case CodeAsset.type:
-          assets.add(CodeAsset.fromJson(jsonMap));
-        case DataAsset.type:
-          assets.add(DataAsset.fromJson(jsonMap));
-        default:
-        // Do nothing, some other launcher might define it's own asset types.
-      }
-    }
-    return assets;
-  }
-
-  static List<Map<String, Object>> listToJson(Iterable<Asset> assets) => [
-        for (final asset in assets) asset.toJson(),
-      ];
-}
-
-const _architectureKey = 'architecture';
-const _fileKey = 'file';
-const _idKey = 'id';
-const _linkModeKey = 'link_mode';
-const _nameKey = 'name';
-const _osKey = 'os';
-const _packageKey = 'package';
-const _typeKey = 'type';
diff --git a/pkgs/native_assets_cli/lib/src/code_asset.dart b/pkgs/native_assets_cli/lib/src/code_assets/code_asset.dart
similarity index 71%
rename from pkgs/native_assets_cli/lib/src/code_asset.dart
rename to pkgs/native_assets_cli/lib/src/code_assets/code_asset.dart
index 51789000e..2a13e71ca 100644
--- a/pkgs/native_assets_cli/lib/src/code_asset.dart
+++ b/pkgs/native_assets_cli/lib/src/code_assets/code_asset.dart
@@ -2,9 +2,18 @@
 // 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.
 
-part of 'asset.dart';
+import '../api/build_config.dart';
+import '../api/build_output.dart';
+import '../api/link_config.dart';
+import '../architecture.dart';
+import '../encoded_asset.dart';
+import '../json_utils.dart';
+import '../link_mode.dart';
+import '../os.dart';
+import '../utils/json.dart';
+import '../utils/map.dart';
 
-/// A code [Asset] which respects the native application binary interface (ABI).
+/// A code asset which respects the native application binary interface (ABI).
 ///
 /// Typical languages which produce code assets that respect the native ABI
 /// include C, C++ (with `extern "C"`), Rust (with `extern "C"`), and a subset
@@ -45,9 +54,8 @@ part of 'asset.dart';
 /// "manually", the Dart or Flutter SDK will take care of copying the asset
 /// [file] from its specified location on the current system into the
 /// application bundle.
-final class CodeAsset implements Asset {
+final class CodeAsset {
   /// The id of this code asset.
-  @override
   final String id;
 
   /// The operating system this asset can run on.
@@ -73,7 +81,6 @@ final class CodeAsset implements Asset {
   /// If the [linkMode] is [DynamicLoadingSystem], [LookupInProcess], or
   /// [LookupInExecutable] the file must be omitted in the [BuildOutput] for
   /// [BuildConfig.dryRun].
-  @override
   final Uri? file;
 
   /// Constructs a native code asset.
@@ -101,24 +108,18 @@ final class CodeAsset implements Asset {
     required this.os,
     required this.file,
     required this.architecture,
-  }) {
-    if (linkMode is DynamicLoading &&
-        linkMode is! DynamicLoadingBundled &&
-        file != null) {
-      throw ArgumentError.value(
-        file,
-        'file',
-        'Must be null if dynamicLoading is not BundledDylib.',
-      );
-    }
-  }
+  });
+
+  factory CodeAsset.fromEncoded(EncodedAsset asset) {
+    assert(asset.type == CodeAsset.type);
+    final jsonMap = asset.encoding;
 
-  factory CodeAsset.fromJson(Map<String, Object?> jsonMap) {
-    final linkMode = LinkMode.fromJson(jsonMap.map$(_linkModeKey));
+    final linkMode =
+        LinkMode.fromJson(as<Map<String, Object?>>(jsonMap[_linkModeKey]));
     final fileString = jsonMap.optionalString(_fileKey);
     final Uri? file;
     if (fileString != null) {
-      file = Uri(path: fileString);
+      file = Uri.file(fileString);
     } else {
       file = null;
     }
@@ -176,19 +177,72 @@ final class CodeAsset implements Asset {
         file,
       );
 
-  @override
-  Map<String, Object> toJson() => {
+  EncodedAsset encode() => EncodedAsset(
+      CodeAsset.type,
+      <String, Object>{
         if (architecture != null) _architectureKey: architecture.toString(),
         if (file != null) _fileKey: file!.toFilePath(),
         _idKey: id,
         _linkModeKey: linkMode.toJson(),
         _osKey: os.toString(),
-        _typeKey: CodeAsset.type,
-      }..sortOnKey();
+      }..sortOnKey());
 
   static const String type = 'native_code';
 }
 
+/// Build output extension for code assets.
+extension CodeAssetsBuildOutput on BuildOutput {
+  BuildOutputCodeAssets get codeAssets => BuildOutputCodeAssets(this);
+}
+
+extension type BuildOutputCodeAssets(BuildOutput _output) {
+  void add(CodeAsset asset, {String? linkInPackage}) =>
+      _output.addEncodedAsset(asset.encode(), linkInPackage: linkInPackage);
+
+  void addAll(Iterable<CodeAsset> assets, {String? linkInPackage}) {
+    for (final asset in assets) {
+      add(asset, linkInPackage: linkInPackage);
+    }
+  }
+
+  Iterable<CodeAsset> get all => _output.encodedAssets
+      .where((e) => e.type == CodeAsset.type)
+      .map(CodeAsset.fromEncoded);
+}
+
+/// Link output extension for code assets.
+extension CodeAssetsLinkConfig on LinkConfig {
+  LinkConfigCodeAssets get codeAssets => LinkConfigCodeAssets(this);
+}
+
+extension type LinkConfigCodeAssets(LinkConfig _config) {
+  // Returns the code assets that were sent to this linker.
+  //
+  // NOTE: If the linker implementation depends on the contents of the files the
+  // code assets refer (e.g. looks at static archives and links them) then the
+  // linker script has to add those files as dependencies via
+  // [LinkOutput.addDependency] to ensure the linker script will be re-run if
+  // the content of the files changes.
+  Iterable<CodeAsset> get all => _config.encodedAssets
+      .where((e) => e.type == CodeAsset.type)
+      .map(CodeAsset.fromEncoded);
+}
+
+/// Link output extension for code assets.
+extension CodeAssetsLinkOutput on LinkOutput {
+  LinkOutputCodeAssets get codeAssets => LinkOutputCodeAssets(this);
+}
+
+extension type LinkOutputCodeAssets(LinkOutput _output) {
+  void add(CodeAsset asset) => _output.addEncodedAsset(asset.encode());
+
+  void addAll(Iterable<CodeAsset> assets) => assets.forEach(add);
+
+  Iterable<CodeAsset> get all => _output.encodedAssets
+      .where((e) => e.type == CodeAsset.type)
+      .map(CodeAsset.fromEncoded);
+}
+
 extension OSLibraryNaming on OS {
   /// The default dynamic library file name on this os.
   String dylibFileName(String name) {
@@ -263,3 +317,9 @@ const _executableExtension = {
   OS.macOS: '',
   OS.windows: 'exe',
 };
+
+const _idKey = 'id';
+const _linkModeKey = 'link_mode';
+const _fileKey = 'file';
+const _osKey = 'os';
+const _architectureKey = 'architecture';
diff --git a/pkgs/native_assets_cli/lib/src/code_assets/validation.dart b/pkgs/native_assets_cli/lib/src/code_assets/validation.dart
new file mode 100644
index 000000000..4910f2ed6
--- /dev/null
+++ b/pkgs/native_assets_cli/lib/src/code_assets/validation.dart
@@ -0,0 +1,144 @@
+// 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 'dart:io';
+
+import '../../native_assets_cli_internal.dart';
+import '../link_mode.dart';
+
+Future<ValidationErrors> validateCodeAssetBuildOutput(
+  HookConfig config,
+  BuildOutput output,
+) =>
+    _validateCodeAssetBuildOrLinkOutput(config, output as HookOutputImpl, true);
+
+Future<ValidationErrors> validateCodeAssetLinkOutput(
+  HookConfig config,
+  LinkOutput output,
+) =>
+    _validateCodeAssetBuildOrLinkOutput(
+        config, output as HookOutputImpl, false);
+
+/// Validates that the given code assets can be used together in an application.
+///
+/// Some restrictions - e.g. unique shared library names - have to be validated
+/// on the entire application build and not on individual `hook/build.dart`
+/// invocations.
+Future<ValidationErrors> validateCodeAssetsInApplication(
+    List<EncodedAsset> assets) async {
+  final fileNameToEncodedAssetId = <String, Set<String>>{};
+  for (final asset in assets) {
+    if (asset.type != CodeAsset.type) continue;
+    _groupCodeAssetsByFilename(
+        CodeAsset.fromEncoded(asset), fileNameToEncodedAssetId);
+  }
+  final errors = <String>[];
+  _validateNoDuplicateDylibNames(errors, fileNameToEncodedAssetId);
+  return errors;
+}
+
+Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput(
+  HookConfig config,
+  HookOutputImpl output,
+  bool isBuild,
+) async {
+  final errors = <String>[];
+  final ids = <String>{};
+  final fileNameToEncodedAssetId = <String, Set<String>>{};
+
+  for (final asset in output.encodedAssets) {
+    if (asset.type != CodeAsset.type) continue;
+    _validateCodeAssets(
+      config,
+      config.dryRun,
+      CodeAsset.fromEncoded(asset),
+      errors,
+      ids,
+      isBuild,
+    );
+    _groupCodeAssetsByFilename(
+        CodeAsset.fromEncoded(asset), fileNameToEncodedAssetId);
+  }
+  _validateNoDuplicateDylibNames(errors, fileNameToEncodedAssetId);
+  return errors;
+}
+
+void _validateCodeAssets(
+  HookConfig config,
+  bool dryRun,
+  CodeAsset codeAsset,
+  List<String> errors,
+  Set<String> ids,
+  bool isBuild,
+) {
+  final id = codeAsset.id;
+  final prefix = 'package:${config.packageName}/';
+  if (isBuild && !id.startsWith(prefix)) {
+    errors.add('Code asset "$id" does not start with "$prefix".');
+  }
+  if (!ids.add(id)) {
+    errors.add('More than one code asset with same "$id" id.');
+  }
+
+  final preference = config.linkModePreference;
+  final linkMode = codeAsset.linkMode;
+  if ((linkMode is DynamicLoading && preference == LinkModePreference.static) ||
+      (linkMode is StaticLinking && preference == LinkModePreference.dynamic)) {
+    errors.add('CodeAsset "$id" has a link mode "$linkMode", which '
+        'is not allowed by by the config link mode preference '
+        '"$preference".');
+  }
+
+  final os = codeAsset.os;
+  if (config.targetOS != os) {
+    final error = 'CodeAsset "$id" has a os "$os", which '
+        'is not the target os "${config.targetOS}".';
+    errors.add(error);
+  }
+
+  final architecture = codeAsset.architecture;
+  if (!dryRun) {
+    if (architecture == null) {
+      errors.add('CodeAsset "$id" has no architecture.');
+    } else if (architecture != config.targetArchitecture) {
+      errors.add('CodeAsset "$id" has an architecture "$architecture", which '
+          'is not the target architecture "${config.targetArchitecture}".');
+    }
+  }
+
+  final file = codeAsset.file;
+  if (file == null && !dryRun) {
+    errors.add('CodeAsset "$id" has no file.');
+  }
+  if (file != null && !dryRun && !File.fromUri(file).existsSync()) {
+    errors.add('CodeAsset "$id" has a file "${file.toFilePath()}", which '
+        'does not exist.');
+  }
+}
+
+void _groupCodeAssetsByFilename(
+  CodeAsset codeAsset,
+  Map<String, Set<String>> fileNameToEncodedAssetId,
+) {
+  final file = codeAsset.file;
+  if (file != null) {
+    final fileName = file.pathSegments.where((s) => s.isNotEmpty).last;
+    fileNameToEncodedAssetId[fileName] ??= {};
+    fileNameToEncodedAssetId[fileName]!.add(codeAsset.id);
+  }
+}
+
+void _validateNoDuplicateDylibNames(
+    List<String> errors, Map<String, Set<String>> fileNameToEncodedAssetId) {
+  for (final fileName in fileNameToEncodedAssetId.keys) {
+    final assetIds = fileNameToEncodedAssetId[fileName]!;
+    if (assetIds.length > 1) {
+      final assetIdsString = assetIds.map((e) => '"$e"').join(', ');
+      final error =
+          'Duplicate dynamic library file name "$fileName" for the following'
+          ' asset ids: $assetIdsString.';
+      errors.add(error);
+    }
+  }
+}
diff --git a/pkgs/native_assets_cli/lib/src/data_asset.dart b/pkgs/native_assets_cli/lib/src/data_asset.dart
deleted file mode 100644
index f8493b875..000000000
--- a/pkgs/native_assets_cli/lib/src/data_asset.dart
+++ /dev/null
@@ -1,87 +0,0 @@
-// 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.
-
-part of 'asset.dart';
-
-/// Data bundled with a Dart or Flutter application.
-///
-/// A data asset is accessible in a Dart or Flutter application. To retrieve an
-/// asset at runtime, the [id] is used. This enables access to the asset
-/// irrespective of how and where the application is run.
-///
-/// An data asset must provide a [Asset.file]. The Dart and Flutter SDK will
-/// bundle this code in the final application.
-final class DataAsset extends Asset {
-  /// The file to be bundled with the Dart or Flutter application.
-  ///
-  /// The file can be omitted in the [BuildOutput] for [BuildConfig.dryRun].
-  ///
-  /// The file can also be omitted for asset types which refer to an asset
-  /// already present on the target system or an asset already present in Dart
-  /// or Flutter.
-  @override
-  final Uri file;
-
-  /// The name of this asset, which must be unique for the package.
-  final String name;
-
-  /// The package which contains this asset.
-  final String package;
-
-  /// The identifier for this data asset.
-  ///
-  /// An [DataAsset] has a string identifier called "asset id". Dart code that
-  /// uses an asset references the asset using this asset id.
-  ///
-  /// An asset identifier consists of two elements, the `package` and `name`,
-  /// which together make a library uri `package:<package>/<name>`. The package
-  /// being part of the identifer prevents name collisions between assets of
-  /// different packages.
-  @override
-  String get id => 'package:$package/$name';
-
-  DataAsset({
-    required this.file,
-    required this.name,
-    required this.package,
-  });
-
-  /// Constructs a [DataAsset] from a json representation obtained via
-  /// [DataAsset.toJson].
-  factory DataAsset.fromJson(Map<String, Object?> jsonMap) => DataAsset(
-        name: jsonMap.string(_nameKey),
-        package: jsonMap.string(_packageKey),
-        file: jsonMap.path(_fileKey),
-      );
-
-  @override
-  bool operator ==(Object other) {
-    if (other is! DataAsset) {
-      return false;
-    }
-    return other.package == package &&
-        other.file.toFilePath() == file.toFilePath() &&
-        other.name == name;
-  }
-
-  @override
-  int get hashCode => Object.hash(
-        package,
-        name,
-        file.toFilePath(),
-      );
-
-  @override
-  Map<String, Object> toJson() => {
-        _nameKey: name,
-        _packageKey: package,
-        _fileKey: file.toFilePath(),
-        _typeKey: DataAsset.type,
-      }..sortOnKey();
-
-  @override
-  String toString() => 'DataAsset(${toJson()})';
-
-  static const String type = 'data';
-}
diff --git a/pkgs/native_assets_cli/lib/src/data_assets/data_asset.dart b/pkgs/native_assets_cli/lib/src/data_assets/data_asset.dart
new file mode 100644
index 000000000..9c79b0629
--- /dev/null
+++ b/pkgs/native_assets_cli/lib/src/data_assets/data_asset.dart
@@ -0,0 +1,150 @@
+// 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 '../api/build_config.dart';
+import '../api/build_output.dart';
+import '../api/link_config.dart';
+import '../encoded_asset.dart';
+import '../json_utils.dart';
+import '../utils/map.dart';
+
+/// Data bundled with a Dart or Flutter application.
+///
+/// A data asset is accessible in a Dart or Flutter application. To retrieve an
+/// asset at runtime, the [id] is used. This enables access to the asset
+/// irrespective of how and where the application is run.
+///
+/// An data asset must provide a [DataAsset.file]. The Dart and Flutter SDK will
+/// bundle this code in the final application.
+final class DataAsset {
+  /// The file to be bundled with the Dart or Flutter application.
+  ///
+  /// The file can be omitted in the [BuildOutput] for [BuildConfig.dryRun].
+  ///
+  /// The file can also be omitted for asset types which refer to an asset
+  /// already present on the target system or an asset already present in Dart
+  /// or Flutter.
+  final Uri file;
+
+  /// The name of this asset, which must be unique for the package.
+  final String name;
+
+  /// The package which contains this asset.
+  final String package;
+
+  /// The identifier for this data asset.
+  ///
+  /// An [DataAsset] has a string identifier called "asset id". Dart code that
+  /// uses an asset references the asset using this asset id.
+  ///
+  /// An asset identifier consists of two elements, the `package` and `name`,
+  /// which together make a library uri `package:<package>/<name>`. The package
+  /// being part of the identifer prevents name collisions between assets of
+  /// different packages.
+  String get id => 'package:$package/$name';
+
+  DataAsset({
+    required this.file,
+    required this.name,
+    required this.package,
+  });
+
+  /// Constructs a [DataAsset] from an [EncodedAsset].
+  factory DataAsset.fromEncoded(EncodedAsset asset) {
+    assert(asset.type == DataAsset.type);
+    final jsonMap = asset.encoding;
+    return DataAsset(
+      name: jsonMap.string(_nameKey),
+      package: jsonMap.string(_packageKey),
+      file: jsonMap.path(_fileKey),
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other is! DataAsset) {
+      return false;
+    }
+    return other.package == package &&
+        other.file.toFilePath() == file.toFilePath() &&
+        other.name == name;
+  }
+
+  @override
+  int get hashCode => Object.hash(
+        package,
+        name,
+        file.toFilePath(),
+      );
+
+  EncodedAsset encode() => EncodedAsset(
+      DataAsset.type,
+      <String, Object>{
+        _nameKey: name,
+        _packageKey: package,
+        _fileKey: file.toFilePath(),
+      }..sortOnKey());
+
+  @override
+  String toString() => 'DataAsset(${encode().encoding})';
+
+  static const String type = 'data';
+}
+
+/// Build output extension for data assets.
+extension DataAssetsBuildOutput on BuildOutput {
+  BuildOutputDataAssets get dataAssets => BuildOutputDataAssets(this);
+}
+
+extension type BuildOutputDataAssets(BuildOutput _output) {
+  void add(DataAsset asset, {String? linkInPackage}) =>
+      _output.addEncodedAsset(asset.encode(), linkInPackage: linkInPackage);
+
+  void addAll(Iterable<DataAsset> assets, {String? linkInPackage}) {
+    for (final asset in assets) {
+      add(asset, linkInPackage: linkInPackage);
+    }
+  }
+
+  Iterable<DataAsset> get all => _output.encodedAssets
+      .where((e) => e.type == DataAsset.type)
+      .map(DataAsset.fromEncoded);
+}
+
+/// Link output extension for data assets.
+extension DataAssetsLinkConfig on LinkConfig {
+  LinkConfigDataAssets get dataAssets => LinkConfigDataAssets(this);
+}
+
+extension type LinkConfigDataAssets(LinkConfig _config) {
+  // Returns the data assets that were sent to this linker.
+  //
+  // NOTE: If the linker implementation depends on the contents of the files of
+  // the data assets (e.g. by transforming them, merging with other files, etc)
+  // then the linker script has to add those files as dependencies via
+  // [LinkOutput.addDependency] to ensure the linker script will be re-run if
+  // the content of the files changes.
+  Iterable<DataAsset> get all => _config.encodedAssets
+      .where((e) => e.type == DataAsset.type)
+      .map(DataAsset.fromEncoded);
+}
+
+/// Link output extension for data assets.
+extension DataAssetsLinkOutput on LinkOutput {
+  LinkOutputDataAssets get dataAssets => LinkOutputDataAssets(this);
+}
+
+extension type LinkOutputDataAssets(LinkOutput _output) {
+  void add(DataAsset asset) => _output.addEncodedAsset(asset.encode());
+
+  void addAll(Iterable<DataAsset> assets) => assets.forEach(add);
+
+  Iterable<DataAsset> get all => _output.encodedAssets
+      .where((e) => e.type == DataAsset.type)
+      .map(DataAsset.fromEncoded);
+}
+
+const _nameKey = 'name';
+const _packageKey = 'package';
+const _fileKey = 'file';
diff --git a/pkgs/native_assets_cli/lib/src/data_assets/validation.dart b/pkgs/native_assets_cli/lib/src/data_assets/validation.dart
new file mode 100644
index 000000000..d6dd9de36
--- /dev/null
+++ b/pkgs/native_assets_cli/lib/src/data_assets/validation.dart
@@ -0,0 +1,58 @@
+// 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 'dart:io';
+
+import '../../native_assets_cli_internal.dart';
+
+Future<ValidationErrors> validateDataAssetBuildOutput(
+  HookConfig config,
+  BuildOutput output,
+) =>
+    _validateDataAssetBuildOrLinkOutput(config, output as HookOutputImpl, true);
+
+Future<ValidationErrors> validateDataAssetLinkOutput(
+  HookConfig config,
+  LinkOutput output,
+) =>
+    _validateDataAssetBuildOrLinkOutput(
+        config, output as HookOutputImpl, false);
+
+Future<ValidationErrors> _validateDataAssetBuildOrLinkOutput(
+  HookConfig config,
+  HookOutputImpl output,
+  bool isBuild,
+) async {
+  final errors = <String>[];
+  final ids = <String>{};
+
+  for (final asset in output.encodedAssets) {
+    if (asset.type != DataAsset.type) continue;
+    _validateDataAssets(config, config.dryRun, DataAsset.fromEncoded(asset),
+        errors, ids, isBuild);
+  }
+  return errors;
+}
+
+void _validateDataAssets(
+  HookConfig config,
+  bool dryRun,
+  DataAsset dataAsset,
+  List<String> errors,
+  Set<String> ids,
+  bool isBuild,
+) {
+  if (isBuild && dataAsset.package != config.packageName) {
+    errors.add('Data asset must have package name ${config.packageName}');
+  }
+  if (!ids.add(dataAsset.name)) {
+    errors.add('More than one code asset with same "${dataAsset.name}" name.');
+  }
+  final file = dataAsset.file;
+  if (!dryRun && (!File.fromUri(file).existsSync())) {
+    errors.add(
+        'EncodedAsset "${dataAsset.name}" has a file "${file.toFilePath()}", '
+        'which does not exist.');
+  }
+}
diff --git a/pkgs/native_assets_cli/lib/src/encoded_asset.dart b/pkgs/native_assets_cli/lib/src/encoded_asset.dart
new file mode 100644
index 000000000..4232bb890
--- /dev/null
+++ b/pkgs/native_assets_cli/lib/src/encoded_asset.dart
@@ -0,0 +1,46 @@
+// 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:collection/collection.dart';
+
+import 'json_utils.dart';
+import 'utils/map.dart';
+
+/// An encoding of a particular asset type.
+final class EncodedAsset {
+  /// The type of the asset (e.g. whether it's a code asset, data asset or ...)
+  final String type;
+
+  /// The json encoding of the asset.
+  final Map<String, Object?> encoding;
+
+  EncodedAsset(this.type, this.encoding);
+
+  /// Decode an [EncodedAsset] from json.
+  factory EncodedAsset.fromJson(Map<String, Object?> json) =>
+      EncodedAsset(json.get<String>(_typeKey), {
+        for (final key in json.keys)
+          if (key != _typeKey) key: json[key]
+      });
+
+  /// Encode this [EncodedAsset] tojson.
+  Map<String, Object?> toJson() => {
+        for (final key in encoding.keys) key: encoding[key],
+        _typeKey: type,
+      }..sortOnKey();
+
+  @override
+  String toString() => 'EncodedAsset($type, $encoding)';
+
+  @override
+  int get hashCode => Object.hash(type, const DeepCollectionEquality().hash);
+
+  @override
+  bool operator ==(Object other) =>
+      other is EncodedAsset &&
+      type == other.type &&
+      const DeepCollectionEquality().equals(encoding, other.encoding);
+}
+
+const String _typeKey = 'type';
diff --git a/pkgs/native_assets_cli/lib/src/json_utils.dart b/pkgs/native_assets_cli/lib/src/json_utils.dart
index e177ff4a1..1680a1247 100644
--- a/pkgs/native_assets_cli/lib/src/json_utils.dart
+++ b/pkgs/native_assets_cli/lib/src/json_utils.dart
@@ -41,6 +41,8 @@ extension MapJsonUtils on Map<String, Object?> {
     return value.cast<String>();
   }
 
+  List<String> stringList(String key) => get<List<Object?>>(key).cast<String>();
+
   List<Object?> list(String key) => get<List<Object?>>(key);
   List<Object?>? optionalList(String key) => getOptional<List<Object?>>(key);
   Map<String, Object?> map$(String key) => get<Map<String, Object?>>(key);
diff --git a/pkgs/native_assets_cli/lib/src/link_mode.dart b/pkgs/native_assets_cli/lib/src/link_mode.dart
index 53b3295c7..bd02c1a7d 100644
--- a/pkgs/native_assets_cli/lib/src/link_mode.dart
+++ b/pkgs/native_assets_cli/lib/src/link_mode.dart
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'api/build_config.dart';
-import 'asset.dart';
+import 'code_assets/code_asset.dart';
 
 /// The link mode for a [CodeAsset].
 ///
diff --git a/pkgs/native_assets_cli/lib/src/link_mode_preference.dart b/pkgs/native_assets_cli/lib/src/link_mode_preference.dart
index f1d667515..f5a37580b 100644
--- a/pkgs/native_assets_cli/lib/src/link_mode_preference.dart
+++ b/pkgs/native_assets_cli/lib/src/link_mode_preference.dart
@@ -2,7 +2,7 @@
 // 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 'asset.dart';
+import 'code_assets/code_asset.dart';
 
 /// The preferred linkMode method for [CodeAsset]s.
 final class LinkModePreference {
diff --git a/pkgs/native_assets_cli/lib/src/model/build_config.dart b/pkgs/native_assets_cli/lib/src/model/build_config.dart
index c7a64f4e5..01b3ca7ce 100644
--- a/pkgs/native_assets_cli/lib/src/model/build_config.dart
+++ b/pkgs/native_assets_cli/lib/src/model/build_config.dart
@@ -40,11 +40,6 @@ final class BuildConfigImpl extends HookConfigImpl implements BuildConfig {
 
   final bool? _linkingEnabled;
 
-  static List<String> _supportedAssetTypesBackwardsCompatibility(
-    Iterable<String>? supportedAssetTypes,
-  ) =>
-      supportedAssetTypes?.toList() ?? [CodeAsset.type];
-
   BuildConfigImpl({
     required super.outputDirectory,
     required super.outputDirectoryShared,
@@ -53,7 +48,7 @@ final class BuildConfigImpl extends HookConfigImpl implements BuildConfig {
     Version? version,
     super.buildMode,
     super.cCompiler,
-    Iterable<String>? supportedAssetTypes,
+    required super.supportedAssetTypes,
     super.targetAndroidNdkApi,
     required super.targetArchitecture,
     super.targetIOSSdk,
@@ -69,8 +64,6 @@ final class BuildConfigImpl extends HookConfigImpl implements BuildConfig {
         super(
           hook: Hook.build,
           version: version ?? HookConfigImpl.latestVersion,
-          supportedAssetTypes:
-              _supportedAssetTypesBackwardsCompatibility(supportedAssetTypes),
         ) {
     if (this.version < Version(1, 4, 0)) {
       assert(linkingEnabled == null);
@@ -87,14 +80,12 @@ final class BuildConfigImpl extends HookConfigImpl implements BuildConfig {
     required super.targetOS,
     required super.linkModePreference,
     required bool? linkingEnabled,
-    Iterable<String>? supportedAssetTypes,
+    required super.supportedAssetTypes,
   })  : _dependencyMetadata = null,
         _linkingEnabled = linkingEnabled,
         super.dryRun(
           hook: Hook.build,
           version: HookConfigImpl.latestVersion,
-          supportedAssetTypes:
-              _supportedAssetTypesBackwardsCompatibility(supportedAssetTypes),
         );
 
   static BuildConfigImpl fromArguments(
@@ -131,7 +122,8 @@ final class BuildConfigImpl extends HookConfigImpl implements BuildConfig {
       linkingEnabled: parseHasLinkPhase(config),
       version: HookConfigImpl.parseVersion(config),
       cCompiler: HookConfigImpl.parseCCompiler(config, dryRun),
-      supportedAssetTypes: HookConfigImpl.parseSupportedAssetTypes(config),
+      supportedAssetTypes:
+          HookConfigImpl.parseSupportedEncodedAssetTypes(config),
       targetAndroidNdkApi:
           HookConfigImpl.parseTargetAndroidNdkApi(config, dryRun, targetOS),
       targetIOSSdk: HookConfigImpl.parseTargetIOSSdk(config, dryRun, targetOS),
diff --git a/pkgs/native_assets_cli/lib/src/model/hook_config.dart b/pkgs/native_assets_cli/lib/src/model/hook_config.dart
index 5445e76fd..e209ab38c 100644
--- a/pkgs/native_assets_cli/lib/src/model/hook_config.dart
+++ b/pkgs/native_assets_cli/lib/src/model/hook_config.dart
@@ -182,8 +182,7 @@ abstract class HookConfigImpl implements HookConfig {
       packageNameConfigKey: packageName,
       packageRootConfigKey: packageRoot.toFilePath(),
       _targetOSConfigKey: targetOS.toString(),
-      if (supportedAssetTypes.isNotEmpty)
-        supportedAssetTypesKey: supportedAssetTypes,
+      supportedAssetTypesKey: supportedAssetTypes,
       _versionKey: version.toString(),
       if (dryRun) dryRunConfigKey: dryRun,
       if (!dryRun) ...{
@@ -366,8 +365,9 @@ abstract class HookConfigImpl implements HookConfig {
     }
   }
 
-  static List<String> parseSupportedAssetTypes(Map<String, Object?> config) =>
-      config.optionalStringList(supportedAssetTypesKey) ?? [CodeAsset.type];
+  static List<String> parseSupportedEncodedAssetTypes(
+          Map<String, Object?> config) =>
+      config.optionalStringList(supportedAssetTypesKey) ?? [];
 
   static CCompilerConfig parseCCompiler(
       Map<String, Object?> config, bool dryRun) {
@@ -466,7 +466,7 @@ can _only_ depend on OS.''');
     CCompilerConfig? cCompiler,
     required LinkModePreference linkModePreference,
     Map<String, Metadata>? dependencyMetadata,
-    Iterable<String>? supportedAssetTypes,
+    required Iterable<String> supportedAssetTypes,
     Version? version,
     required Hook hook,
     required bool? linkingEnabled,
@@ -490,7 +490,7 @@ can _only_ depend on OS.''');
           entry.key,
           json.encode(entry.value.toJson()),
         ],
-      ...supportedAssetTypes ?? [CodeAsset.type],
+      ...supportedAssetTypes,
       hook.name,
       linkingEnabled,
     ].join('###');
diff --git a/pkgs/native_assets_cli/lib/src/model/build_output_CHANGELOG.md b/pkgs/native_assets_cli/lib/src/model/hook_config_CHANGELOG.md
similarity index 73%
rename from pkgs/native_assets_cli/lib/src/model/build_output_CHANGELOG.md
rename to pkgs/native_assets_cli/lib/src/model/hook_config_CHANGELOG.md
index 43daa84e6..b295f1428 100644
--- a/pkgs/native_assets_cli/lib/src/model/build_output_CHANGELOG.md
+++ b/pkgs/native_assets_cli/lib/src/model/hook_config_CHANGELOG.md
@@ -1,6 +1,15 @@
 ## 1.6.0
 
 - No changes, but rev version due to BuildConfig change.
+- **Breaking change** Link hooks now have to explicitly add any file contents
+  they rely on via `output.addDependency()` to ensure they re-run if the
+  content of those files changes. (Previously if a linker script obtained code
+  or data assets, the files referred to by those assets were implicitly added as
+  a dependency, but adding custom asset types changed this behavior)
+  NOTE: Newer Dart & Flutter SDKs will no longer add those dependencies
+  implicitly which may make some older linker implementations that do not add
+  dependencies explicitly not work correctly anymore: The linker scripts have
+  to be updated to add those dependencies explicitly.
 
 ## 1.5.0
 
diff --git a/pkgs/native_assets_cli/lib/src/model/hook_output.dart b/pkgs/native_assets_cli/lib/src/model/hook_output.dart
index ac8060586..0d48d9fb7 100644
--- a/pkgs/native_assets_cli/lib/src/model/hook_output.dart
+++ b/pkgs/native_assets_cli/lib/src/model/hook_output.dart
@@ -8,15 +8,16 @@ final class HookOutputImpl implements BuildOutput, LinkOutput {
   @override
   final DateTime timestamp;
 
-  final List<Asset> _assets;
+  final List<EncodedAsset> _assets;
 
   @override
-  Iterable<Asset> get assets => _assets;
+  Iterable<EncodedAsset> get encodedAssets => _assets;
 
-  final Map<String, List<Asset>> _assetsForLinking;
+  final Map<String, List<EncodedAsset>> _assetsForLinking;
 
   @override
-  Map<String, List<Asset>> get assetsForLinking => _assetsForLinking;
+  Map<String, List<EncodedAsset>> get encodedAssetsForLinking =>
+      _assetsForLinking;
 
   final Dependencies _dependencies;
 
@@ -29,15 +30,15 @@ final class HookOutputImpl implements BuildOutput, LinkOutput {
 
   HookOutputImpl({
     DateTime? timestamp,
-    Iterable<Asset>? assets,
-    Map<String, List<Asset>>? assetsForLinking,
+    Iterable<EncodedAsset>? encodedAssets,
+    Map<String, List<EncodedAsset>>? encodedAssetsForLinking,
     Dependencies? dependencies,
     Metadata? metadata,
   })  : timestamp = (timestamp ?? DateTime.now()).roundDownToSeconds(),
         _assets = [
-          ...?assets,
+          ...?encodedAssets,
         ],
-        _assetsForLinking = assetsForLinking ?? {},
+        _assetsForLinking = encodedAssetsForLinking ?? {},
         // ignore: prefer_const_constructors
         _dependencies = dependencies ?? Dependencies([]),
         // ignore: prefer_const_constructors
@@ -63,7 +64,7 @@ final class HookOutputImpl implements BuildOutput, LinkOutput {
     return HookOutputImpl.fromJson(as<Map<String, Object?>>(json));
   }
 
-  factory HookOutputImpl.fromJson(Map<Object?, Object?> jsonMap) {
+  factory HookOutputImpl.fromJson(Map<String, Object?> jsonMap) {
     final outputVersion = Version.parse(get<String>(jsonMap, 'version'));
     if (outputVersion.major > latestVersion.major) {
       throw FormatException(
@@ -81,11 +82,19 @@ final class HookOutputImpl implements BuildOutput, LinkOutput {
     }
     return HookOutputImpl(
       timestamp: DateTime.parse(get<String>(jsonMap, _timestampKey)),
-      assets: Asset.listFromJson(get<List<Object?>?>(jsonMap, _assetsKey)),
-      assetsForLinking:
-          get<Map<String, Object?>?>(jsonMap, _assetsForLinkingKey)?.map(
-              (packageName, assets) => MapEntry(
-                  packageName, Asset.listFromJson(as<List<Object?>>(assets)))),
+      encodedAssets: [
+        for (final json in jsonMap.optionalList(_assetsKey) ?? [])
+          EncodedAsset.fromJson(json as Map<String, Object?>),
+      ],
+      encodedAssetsForLinking: {
+        for (final MapEntry(:key, :value)
+            in (get<Map<String, Object?>?>(jsonMap, _assetsForLinkingKey) ?? {})
+                .entries)
+          key: [
+            for (final json in value as List<Object?>)
+              EncodedAsset.fromJson(json as Map<String, Object?>),
+          ],
+      },
       dependencies:
           Dependencies.fromJson(get<List<Object?>?>(jsonMap, _dependenciesKey)),
       metadata:
@@ -95,13 +104,16 @@ final class HookOutputImpl implements BuildOutput, LinkOutput {
 
   Map<String, Object> toJson(Version version) => {
         _timestampKey: timestamp.toString(),
-        if (_assets.isNotEmpty) _assetsKey: Asset.listToJson(_assets),
+        if (_assets.isNotEmpty)
+          _assetsKey: [
+            for (final asset in encodedAssets) asset.toJson(),
+          ],
         if (_assetsForLinking.isNotEmpty)
-          _assetsForLinkingKey:
-              _assetsForLinking.map((packageName, assets) => MapEntry(
-                    packageName,
-                    Asset.listToJson(assets),
-                  )),
+          _assetsForLinkingKey: {
+            for (final MapEntry(:key, :value)
+                in encodedAssetsForLinking.entries)
+              key: [for (final asset in value) asset.toJson()],
+          },
         if (_dependencies.dependencies.isNotEmpty)
           _dependenciesKey: _dependencies.toJson(),
         if (metadata.metadata.isNotEmpty) _metadataKey: metadata.toJson(),
@@ -154,7 +166,7 @@ final class HookOutputImpl implements BuildOutput, LinkOutput {
       return false;
     }
     return other.timestamp == timestamp &&
-        const ListEquality<Asset>().equals(other._assets, _assets) &&
+        const ListEquality<EncodedAsset>().equals(other._assets, _assets) &&
         other._dependencies == _dependencies &&
         other.metadata == metadata;
   }
@@ -162,7 +174,7 @@ final class HookOutputImpl implements BuildOutput, LinkOutput {
   @override
   int get hashCode => Object.hash(
         timestamp.hashCode,
-        const ListEquality<Asset>().hash(_assets),
+        const ListEquality<EncodedAsset>().hash(_assets),
         _dependencies,
         metadata,
       );
@@ -180,23 +192,26 @@ final class HookOutputImpl implements BuildOutput, LinkOutput {
   Metadata get metadataModel => metadata;
 
   @override
-  void addAsset(Asset asset, {String? linkInPackage}) {
-    _getAssetList(linkInPackage).add(asset);
+  void addEncodedAsset(EncodedAsset asset, {String? linkInPackage}) {
+    _getEncodedAssetList(linkInPackage).add(asset);
   }
 
   @override
-  void addAssets(Iterable<Asset> assets, {String? linkInPackage}) {
-    _getAssetList(linkInPackage).addAll(assets.cast());
+  void addEncodedAssets(Iterable<EncodedAsset> assets,
+      {String? linkInPackage}) {
+    _getEncodedAssetList(linkInPackage).addAll(assets.cast());
   }
 
-  List<Asset> _getAssetList(String? linkInPackage) => linkInPackage == null
-      ? _assets
-      : (_assetsForLinking[linkInPackage] ??= []);
+  List<EncodedAsset> _getEncodedAssetList(String? linkInPackage) =>
+      linkInPackage == null
+          ? _assets
+          : (_assetsForLinking[linkInPackage] ??= []);
 
-  HookOutputImpl copyWith({Iterable<Asset>? assets}) => HookOutputImpl(
+  HookOutputImpl copyWith({Iterable<EncodedAsset>? encodedAssets}) =>
+      HookOutputImpl(
         timestamp: timestamp,
-        assets: assets?.toList() ?? _assets,
-        assetsForLinking: assetsForLinking,
+        encodedAssets: encodedAssets?.toList() ?? _assets,
+        encodedAssetsForLinking: encodedAssetsForLinking,
         dependencies: _dependencies,
         metadata: metadata,
       );
diff --git a/pkgs/native_assets_cli/lib/src/model/link_config.dart b/pkgs/native_assets_cli/lib/src/model/link_config.dart
index f3507979a..0f46876a7 100644
--- a/pkgs/native_assets_cli/lib/src/model/link_config.dart
+++ b/pkgs/native_assets_cli/lib/src/model/link_config.dart
@@ -6,15 +6,15 @@ part of '../api/link_config.dart';
 
 /// The input to the linking script.
 ///
-/// It consists of the fields inherited from the [HookConfig] and the [assets]
-/// from the build step.
+/// It consists of the fields inherited from the [HookConfig] and the
+/// [encodedAssets] from the build step.
 class LinkConfigImpl extends HookConfigImpl implements LinkConfig {
   static const resourceIdentifierKey = 'resource_identifiers';
 
   static const assetsKey = 'assets';
 
   @override
-  final Iterable<Asset> assets;
+  final Iterable<EncodedAsset> encodedAssets;
 
   // TODO: Placeholder for the resources.json file URL. We don't want to change
   // native_assets_builder when implementing the parsing.
@@ -22,7 +22,7 @@ class LinkConfigImpl extends HookConfigImpl implements LinkConfig {
   final Uri? recordedUsagesFile;
 
   LinkConfigImpl({
-    required this.assets,
+    required this.encodedAssets,
     this.recordedUsagesFile,
     required super.outputDirectory,
     required super.outputDirectoryShared,
@@ -31,7 +31,7 @@ class LinkConfigImpl extends HookConfigImpl implements LinkConfig {
     Version? version,
     required super.buildMode,
     super.cCompiler,
-    Iterable<String>? supportedAssetTypes,
+    required super.supportedAssetTypes,
     super.targetAndroidNdkApi,
     super.targetArchitecture,
     super.targetIOSSdk,
@@ -43,24 +43,22 @@ class LinkConfigImpl extends HookConfigImpl implements LinkConfig {
   }) : super(
           hook: Hook.link,
           version: version ?? HookConfigImpl.latestVersion,
-          supportedAssetTypes: supportedAssetTypes ?? [CodeAsset.type],
         );
 
   LinkConfigImpl.dryRun({
-    required this.assets,
+    required this.encodedAssets,
     this.recordedUsagesFile,
     required super.outputDirectory,
     required super.outputDirectoryShared,
     required super.packageName,
     required super.packageRoot,
     Version? version,
-    Iterable<String>? supportedAssetTypes,
+    required super.supportedAssetTypes,
     required super.linkModePreference,
     required super.targetOS,
   }) : super.dryRun(
           hook: Hook.link,
           version: version ?? HookConfigImpl.latestVersion,
-          supportedAssetTypes: supportedAssetTypes ?? [CodeAsset.type],
         );
 
   @override
@@ -74,7 +72,10 @@ class LinkConfigImpl extends HookConfigImpl implements LinkConfig {
         ...hookToJson(),
         if (recordedUsagesFile != null)
           resourceIdentifierKey: recordedUsagesFile!.toFilePath(),
-        assetsKey: Asset.listToJson(assets),
+        if (encodedAssets.isNotEmpty)
+          assetsKey: [
+            for (final asset in encodedAssets) asset.toJson(),
+          ],
       }.sortOnKey();
 
   static LinkConfig fromArguments(List<String> arguments) {
@@ -101,7 +102,8 @@ class LinkConfigImpl extends HookConfigImpl implements LinkConfig {
       linkModePreference: HookConfigImpl.parseLinkModePreference(config),
       version: HookConfigImpl.parseVersion(config),
       cCompiler: HookConfigImpl.parseCCompiler(config, dryRun),
-      supportedAssetTypes: HookConfigImpl.parseSupportedAssetTypes(config),
+      supportedAssetTypes:
+          HookConfigImpl.parseSupportedEncodedAssetTypes(config),
       targetAndroidNdkApi:
           HookConfigImpl.parseTargetAndroidNdkApi(config, dryRun, targetOS),
       targetIOSSdk: HookConfigImpl.parseTargetIOSSdk(config, dryRun, targetOS),
@@ -109,7 +111,10 @@ class LinkConfigImpl extends HookConfigImpl implements LinkConfig {
           HookConfigImpl.parseTargetIosVersion(config, dryRun, targetOS),
       targetMacOSVersion:
           HookConfigImpl.parseTargetMacOSVersion(config, dryRun, targetOS),
-      assets: parseAssets(config),
+      encodedAssets: [
+        for (final json in config.optionalList(assetsKey) ?? [])
+          EncodedAsset.fromJson(json as Map<String, Object?>),
+      ],
       recordedUsagesFile: parseRecordedUsagesUri(config),
       dryRun: dryRun,
     );
@@ -118,9 +123,6 @@ class LinkConfigImpl extends HookConfigImpl implements LinkConfig {
   static Uri? parseRecordedUsagesUri(Map<String, Object?> config) =>
       config.optionalPath(resourceIdentifierKey);
 
-  static List<Asset> parseAssets(Map<String, Object?> config) =>
-      Asset.listFromJson(config.optionalList(assetsKey));
-
   @override
   bool operator ==(Object other) {
     if (super != other) {
@@ -132,7 +134,8 @@ class LinkConfigImpl extends HookConfigImpl implements LinkConfig {
     if (other.recordedUsagesFile != recordedUsagesFile) {
       return false;
     }
-    if (!const DeepCollectionEquality().equals(other.assets, assets)) {
+    if (!const DeepCollectionEquality()
+        .equals(other.encodedAssets, encodedAssets)) {
       return false;
     }
     return true;
@@ -142,7 +145,7 @@ class LinkConfigImpl extends HookConfigImpl implements LinkConfig {
   int get hashCode => Object.hashAll([
         super.hashCode,
         recordedUsagesFile,
-        const DeepCollectionEquality().hash(assets),
+        const DeepCollectionEquality().hash(encodedAssets),
       ]);
 
   @override
diff --git a/pkgs/native_assets_cli/lib/src/utils/map.dart b/pkgs/native_assets_cli/lib/src/utils/map.dart
index bb767ed99..8948770fb 100644
--- a/pkgs/native_assets_cli/lib/src/utils/map.dart
+++ b/pkgs/native_assets_cli/lib/src/utils/map.dart
@@ -2,13 +2,12 @@
 // 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.
 
-extension MapSorting<K extends Comparable<K>, V extends Object> on Map<K, V> {
+extension MapSorting<K extends Comparable<K>, V extends Object?> on Map<K, V> {
   Map<K, V> sortOnKey() {
     final result = <K, V>{};
     final keysSorted = keys.toList()..sort();
     for (final key in keysSorted) {
-      final value = this[key]!;
-      result[key] = value;
+      result[key] = this[key] as V;
     }
     return result;
   }
diff --git a/pkgs/native_assets_cli/lib/src/validation.dart b/pkgs/native_assets_cli/lib/src/validation.dart
new file mode 100644
index 000000000..5a17dc31f
--- /dev/null
+++ b/pkgs/native_assets_cli/lib/src/validation.dart
@@ -0,0 +1,69 @@
+// 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 '../native_assets_cli_internal.dart';
+
+typedef ValidationErrors = List<String>;
+
+/// Invoked by package:native_assets_builder
+Future<ValidationErrors> validateBuildOutput(
+  BuildConfig config,
+  BuildOutput output,
+) async {
+  final errors = [
+    ..._validateAssetsForLinking(config, output),
+    ..._validateOutputAssetTypes(config, output.encodedAssets),
+  ];
+  if (config.linkingEnabled) {
+    for (final assets in output.encodedAssetsForLinking.values) {
+      errors.addAll(_validateOutputAssetTypes(config, assets));
+    }
+  }
+  return errors;
+}
+
+/// Invoked by package:native_assets_builder
+Future<ValidationErrors> validateLinkOutput(
+  LinkConfig config,
+  LinkOutput output,
+) async {
+  final errors = [
+    ..._validateOutputAssetTypes(config, output.encodedAssets),
+  ];
+  return errors;
+}
+
+/// Only output asset types that are supported by the embedder.
+List<String> _validateOutputAssetTypes(
+  HookConfig config,
+  Iterable<EncodedAsset> assets,
+) {
+  final errors = <String>[];
+  final supportedAssetTypes = config.supportedAssetTypes;
+  for (final asset in assets) {
+    if (!supportedAssetTypes.contains(asset.type)) {
+      final error =
+          'Asset with type "${asset.type}" is not a supported asset type '
+          '(${supportedAssetTypes.join(' ')} are supported)';
+      errors.add(error);
+    }
+  }
+  return errors;
+}
+
+/// EncodedAssetsForLinking should be empty if linking is not supported.
+List<String> _validateAssetsForLinking(
+  BuildConfig config,
+  BuildOutput output,
+) {
+  final errors = <String>[];
+  if (!config.linkingEnabled) {
+    if (output.encodedAssetsForLinking.isNotEmpty) {
+      const error = 'BuildOutput.assetsForLinking is not empty while '
+          'BuildConfig.linkingEnabled is false';
+      errors.add(error);
+    }
+  }
+  return errors;
+}
diff --git a/pkgs/native_assets_cli/lib/src/validator/validator.dart b/pkgs/native_assets_cli/lib/src/validator/validator.dart
deleted file mode 100644
index 1cfc5f2ea..000000000
--- a/pkgs/native_assets_cli/lib/src/validator/validator.dart
+++ /dev/null
@@ -1,237 +0,0 @@
-// 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 'dart:io';
-
-import '../api/build_config.dart';
-import '../api/build_output.dart';
-import '../api/hook_config.dart';
-import '../api/link_config.dart';
-import '../asset.dart';
-import '../link_mode.dart';
-import '../link_mode_preference.dart';
-
-typedef ValidateResult = ({
-  bool success,
-  List<String> errors,
-});
-
-Future<ValidateResult> validateBuild(
-  BuildConfig config,
-  BuildOutput output,
-) async {
-  output as HookOutputImpl;
-  final errors = [
-    ...validateAssetsForLinking(config, output),
-    ...validateOutputAssetTypes(config, output),
-    if (!config.dryRun) ...await validateFilesExist(config, output),
-    ...validateCodeAssets(config, output),
-    ...validateAssetId(config, output),
-    if (!config.dryRun) ...validateNoDuplicateAssetIds(output),
-    ...validateNoDuplicateDylibs(output.assets),
-  ];
-  return (
-    success: errors.isEmpty,
-    errors: errors,
-  );
-}
-
-Future<ValidateResult> validateLink(
-  LinkConfig config,
-  LinkOutput output,
-) async {
-  output as HookOutputImpl;
-  final errors = [
-    ...validateOutputAssetTypes(config, output),
-    if (!config.dryRun) ...await validateFilesExist(config, output),
-    ...validateCodeAssets(config, output),
-    if (!config.dryRun) ...validateNoDuplicateAssetIds(output),
-    ...validateNoDuplicateDylibs(output.assets),
-  ];
-
-  return (
-    success: errors.isEmpty,
-    errors: errors,
-  );
-}
-
-/// AssetsForLinking should be empty if linking is not supported.
-List<String> validateAssetsForLinking(
-  BuildConfig config,
-  BuildOutput output,
-) {
-  final errors = <String>[];
-  if (!config.linkingEnabled) {
-    if (output.assetsForLinking.isNotEmpty) {
-      const error = 'BuildOutput.assetsForLinking is not empty while '
-          'BuildConfig.linkingEnabled is false';
-      errors.add(error);
-    }
-  }
-  return errors;
-}
-
-/// Only output asset types that are supported by the embedder.
-List<String> validateOutputAssetTypes(
-  HookConfig config,
-  HookOutputImpl output,
-) {
-  final errors = <String>[];
-  final supportedAssetTypes = config.supportedAssetTypes;
-  for (final asset in output.assets) {
-    if (!supportedAssetTypes.contains(asset.type)) {
-      final error =
-          'Asset "${asset.id}" has asset type "${asset.type}", which is '
-          'not in supportedAssetTypes';
-      errors.add(error);
-    }
-  }
-  return errors;
-}
-
-/// Files mentioned in assets must exist.
-Future<List<String>> validateFilesExist(
-  HookConfig config,
-  HookOutputImpl output,
-) async {
-  final errors = <String>[];
-
-  await Future.wait(output.allAssets.map((asset) async {
-    final file = asset.file;
-    if (file == null && !config.dryRun) {
-      final error = 'Asset "${asset.id}" has no file.';
-      errors.add(error);
-    }
-    if (file != null && !config.dryRun && !await File.fromUri(file).exists()) {
-      final error =
-          'Asset "${asset.id}" has a file "${asset.file!.toFilePath()}", which '
-          'does not exist.';
-      errors.add(error);
-    }
-  }));
-
-  return errors;
-}
-
-extension on Asset {
-  String get type {
-    switch (this) {
-      case CodeAsset _:
-        return CodeAsset.type;
-      case DataAsset _:
-        return DataAsset.type;
-    }
-    throw UnsupportedError('Unknown asset type');
-  }
-}
-
-extension on HookOutputImpl {
-  Iterable<Asset> get allAssets =>
-      [...assets, ...assetsForLinking.values.expand((e) => e)];
-}
-
-/// Native code assets for bundling should have a supported linking type.
-List<String> validateCodeAssets(
-  HookConfig config,
-  HookOutputImpl output,
-) {
-  final errors = <String>[];
-  final linkModePreference = config.linkModePreference;
-  for (final asset in output.assets.whereType<CodeAsset>()) {
-    final linkMode = asset.linkMode;
-    if ((linkMode is DynamicLoading &&
-            linkModePreference == LinkModePreference.static) ||
-        (linkMode is StaticLinking &&
-            linkModePreference == LinkModePreference.dynamic)) {
-      final error = 'Asset "${asset.id}" has a link mode "$linkMode", which '
-          'is not allowed by by the config link mode preference '
-          '"$linkModePreference".';
-      errors.add(error);
-    }
-
-    final os = asset.os;
-    if (config.targetOS != os) {
-      final error = 'Asset "${asset.id}" has a os "$os", which '
-          'is not the target os "${config.targetOS}".';
-      errors.add(error);
-    }
-
-    final architecture = asset.architecture;
-    if (!config.dryRun) {
-      if (architecture == null) {
-        final error = 'Asset "${asset.id}" has no architecture.';
-        errors.add(error);
-      } else if (architecture != config.targetArchitecture) {
-        final error =
-            'Asset "${asset.id}" has an architecture "$architecture", which '
-            'is not the target architecture "${config.targetArchitecture}".';
-        errors.add(error);
-      }
-    }
-  }
-  return errors;
-}
-
-/// Build hooks must only output assets in their own package namespace.
-List<String> validateAssetId(
-  HookConfig config,
-  BuildOutput output,
-) {
-  final errors = <String>[];
-  final packageName = config.packageName;
-  for (final asset in output.assets) {
-    if (!asset.id.startsWith('package:$packageName/')) {
-      final error = 'Asset "${asset.id}" does not start with '
-          '"package:$packageName/".';
-      errors.add(error);
-    }
-  }
-  return errors;
-}
-
-List<String> validateNoDuplicateAssetIds(
-  BuildOutput output,
-) {
-  final errors = <String>[];
-  final assetIds = <String>{};
-  for (final asset in output.assets) {
-    if (assetIds.contains(asset.id)) {
-      final error = 'Duplicate asset id: "${asset.id}".';
-      errors.add(error);
-    } else {
-      assetIds.add(asset.id);
-    }
-  }
-  return errors;
-}
-
-List<String> validateNoDuplicateDylibs(
-  Iterable<Asset> assets,
-) {
-  final errors = <String>[];
-  final fileNameToAssetId = <String, Set<String>>{};
-  for (final asset in assets.whereType<CodeAsset>()) {
-    if (asset.linkMode is! DynamicLoadingBundled) {
-      continue;
-    }
-    final file = asset.file;
-    if (file == null) {
-      continue;
-    }
-    final fileName = file.pathSegments.where((s) => s.isNotEmpty).last;
-    fileNameToAssetId[fileName] ??= {};
-    fileNameToAssetId[fileName]!.add(asset.id);
-  }
-  for (final fileName in fileNameToAssetId.keys) {
-    final assetIds = fileNameToAssetId[fileName]!;
-    if (assetIds.length > 1) {
-      final assetIdsString = assetIds.map((e) => '"$e"').join(', ');
-      final error =
-          'Duplicate dynamic library file name "$fileName" for the following'
-          ' asset ids: $assetIdsString.';
-      errors.add(error);
-    }
-  }
-  return errors;
-}
diff --git a/pkgs/native_assets_cli/test/api/asset_test.dart b/pkgs/native_assets_cli/test/api/asset_test.dart
index 92a6af506..8c260e281 100644
--- a/pkgs/native_assets_cli/test/api/asset_test.dart
+++ b/pkgs/native_assets_cli/test/api/asset_test.dart
@@ -69,18 +69,4 @@ void main() {
     LookupInProcess().toString();
     LookupInExecutable().toString();
   });
-
-  test('Errors', () {
-    expect(
-      () => CodeAsset(
-        package: 'my_package',
-        name: 'foo',
-        file: Uri.file('path/to/libfoo.so'),
-        linkMode: LookupInExecutable(),
-        os: OS.android,
-        architecture: Architecture.x64,
-      ),
-      throwsArgumentError,
-    );
-  });
 }
diff --git a/pkgs/native_assets_cli/test/api/build_config_test.dart b/pkgs/native_assets_cli/test/api/build_config_test.dart
index 0480536f7..613c60dce 100644
--- a/pkgs/native_assets_cli/test/api/build_config_test.dart
+++ b/pkgs/native_assets_cli/test/api/build_config_test.dart
@@ -119,10 +119,12 @@ void main() async {
       buildMode: BuildMode.release,
       linkModePreference: LinkModePreference.preferStatic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     final config = {
       'build_mode': 'release',
+      'supported_asset_types': [CodeAsset.type],
       'dry_run': false,
       'linking_enabled': false,
       'link_mode_preference': 'prefer-static',
@@ -154,6 +156,7 @@ void main() async {
 
     final config = {
       'dry_run': true,
+      'supported_asset_types': [CodeAsset.type],
       'linking_enabled': true,
       'link_mode_preference': 'prefer-static',
       'out_dir': outDirUri.toFilePath(),
@@ -189,6 +192,7 @@ void main() async {
         },
       },
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     final buildConfig2 = BuildConfig.build(
@@ -210,6 +214,7 @@ void main() async {
         },
       },
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     expect(buildConfig1, equals(buildConfig1));
@@ -230,6 +235,7 @@ void main() async {
       buildMode: BuildMode.release,
       linkModePreference: LinkModePreference.preferStatic,
       linkingEnabled: true,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     final buildConfig2 = BuildConfig.build(
@@ -242,6 +248,7 @@ void main() async {
       buildMode: BuildMode.release,
       linkModePreference: LinkModePreference.preferStatic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     expect(buildConfig1, equals(buildConfig1));
@@ -261,6 +268,7 @@ void main() async {
       buildMode: BuildMode.release,
       linkModePreference: LinkModePreference.preferStatic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
     final configFileContents = (buildConfig as BuildConfigImpl).toJsonString();
     final configUri = tempUri.resolve('config.json');
diff --git a/pkgs/native_assets_cli/test/api/build_output_test.dart b/pkgs/native_assets_cli/test/api/build_output_test.dart
index 797703871..9bd10f474 100644
--- a/pkgs/native_assets_cli/test/api/build_output_test.dart
+++ b/pkgs/native_assets_cli/test/api/build_output_test.dart
@@ -21,7 +21,7 @@ void main() {
   test('BuildOutput constructor', () {
     BuildOutput(
       timestamp: DateTime.parse('2022-11-10 13:25:01.000'),
-      assets: [
+      encodedAssets: [
         CodeAsset(
           package: 'my_package',
           name: 'foo',
@@ -29,14 +29,14 @@ void main() {
           linkMode: DynamicLoadingBundled(),
           os: OS.android,
           architecture: Architecture.x64,
-        ),
+        ).encode(),
         CodeAsset(
           package: 'my_package',
           name: 'foo2',
           linkMode: DynamicLoadingSystem(Uri(path: 'path/to/libfoo2.so')),
           os: OS.android,
           architecture: Architecture.x64,
-        ),
+        ).encode(),
       ],
       dependencies: [
         Uri.file('path/to/file.ext'),
diff --git a/pkgs/native_assets_cli/test/api/build_test.dart b/pkgs/native_assets_cli/test/api/build_test.dart
index 186aa7969..baae8eba6 100644
--- a/pkgs/native_assets_cli/test/api/build_test.dart
+++ b/pkgs/native_assets_cli/test/api/build_test.dart
@@ -59,6 +59,7 @@ void main() async {
       buildMode: BuildMode.release,
       linkModePreference: LinkModePreference.preferDynamic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
     final configJson = (config1 as BuildConfigImpl).toJsonString();
     buildConfigUri = tempUri.resolve('build_config.json');
diff --git a/pkgs/native_assets_cli/test/api/link_config_test.dart b/pkgs/native_assets_cli/test/api/link_config_test.dart
index c4cf243bd..bd739b65b 100644
--- a/pkgs/native_assets_cli/test/api/link_config_test.dart
+++ b/pkgs/native_assets_cli/test/api/link_config_test.dart
@@ -112,11 +112,13 @@ void main() async {
       buildMode: BuildMode.release,
       assets: [],
       linkModePreference: LinkModePreference.preferStatic,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     final config = {
       'build_mode': 'release',
       'dry_run': false,
+      'supported_asset_types': [CodeAsset.type],
       'link_mode_preference': 'prefer-static',
       'out_dir': outDirUri.toFilePath(),
       'out_dir_shared': outputDirectoryShared.toFilePath(),
@@ -147,6 +149,7 @@ void main() async {
 
     final config = {
       'dry_run': true,
+      'supported_asset_types': [CodeAsset.type],
       'link_mode_preference': 'prefer-static',
       'out_dir': outDirUri.toFilePath(),
       'out_dir_shared': outputDirectoryShared.toFilePath(),
@@ -173,6 +176,7 @@ void main() async {
       buildMode: BuildMode.release,
       assets: [],
       linkModePreference: LinkModePreference.preferStatic,
+      supportedAssetTypes: [CodeAsset.type],
     );
     final configFileContents = (linkConfig as LinkConfigImpl).toJsonString();
     final configUri = tempUri.resolve('config.json');
diff --git a/pkgs/native_assets_cli/test/example/local_asset_test.dart b/pkgs/native_assets_cli/test/example/local_asset_test.dart
index b556a402d..31f15c0a8 100644
--- a/pkgs/native_assets_cli/test/example/local_asset_test.dart
+++ b/pkgs/native_assets_cli/test/example/local_asset_test.dart
@@ -53,6 +53,7 @@ void main() async {
         targetArchitecture: dryRun ? null : Architecture.current,
         buildMode: dryRun ? null : BuildMode.debug,
         cCompiler: dryRun ? null : cCompiler,
+        supportedAssetTypes: [CodeAsset.type],
       );
 
       final buildConfigUri = testTempUri.resolve('build_config.json');
@@ -76,12 +77,13 @@ void main() async {
 
       final buildOutputUri = outputDirectory.resolve('build_output.json');
       final buildOutput = HookOutputImpl.fromJsonString(
-          await File.fromUri(buildOutputUri).readAsString());
-      final assets = buildOutput.assets;
+          await File.fromUri(buildOutputUri).readAsString()) as BuildOutput;
+      final assets = buildOutput.encodedAssets;
       final dependencies = buildOutput.dependencies;
       if (dryRun) {
+        final codeAsset = buildOutput.codeAssets.all.first;
         expect(assets.length, greaterThanOrEqualTo(1));
-        expect(await File.fromUri(assets.first.file!).exists(), false);
+        expect(await File.fromUri(codeAsset.file!).exists(), false);
         expect(dependencies, <Uri>[]);
       } else {
         expect(assets.length, 1);
diff --git a/pkgs/native_assets_cli/test/example/native_add_library_test.dart b/pkgs/native_assets_cli/test/example/native_add_library_test.dart
index 8f3fb3cb6..5c82eca99 100644
--- a/pkgs/native_assets_cli/test/example/native_add_library_test.dart
+++ b/pkgs/native_assets_cli/test/example/native_add_library_test.dart
@@ -53,6 +53,7 @@ void main() async {
         targetArchitecture: dryRun ? null : Architecture.current,
         buildMode: dryRun ? null : BuildMode.debug,
         cCompiler: dryRun ? null : cCompiler,
+        supportedAssetTypes: [CodeAsset.type],
       );
 
       final buildConfigUri = testTempUri.resolve('build_config.json');
@@ -77,7 +78,7 @@ void main() async {
       final buildOutputUri = outputDirectory.resolve('build_output.json');
       final buildOutput = HookOutputImpl.fromJsonString(
           await File.fromUri(buildOutputUri).readAsString());
-      final assets = buildOutput.assets;
+      final assets = buildOutput.encodedAssets;
       final dependencies = buildOutput.dependencies;
       if (dryRun) {
         expect(assets.length, greaterThanOrEqualTo(1));
diff --git a/pkgs/native_assets_cli/test/example/native_dynamic_linking_test.dart b/pkgs/native_assets_cli/test/example/native_dynamic_linking_test.dart
index 00b1f706b..4d7ea3feb 100644
--- a/pkgs/native_assets_cli/test/example/native_dynamic_linking_test.dart
+++ b/pkgs/native_assets_cli/test/example/native_dynamic_linking_test.dart
@@ -62,6 +62,7 @@ void main() async {
         targetArchitecture: dryRun ? null : Architecture.current,
         buildMode: dryRun ? null : BuildMode.debug,
         cCompiler: dryRun ? null : cCompiler,
+        supportedAssetTypes: [CodeAsset.type],
       );
 
       final buildConfigUri = testTempUri.resolve('build_config.json');
@@ -86,7 +87,7 @@ void main() async {
       final buildOutputUri = outputDirectory.resolve('build_output.json');
       final buildOutput = HookOutputImpl.fromJsonString(
           await File.fromUri(buildOutputUri).readAsString());
-      final assets = buildOutput.assets;
+      final assets = buildOutput.encodedAssets;
       final dependencies = buildOutput.dependencies;
       if (dryRun) {
         expect(assets.length, greaterThanOrEqualTo(3));
diff --git a/pkgs/native_assets_cli/test/helpers.dart b/pkgs/native_assets_cli/test/helpers.dart
index 36a45c744..6f02de316 100644
--- a/pkgs/native_assets_cli/test/helpers.dart
+++ b/pkgs/native_assets_cli/test/helpers.dart
@@ -127,21 +127,24 @@ extension on String {
   Uri asFileUri() => Uri.file(this);
 }
 
-extension AssetIterable on Iterable<Asset> {
+extension AssetIterable on Iterable<EncodedAsset> {
   Future<bool> allExist() async {
-    final allResults = await Future.wait(map((e) => e.exists()));
-    final missing = allResults.contains(false);
-    return !missing;
-  }
-}
-
-extension on Asset {
-  Future<bool> exists() async {
-    final path_ = file;
-    return switch (path_) {
-      null => true,
-      _ => await path_.fileSystemEntity.exists(),
-    };
+    for (final encodedAsset in this) {
+      if (encodedAsset.type == DataAsset.type) {
+        final dataAsset = DataAsset.fromEncoded(encodedAsset);
+        if (!await dataAsset.file.fileSystemEntity.exists()) {
+          return false;
+        }
+      } else if (encodedAsset.type == CodeAsset.type) {
+        final codeAsset = CodeAsset.fromEncoded(encodedAsset);
+        if (!await (codeAsset.file?.fileSystemEntity.exists() ?? true)) {
+          return false;
+        }
+      } else {
+        throw UnimplementedError('Unknown asset type ${encodedAsset.type}');
+      }
+    }
+    return true;
   }
 }
 
diff --git a/pkgs/native_assets_cli/test/model/asset_test.dart b/pkgs/native_assets_cli/test/model/asset_test.dart
index 5d3df2fdf..e6d268812 100644
--- a/pkgs/native_assets_cli/test/model/asset_test.dart
+++ b/pkgs/native_assets_cli/test/model/asset_test.dart
@@ -73,8 +73,8 @@ void main() {
     ),
   ];
   final assets = [
-    ...nativeCodeAssets,
-    ...dataAssets,
+    for (final asset in nativeCodeAssets) asset.encode(),
+    for (final asset in dataAssets) asset.encode(),
   ];
 
   final assetsJsonEncoding = [
@@ -143,7 +143,7 @@ void main() {
   test('asset json', () {
     final json = [for (final item in assets) item.toJson()];
     expect(json, assetsJsonEncoding);
-    final assets2 = Asset.listFromJson(json);
+    final assets2 = [for (final e in json) EncodedAsset.fromJson(e)];
     expect(assets, assets2);
   });
 
diff --git a/pkgs/native_assets_cli/test/model/build_config_test.dart b/pkgs/native_assets_cli/test/model/build_config_test.dart
index 34dcc89ad..08afdbb0d 100644
--- a/pkgs/native_assets_cli/test/model/build_config_test.dart
+++ b/pkgs/native_assets_cli/test/model/build_config_test.dart
@@ -66,6 +66,7 @@ void main() async {
       buildMode: BuildMode.release,
       linkModePreference: LinkModePreference.preferStatic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     final config2 = BuildConfigImpl(
@@ -79,6 +80,7 @@ void main() async {
       buildMode: BuildMode.release,
       linkModePreference: LinkModePreference.preferStatic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     expect(config1, equals(config1));
@@ -112,9 +114,11 @@ void main() async {
       buildMode: BuildMode.release,
       linkModePreference: LinkModePreference.preferStatic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     final config = {
+      'supported_asset_types': [CodeAsset.type],
       'build_mode': 'release',
       'dry_run': false,
       'linking_enabled': false,
@@ -142,10 +146,12 @@ void main() async {
       targetOS: OS.android,
       linkModePreference: LinkModePreference.preferStatic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     final config = {
       'dry_run': true,
+      'supported_asset_types': [CodeAsset.type],
       'linking_enabled': false,
       'link_mode_preference': 'prefer-static',
       'out_dir': outDirUri.toFilePath(),
@@ -176,6 +182,7 @@ void main() async {
       buildMode: BuildMode.release,
       linkModePreference: LinkModePreference.preferStatic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     final configFile = buildConfig1.toJson();
@@ -204,6 +211,7 @@ void main() async {
         }),
       },
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     final buildConfig2 = BuildConfigImpl(
@@ -225,6 +233,7 @@ void main() async {
         }),
       },
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     expect(buildConfig1, equals(buildConfig1));
@@ -259,6 +268,7 @@ void main() async {
         }),
       },
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     final jsonObject = buildConfig1.toJson();
@@ -313,6 +323,7 @@ void main() async {
         'target_os': 'android',
         'target_android_ndk_api': 30,
         'link_mode_preference': 'prefer-static',
+        'supported_asset_types': [CodeAsset.type],
       }),
       throwsA(predicate(
         (e) =>
@@ -334,6 +345,7 @@ void main() async {
         'target_android_ndk_api': 30,
         'link_mode_preference': 'prefer-static',
         'build_mode': BuildMode.release.name,
+        'supported_asset_types': [CodeAsset.type],
         'dependency_metadata': {
           'bar': {'key': 'value'},
           'foo': <int>[],
@@ -358,6 +370,7 @@ void main() async {
         'target_architecture': 'arm64',
         'target_os': 'android',
         'link_mode_preference': 'prefer-static',
+        'supported_asset_types': [CodeAsset.type],
         'build_mode': BuildMode.release.name,
       }),
       throwsA(predicate(
@@ -386,6 +399,7 @@ void main() async {
       buildMode: BuildMode.release,
       linkModePreference: LinkModePreference.preferStatic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
     config.toString();
   });
@@ -402,6 +416,7 @@ void main() async {
       buildMode: BuildMode.release,
       linkModePreference: LinkModePreference.preferStatic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
     final configFileContents = buildConfig.toJsonString();
     final configUri = tempUri.resolve('config.json');
@@ -430,6 +445,7 @@ void main() async {
       buildMode: BuildMode.release,
       linkModePreference: LinkModePreference.dynamic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
 
     final configFile = buildConfig1.toJson();
@@ -448,6 +464,7 @@ void main() async {
         'target_os': 'linux',
         'version': version,
         'package_name': packageName,
+        'supported_asset_types': [CodeAsset.type],
         'dry_run': true,
       };
       expect(
@@ -473,6 +490,7 @@ void main() async {
       'target_os': 'windows',
       'target_architecture': 'arm',
       'build_mode': 'debug',
+      'supported_asset_types': [CodeAsset.type],
       'version': HookConfigImpl.latestVersion.toString(),
     };
     expect(
@@ -495,6 +513,7 @@ void main() async {
       'target_architecture': 'arm64',
       'build_mode': 'debug',
       'dry_run': true,
+      'supported_asset_types': [CodeAsset.type],
       'version': HookConfigImpl.latestVersion.toString(),
     };
     expect(
@@ -517,6 +536,7 @@ void main() async {
       'package_name': packageName,
       'package_root': tempUri.toFilePath(),
       'target_os': 'android',
+      'supported_asset_types': [CodeAsset.type],
       'version': HookConfigImpl.latestVersion.toString(),
     };
     final buildConfig = BuildConfigImpl.fromJson(config);
@@ -539,6 +559,7 @@ void main() async {
       'package_name': packageName,
       'package_root': tempUri.toFilePath(),
       'target_os': 'windows',
+      'supported_asset_types': [CodeAsset.type],
       'version': HookConfigImpl.latestVersion.toString(),
     };
     final buildConfig = BuildConfigImpl.fromJson(config);
@@ -554,6 +575,7 @@ void main() async {
       targetOS: OS.windows,
       linkModePreference: LinkModePreference.dynamic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
     buildConfig.toJsonString();
     // No crash.
@@ -571,6 +593,7 @@ void main() async {
       'target_android_ndk_api': 30,
       'target_architecture': 'invalid_architecture',
       'target_os': 'android',
+      'supported_asset_types': [CodeAsset.type],
       'version': HookOutputImpl.latestVersion.toString(),
     };
     expect(
diff --git a/pkgs/native_assets_cli/test/model/build_output_test.dart b/pkgs/native_assets_cli/test/model/build_output_test.dart
index 80c47c4ad..7f28a0ffb 100644
--- a/pkgs/native_assets_cli/test/model/build_output_test.dart
+++ b/pkgs/native_assets_cli/test/model/build_output_test.dart
@@ -120,6 +120,7 @@ void main() {
       targetOS: OS.macOS,
       linkModePreference: LinkModePreference.dynamic,
       linkingEnabled: false,
+      supportedAssetTypes: [CodeAsset.type],
     );
     final buildOutput = getBuildOutput();
     await buildOutput.writeToFile(config: config);
@@ -145,6 +146,7 @@ void main() {
       linkModePreference: LinkModePreference.dynamic,
       version: Version(1, 1, 0),
       linkingEnabled: null, // version < 1.4.0
+      supportedAssetTypes: [CodeAsset.type],
     );
     final buildOutput = getBuildOutput(withLinkedAssets: false);
     await buildOutput.writeToFile(config: config);
@@ -184,7 +186,7 @@ void main() {
   test('BuildOutput setters', () {
     final buildOutput = HookOutputImpl(
       timestamp: DateTime.parse('2022-11-10 13:25:01.000'),
-      assets: [
+      encodedAssets: [
         CodeAsset(
           package: 'my_package',
           name: 'foo',
@@ -192,14 +194,14 @@ void main() {
           linkMode: DynamicLoadingBundled(),
           os: OS.android,
           architecture: Architecture.x64,
-        ),
+        ).encode(),
         CodeAsset(
           package: 'my_package',
           name: 'foo2',
           linkMode: DynamicLoadingSystem(Uri(path: 'path/to/libfoo2.so')),
           os: OS.android,
           architecture: Architecture.x64,
-        ),
+        ).encode(),
       ],
       dependencies: Dependencies([
         Uri.file('path/to/file.ext'),
@@ -214,7 +216,7 @@ void main() {
     final buildOutput2 = HookOutputImpl(
       timestamp: DateTime.parse('2022-11-10 13:25:01.000'),
     );
-    buildOutput2.addAsset(
+    buildOutput2.addEncodedAsset(
       CodeAsset(
         package: 'my_package',
         name: 'foo',
@@ -222,16 +224,16 @@ void main() {
         linkMode: DynamicLoadingBundled(),
         os: OS.android,
         architecture: Architecture.x64,
-      ),
+      ).encode(),
     );
-    buildOutput2.addAssets([
+    buildOutput2.addEncodedAssets([
       CodeAsset(
         package: 'my_package',
         name: 'foo2',
         linkMode: DynamicLoadingSystem(Uri(path: 'path/to/libfoo2.so')),
         os: OS.android,
         architecture: Architecture.x64,
-      ),
+      ).encode(),
     ]);
     buildOutput2.addDependency(
       Uri.file('path/to/file.ext'),
@@ -253,7 +255,7 @@ void main() {
 
 HookOutputImpl getBuildOutput({bool withLinkedAssets = true}) => HookOutputImpl(
       timestamp: DateTime.parse('2022-11-10 13:25:01.000'),
-      assets: [
+      encodedAssets: [
         CodeAsset(
           package: 'my_package',
           name: 'foo',
@@ -261,44 +263,44 @@ HookOutputImpl getBuildOutput({bool withLinkedAssets = true}) => HookOutputImpl(
           linkMode: DynamicLoadingBundled(),
           os: OS.android,
           architecture: Architecture.x64,
-        ),
+        ).encode(),
         CodeAsset(
           package: 'my_package',
           name: 'foo2',
           linkMode: DynamicLoadingSystem(Uri(path: 'path/to/libfoo2.so')),
           os: OS.android,
           architecture: Architecture.x64,
-        ),
+        ).encode(),
         CodeAsset(
           package: 'my_package',
           name: 'foo3',
           linkMode: LookupInProcess(),
           os: OS.android,
           architecture: Architecture.x64,
-        ),
+        ).encode(),
         CodeAsset(
           package: 'my_package',
           name: 'foo4',
           linkMode: LookupInExecutable(),
           os: OS.android,
           architecture: Architecture.x64,
-        ),
+        ).encode(),
       ],
-      assetsForLinking: withLinkedAssets
+      encodedAssetsForLinking: withLinkedAssets
           ? {
               'my_package': [
                 DataAsset(
                   file: Uri.file('path/to/data'),
                   name: 'data',
                   package: 'my_package',
-                )
+                ).encode()
               ],
               'my_package_2': [
                 DataAsset(
                   file: Uri.file('path/to/data2'),
                   name: 'data',
                   package: 'my_package',
-                )
+                ).encode()
               ]
             }
           : null,
diff --git a/pkgs/native_assets_cli/test/model/checksum_test.dart b/pkgs/native_assets_cli/test/model/checksum_test.dart
index f005c213b..cff247336 100644
--- a/pkgs/native_assets_cli/test/model/checksum_test.dart
+++ b/pkgs/native_assets_cli/test/model/checksum_test.dart
@@ -41,6 +41,7 @@ void main() {
         targetOS: OS.linux,
         buildMode: BuildMode.release,
         linkModePreference: LinkModePreference.dynamic,
+        supportedAssetTypes: [CodeAsset.type],
         dependencyMetadata: {
           'foo': const Metadata({'key': 'value'})
         },
@@ -59,6 +60,7 @@ void main() {
         targetOS: OS.linux,
         buildMode: BuildMode.release,
         linkModePreference: LinkModePreference.dynamic,
+        supportedAssetTypes: [CodeAsset.type],
         cCompiler: CCompilerConfig(
           compiler: fakeClangUri,
         ),
@@ -77,6 +79,7 @@ void main() {
         targetOS: OS.linux,
         buildMode: BuildMode.release,
         linkModePreference: LinkModePreference.dynamic,
+        supportedAssetTypes: [CodeAsset.type],
         cCompiler: CCompilerConfig(
           compiler: fakeClangUri,
         ),
diff --git a/pkgs/native_assets_cli/test/model/link_config_test.dart b/pkgs/native_assets_cli/test/model/link_config_test.dart
index 64fa70f5e..1f07cc4e2 100644
--- a/pkgs/native_assets_cli/test/model/link_config_test.dart
+++ b/pkgs/native_assets_cli/test/model/link_config_test.dart
@@ -26,7 +26,7 @@ void main() async {
       package: packageName,
       name: 'name',
       file: Uri.file('nonexistent'),
-    ),
+    ).encode(),
     CodeAsset(
       package: packageName,
       name: 'name2',
@@ -34,7 +34,7 @@ void main() async {
       os: OS.android,
       file: Uri.file('not there'),
       architecture: Architecture.riscv64,
-    )
+    ).encode(),
   ];
 
   setUp(() async {
@@ -69,6 +69,7 @@ void main() async {
 
   test('LinkConfig ==', () {
     final config1 = LinkConfigImpl(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: outDirUri,
       outputDirectoryShared: outputDirectoryShared,
       packageName: packageName,
@@ -82,12 +83,13 @@ void main() async {
         archiver: fakeAr,
       ),
       buildMode: BuildMode.release,
-      assets: assets,
+      encodedAssets: assets,
       recordedUsagesFile: recordedUsagesFile,
       linkModePreference: LinkModePreference.preferStatic,
     );
 
     final config2 = LinkConfigImpl(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: outDir2Uri,
       outputDirectoryShared: outputDirectoryShared,
       packageName: packageName,
@@ -96,7 +98,7 @@ void main() async {
       targetOS: OS.android,
       targetAndroidNdkApi: 30,
       buildMode: BuildMode.release,
-      assets: [],
+      encodedAssets: [],
       recordedUsagesFile: null,
       linkModePreference: LinkModePreference.preferStatic,
     );
@@ -116,11 +118,12 @@ void main() async {
     expect(config1.cCompiler.envScriptArgs == config2.cCompiler.envScriptArgs,
         true);
     expect(config1.cCompiler != config2.cCompiler, true);
-    expect(config1.assets != config2.assets, true);
+    expect(config1.encodedAssets != config2.encodedAssets, true);
   });
 
   test('LinkConfig fromConfig', () {
     final buildConfig2 = LinkConfigImpl(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: outDirUri,
       outputDirectoryShared: outputDirectoryShared,
       packageName: packageName,
@@ -129,12 +132,13 @@ void main() async {
       targetOS: OS.android,
       targetAndroidNdkApi: 30,
       buildMode: BuildMode.release,
-      assets: assets,
+      encodedAssets: assets,
       linkModePreference: LinkModePreference.preferStatic,
     );
 
     final config = {
       'build_mode': 'release',
+      'supported_asset_types': [CodeAsset.type],
       'dry_run': false,
       'link_mode_preference': 'prefer-static',
       'out_dir': outDirUri.toFilePath(),
@@ -145,7 +149,7 @@ void main() async {
       'target_architecture': 'arm64',
       'target_os': 'android',
       'version': HookOutputImpl.latestVersion.toString(),
-      'assets': Asset.listToJson(assets),
+      'assets': [for (final asset in assets) asset.toJson()],
     };
 
     final fromConfig = LinkConfigImpl.fromJson(config);
@@ -154,17 +158,19 @@ void main() async {
 
   test('LinkConfig.dryRun', () {
     final buildConfig2 = LinkConfigImpl.dryRun(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: outDirUri,
       outputDirectoryShared: outputDirectoryShared,
       packageName: packageName,
       packageRoot: packageRootUri,
       targetOS: OS.android,
-      assets: [],
+      encodedAssets: [],
       linkModePreference: LinkModePreference.preferStatic,
     );
 
     final config = {
       'dry_run': true,
+      'supported_asset_types': [CodeAsset.type],
       'link_mode_preference': 'prefer-static',
       'out_dir': outDirUri.toFilePath(),
       'out_dir_shared': outputDirectoryShared.toFilePath(),
@@ -181,6 +187,7 @@ void main() async {
 
   test('LinkConfig toJson fromConfig', () {
     final buildConfig1 = LinkConfigImpl(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: outDirUri,
       outputDirectoryShared: outputDirectoryShared,
       packageName: packageName,
@@ -193,7 +200,7 @@ void main() async {
         linker: fakeLd,
       ),
       buildMode: BuildMode.release,
-      assets: assets,
+      encodedAssets: assets,
       linkModePreference: LinkModePreference.preferStatic,
     );
 
@@ -205,6 +212,7 @@ void main() async {
   test('LinkConfig toJson fromJson', () {
     final outDir = outDirUri;
     final buildConfig1 = LinkConfigImpl(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: outDir,
       outputDirectoryShared: outputDirectoryShared,
       packageName: packageName,
@@ -217,20 +225,20 @@ void main() async {
         linker: fakeLd,
       ),
       buildMode: BuildMode.release,
-      assets: assets,
+      encodedAssets: assets,
       linkModePreference: LinkModePreference.preferStatic,
     );
 
     final jsonObject = buildConfig1.toJson();
     final expectedJson = {
-      'assets': Asset.listToJson(assets),
+      'assets': [for (final asset in assets) asset.toJson()],
+      'supported_asset_types': [CodeAsset.type],
       'build_mode': 'release',
       'c_compiler': {'cc': fakeClang.toFilePath(), 'ld': fakeLd.toFilePath()},
       'out_dir': outDir.toFilePath(),
       'out_dir_shared': outputDirectoryShared.toFilePath(),
       'package_name': packageName,
       'package_root': tempUri.toFilePath(),
-      'supported_asset_types': [CodeAsset.type],
       'target_architecture': 'arm64',
       'target_ios_sdk': 'iphoneos',
       'target_os': 'ios',
@@ -256,6 +264,7 @@ void main() async {
     expect(
       () => LinkConfigImpl.fromJson({
         'version': HookConfigImpl.latestVersion.toString(),
+        'supported_asset_types': [CodeAsset.type],
         'package_name': packageName,
         'package_root': packageRootUri.toFilePath(),
         'target_architecture': 'arm64',
@@ -274,6 +283,7 @@ void main() async {
     expect(
       () => LinkConfigImpl.fromJson({
         'version': HookConfigImpl.latestVersion.toString(),
+        'supported_asset_types': [CodeAsset.type],
         'out_dir': outDirUri.toFilePath(),
         'out_dir_shared': outputDirectoryShared.toFilePath(),
         'package_name': packageName,
@@ -296,6 +306,7 @@ void main() async {
     );
     expect(
       () => LinkConfigImpl.fromJson({
+        'supported_asset_types': [CodeAsset.type],
         'out_dir': outDirUri.toFilePath(),
         'out_dir_shared': outputDirectoryShared.toFilePath(),
         'version': HookConfigImpl.latestVersion.toString(),
@@ -318,6 +329,7 @@ void main() async {
 
   test('LinkConfig toString', () {
     final config = LinkConfigImpl(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: outDirUri,
       outputDirectoryShared: outputDirectoryShared,
       packageName: packageName,
@@ -330,7 +342,7 @@ void main() async {
         linker: fakeLd,
       ),
       buildMode: BuildMode.release,
-      assets: assets,
+      encodedAssets: assets,
       linkModePreference: LinkModePreference.preferStatic,
     );
     expect(config.toString(), isNotEmpty);
@@ -338,6 +350,7 @@ void main() async {
 
   test('LinkConfig fromArgs', () async {
     final buildConfig = LinkConfigImpl(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: outDirUri,
       outputDirectoryShared: outputDirectoryShared,
       packageName: packageName,
@@ -346,7 +359,7 @@ void main() async {
       targetOS: OS.android,
       targetAndroidNdkApi: 30,
       buildMode: BuildMode.release,
-      assets: assets,
+      encodedAssets: assets,
       recordedUsagesFile: recordedUsagesFile,
       linkModePreference: LinkModePreference.preferStatic,
     );
@@ -432,6 +445,7 @@ void main() async {
     final outDir = outDirUri;
     final config = {
       'link_mode_preference': 'prefer-static',
+      'supported_asset_types': [CodeAsset.type],
       'out_dir': outDir.toFilePath(),
       'out_dir_shared': outputDirectoryShared.toFilePath(),
       'package_name': packageName,
@@ -452,6 +466,7 @@ void main() async {
   test('LinkConfig dry_run target arch', () {
     final outDir = outDirUri;
     final config = {
+      'supported_asset_types': [CodeAsset.type],
       'link_mode_preference': 'prefer-static',
       'out_dir': outDir.toFilePath(),
       'out_dir_shared': outputDirectoryShared.toFilePath(),
@@ -467,12 +482,13 @@ void main() async {
 
   test('LinkConfig dry_run toString', () {
     final buildConfig = LinkConfigImpl.dryRun(
+      supportedAssetTypes: [CodeAsset.type],
       packageName: packageName,
       outputDirectory: outDirUri,
       outputDirectoryShared: outputDirectoryShared,
       packageRoot: tempUri,
       targetOS: OS.windows,
-      assets: assets,
+      encodedAssets: assets,
       linkModePreference: LinkModePreference.preferStatic,
     );
     expect(buildConfig.toJsonString(), isNotEmpty);
@@ -480,6 +496,7 @@ void main() async {
 
   test('invalid architecture', () {
     final config = {
+      'supported_asset_types': [CodeAsset.type],
       'build_mode': 'release',
       'dry_run': false,
       'link_mode_preference': 'prefer-static',
diff --git a/pkgs/native_assets_cli/test/validator/validator_test.dart b/pkgs/native_assets_cli/test/validator/validator_test.dart
index fa831a5bd..e37ab9de7 100644
--- a/pkgs/native_assets_cli/test/validator/validator_test.dart
+++ b/pkgs/native_assets_cli/test/validator/validator_test.dart
@@ -4,8 +4,7 @@
 
 import 'dart:io';
 
-import 'package:native_assets_cli/native_assets_cli.dart';
-import 'package:native_assets_cli/src/validator/validator.dart';
+import 'package:native_assets_cli/native_assets_cli_internal.dart';
 import 'package:test/test.dart';
 
 void main() {
@@ -47,7 +46,7 @@ void main() {
     final output = BuildOutput();
     final assetFile = File.fromUri(outDirUri.resolve('foo.dylib'));
     await assetFile.writeAsBytes([1, 2, 3]);
-    output.addAsset(
+    output.codeAssets.add(
       CodeAsset(
         package: config.packageName,
         name: 'foo.dart',
@@ -58,10 +57,9 @@ void main() {
       ),
       linkInPackage: 'bar',
     );
-    final result = await validateBuild(config, output);
-    expect(result.success, isFalse);
+    final errors = await validateBuildOutput(config, output);
     expect(
-      result.errors,
+      errors,
       contains(contains('linkingEnabled is false')),
     );
   });
@@ -83,16 +81,15 @@ void main() {
     final output = BuildOutput();
     final assetFile = File.fromUri(outDirUri.resolve('foo.dylib'));
     await assetFile.writeAsBytes([1, 2, 3]);
-    output.addAsset(DataAsset(
+    output.dataAssets.add(DataAsset(
       package: config.packageName,
       name: 'foo.txt',
       file: assetFile.uri,
     ));
-    final result = await validateBuild(config, output);
-    expect(result.success, isFalse);
+    final errors = await validateBuildOutput(config, output);
     expect(
-      result.errors,
-      contains(contains('which is not in supportedAssetTypes')),
+      errors,
+      contains(contains('"data" is not a supported asset type')),
     );
   });
 
@@ -112,15 +109,14 @@ void main() {
     );
     final output = BuildOutput();
     final assetFile = File.fromUri(outDirUri.resolve('foo.dylib'));
-    output.addAsset(DataAsset(
+    output.dataAssets.add(DataAsset(
       package: config.packageName,
       name: 'foo.txt',
       file: assetFile.uri,
     ));
-    final result = await validateBuild(config, output);
-    expect(result.success, isFalse);
+    final errors = await validateDataAssetBuildOutput(config, output);
     expect(
-      result.errors,
+      errors,
       contains(contains('which does not exist')),
     );
   });
@@ -140,17 +136,16 @@ void main() {
       linkingEnabled: false,
     );
     final output = BuildOutput();
-    output.addAsset(CodeAsset(
+    output.codeAssets.add(CodeAsset(
       package: config.packageName,
       name: 'foo.dylib',
       architecture: config.targetArchitecture,
       os: config.targetOS,
       linkMode: DynamicLoadingBundled(),
     ));
-    final result = await validateBuild(config, output);
-    expect(result.success, isFalse);
+    final errors = await validateCodeAssetBuildOutput(config, output);
     expect(
-      result.errors,
+      errors,
       contains(contains('has no file')),
     );
   });
@@ -176,7 +171,7 @@ void main() {
       final output = BuildOutput();
       final assetFile = File.fromUri(outDirUri.resolve('foo.dylib'));
       await assetFile.writeAsBytes([1, 2, 3]);
-      output.addAsset(
+      output.codeAssets.add(
         CodeAsset(
           package: config.packageName,
           name: 'foo.dart',
@@ -186,10 +181,9 @@ void main() {
           architecture: config.targetArchitecture,
         ),
       );
-      final result = await validateBuild(config, output);
-      expect(result.success, isFalse);
+      final errors = await validateCodeAssetBuildOutput(config, output);
       expect(
-        result.errors,
+        errors,
         contains(contains(
           'which is not allowed by by the config link mode preference',
         )),
@@ -214,7 +208,7 @@ void main() {
     final output = BuildOutput();
     final assetFile = File.fromUri(outDirUri.resolve('foo.dylib'));
     await assetFile.writeAsBytes([1, 2, 3]);
-    output.addAsset(
+    output.codeAssets.add(
       CodeAsset(
         package: config.packageName,
         name: 'foo.dart',
@@ -224,10 +218,9 @@ void main() {
         architecture: Architecture.x64,
       ),
     );
-    final result = await validateBuild(config, output);
-    expect(result.success, isFalse);
+    final errors = await validateCodeAssetBuildOutput(config, output);
     expect(
-      result.errors,
+      errors,
       contains(contains(
         'which is not the target architecture',
       )),
@@ -251,7 +244,7 @@ void main() {
     final output = BuildOutput();
     final assetFile = File.fromUri(outDirUri.resolve('foo.dylib'));
     await assetFile.writeAsBytes([1, 2, 3]);
-    output.addAsset(
+    output.codeAssets.add(
       CodeAsset(
         package: config.packageName,
         name: 'foo.dart',
@@ -260,10 +253,9 @@ void main() {
         os: config.targetOS,
       ),
     );
-    final result = await validateBuild(config, output);
-    expect(result.success, isFalse);
+    final errors = await validateCodeAssetBuildOutput(config, output);
     expect(
-      result.errors,
+      errors,
       contains(contains(
         'has no architecture',
       )),
@@ -287,7 +279,7 @@ void main() {
     final output = BuildOutput();
     final assetFile = File.fromUri(outDirUri.resolve('foo.dylib'));
     await assetFile.writeAsBytes([1, 2, 3]);
-    output.addAsset(
+    output.codeAssets.add(
       CodeAsset(
         package: config.packageName,
         name: 'foo.dart',
@@ -297,10 +289,9 @@ void main() {
         architecture: config.targetArchitecture,
       ),
     );
-    final result = await validateBuild(config, output);
-    expect(result.success, isFalse);
+    final errors = await validateCodeAssetBuildOutput(config, output);
     expect(
-      result.errors,
+      errors,
       contains(contains(
         'which is not the target os',
       )),
@@ -324,16 +315,15 @@ void main() {
     final output = BuildOutput();
     final assetFile = File.fromUri(outDirUri.resolve('foo.dylib'));
     await assetFile.writeAsBytes([1, 2, 3]);
-    output.addAsset(DataAsset(
+    output.dataAssets.add(DataAsset(
       package: 'different_package',
       name: 'foo.txt',
       file: assetFile.uri,
     ));
-    final result = await validateBuild(config, output);
-    expect(result.success, isFalse);
+    final errors = await validateDataAssetBuildOutput(config, output);
     expect(
-      result.errors,
-      contains(contains('does not start with')),
+      errors,
+      contains(contains('Data asset must have package name my_package')),
     );
   });
 
@@ -354,7 +344,7 @@ void main() {
     final output = BuildOutput();
     final assetFile = File.fromUri(outDirUri.resolve('foo.dylib'));
     await assetFile.writeAsBytes([1, 2, 3]);
-    output.addAssets([
+    output.dataAssets.addAll([
       DataAsset(
         package: config.packageName,
         name: 'foo.txt',
@@ -366,11 +356,10 @@ void main() {
         file: assetFile.uri,
       ),
     ]);
-    final result = await validateBuild(config, output);
-    expect(result.success, isFalse);
+    final errors = await validateDataAssetBuildOutput(config, output);
     expect(
-      result.errors,
-      contains(contains('Duplicate asset id')),
+      errors,
+      contains(contains('More than one')),
     );
   });
 
@@ -391,16 +380,15 @@ void main() {
     final output = LinkOutput();
     final assetFile = File.fromUri(outDirUri.resolve('foo.dylib'));
     await assetFile.writeAsBytes([1, 2, 3]);
-    output.addAsset(DataAsset(
+    output.dataAssets.add(DataAsset(
       package: config.packageName,
       name: 'foo.txt',
       file: assetFile.uri,
     ));
-    final result = await validateLink(config, output);
-    expect(result.success, isFalse);
+    final errors = await validateLinkOutput(config, output);
     expect(
-      result.errors,
-      contains(contains('which is not in supportedAssetTypes')),
+      errors,
+      contains(contains('"data" is not a supported asset type')),
     );
   });
 
@@ -422,7 +410,7 @@ void main() {
     final fileName = config.targetOS.dylibFileName('foo');
     final assetFile = File.fromUri(outDirUri.resolve(fileName));
     await assetFile.writeAsBytes([1, 2, 3]);
-    output.addAssets([
+    output.codeAssets.addAll([
       CodeAsset(
         package: config.packageName,
         name: 'src/foo.dart',
@@ -440,10 +428,9 @@ void main() {
         architecture: config.targetArchitecture,
       ),
     ]);
-    final result = await validateBuild(config, output);
-    expect(result.success, isFalse);
+    final errors = await validateCodeAssetBuildOutput(config, output);
     expect(
-      result.errors,
+      errors,
       contains(contains('Duplicate dynamic library file name')),
     );
   });
diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart
index 1f38aa885..34c28ae1e 100644
--- a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart
+++ b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart
@@ -162,17 +162,15 @@ class CBuilder extends CTool implements Builder {
     }
 
     if (assetName != null) {
-      output.addAssets(
-        [
-          CodeAsset(
-            package: config.packageName,
-            name: assetName!,
-            file: libUri,
-            linkMode: linkMode,
-            os: config.targetOS,
-            architecture: config.dryRun ? null : config.targetArchitecture,
-          )
-        ],
+      output.codeAssets.add(
+        CodeAsset(
+          package: config.packageName,
+          name: assetName!,
+          file: libUri,
+          linkMode: linkMode,
+          os: config.targetOS,
+          architecture: config.dryRun ? null : config.targetArchitecture,
+        ),
         linkInPackage: linkInPackage,
       );
     }
diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart
index 45790535e..3ac153901 100644
--- a/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart
+++ b/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart
@@ -89,17 +89,15 @@ class CLinker extends CTool implements Linker {
     }
 
     if (assetName != null) {
-      output.addAssets(
-        [
-          CodeAsset(
-            package: config.packageName,
-            name: assetName!,
-            file: libUri,
-            linkMode: linkMode,
-            os: config.targetOS,
-            architecture: config.dryRun ? null : config.targetArchitecture,
-          )
-        ],
+      output.codeAssets.add(
+        CodeAsset(
+          package: config.packageName,
+          name: assetName!,
+          file: libUri,
+          linkMode: linkMode,
+          os: config.targetOS,
+          architecture: config.dryRun ? null : config.targetArchitecture,
+        ),
       );
     }
     if (!config.dryRun) {
diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/ctool.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/ctool.dart
index e38bdbf7d..3902ea6d0 100644
--- a/pkgs/native_toolchain_c/lib/src/cbuilder/ctool.dart
+++ b/pkgs/native_toolchain_c/lib/src/cbuilder/ctool.dart
@@ -24,7 +24,7 @@ abstract class CTool {
 
   /// Asset identifier.
   ///
-  /// Used to output the [LinkConfig.assets].
+  /// Used to output the [CodeAsset].
   ///
   /// If omitted, no asset will be added to the build output.
   final String? assetName;
diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_build_failure_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_build_failure_test.dart
index e8f9182d1..1d9b85f37 100644
--- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_build_failure_test.dart
+++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_build_failure_test.dart
@@ -31,6 +31,7 @@ void main() {
     const name = 'add';
 
     final buildConfig = BuildConfig.build(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: tempUri,
       outputDirectoryShared: tempUri2,
       packageName: name,
diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart
index b00696455..12c0cd713 100644
--- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart
+++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart
@@ -111,6 +111,7 @@ Future<Uri> buildLib(
   await Directory.fromUri(tempUriShared).create();
 
   final buildConfig = BuildConfig.build(
+    supportedAssetTypes: [CodeAsset.type],
     outputDirectory: tempUri,
     outputDirectoryShared: tempUriShared,
     packageName: name,
diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_ios_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_ios_test.dart
index 6f4b08599..34afa5f47 100644
--- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_ios_test.dart
+++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_ios_test.dart
@@ -64,6 +64,7 @@ void main() {
                 Language() => throw UnimplementedError(),
               };
               final buildConfig = BuildConfig.build(
+                supportedAssetTypes: [CodeAsset.type],
                 outputDirectory: tempUri,
                 outputDirectoryShared: tempUri2,
                 packageName: name,
@@ -209,6 +210,7 @@ Future<Uri> buildLib(
   const name = 'add';
 
   final buildConfig = BuildConfig.build(
+    supportedAssetTypes: [CodeAsset.type],
     outputDirectory: tempUri,
     outputDirectoryShared: tempUri2,
     packageName: name,
diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_linux_host_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_linux_host_test.dart
index 9a3e42769..c659f2f7a 100644
--- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_linux_host_test.dart
+++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_linux_host_test.dart
@@ -37,6 +37,7 @@ void main() {
         const name = 'add';
 
         final buildConfig = BuildConfig.build(
+          supportedAssetTypes: [CodeAsset.type],
           outputDirectory: tempUri,
           outputDirectoryShared: tempUri2,
           packageName: name,
diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart
index 79ad82bd2..a24bd5c7b 100644
--- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart
+++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart
@@ -50,6 +50,7 @@ void main() {
           const name = 'add';
 
           final buildConfig = BuildConfig.build(
+            supportedAssetTypes: [CodeAsset.type],
             outputDirectory: tempUri,
             outputDirectoryShared: tempUri2,
             packageName: name,
@@ -139,6 +140,7 @@ Future<Uri> buildLib(
   const name = 'add';
 
   final buildConfig = BuildConfig.build(
+    supportedAssetTypes: [CodeAsset.type],
     outputDirectory: tempUri,
     outputDirectoryShared: tempUri2,
     packageName: name,
diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_windows_host_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_windows_host_test.dart
index a68fa3ac9..9fe63c0ee 100644
--- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_windows_host_test.dart
+++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_windows_host_test.dart
@@ -56,6 +56,7 @@ void main() {
         const name = 'add';
 
         final buildConfig = BuildConfig.build(
+          supportedAssetTypes: [CodeAsset.type],
           outputDirectory: tempUri,
           outputDirectoryShared: tempUri2,
           packageName: name,
diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart
index 77e35132b..faa0c7ece 100644
--- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart
+++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart
@@ -45,6 +45,7 @@ void main() {
         final logger = createCapturingLogger(logMessages);
 
         final buildConfig = BuildConfig.build(
+          supportedAssetTypes: [CodeAsset.type],
           outputDirectory: tempUri,
           outputDirectoryShared: tempUri2,
           packageName: name,
@@ -116,6 +117,7 @@ void main() {
 
         final buildConfig = dryRun
             ? BuildConfig.dryRun(
+                supportedAssetTypes: [CodeAsset.type],
                 outputDirectory: tempUri,
                 outputDirectoryShared: tempUri2,
                 packageName: name,
@@ -125,6 +127,7 @@ void main() {
                 linkingEnabled: false,
               )
             : BuildConfig.build(
+                supportedAssetTypes: [CodeAsset.type],
                 outputDirectory: tempUri,
                 outputDirectoryShared: tempUri2,
                 packageName: name,
@@ -216,6 +219,7 @@ void main() {
     final logger = createCapturingLogger(logMessages);
 
     final buildConfig = BuildConfig.build(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: tempUri,
       outputDirectoryShared: tempUri2,
       packageName: name,
@@ -273,6 +277,7 @@ void main() {
     const name = 'includes';
 
     final buildConfig = BuildConfig.build(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: tempUri,
       outputDirectoryShared: tempUri2,
       packageName: name,
@@ -317,6 +322,7 @@ void main() {
     final logger = createCapturingLogger(logMessages);
 
     final buildConfig = BuildConfig.build(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: tempUri,
       outputDirectoryShared: tempUri2,
       packageName: name,
@@ -374,6 +380,7 @@ void main() {
     final logger = createCapturingLogger(logMessages);
 
     final buildConfig = BuildConfig.build(
+      supportedAssetTypes: [CodeAsset.type],
       buildMode: BuildMode.release,
       outputDirectory: tempUri,
       outputDirectoryShared: tempUri2,
@@ -437,6 +444,7 @@ void main() {
     final logger = createCapturingLogger(logMessages);
 
     final buildConfig = BuildConfig.build(
+      supportedAssetTypes: [CodeAsset.type],
       buildMode: BuildMode.release,
       outputDirectory: tempUri,
       outputDirectoryShared: tempUri2,
@@ -507,6 +515,7 @@ Future<void> testDefines({
   const name = 'defines';
 
   final buildConfig = BuildConfig.build(
+    supportedAssetTypes: [CodeAsset.type],
     outputDirectory: tempUri,
     outputDirectoryShared: tempUri2,
     packageName: name,
diff --git a/pkgs/native_toolchain_c/test/cbuilder/compiler_resolver_test.dart b/pkgs/native_toolchain_c/test/cbuilder/compiler_resolver_test.dart
index 86be27864..4f1b58e98 100644
--- a/pkgs/native_toolchain_c/test/cbuilder/compiler_resolver_test.dart
+++ b/pkgs/native_toolchain_c/test/cbuilder/compiler_resolver_test.dart
@@ -42,6 +42,7 @@ void main() {
       ...await msvc.vcvars64.defaultResolver!.resolve(logger: logger)
     ].firstOrNull?.uri;
     final buildConfig = BuildConfig.build(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: tempUri,
       outputDirectoryShared: tempUri2,
       packageName: 'dummy',
@@ -69,6 +70,7 @@ void main() {
     final tempUri = await tempDirForTest();
     final tempUri2 = await tempDirForTest();
     final buildConfig = BuildConfig.build(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: tempUri,
       outputDirectoryShared: tempUri2,
       packageName: 'dummy',
diff --git a/pkgs/native_toolchain_c/test/cbuilder/objective_c_test.dart b/pkgs/native_toolchain_c/test/cbuilder/objective_c_test.dart
index 50f725994..4d76aaf14 100644
--- a/pkgs/native_toolchain_c/test/cbuilder/objective_c_test.dart
+++ b/pkgs/native_toolchain_c/test/cbuilder/objective_c_test.dart
@@ -37,6 +37,7 @@ void main() {
     final logger = createCapturingLogger(logMessages);
 
     final buildConfig = BuildConfig.build(
+      supportedAssetTypes: [CodeAsset.type],
       buildMode: BuildMode.release,
       outputDirectory: tempUri,
       outputDirectoryShared: tempUri2,
diff --git a/pkgs/native_toolchain_c/test/clinker/build_testfiles.dart b/pkgs/native_toolchain_c/test/clinker/build_testfiles.dart
index e215afb44..57f37bbda 100644
--- a/pkgs/native_toolchain_c/test/clinker/build_testfiles.dart
+++ b/pkgs/native_toolchain_c/test/clinker/build_testfiles.dart
@@ -27,6 +27,7 @@ Future<Uri> buildTestArchive(
   final logger = createCapturingLogger(logMessages);
 
   final buildConfig = BuildConfig.build(
+    supportedAssetTypes: [CodeAsset.type],
     outputDirectory: tempUri,
     outputDirectoryShared: tempUri2,
     packageName: name,
@@ -51,5 +52,5 @@ Future<Uri> buildTestArchive(
     logger: logger,
   );
 
-  return buildOutput.assets.first.file!;
+  return buildOutput.codeAssets.all.first.file!;
 }
diff --git a/pkgs/native_toolchain_c/test/clinker/objects_test.dart b/pkgs/native_toolchain_c/test/clinker/objects_test.dart
index 1d9cd47e0..1f9654732 100644
--- a/pkgs/native_toolchain_c/test/clinker/objects_test.dart
+++ b/pkgs/native_toolchain_c/test/clinker/objects_test.dart
@@ -33,6 +33,7 @@ Future<void> main() async {
 
     final uri = await buildTestArchive(tempUri, tempUri2, os, architecture);
     final linkConfig = LinkConfig.build(
+      supportedAssetTypes: [CodeAsset.type],
       outputDirectory: tempUri,
       outputDirectoryShared: tempUri2,
       packageName: 'testpackage',
@@ -57,11 +58,11 @@ Future<void> main() async {
       logger: logger,
     );
 
-    expect(linkOutput.assets, hasLength(1));
-    final asset = linkOutput.assets.first;
+    expect(linkOutput.codeAssets.all, hasLength(1));
+    final asset = linkOutput.codeAssets.all.first;
     expect(asset, isA<CodeAsset>());
     await expectSymbols(
-      asset: asset as CodeAsset,
+      asset: asset,
       symbols: [
         'my_func',
         'my_other_func',
diff --git a/pkgs/native_toolchain_c/test/clinker/throws_test.dart b/pkgs/native_toolchain_c/test/clinker/throws_test.dart
index e69a39f9c..48e5838aa 100644
--- a/pkgs/native_toolchain_c/test/clinker/throws_test.dart
+++ b/pkgs/native_toolchain_c/test/clinker/throws_test.dart
@@ -30,6 +30,7 @@ Future<void> main() async {
         await expectLater(
           () => cLinker.run(
             config: LinkConfig.build(
+              supportedAssetTypes: [CodeAsset.type],
               outputDirectory: tempUri,
               outputDirectoryShared: tempUri2,
               packageName: 'testpackage',
diff --git a/pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart b/pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart
index 56eec3a70..6bf1b5d71 100644
--- a/pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart
+++ b/pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart
@@ -65,6 +65,7 @@ Future<void> runTests(List<Architecture> architectures) async {
         final linkOutput = LinkOutput();
 
         final config = LinkConfig.build(
+          supportedAssetTypes: [CodeAsset.type],
           outputDirectory: tempUri,
           outputDirectoryShared: tempUri2,
           packageName: 'testpackage',
@@ -84,7 +85,7 @@ Future<void> runTests(List<Architecture> architectures) async {
           logger: logger,
         );
 
-        final asset = linkOutput.assets.first as CodeAsset;
+        final asset = linkOutput.codeAssets.all.first;
         final filePath = asset.file!.toFilePath();
 
         final machine = await readelfMachine(filePath);