Library / SDK
shashwatak/satellite-js avatar
shashwatak/satellite-js

satellite.js propagates orbits in the browser, with a WebAssembly SGP4 core

Modular set of functions for SGP4 and SDP4 propagation of TLE and OMM.

1,097 stars177 forksTypeScriptMIT

At a glance

What is it?
satellite.js is a TypeScript library that makes satellite propagation possible in the web, taking a two line element set or an Orbit Mean Message, running the SGP4 and SDP4 algorithms, and returning positions in inertial and earth fixed frames along with look angles and a doppler factor. The numeric core is C++ compiled to WebAssembly, and the library is a derivative of Brandon Rhodes' Python sgp4 and David Vallado's reference code.
Who is it for?
satellite.js is the right library if you are building a satellite tracker, a pass prediction tool or anything that needs to know where a spacecraft is, because the SGP4 core is a WebAssembly build of the reference implementations rather than a re-derivation, and the coordinate transforms follow the same published source.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 25 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • shashwatak/satellite-js1,097 ★ · +1
Star history
Repository2026-10-202026-11-07
shashwatak/satellite-js1.1k1.1k

1 Stars in the last 18 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Propagation from a TLE or from an OMM

The library's stated purpose is to make satellite propagation possible in the web, providing the functions necessary for SGP4 and SDP4 calculations as callable JavaScript, along with functions for coordinate transforms and the essentials such as the doppler factor and the shadow calculation. The input is either the familiar two line element set or an Orbit Mean Message, and the README is explicit that OMM is preferred for new applications. The TLE path is a pair of strings and the OMM path is a plain object with the mean motion, eccentricity, inclination and the rest of the orbital elements, and both end in the same internal representation:

js
import * as satellite from 'satellite.js';

const satrec = satellite.twoline2satrec(tleLine1, tleLine2);

const omm = {
  "OBJECT_NAME": "HELIOS 2A",
  "MEAN_MOTION": 15.00555103,
  "ECCENTRICITY": 0.000583,
  "INCLINATION": 98.3164,
  ...
};

const satrecFromOmm = satellite.json2satrec(omm);

The two formats describe the same thing, and the library's job is to turn either into something the propagator can chew on.

The numeric core is C++ compiled to WebAssembly

The repository is not a pure TypeScript implementation, and the build scripts make the architecture visible. There is a src-cpp/ directory with the C++ sources, common.cpp, base.cpp, SGP4.cpp and debug.cpp, and a wasm-build/ directory for the output, and the build script compiles them with em++ using SIMD flags, -msimd128, with the WebAssembly memory growth allowed, and the exported runtime methods listed so that the JavaScript side can read the heap. There are two variants, a single thread and a pthreads build, and the package.json maps them to the internal import specifiers #wasm-single-thread and #wasm-multi-thread. The debug build is compiled with the address and leak sanitizers enabled and with a recoverable leak check exported. That is a significant amount of machinery, and it is there for a reason, since the SGP4 recurrence is a long chain of floating point operations and a JavaScript implementation of it is either slow or subtly different from the reference.

The propagation call, and what happens when it fails

Propagating is one call, and the time argument can be either a number of minutes since the TLE epoch or a JavaScript Date:

js
const positionAndVelocity = satellite.sgp4(satrec, timeSinceTleEpochMinutes);

const positionAndVelocity = satellite.propagate(satrec, new Date());

The failure mode is worth reading carefully, because it is the difference between a tracker that degrades and one that lies. If the result is null, the propagation failed, and you consult the satrec.error property for a specific reason, with the possible values listed in the SatRecError enum:

js
if (positionAndVelocity === null) {
  switch (satrec.error) {
    case satellite.SatRecError.Decayed:
      console.log('The satellite has decayed')
    // ...
  }
}

A satellite that has decayed, or a propagated period that has run out, returns null rather than a plausible-looking position, and the error code tells you which of the failure modes you hit. Any application built on this has to check, because the alternative is a satellite drawn at the origin.

The transform chain, from ECI to a look angle

The propagation result is a pair of ECI coordinates, which the README calls the base results from which all other coordinates are derived, and the rest of the library is the chain that turns them into something a human or a radio can use. You need the Greenwich mean sidereal time for some of the transforms, obtained with satellite.gstime(new Date()), and then:

js
const positionEcf   = satellite.eciToEcf(positionEci, gmst),
      observerEcf   = satellite.geodeticToEcf(observerGd),
      positionGd    = satellite.eciToGeodetic(positionEci, gmst),
      lookAngles    = satellite.ecfToLookAngles(observerGd, positionEcf),
      dopplerFactor = satellite.dopplerFactor(observerCoordsEcf, positionEcf, velocityEcf);

The observer is given in geodetic coordinates in radians, which is why the example converts with satellite.degreesToRadians, and the outputs are key-value pairs with named accessors, x, y and z for the cartesian frames, azimuth, elevation and rangeSat for the look angles, and longitude, latitude and height for the geodetic ones, with degreesLong and degreesLat to convert the radians back at the end.

The lineage, and why the code looks the way it does

The provenance is documented in detail, and it explains the licence. The TypeScript in this library is heavily based, with the propagation procedures straight copied, from the Python sgp4 by Brandon Rhodes and the C++ code by David Vallado and others, and the coordinate transforms are based on T. S. Kelso's columns, parts one, two and three, along with coursework for UC Boulder's ASEN students. The MIT licence was chosen because the library is originally a derivative work of Brandon Rhodes' sgp4, which is licensed with MIT, and the original PKG-INFO file from the Python library is included in the repository, which is the sort of detail a licence audit asks about. There is also a note about code conventions that explains the odd style, that as in the prior works on the SGP4 algorithm there is as little difference as possible between this implementation and them, so that adapting future changes suggested by Vallado is much simpler, and therefore some of the conventions used are very weird. The acknowledgements name Brandon Rhodes, T. S. Kelso, David Vallado's team and Professor Steve Petersen of UCSC.

The build, the tests, and where the library fits

The package is a modern TypeScript project with a WebAssembly build, and the test story shows how seriously the numerics are taken. The test script runs vitest across three projects, js, wasm_release and wasm_debug, so the JavaScript implementation and both WebAssembly builds are all exercised, and there is a separate type test with tsc --noEmit against a dedicated tsconfig, a separate catalogue test project, and a coverage script that runs the same three projects. The build script cleans both dist and wasm-build, builds the WebAssembly and then runs tsc, and the publish hook runs the lint, the test lint, the tests and the build. The linter is Biome, configured in biome.jsonc, and the version is 7.1.0 with 7.0.1 in May 2026 and 7.0.0 in March, with the last push to the repository on 2026-10-14.

The limit is the age of the element set

The library is a propagator, and the accuracy of its output is bounded by the input, which is a snapshot of a satellite's orbit at a particular epoch. The README points you at the resources, with TS Kelso's columns on satellite times, the Wikipedia article on the simplified perturbations model, and the SpaceTrack report number 3 by Hoots and Roehrich, and it recommends getting a free Space Track account and downloading your own up to date TLEs or OMM for use with the library. That recommendation is the real caveat. A stale element set propagates to a position that is confidently wrong, and no propagator can fix that, because the information is not in the data. The second limit is the failure case again, that a decayed satellite or an expired element set returns null, and a tracker that does not check for it will draw a spacecraft that stopped existing years ago. The third is the scope, since this is the orbital mechanics and nothing else, so the ground station, the pass prediction schedule, the map and the notification are all yours to build.

Editorial conclusion

satellite.js is the right library if you are building a satellite tracker, a pass prediction tool or anything that needs to know where a spacecraft is, because the SGP4 core is a WebAssembly build of the reference implementations rather than a re-derivation, and the coordinate transforms follow the same published source. It is the wrong tool if you need high precision beyond the TLE itself, since an element set is a snapshot and the propagator cannot invent accuracy the data does not contain, and a satellite that has decayed will report an error rather than a position. Before you rely on it, read the error handling, since a null result carries a satrec.error code and a silent null is how a tracker ends up drawing a satellite at the origin.

Frequently asked questions

What does satellite.js actually compute?

SGP4 and SDP4 satellite propagation from a two line element set or an Orbit Mean Message, plus the coordinate transforms: ECI to ECF, ECI to geodetic, ECF to look angles, and the doppler factor, with sidereal time from gstime and helpers to convert degrees to radians and back.

How do I know when a propagation fails?

The propagation returns null when it fails, and the satrec.error property carries the reason, with the possible values listed in the SatRecError enum, such as SatRecError.Decayed for a satellite that has decayed. A caller that does not check for null will draw the satellite at the origin.

Is satellite.js a pure JavaScript implementation?

No. The numeric core is C++ in the src-cpp directory, compiled to WebAssembly with em++, in single thread and pthreads variants, and the package maps them to the internal import specifiers #wasm-single-thread and #wasm-multi-thread. The tests run against the JavaScript build and both WebAssembly builds.

Why is satellite.js licensed under MIT?

Because the library is originally a derivative work of Brandon Rhodes' Python sgp4, which is licensed with MIT. The propagation procedures were copied from that project and from David Vallado's C++ code, the coordinate transforms are based on T. S. Kelso's columns, and the original PKG-INFO file from the Python library is included in the repository.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. shashwatak/satellite-js on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/shashwatak-satellite-js.svg)](https://hysenlabs.com/projects/shashwatak-satellite-js)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/shashwatak-satellite-js.svg)](https://hysenlabs.com/projects/shashwatak-satellite-js)

Was this useful?