diff --git a/packages/firebase_ai/firebase_ai/android/build.gradle b/packages/firebase_ai/firebase_ai/android/build.gradle
new file mode 100644
index 000000000000..13affc9f6740
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/android/build.gradle
@@ -0,0 +1,49 @@
+group 'io.flutter.plugins.firebase.ai'
+version '1.0-SNAPSHOT'
+
+apply plugin: 'com.android.library'
+apply from: file("local-config.gradle")
+
+// AGP 9+ has built-in Kotlin support; older versions need the plugin explicitly.
+def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int
+if (agpMajor < 9) {
+ apply plugin: 'kotlin-android'
+}
+
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+android {
+ if (project.android.hasProperty("namespace")) {
+ namespace 'io.flutter.plugins.firebase.ai'
+ }
+
+ compileSdkVersion project.ext.compileSdk
+
+ defaultConfig {
+ minSdkVersion project.ext.minSdk
+ }
+
+ compileOptions {
+ sourceCompatibility project.ext.javaVersion
+ targetCompatibility project.ext.javaVersion
+ }
+
+ if (agpMajor < 9) {
+ kotlinOptions {
+ jvmTarget = project.ext.javaVersion
+ }
+ }
+
+ sourceSets {
+ main.java.srcDirs += "src/main/kotlin"
+ }
+
+ lintOptions {
+ disable 'InvalidPackage'
+ }
+}
diff --git a/packages/firebase_ai/firebase_ai/android/local-config.gradle b/packages/firebase_ai/firebase_ai/android/local-config.gradle
new file mode 100644
index 000000000000..2adcdf5c1729
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/android/local-config.gradle
@@ -0,0 +1,7 @@
+ext {
+ compileSdk=34
+ minSdk=23
+ targetSdk=34
+ javaVersion = JavaVersion.toVersion(17)
+ androidGradlePluginVersion = '8.3.0'
+}
diff --git a/packages/firebase_ai/firebase_ai/android/src/main/AndroidManifest.xml b/packages/firebase_ai/firebase_ai/android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000000..db54cec9bea5
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/packages/firebase_ai/firebase_ai/android/src/main/kotlin/io/flutter/plugins/firebase/ai/FirebaseAIPlugin.kt b/packages/firebase_ai/firebase_ai/android/src/main/kotlin/io/flutter/plugins/firebase/ai/FirebaseAIPlugin.kt
new file mode 100644
index 000000000000..3377f693d3e5
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/android/src/main/kotlin/io/flutter/plugins/firebase/ai/FirebaseAIPlugin.kt
@@ -0,0 +1,101 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package io.flutter.plugins.firebase.ai
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.util.Log
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import java.security.MessageDigest
+import java.security.NoSuchAlgorithmException
+
+class FirebaseAIPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
+ private lateinit var channel: MethodChannel
+ private lateinit var context: Context
+
+ override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ context = binding.applicationContext
+ channel = MethodChannel(binding.binaryMessenger, "plugins.flutter.io/firebase_ai")
+ channel.setMethodCallHandler(this)
+ }
+
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ channel.setMethodCallHandler(null)
+ }
+
+ override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
+ when (call.method) {
+ "getPlatformHeaders" -> {
+ val headers = mapOf(
+ "X-Android-Package" to context.packageName,
+ "X-Android-Cert" to (getSigningCertFingerprint() ?: "")
+ )
+ result.success(headers)
+ }
+ else -> result.notImplemented()
+ }
+ }
+
+ @OptIn(ExperimentalStdlibApi::class)
+ private fun getSigningCertFingerprint(): String? {
+ val packageName = context.packageName
+ val signature = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ val packageInfo = try {
+ context.packageManager.getPackageInfo(
+ packageName,
+ PackageManager.GET_SIGNING_CERTIFICATES
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(TAG, "PackageManager couldn't find the package \"$packageName\"", e)
+ return null
+ }
+ val signingInfo = packageInfo?.signingInfo ?: return null
+ if (signingInfo.hasMultipleSigners()) {
+ signingInfo.apkContentsSigners.firstOrNull()
+ } else {
+ signingInfo.signingCertificateHistory.lastOrNull()
+ }
+ } else {
+ @Suppress("DEPRECATION")
+ val packageInfo = try {
+ context.packageManager.getPackageInfo(
+ packageName,
+ PackageManager.GET_SIGNATURES
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(TAG, "PackageManager couldn't find the package \"$packageName\"", e)
+ return null
+ }
+ @Suppress("DEPRECATION")
+ packageInfo?.signatures?.firstOrNull()
+ } ?: return null
+
+ return try {
+ val messageDigest = MessageDigest.getInstance("SHA-1")
+ val digest = messageDigest.digest(signature.toByteArray())
+ digest.toHexString(HexFormat.UpperCase)
+ } catch (e: NoSuchAlgorithmException) {
+ Log.w(TAG, "No support for SHA-1 algorithm found.", e)
+ null
+ }
+ }
+
+ companion object {
+ private const val TAG = "FirebaseAIPlugin"
+ }
+}
diff --git a/packages/firebase_ai/firebase_ai/ios/firebase_ai.podspec b/packages/firebase_ai/firebase_ai/ios/firebase_ai.podspec
new file mode 100644
index 000000000000..25d4ecfe3e5d
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/ios/firebase_ai.podspec
@@ -0,0 +1,28 @@
+require 'yaml'
+
+pubspec = YAML.load_file(File.join('..', 'pubspec.yaml'))
+library_version = pubspec['version'].gsub('+', '-')
+
+Pod::Spec.new do |s|
+ s.name = pubspec['name']
+ s.version = library_version
+ s.summary = pubspec['description']
+ s.description = pubspec['description']
+ s.homepage = pubspec['homepage']
+ s.license = { :file => '../LICENSE' }
+ s.authors = 'The Chromium Authors'
+ s.source = { :path => '.' }
+
+ s.source_files = 'firebase_ai/Sources/firebase_ai/**/*.swift'
+
+ s.ios.deployment_target = '15.0'
+ s.dependency 'Flutter'
+
+ s.swift_version = '5.0'
+
+ s.static_framework = true
+ s.pod_target_xcconfig = {
+ 'GCC_PREPROCESSOR_DEFINITIONS' => "LIBRARY_VERSION=\\\"#{library_version}\\\" LIBRARY_NAME=\\\"flutter-fire-ai\\\"",
+ 'DEFINES_MODULE' => 'YES'
+ }
+end
diff --git a/packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/FirebaseAIPlugin.swift b/packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/FirebaseAIPlugin.swift
new file mode 100644
index 000000000000..a76212182bda
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/FirebaseAIPlugin.swift
@@ -0,0 +1,49 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if canImport(FlutterMacOS)
+ import FlutterMacOS
+#else
+ import Flutter
+#endif
+
+public class FirebaseAIPlugin: NSObject, FlutterPlugin {
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ #if canImport(FlutterMacOS)
+ let messenger = registrar.messenger
+ #else
+ let messenger = registrar.messenger()
+ #endif
+
+ let channel = FlutterMethodChannel(
+ name: "plugins.flutter.io/firebase_ai",
+ binaryMessenger: messenger
+ )
+ let instance = FirebaseAIPlugin()
+ registrar.addMethodCallDelegate(instance, channel: channel)
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ switch call.method {
+ case "getPlatformHeaders":
+ var headers: [String: String] = [:]
+ if let bundleId = Bundle.main.bundleIdentifier {
+ headers["x-ios-bundle-identifier"] = bundleId
+ }
+ result(headers)
+ default:
+ result(FlutterMethodNotImplemented)
+ }
+ }
+}
diff --git a/packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/Resources/.gitkeep b/packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/Resources/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/firebase_ai/firebase_ai/lib/src/base_model.dart b/packages/firebase_ai/firebase_ai/lib/src/base_model.dart
index 01ac7eb834b3..cf1f98db8b1a 100644
--- a/packages/firebase_ai/firebase_ai/lib/src/base_model.dart
+++ b/packages/firebase_ai/firebase_ai/lib/src/base_model.dart
@@ -36,6 +36,7 @@ import 'imagen/imagen_edit.dart';
import 'imagen/imagen_reference.dart';
import 'live_api.dart';
import 'live_session.dart';
+import 'platform_header_helper.dart';
import 'tool.dart';
part 'generative_model.dart';
@@ -300,6 +301,10 @@ abstract class BaseModel {
if (app != null && app.isAutomaticDataCollectionEnabled) {
headers['X-Firebase-AppId'] = app.options.appId;
}
+ // Add platform-specific headers for API key restrictions.
+ // Android: X-Android-Package + X-Android-Cert
+ // iOS/macOS: x-ios-bundle-identifier
+ headers.addAll(await getPlatformSecurityHeaders());
return headers;
};
}
diff --git a/packages/firebase_ai/firebase_ai/lib/src/platform_header_helper.dart b/packages/firebase_ai/firebase_ai/lib/src/platform_header_helper.dart
new file mode 100644
index 000000000000..9f0d115756de
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/lib/src/platform_header_helper.dart
@@ -0,0 +1,50 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+
+/// Method channel for the native platform helper plugin.
+@visibleForTesting
+const platformHeaderChannel = MethodChannel('plugins.flutter.io/firebase_ai');
+
+Map? _cachedHeaders;
+
+/// Clears the cached platform headers. Only for use in tests.
+@visibleForTesting
+void clearPlatformSecurityHeadersCache() {
+ _cachedHeaders = null;
+}
+
+/// Returns platform-specific security headers for API key restrictions.
+///
+/// Each platform's native plugin returns the appropriate headers:
+/// - **Android**: `X-Android-Package` and `X-Android-Cert`
+/// - **iOS/macOS**: `x-ios-bundle-identifier`
+/// - **Web/other**: empty map (no plugin registered)
+///
+/// Results are cached since platform identity does not change at runtime.
+Future