diff --git a/.gitignore b/.gitignore
index 7c4e6f7c5..6499c2815 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ __pycache__/
# Distribution / packaging
.Python
build/
+build_wasm/
develop-eggs/
dist/
downloads/
diff --git a/symforce/wasm/README.md b/symforce/wasm/README.md
new file mode 100644
index 000000000..d61bb1a8e
--- /dev/null
+++ b/symforce/wasm/README.md
@@ -0,0 +1,51 @@
+# Compiling SymForce to Web Assembly
+
+The goal is to create Javascript (ideally Typescript) bindings
+for select C++ implementations within [SymForce](https://symforce.org/)
+using [Embind](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html)
+so that symforce can be imported and used in JS like any other package. The strategy is similar to how we use pybind for Python.
+
+Step 1: Source the emscripten SDK:
+```
+source ~/projects/wasymforce/.venv/bin/activate
+source ~/projects/emsdk/emsdk_env.sh
+```
+
+Step 2: Run emcmake:
+```
+mkdir build_wasm
+cd build_wasm
+
+emcmake cmake .. \
+-DSYMFORCE_BUILD_STATIC_LIBRARIES=ON \
+-DSYMFORCE_BUILD_OPT=ON \
+-DSYMFORCE_BUILD_CC_SYM=OFF \
+-DSYMFORCE_BUILD_EXAMPLES=OFF \
+-DSYMFORCE_BUILD_TESTS=OFF \
+-DSYMFORCE_BUILD_SYMENGINE=OFF \
+-DSYMFORCE_GENERATE_MANIFEST=OFF \
+-DSYMFORCE_BUILD_BENCHMARKS=OFF
+```
+
+Step 3: Run emmake:
+```
+emmake make -j 7
+```
+
+Step 4: Run embind:
+```
+emcc -lembind -o symforce_bindings.js ../symforce/wasm/wasm_bindings.cc \
+-s LLD_REPORT_UNDEFINED --no-entry \
+-Wl,--whole-archive \
+./_deps/spdlog-build/libspdlog.a \
+./_deps/metis-build/libmetis/libmetis.a \
+./_deps/fmtlib-build/libfmt.a \
+./libsymforce_gen.a \
+./symforce/opt/libsymforce_opt.a \
+-Wl,--no-whole-archive \
+-I ../gen/cpp \
+-I ./_deps/eigen3-src \
+-I ./lcmtypes/cpp \
+-I ../third_party/skymarshal/include
+```
+
diff --git a/symforce/wasm/index.html b/symforce/wasm/index.html
new file mode 100644
index 000000000..a9afd36aa
--- /dev/null
+++ b/symforce/wasm/index.html
@@ -0,0 +1,58 @@
+
+
+
+ SymForce WASM Demo
+
+
+
+
+
+
SymForce Web Assembly Demo
+
Output:
+
+
+
+
+
+
+
diff --git a/symforce/wasm/symforce_bindings.js b/symforce/wasm/symforce_bindings.js
new file mode 120000
index 000000000..adf7ffdf7
--- /dev/null
+++ b/symforce/wasm/symforce_bindings.js
@@ -0,0 +1 @@
+../../build_wasm/symforce_bindings.js
\ No newline at end of file
diff --git a/symforce/wasm/symforce_bindings.wasm b/symforce/wasm/symforce_bindings.wasm
new file mode 120000
index 000000000..aec0f49f2
--- /dev/null
+++ b/symforce/wasm/symforce_bindings.wasm
@@ -0,0 +1 @@
+../../build_wasm/symforce_bindings.wasm
\ No newline at end of file
diff --git a/symforce/wasm/wasm_bindings.cc b/symforce/wasm/wasm_bindings.cc
new file mode 100644
index 000000000..8e0b583aa
--- /dev/null
+++ b/symforce/wasm/wasm_bindings.cc
@@ -0,0 +1,57 @@
+#include
+#include
+
+#include
+
+using namespace emscripten;
+
+// TODO(rachel): Use emscripten::val for template params. See:
+// https://github.com/emscripten-core/emscripten/issues/4887#issuecomment-283285974
+EMSCRIPTEN_BINDINGS(symforce)
+{
+ class_("Rot3")
+ .constructor<>()
+ .function("data", &sym::Rot3d::Data)
+ .function("inverse", &sym::Rot3d::Inverse)
+ .function("toRotationMatrix", &sym::Rot3d::ToRotationMatrix)
+ .function("toYawPitchRoll", &sym::Rot3d::ToYawPitchRoll)
+ .class_function("identity", &sym::Rot3d::Identity)
+ // TODO(hayk): Figure this out.
+ // .class_function("fromYawPitchRoll", static_cast(&sym::Rot3d::FromYawPitchRoll))
+ // Added print for testing.
+ .function("toString", optional_override([](const sym::Rot3d &self) {
+ std::stringstream buf;
+ buf << self.Data().transpose() << std::endl;
+ return buf.str();
+ }));
+
+ // Need Eigen return values.
+
+ // TODO(rachel): How to handle overloads? Errors:
+ // JS: Cannot register public name 'Matrix' twice.
+ // emcc: template argument for non-type template parameter must be an expression
+ class_>("Matrix33")
+ .constructor<>()
+ .function("toString", optional_override([](const Eigen::Matrix &self) {
+ std::stringstream buf;
+ buf << self << std::endl;
+ return buf.str();
+ }));
+
+ class_>("Matrix31")
+ .constructor<>()
+ .function("toString", optional_override([](const Eigen::Matrix &self) {
+ std::stringstream buf;
+ buf << self << std::endl;
+ return buf.str();
+ }));
+
+ class_>("Matrix41")
+ .constructor<>()
+ // Added print for testing.
+ .function("toString", optional_override([](const Eigen::Matrix &self) {
+ std::stringstream buf;
+ buf << self << std::endl;
+ return buf.str();
+ }));
+}