diff --git a/docs/python-web.md b/docs/python-web.md
new file mode 100644
index 0000000..d1e6db7
--- /dev/null
+++ b/docs/python-web.md
@@ -0,0 +1,242 @@
+# Cross-Platform Python Companion for Flutter
+
+This guide explains how to create and use a Python companion application that works across different platforms (Web, Desktop) using the serious_python package.
+
+## Setup
+
+### Project Structure
+```
+your_flutter_project/
+├── python_companion/
+│     ├── desktop/
+│     │   ├── __init__.py
+│     │   └── companion_server.py    # Flask server for desktop
+│     ├── web/
+│     │   ├── __init__.py
+│     │   └── command_handler.py     # Command handling for web
+│     ├── functionality/
+│     │   ├── __init__.py
+│     │   └── your_functions.py      # Shared functionality
+│     ├── requirements/
+│     │   ├── base.txt              # Shared dependencies
+│     │   ├── desktop.txt           # Desktop-specific requirements
+│     │   └── web.txt               # Web-specific (Pyodide) requirements
+│     ├── python_companion_desktop.py  # Desktop entry point
+│     └── python_companion_web.py      # Web entry point
+```
+
+### Requirements Files
+
+```txt
+# requirements/base.txt
+numpy>=1.20.0
+scipy>=1.7.0
+
+# requirements/web.txt
+-r base.txt
+# Only Pyodide-compatible versions
+h5py==3.8.0
+
+# requirements/desktop.txt
+-r base.txt
+flask>=2.0.0
+h5py==3.9.0
+```
+
+### Implementation
+
+1. Desktop Implementation (Flask Server):
+```python
+# desktop/companion_server.py
+from flask import Flask, request, jsonify
+import functionality
+
+app = Flask(__name__)
+
+@app.route('/your_endpoint', methods=['POST'])
+def your_endpoint():
+    result = functionality.your_function(request.json)
+    return jsonify(result)
+
+def run_server():
+    app.run(port=50001, debug=False, use_reloader=False)
+```
+
+2. Web Implementation (Pyodide):
+```python
+# web/command_handler.py
+import json
+import functionality
+
+_command_functions = {
+    "your_command": lambda data: functionality.your_function(data),
+}
+
+def handle_command(command: str, data):
+    command_function = _command_functions.get(command)
+    try:
+        loaded_data = json.loads(data)
+    except:
+        loaded_data = data
+    return command_function(data)
+```
+
+3. Shared Functionality:
+```python
+# functionality/your_functions.py
+
+def your_function(json_data):
+    # Your implementation
+    return {"result": "success"}
+```
+
+4. Entry Points:
+```python
+# python_companion_desktop.py
+from desktop import run_server
+
+if __name__ == '__main__':
+    run_server()
+
+# python_companion_web.py
+import os
+from web import handle_command
+
+if __name__ == '__main__':
+    command = os.environ.get("PYODIDE_COMMAND", "")
+    data = os.environ.get("PYODIDE_DATA", None)
+    pyodide_result = handle_command(command, data)
+```
+
+### Packaging
+
+Package your Python companion for different platforms:
+
+```bash
+# For Web (Pyodide)
+dart run serious_python:main package \
+  --asset assets/python_companion.zip python_companion/ \
+  -p Pyodide \
+  --requirements "-r,python_companion/requirements/web.txt"
+
+# For Desktop (Linux)
+dart run serious_python:main package \
+  --asset assets/python_companion.zip python_companion/ \
+  -p Linux \
+  --requirements "-r,python_companion/requirements/desktop.txt"
+```
+
+## Usage in Flutter
+
+1. Add serious_python to your pubspec.yaml:
+```yaml
+dependencies:
+  serious_python: ^latest_version
+```
+
+2. Create a service class:
+```dart
+class PythonCompanionService {
+  Future<Either<Exception, Map<String, dynamic>>> callPythonFunction(List<double> data);
+}
+
+class PythonCompanionServiceWeb implements PythonCompanionService {
+  Future<Either<Exception, Map<String, dynamic>>> callPythonFunction(List<double> data) async {
+    final String? result = await SeriousPython.run(
+      'assets/python_companion.zip',
+      appFileName: 'python_companion_web.py',
+      modulePaths: ['python_companion/web', 'python_companion/functionality'],
+      environmentVariables:
+      {
+        'PYODIDE_COMMAND': 'your_command',
+        'PYODIDE_DATA': jsonEncode({'data': data})
+      },
+      sync: true,
+    );
+
+    if (result == null || result.isEmpty) {
+      return left(Exception('Failed to execute Python function'));
+    }
+
+    return right(jsonDecode(result));
+  }
+}
+
+class PythonCompanionServiceDesktop implements PythonCompanionService {
+  @override
+  Future<void> startServer() async {
+    try {
+      // Start the Python server
+      await SeriousPython.run(
+        'assets/python_companion.zip',
+        appFileName: 'python_companion_desktop.py',
+      );
+
+      // Wait for server to be ready, e.g. by checking the health endpoint
+      await _waitForServer();
+      _isServerRunning = true;
+    } catch (e) {
+      throw Exception('Failed to start Python server: $e');
+    }
+  }
+
+  @override
+  Future<Either<Exception, Map<String, dynamic>>> callPythonFunction(List<double> data) async {
+    try {
+      final response = await http.post(
+        Uri.parse('$_baseUrl/your_endpoint'),
+        headers: {'Content-Type': 'application/json'},
+        body: jsonEncode({'data': data}),
+      );
+
+      if (response.statusCode != 200) {
+        return left(Exception('Server error: ${response.statusCode}'));
+      }
+
+      return right(jsonDecode(response.body));
+    } catch (e) {
+      return left(Exception('Error calling Python function: $e'));
+    }
+  }
+}
+```
+
+3. Use in your Flutter app:
+```dart
+final pythonService = PythonCompanionService();
+
+void yourFunction() async {
+  final result = await pythonService.callPythonFunction([1.0, 2.0, 3.0]);
+  result.fold(
+    (error) => print('Error: $error'),
+    (success) => print('Success: $success'),
+  );
+}
+```
+
+## Important Notes
+
+1. **Web Compatibility**: Ensure all Python packages used in web implementation are [Pyodide-compatible](https://pyodide.org/en/stable/usage/packages-in-pyodide.html).
+
+2. **Package Versions**: Use platform-specific package versions when needed (e.g., different h5py versions for web and desktop).
+
+3. **Error Handling**: Implement proper error handling in both Python and Dart code.
+
+4. **Data Transfer**: Use JSON for data transfer between Flutter and Python.
+
+5. **Resource Management**: Properly manage resources (close files, connections, etc.).
+
+## Troubleshooting
+
+1. **Module Import Issues**: Ensure correct module paths and dependencies.
+2. **Platform Compatibility**: Check package compatibility for each platform.
+3. **Port Conflicts**: For desktop, ensure the Flask server port (50001) is available.
+4. **Memory Management**: Be mindful of memory usage, especially with large data operations.
+
+## Best Practices
+
+1. Keep shared functionality platform-independent
+2. Implement proper error handling and logging
+3. Use type hints and documentation
+4. Follow platform-specific conventions
+5. Test on all target platforms
diff --git a/src/serious_python/README.md b/src/serious_python/README.md
index 5e28907..4b7c1e4 100644
--- a/src/serious_python/README.md
+++ b/src/serious_python/README.md
@@ -10,9 +10,9 @@ Serious Python is part of [Flet](https://flet.dev) project - the fastest way to
 
 ## Platform Support
 
-| iOS     |   Android    |   macOS    |   Linux    |   Windows    |
-| :-----: | :----------: | :---------: | :-------: | :----------: |
-|   ✅    |       ✅      |       ✅    |     ✅     |      ✅      |
+| iOS | Android | macOS | Linux | Windows | Web |
+|:---:|:-------:|:-----:|:-----:|:-------:|:---:|
+|  ✅  |    ✅    |   ✅   |   ✅   |    ✅    |  ✅  |
 
 ### Python versions
 
diff --git a/src/serious_python/lib/serious_python.dart b/src/serious_python/lib/serious_python.dart
index 968d35f..d5d50fe 100644
--- a/src/serious_python/lib/serious_python.dart
+++ b/src/serious_python/lib/serious_python.dart
@@ -1,8 +1,10 @@
-import 'dart:io';
-
+import 'package:flutter/foundation.dart';
 import 'package:path/path.dart' as path;
 import 'package:serious_python_platform_interface/serious_python_platform_interface.dart';
 
+// Conditional import for IO operations
+import 'src/io_stub.dart' if (dart.library.io) 'src/io_impl.dart';
+
 export 'package:serious_python_platform_interface/src/utils.dart';
 
 /// Provides cross-platform functionality for running Python programs.
@@ -15,56 +17,80 @@ class SeriousPython {
   }
 
   /// Runs Python program from an asset.
-  ///
-  /// [assetPath] is the path to an asset which is a zip archive
-  /// with a Python program. When the app starts the archive is unpacked
-  /// to a temporary directory and Serious Python plugin will try to run
-  /// `main.py` in the root of the archive. Current directory is changed to
-  /// a temporary directory.
-  ///
-  /// If a Python app has a different entry point
-  /// it could be specified with [appFileName] parameter.
-  ///
-  /// Environment variables that must be available to a Python program could
-  /// be passed in [environmentVariables].
-  ///
-  /// By default, Serious Python expects Python dependencies installed into
-  /// `__pypackages__` directory in the root of app directory. Additional paths
-  /// to look for 3rd-party packages can be specified with [modulePaths] parameter.
-  ///
-  /// Set [sync] to `true` to sychronously run Python program; otherwise the
-  /// program starts in a new thread.
   static Future<String?> run(String assetPath,
       {String? appFileName,
-      List<String>? modulePaths,
-      Map<String, String>? environmentVariables,
-      bool? sync}) async {
-    // unpack app from asset
+        List<String>? modulePaths,
+        Map<String, String>? environmentVariables,
+        bool? sync}) async {
+    // Handle web platform differently
+    if (kIsWeb) {
+      return _runWeb(assetPath,
+          appFileName: appFileName,
+          modulePaths: modulePaths,
+          environmentVariables: environmentVariables,
+          sync: sync);
+    } else {
+      return _runDesktop(assetPath,
+          appFileName: appFileName,
+          modulePaths: modulePaths,
+          environmentVariables: environmentVariables,
+          sync: sync);
+    }
+  }
+
+  /// Web-specific implementation
+  static Future<String?> _runWeb(String assetPath,
+      {String? appFileName,
+        List<String>? modulePaths,
+        Map<String, String>? environmentVariables,
+        bool? sync}) async {
+
+    String virtualPath;
+    if (path.extension(assetPath) == ".zip") {
+      virtualPath = assetPath.replaceAll(".zip", "");
+      // TODO Check if path exists and except with unzip hint if not
+    } else {
+      virtualPath = assetPath;
+    }
+
+    if (appFileName != null) {
+      virtualPath = '$virtualPath/$appFileName';
+    } else {
+      virtualPath = '$virtualPath/main.py';
+    }
+
+    return runProgram(virtualPath,
+        modulePaths: modulePaths,
+        environmentVariables: environmentVariables,
+        sync: sync);
+  }
+
+  /// Desktop-specific implementation
+  static Future<String?> _runDesktop(String assetPath,
+      {String? appFileName,
+        List<String>? modulePaths,
+        Map<String, String>? environmentVariables,
+        bool? sync}) async {
     String appPath = "";
     if (path.extension(assetPath) == ".zip") {
       appPath = await extractAssetZip(assetPath);
       if (appFileName != null) {
         appPath = path.join(appPath, appFileName);
-      } else if (await File(path.join(appPath, "main.pyc")).exists()) {
-        appPath = path.join(appPath, "main.pyc");
-      } else if (await File(path.join(appPath, "main.py")).exists()) {
-        appPath = path.join(appPath, "main.py");
       } else {
-        throw Exception(
-            "App archive must contain either `main.py` or `main.pyc`; otherwise `appFileName` must be specified.");
+        appPath = await FileSystem.findMainFile(appPath);
       }
     } else {
       appPath = await extractAsset(assetPath);
     }
 
     // set current directory to app path
-    Directory.current = path.dirname(appPath);
+    await FileSystem.setCurrentDirectory(path.dirname(appPath));
 
     // run python program
     return runProgram(appPath,
         modulePaths: modulePaths,
         environmentVariables: environmentVariables,
-        script: Platform.isWindows ? "" : null,
+        script: FileSystem.isWindows ? "" : null,
         sync: sync);
   }
 
@@ -82,14 +108,13 @@ class SeriousPython {
   /// `__pypackages__` directory in the root of app directory. Additional paths
   /// to look for 3rd-party packages can be specified with [modulePaths] parameter.
   ///
-  /// Set [sync] to `true` to sychronously run Python program; otherwise the
+  /// Set [sync] to `true` to synchronously run Python program; otherwise the
   /// program starts in a new thread.
   static Future<String?> runProgram(String appPath,
       {String? script,
-      List<String>? modulePaths,
-      Map<String, String>? environmentVariables,
-      bool? sync}) async {
-    // run python program
+        List<String>? modulePaths,
+        Map<String, String>? environmentVariables,
+        bool? sync}) async {
     return SeriousPythonPlatform.instance.run(appPath,
         script: script,
         modulePaths: modulePaths,
@@ -100,4 +125,4 @@ class SeriousPython {
   static void terminate() {
     SeriousPythonPlatform.instance.terminate();
   }
-}
+}
\ No newline at end of file
diff --git a/src/serious_python/lib/src/io_impl.dart b/src/serious_python/lib/src/io_impl.dart
new file mode 100644
index 0000000..3f51d4e
--- /dev/null
+++ b/src/serious_python/lib/src/io_impl.dart
@@ -0,0 +1,20 @@
+import 'dart:io';
+import 'package:path/path.dart' as path;
+
+class FileSystem {
+  static Future<String> findMainFile(String appPath) async {
+    if (await File(path.join(appPath, "main.pyc")).exists()) {
+      return path.join(appPath, "main.pyc");
+    } else if (await File(path.join(appPath, "main.py")).exists()) {
+      return path.join(appPath, "main.py");
+    }
+    throw Exception(
+        "App archive must contain either `main.py` or `main.pyc`; otherwise `appFileName` must be specified.");
+  }
+
+  static Future<void> setCurrentDirectory(String path) async {
+    Directory.current = path;
+  }
+
+  static bool get isWindows => Platform.isWindows;
+}
\ No newline at end of file
diff --git a/src/serious_python/lib/src/io_stub.dart b/src/serious_python/lib/src/io_stub.dart
new file mode 100644
index 0000000..881ecce
--- /dev/null
+++ b/src/serious_python/lib/src/io_stub.dart
@@ -0,0 +1,12 @@
+// Stub implementation for web
+class FileSystem {
+  static Future<String> findMainFile(String appPath) async {
+    return '$appPath/main.py';
+  }
+
+  static Future<void> setCurrentDirectory(String path) async {
+    // No-op for web
+  }
+
+  static bool get isWindows => false;
+}
\ No newline at end of file
diff --git a/src/serious_python/pubspec.yaml b/src/serious_python/pubspec.yaml
index ce36712..dab5d02 100644
--- a/src/serious_python/pubspec.yaml
+++ b/src/serious_python/pubspec.yaml
@@ -10,6 +10,7 @@ platforms:
   macos:
   windows:
   linux:
+  web:
 
 environment:
   sdk: ">=3.0.0 <4.0.0"
@@ -28,6 +29,8 @@ flutter:
         default_package: serious_python_windows
       linux:
         default_package: serious_python_linux
+      web:
+        default_package: serious_python_web
 
 dependencies:
   flutter:
@@ -42,6 +45,8 @@ dependencies:
     path: ../serious_python_windows
   serious_python_linux:
     path: ../serious_python_linux
+  serious_python_web:
+    path: ../serious_python_web
 
   path_provider: ^2.1.3
   archive: ^3.6.1
diff --git a/src/serious_python_linux/README.md b/src/serious_python_linux/README.md
index fd20ffa..b7909cd 100644
--- a/src/serious_python_linux/README.md
+++ b/src/serious_python_linux/README.md
@@ -1,6 +1,6 @@
-# serious_python_ios
+# serious_python_linux
 
-The Windows implementation of `serious_python`.
+The Linux implementation of `serious_python`.
 
 ## Usage
 
diff --git a/src/serious_python_platform_interface/lib/src/utils.dart b/src/serious_python_platform_interface/lib/src/utils.dart
index e364c4e..9b31809 100644
--- a/src/serious_python_platform_interface/lib/src/utils.dart
+++ b/src/serious_python_platform_interface/lib/src/utils.dart
@@ -1,123 +1,27 @@
-import 'dart:io';
-
-import 'package:archive/archive_io.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter/widgets.dart';
-import 'package:path/path.dart' as p;
-import 'package:path_provider/path_provider.dart';
+import 'utils_web.dart' if (dart.library.io) 'utils_io.dart';
 
 Future<String> extractAssetOrFile(String path,
-    {bool isAsset = true, String? targetPath, bool checkHash = false}) async {
-  WidgetsFlutterBinding.ensureInitialized();
-  final supportDir = await getApplicationSupportDirectory();
-  final destDir =
-      Directory(p.join(supportDir.path, "flet", targetPath ?? p.dirname(path)));
-
-  String assetHash = "";
-  String destHash = "";
-  var hashFile = File(p.join(destDir.path, ".hash"));
-
-  // re-create dir
-  if (await destDir.exists()) {
-    if (kDebugMode) {
-      // always re-create in debug mode
-      await destDir.delete(recursive: true);
-    } else {
-      if (checkHash) {
-        // read asset hash from asset
-        try {
-          assetHash = (await rootBundle.loadString("$path.hash")).trim();
-          // ignore: empty_catches
-        } catch (e) {}
-        if (await hashFile.exists()) {
-          destHash = (await hashFile.readAsString()).trim();
-        }
-      }
-
-      if (assetHash != destHash ||
-          (checkHash && assetHash == "" && destHash == "")) {
-        await destDir.delete(recursive: true);
-      } else {
-        debugPrint("Application archive already unpacked to ${destDir.path}");
-        return destDir.path;
-      }
-    }
-  }
-
-  debugPrint("extractAssetOrFile directory: ${destDir.path}");
-  await destDir.create(recursive: true);
-
-  // unpack from asset or file
-  debugPrint("Start unpacking archive: $path");
-  Stopwatch stopwatch = Stopwatch()..start();
-
-  try {
-    Archive archive;
-    if (isAsset) {
-      final bytes = await rootBundle.load(path);
-      var data = bytes.buffer.asUint8List();
-      archive = ZipDecoder().decodeBytes(data);
-    } else {
-      final inputStream = InputFileStream(path);
-      archive = ZipDecoder().decodeBuffer(inputStream);
-    }
-    await extractArchiveToDiskAsync(archive, destDir.path, asyncWrite: true);
-  } catch (e) {
-    debugPrint("Error unpacking archive: $e");
-    await destDir.delete(recursive: true);
-    rethrow;
-  }
-
-  debugPrint("Finished unpacking application archive in ${stopwatch.elapsed}");
-
-  if (checkHash) {
-    await hashFile.writeAsString(assetHash);
-  }
-
-  return destDir.path;
+    {bool isAsset = true, String? targetPath, bool checkHash = false}) {
+  return getPlatformUtils().extractAssetOrFile(path,
+      isAsset: isAsset, targetPath: targetPath, checkHash: checkHash);
 }
 
 Future<String> extractAssetZip(String assetPath,
-    {String? targetPath, bool checkHash = false}) async {
-  return extractAssetOrFile(assetPath,
+    {String? targetPath, bool checkHash = false}) {
+  return getPlatformUtils().extractAssetZip(assetPath,
       targetPath: targetPath, checkHash: checkHash);
 }
 
 Future<String> extractFileZip(String filePath,
-    {String? targetPath, bool checkHash = false}) async {
-  return extractAssetOrFile(filePath,
-      isAsset: false, targetPath: targetPath, checkHash: checkHash);
+    {String? targetPath, bool checkHash = false}) {
+  return getPlatformUtils().extractFileZip(filePath,
+      targetPath: targetPath, checkHash: checkHash);
 }
 
-Future<String> extractAsset(String assetPath) async {
-  WidgetsFlutterBinding.ensureInitialized();
-
-  // (re-)create destination directory
-  final supportDir = await getApplicationSupportDirectory();
-  final destDir =
-      Directory(p.join(supportDir.path, "flet", p.dirname(assetPath)));
-
-  await destDir.create(recursive: true);
-
-  // extract file from assets
-  var destPath = p.join(destDir.path, p.basename(assetPath));
-  if (kDebugMode && await File(destPath).exists()) {
-    await File(destPath).delete();
-  }
-  ByteData data = await rootBundle.load(assetPath);
-  List<int> bytes =
-      data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
-  await File(destPath).writeAsBytes(bytes);
-  return destPath;
+Future<String> extractAsset(String assetPath) {
+  return getPlatformUtils().extractAsset(assetPath);
 }
 
-Future<String> getDirFiles(String path, {bool recursive = false}) async {
-  final dir = Directory(path);
-  if (!await dir.exists()) {
-    return "<not found>";
-  }
-  return (await dir.list(recursive: recursive).toList())
-      .map((file) => file.path)
-      .join('\n');
-}
+Future<String> getDirFiles(String path, {bool recursive = false}) {
+  return getPlatformUtils().getDirFiles(path, recursive: recursive);
+}
\ No newline at end of file
diff --git a/src/serious_python_platform_interface/lib/src/utils_interface.dart b/src/serious_python_platform_interface/lib/src/utils_interface.dart
new file mode 100644
index 0000000..5f6de01
--- /dev/null
+++ b/src/serious_python_platform_interface/lib/src/utils_interface.dart
@@ -0,0 +1,14 @@
+abstract class PlatformUtils {
+  Future<String> extractAssetOrFile(String path,
+      {bool isAsset = true, String? targetPath, bool checkHash = false});
+
+  Future<String> extractAssetZip(String assetPath,
+      {String? targetPath, bool checkHash = false});
+
+  Future<String> extractFileZip(String filePath,
+      {String? targetPath, bool checkHash = false});
+
+  Future<String> extractAsset(String assetPath);
+
+  Future<String> getDirFiles(String path, {bool recursive = false});
+}
\ No newline at end of file
diff --git a/src/serious_python_platform_interface/lib/src/utils_io.dart b/src/serious_python_platform_interface/lib/src/utils_io.dart
new file mode 100644
index 0000000..b08d61f
--- /dev/null
+++ b/src/serious_python_platform_interface/lib/src/utils_io.dart
@@ -0,0 +1,114 @@
+import 'dart:io';
+
+import 'package:archive/archive_io.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:path/path.dart' as p;
+import 'package:path_provider/path_provider.dart';
+
+import 'utils_interface.dart';
+
+class IOUtils implements PlatformUtils {
+  @override
+  Future<String> extractAssetOrFile(String path,
+      {bool isAsset = true, String? targetPath, bool checkHash = false}) async {
+    WidgetsFlutterBinding.ensureInitialized();
+    final supportDir = await getApplicationSupportDirectory();
+    final destDir = Directory(p.join(supportDir.path, "flet", targetPath ?? p.dirname(path)));
+
+    String assetHash = "";
+    String destHash = "";
+    var hashFile = File(p.join(destDir.path, ".hash"));
+
+    if (await destDir.exists()) {
+      if (kDebugMode) {
+        await destDir.delete(recursive: true);
+      } else if (checkHash) {
+        try {
+          assetHash = (await rootBundle.loadString("$path.hash")).trim();
+        } catch (e) {
+          assetHash = "";
+        }
+
+        if (await hashFile.exists()) {
+          destHash = (await hashFile.readAsString()).trim();
+        }
+
+        if (assetHash != destHash || (checkHash && assetHash.isEmpty && destHash.isEmpty)) {
+          await destDir.delete(recursive: true);
+        } else {
+          return destDir.path;
+        }
+      }
+    }
+
+    await _extractArchive(path, destDir, isAsset);
+
+    if (checkHash) {
+      await hashFile.writeAsString(assetHash);
+    }
+
+    return destDir.path;
+  }
+
+  Future<void> _extractArchive(String path, Directory destDir, bool isAsset) async {
+    await destDir.create(recursive: true);
+
+    try {
+      Archive archive;
+      if (isAsset) {
+        final bytes = await rootBundle.load(path);
+        var data = bytes.buffer.asUint8List();
+        archive = ZipDecoder().decodeBytes(data);
+      } else {
+        final inputStream = InputFileStream(path);
+        archive = ZipDecoder().decodeBuffer(inputStream);
+      }
+      await extractArchiveToDiskAsync(archive, destDir.path, asyncWrite: true);
+    } catch (e) {
+      debugPrint("Error unpacking archive: $e");
+      await destDir.delete(recursive: true);
+      rethrow;
+    }
+  }
+
+  @override
+  Future<String> extractAssetZip(String assetPath, {String? targetPath, bool checkHash = false}) {
+    return extractAssetOrFile(assetPath, targetPath: targetPath, checkHash: checkHash);
+  }
+
+  @override
+  Future<String> extractFileZip(String filePath, {String? targetPath, bool checkHash = false}) {
+    return extractAssetOrFile(filePath, isAsset: false, targetPath: targetPath, checkHash: checkHash);
+  }
+
+  @override
+  Future<String> extractAsset(String assetPath) async {
+    WidgetsFlutterBinding.ensureInitialized();
+    final supportDir = await getApplicationSupportDirectory();
+    final destDir = Directory(p.join(supportDir.path, "flet", p.dirname(assetPath)));
+    await destDir.create(recursive: true);
+
+    var destPath = p.join(destDir.path, p.basename(assetPath));
+    if (kDebugMode && await File(destPath).exists()) {
+      await File(destPath).delete();
+    }
+
+    ByteData data = await rootBundle.load(assetPath);
+    List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
+    await File(destPath).writeAsBytes(bytes);
+    return destPath;
+  }
+
+  @override
+  Future<String> getDirFiles(String path, {bool recursive = false}) async {
+    final dir = Directory(path);
+    if (!await dir.exists()) {
+      return "<not found>";
+    }
+    return (await dir.list(recursive: recursive).toList()).map((file) => file.path).join('\n');
+  }
+}
+
+PlatformUtils getPlatformUtils() => IOUtils();
diff --git a/src/serious_python_platform_interface/lib/src/utils_web.dart b/src/serious_python_platform_interface/lib/src/utils_web.dart
new file mode 100644
index 0000000..4fd9e74
--- /dev/null
+++ b/src/serious_python_platform_interface/lib/src/utils_web.dart
@@ -0,0 +1,48 @@
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'utils_interface.dart';
+
+class WebUtils implements PlatformUtils {
+  @override
+  Future<String> extractAssetOrFile(String path,
+      {bool isAsset = true, String? targetPath, bool checkHash = false}) async {
+    WidgetsFlutterBinding.ensureInitialized();
+    try {
+      if (isAsset) {
+        await rootBundle.load(path);
+      }
+      return path;
+    } catch (e) {
+      debugPrint('Error handling web asset: $e');
+      rethrow;
+    }
+  }
+
+  @override
+  Future<String> extractAssetZip(String assetPath,
+      {String? targetPath, bool checkHash = false}) {
+    return extractAssetOrFile(assetPath,
+        targetPath: targetPath, checkHash: checkHash);
+  }
+
+  @override
+  Future<String> extractFileZip(String filePath,
+      {String? targetPath, bool checkHash = false}) {
+    return extractAssetOrFile(filePath,
+        isAsset: false, targetPath: targetPath, checkHash: checkHash);
+  }
+
+  @override
+  Future<String> extractAsset(String assetPath) async {
+    WidgetsFlutterBinding.ensureInitialized();
+    await rootBundle.load(assetPath);
+    return assetPath;
+  }
+
+  @override
+  Future<String> getDirFiles(String path, {bool recursive = false}) async {
+    return path;
+  }
+}
+
+PlatformUtils getPlatformUtils() => WebUtils();
\ No newline at end of file
diff --git a/src/serious_python_web/lib/pyodide_constants.dart b/src/serious_python_web/lib/pyodide_constants.dart
new file mode 100644
index 0000000..f8a6b12
--- /dev/null
+++ b/src/serious_python_web/lib/pyodide_constants.dart
@@ -0,0 +1,5 @@
+class PyodideConstants {
+  static const String pyodideVersion = 'v0.27.2';
+  static const String pyodideBaseURL = 'https://cdn.jsdelivr.net/pyodide/$pyodideVersion/full/';
+  static const String pyodideJS = '${pyodideBaseURL}pyodide.js';
+}
\ No newline at end of file
diff --git a/src/serious_python_web/lib/pyodide_interop.dart b/src/serious_python_web/lib/pyodide_interop.dart
new file mode 100644
index 0000000..492834b
--- /dev/null
+++ b/src/serious_python_web/lib/pyodide_interop.dart
@@ -0,0 +1,27 @@
+import 'package:js/js.dart';
+
+// Define the external JavaScript functions we need
+@JS('loadPyodide')
+external Object loadPyodide(Object config);
+
+@JS()
+@anonymous
+class PyodideInterface {
+  external Object runPythonAsync(String code);
+  external Object runPython(String code);
+  external Object pyimport(String packageName);
+  external FileSystem get FS;
+  external PyodideGlobals get globals;
+}
+
+@JS()
+@anonymous
+class PyodideGlobals {
+  external Object get(String name);
+}
+
+@JS()
+@anonymous
+class FileSystem {
+  external Object writeFile(String path, String data, Object options);
+}
\ No newline at end of file
diff --git a/src/serious_python_web/lib/pyodide_state.dart b/src/serious_python_web/lib/pyodide_state.dart
new file mode 100644
index 0000000..30afe85
--- /dev/null
+++ b/src/serious_python_web/lib/pyodide_state.dart
@@ -0,0 +1,201 @@
+import 'dart:html' as html;
+import 'dart:js_util' as js_util;
+
+import 'package:flutter/services.dart';
+import 'package:serious_python_web/pyodide_constants.dart';
+import 'package:serious_python_web/pyodide_interop.dart';
+import 'package:serious_python_web/pyodide_utils.dart';
+
+
+class PyodideStateManager {
+  PyodideStateInitialize? _initState;
+  PyodideStateLoadDependencies? _depState;
+  PyodideStateLoadModuleCode? _moduleState;
+
+  Future<PyodideInterface> getPyodide(List<String> modulePaths) async {
+    if(_initState == null) {
+      _initState = await PyodideStateInitialize().doSetup();
+    }
+    if(_depState == null) {
+      _depState = await PyodideStateLoadDependencies(_initState!._pyodide!).doSetup();
+    }
+    if(_moduleState == null) {
+      _moduleState = await PyodideStateLoadModuleCode(_depState!._pyodide);
+    }
+    _moduleState = await _moduleState!.doSetup(modulePaths);
+    return _moduleState!._pyodide;
+  }
+
+}
+
+class PyodideStateInitialize {
+  PyodideInterface? _pyodide;
+
+  Future<void> _initializePyodide() async {
+    if (_pyodide != null) return;
+
+    try {
+      // Inject required meta tags first
+      PyodideUtils.injectMetaTags();
+
+      // Create and add the script element
+      final scriptElement = html.ScriptElement()
+        ..src = PyodideConstants.pyodideJS
+        ..type = 'text/javascript';
+
+      html.document.head!.append(scriptElement);
+
+      // Wait for script to load
+      await _waitForPyodide();
+
+      // Initialize Pyodide with correct base URL
+      final config = js_util.jsify({
+        'indexURL': PyodideConstants.pyodideBaseURL,
+        'stdout': (String s) => print('Python stdout: $s'),
+        'stderr': (String s) => print('Python stderr: $s')
+      });
+
+      final pyodidePromise = loadPyodide(config);
+      _pyodide = await js_util.promiseToFuture<PyodideInterface>(pyodidePromise);
+
+      // Test Python initialization
+      await PyodideUtils.runPythonCode(_pyodide, """
+import sys
+print(f"Python version: {sys.version}")
+      """);
+
+      print("Pyodide initialized successfully");
+    } catch (e, stackTrace) {
+      print('Error initializing Pyodide: $e');
+      print('Stack trace: $stackTrace');
+      rethrow;
+    }
+  }
+
+  Future<void> _waitForPyodide() async {
+    for (int attempts = 0; attempts < 100; attempts++) {
+      if (js_util.hasProperty(js_util.globalThis, 'loadPyodide')) {
+        return;
+      }
+      await Future.delayed(const Duration(milliseconds: 100));
+    }
+    throw Exception('Timeout waiting for Pyodide to load');
+  }
+
+  Future<PyodideStateInitialize> doSetup() async {
+    try {
+      await _initializePyodide();
+      return this;
+    } catch (e) {
+      rethrow;
+    }
+  }
+}
+
+class PyodideStateLoadDependencies {
+  final PyodideInterface _pyodide;
+
+  PyodideStateLoadDependencies(this._pyodide);
+
+  Future<void> _loadPythonDependencies() async {
+    try {
+      // Parse requirements.txt
+      final requirementsFile = await PyodideUtils.getRequirementsFilesFromAssets();
+      final packages = await PyodideUtils.parseRequirementsFiles(requirementsFile);
+
+      if (packages.isEmpty) {
+        print("Nothing to do: No packages found in all requirements.txt files.");
+        return;
+      }
+
+      print("Loading Pyodide packages: ${packages.join(', ')}");
+      for (final package in packages) {
+        try {
+          await js_util.promiseToFuture(js_util.callMethod(_pyodide, 'loadPackage', [package]));
+        } catch (e) {
+          print('Could not import package: $package');
+        }
+      }
+
+      print("Packages loaded successfully");
+    } catch (e) {
+      print('Error loading packages: $e');
+      rethrow;
+    }
+  }
+
+  Future<PyodideStateLoadDependencies> doSetup() async {
+    try {
+      await _loadPythonDependencies();
+      return this;
+    } catch (e) {
+      rethrow;
+    }
+  }
+}
+
+class PyodideStateLoadModuleCode {
+  final PyodideInterface _pyodide;
+
+  final Set<String> _loadedModules = {};
+
+  PyodideStateLoadModuleCode(this._pyodide);
+
+  Future<void> _loadModules(String moduleName, List<String> modulePaths) async {
+    // Create a package directory in Pyodide's virtual filesystem
+    await PyodideUtils.runPythonCode(_pyodide, '''
+import os
+import sys
+
+if not os.path.exists('/package'):
+    os.makedirs('/package')
+
+if not os.path.exists('/package/$moduleName'):
+    os.makedirs('/package/$moduleName')
+    
+# Create __init__.py to make it a package
+with open(f'/package/$moduleName/__init__.py', 'w') as f:
+    f.write('')
+    
+if '/package' not in sys.path:
+    sys.path.append('/package')
+''');
+
+    for (final modulePath in modulePaths) {
+      final moduleCode = await rootBundle.loadString(modulePath);
+      final fileName = modulePath.split('/').last;
+
+      // Use Pyodide's filesystem API to write module Code
+      await _pyodide.FS.writeFile('/package/$moduleName/$fileName', moduleCode, {'encoding': 'utf8'});
+    }
+  }
+
+  /// Loads all necessary python code modules and imports them via pyodide
+  Future<void> _loadModuleDirectories(List<String> modulePaths) async {
+    final List<String> moduleNamesToImport = [];
+    for (final directory in modulePaths) {
+      final moduleName = directory.split("/").last;
+      if (_loadedModules.contains(moduleName)) {
+        continue;
+      }
+
+      final pythonFiles = await PyodideUtils.listPythonFilesInDirectory(directory);
+      await _loadModules(moduleName, pythonFiles);
+      _loadedModules.add(moduleName);
+      moduleNamesToImport.add(moduleName);
+    }
+    // Import the modules using pyimport
+    for (final moduleNameToImport in moduleNamesToImport) {
+      await _pyodide.pyimport('$moduleNameToImport');
+    }
+  }
+
+  Future<PyodideStateLoadModuleCode> doSetup(List<String> modulePaths) async {
+    try {
+      await _loadModuleDirectories(modulePaths);
+      return this;
+    } catch(e) {
+      rethrow;
+    }
+  }
+}
diff --git a/src/serious_python_web/lib/pyodide_utils.dart b/src/serious_python_web/lib/pyodide_utils.dart
new file mode 100644
index 0000000..346e59e
--- /dev/null
+++ b/src/serious_python_web/lib/pyodide_utils.dart
@@ -0,0 +1,121 @@
+import 'dart:convert';
+import 'dart:html' as html;
+import 'dart:js_util' as js_util;
+
+import 'package:flutter/services.dart';
+import 'package:serious_python_web/pyodide_interop.dart';
+
+class PyodideUtils {
+  static void injectMetaTags() {
+    try {
+      final head = html.document.head;
+
+      // Check if meta tags already exist
+      if (!head!.querySelectorAll('meta[name="cross-origin-opener-policy"]').isNotEmpty) {
+        final coopMeta = html.MetaElement()
+          ..name = 'cross-origin-opener-policy'
+          ..content = 'same-origin';
+        head.append(coopMeta);
+      }
+
+      if (!head.querySelectorAll('meta[name="cross-origin-embedder-policy"]').isNotEmpty) {
+        final coepMeta = html.MetaElement()
+          ..name = 'cross-origin-embedder-policy'
+          ..content = 'require-corp';
+        head.append(coepMeta);
+      }
+    } catch (e) {
+      print('Error injecting meta tags: $e');
+    }
+  }
+
+  static Future<Set<String>> getRequirementsFilesFromAssets() async {
+    // Load the asset manifest
+    final manifestContent = await rootBundle.loadString('AssetManifest.json');
+    final Map<String, dynamic> manifest = json.decode(manifestContent);
+
+    // Filter for Python files in the specified directory
+    return manifest.keys.where((String key) => key.contains("requirements.txt")).toSet();
+  }
+
+  static Future<List<String>> parseRequirementsFiles(Set<String> requirementsFiles) async {
+    try {
+      final List<String> requirements = [];
+      for(final requirementsFile in requirementsFiles) {
+        final content = await rootBundle.loadString(requirementsFile);
+        final parsedRequirements = content
+            .split('\n')
+            .map((line) => line.trim())
+            .where((line) => line.isNotEmpty && !line.startsWith('#') && !line.startsWith('-'))
+            .map((line) => line.split('==')[0].split('>=')[0].trim())
+            .toList();
+        requirements.addAll(parsedRequirements);
+      }
+      return requirements;
+    } catch (e) {
+      print('Error parsing requirements.txt: $e');
+      rethrow;
+    }
+  }
+
+  static Future<List<String>> listPythonFilesInDirectory(String directory) async {
+    // Load the asset manifest
+    final manifestContent = await rootBundle.loadString('AssetManifest.json');
+    final Map<String, dynamic> manifest = json.decode(manifestContent);
+
+    // Filter for Python files in the specified directory
+    return manifest.keys.where((String key) => key.contains(directory) && key.endsWith('.py')).toList();
+  }
+
+  static Future<void> setupEnvironmentVariables(
+      PyodideInterface? pyodide, Map<String, String>? environmentVariables) async {
+    if (environmentVariables == null) {
+      return;
+    }
+    print("Running python web command with environment variables: $environmentVariables");
+
+    await runPythonCode(pyodide, '''
+import os
+${environmentVariables.entries.map((e) => "os.environ['${e.key}'] = '${e.value}'").join('\n')}
+''');
+  }
+
+  static Future<void> printPythonDebug(PyodideInterface? pyodide) async {
+    final String debugCode = '''
+import os
+import sys
+
+print("Python version:", sys.version)
+print("Python path:", sys.path)
+print("Current working directory:", os.getcwd())
+print("Directory contents:", os.listdir('/package'))
+''';
+    await runPythonCode(pyodide, debugCode);
+  }
+
+  static Future<void> runPythonCode(PyodideInterface? pyodide, String code) async {
+    try {
+      if (pyodide == null) {
+        throw Exception("Trying to run python code on non-existing pyodide object!");
+      }
+      final promise = pyodide.runPythonAsync(code);
+      await js_util.promiseToFuture(promise);
+    } catch (e) {
+      print('Error running Python code: $e');
+      rethrow;
+    }
+  }
+
+  static Future<String> getPyodideResult(PyodideInterface? pyodide) async {
+    try {
+      if (pyodide == null) {
+        throw Exception("Trying to get pyodide result on non-existing pyodide object!");
+      }
+      final result = pyodide.globals.get("pyodide_result");
+      return result.toString();
+    } catch (e) {
+      print('Error getting pyodide result: $e');
+      rethrow;
+    }
+  }
+}
diff --git a/src/serious_python_web/lib/serious_python_web.dart b/src/serious_python_web/lib/serious_python_web.dart
new file mode 100644
index 0000000..83ba61b
--- /dev/null
+++ b/src/serious_python_web/lib/serious_python_web.dart
@@ -0,0 +1,55 @@
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_web_plugins/flutter_web_plugins.dart';
+import 'package:serious_python_platform_interface/serious_python_platform_interface.dart';
+import 'package:serious_python_web/pyodide_state.dart';
+import 'package:serious_python_web/pyodide_utils.dart';
+
+class SeriousPythonWeb extends SeriousPythonPlatform {
+  final PyodideStateManager _pyodideStateManager = PyodideStateManager();
+
+  /// Registers this class as the default instance of [SeriousPythonPlatform]
+  static void registerWith(Registrar registrar) {
+    SeriousPythonPlatform.instance = SeriousPythonWeb();
+  }
+
+  @override
+  Future<String?> getPlatformVersion() async {
+    return 'web';
+  }
+
+  @override
+  Future<String?> run(String appPath,
+      {String? script, List<String>? modulePaths, Map<String, String>? environmentVariables, bool? sync}) async {
+    try {
+      final pyodide = await _pyodideStateManager.getPyodide(modulePaths ?? []);
+
+      // Load the Python code from the asset
+      final pythonCode = await rootBundle.loadString(appPath);
+
+      // Set environment variables if provided
+      await PyodideUtils.setupEnvironmentVariables(pyodide, environmentVariables);
+
+      // Print debug code in debug mode
+      if(kDebugMode) {
+        await PyodideUtils.printPythonDebug(pyodide);
+      }
+
+      // Run actual code
+      await PyodideUtils.runPythonCode(pyodide, pythonCode);
+
+      final result = await PyodideUtils.getPyodideResult(pyodide);
+      return result;
+    } catch (e) {
+      print('Error running Pyodide: $e');
+      return null;
+    }
+  }
+
+  @override
+  void terminate() {
+    // No need to implement for web
+  }
+}
diff --git a/src/serious_python_web/pubspec.yaml b/src/serious_python_web/pubspec.yaml
new file mode 100644
index 0000000..2e3abfa
--- /dev/null
+++ b/src/serious_python_web/pubspec.yaml
@@ -0,0 +1,31 @@
+name: serious_python_web
+description: Web implementations of the serious_python plugin
+homepage: https://flet.dev
+repository: https://github.com/flet-dev/serious-python
+version: 0.8.7
+
+environment:
+  sdk: '>=3.1.3 <4.0.0'
+  flutter: '>=3.3.0'
+
+dependencies:
+  flutter:
+    sdk: flutter
+  flutter_web_plugins:
+    sdk: flutter
+  js: ^0.6.7
+  serious_python_platform_interface:
+    path: ../serious_python_platform_interface
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  flutter_lints: ^2.0.0
+
+flutter:
+  plugin:
+    implements: serious_python
+    platforms:
+      web:
+        pluginClass: SeriousPythonWeb
+        fileName: serious_python_web.dart
\ No newline at end of file
diff --git a/src/serious_python_windows/README.md b/src/serious_python_windows/README.md
index fd20ffa..b61b080 100644
--- a/src/serious_python_windows/README.md
+++ b/src/serious_python_windows/README.md
@@ -1,4 +1,4 @@
-# serious_python_ios
+# serious_python_windows
 
 The Windows implementation of `serious_python`.