# Swiss Ephemeris alternatives in JavaScript

The JavaScript options for chart-grade planetary positions, from Swiss Ephemeris bindings to pure-JavaScript engines, with their licences and how far @zodiacs/engine is from Swiss Ephemeris and NASA JPL Horizons.

Engine 1.0.0: On npm.
Archive identity and release status: https://zodiacs.org/developers/engine/

## The options

Licences and versions are as Astrodienst's repository and the npm registry gave them on 13 October 2026.

Swiss Ephemeris is Astrodienst's C library, whose latest release is 2.10.3b, of August 2026, licensed under the AGPL or the paid Swiss Ephemeris Professional License.

sweph 2.10.3-8 binds it for Node. Its licence is AGPL-3.0-or-later, or LGPL-3.0-or-later for holders of the professional licence; its versions up to 2.10.0 were GPL-2.0.

swisseph 0.5.17, of January 2022, binds an earlier Swiss Ephemeris. It declares no licence on npm; its repository's licence file is the GNU GPL, version 2.

astronomy-engine 2.1.19, under MIT, computes the Sun, the Moon and the planets in JavaScript, with no astrological houses or charts.

ephemeris 3.2.1, under GPL-3.0, computes with a JavaScript port of Moshier's analytic ephemeris, or with Swiss Ephemeris through sweph.

@zodiacs/engine 1.0.0, under MIT AND CC-BY-4.0, builds charts on astronomy-engine: thirteen house systems, aspects, returns, transits, a sidereal zodiac with nine ayanamsas, and receipts that record how a result was computed. It has no Swiss Ephemeris code, dependency or table in it.

The two bindings are native Node addons, so they do not run in a browser or a Worker; @zodiacs/engine and astronomy-engine, which are JavaScript, do.

~~~javascript
import { natalChart } from '@zodiacs/engine';
const chart = natalChart({
  utc: '1990-06-15T12:30:00Z',
  latitude: 48.8566,
  longitude: 2.3522,
  houseSystem: 'placidus',
});
for (const { body, lon, retrograde } of chart.bodies) console.log(body, lon.toFixed(4), retrograde ? 'R' : '');
console.log(chart.houses.system, chart.houses.cusps.map((cusp) => cusp.toFixed(2)).join(' '));
~~~

## How close @zodiacs/engine comes

Against NASA JPL Horizons, the engine's tests hold ten bodies at 2020 and three at 1907; the largest difference is 14.8″, for Neptune in 2020.

Against Swiss Ephemeris 2.10.03, every tenth day from 1800 to 2199 at noon UTC, for the ten bodies and the North Node, its longitudes differed by a median of 2.0″ and at most 22.9″ (Venus, 1878) up to 2026 at the same UT. That record was measured on release candidate 0.1.1-rc.17.

Given the same sidereal time, latitude and obliquity, each of its thirteen house systems agrees with Swiss Ephemeris's house function to within 0.0001″, and Placidus, which iterates, to 0.01″.

## What changes if you move

It is not a port: there is no swe_calc_ut or swe_houses, so calls are rewritten, not renamed. It computes no asteroids. Outside 1800 to 2199 it still computes, and flags the result as outside its reference span.

## Install the release

~~~sh
npm install @zodiacs/engine@1.0.0
~~~

npm's 1.0.0 tarball is byte-identical to the archive the engine repository carries. To install those bytes from the carrier, with their digest checked before npm sees them, run this in the project's directory instead:

~~~sh
( set -eu
# POSIX shell — macOS, Linux or WSL. Not PowerShell. It runs in a subshell, so
# a failure stops the install without closing your terminal.
BASE=$(pwd)
FILE='zodiacs-engine-1.0.0.tgz'
# Run it in your project's directory. Without a package.json here, npm would
# install into the nearest parent directory that has one.
if test ! -f package.json; then
  echo "Stop: there is no package.json here. Run this in your project's directory, or create one first with: npm init -y" >&2
  exit 1
fi
if test -e "$FILE" || test -L "$FILE"; then
  echo "Stop: $FILE already exists here. Move or delete it, then run this again." >&2
  exit 1
fi
# A download that fails verification is removed, so the guard above does not
# then block the retry. Once npm takes over the archive stays: npm reports its
# own failures, and the file is what you would retry with.
trap 'status=$?; if test "$status" -ne 0; then rm -f "$BASE/$FILE"; fi; exit $status' EXIT

curl --disable --fail --silent --show-error --location --proto '=https' --max-time 120 \
  'https://raw.githubusercontent.com/zodiacs-org/engine/38d31854e5716454921b6ce8a211788a0984328e/artifacts/zodiacs-engine-1.0.0.tgz' -o "$FILE"

node --input-type=module <<'JS'
import { readFileSync } from 'node:fs';
import { createHash } from 'node:crypto';
const file = 'zodiacs-engine-1.0.0.tgz';
const expected = 'c4d3754de899fb98882331e6d7b9ce3ce77fb2072baeb002aa0ef8c77b0ab0f6';
const bytes = readFileSync(file);
const actual = createHash('sha256').update(bytes).digest('hex');
if (actual !== expected) {
  console.error(`Stop: this is not the published archive.\n  expected ${expected}\n  got      ${actual}`);
  console.error('Nothing was installed.');
  process.exit(1);
}
console.log(`Archive verified: ${actual}`);
JS

trap - EXIT
npm install --ignore-scripts "./$FILE"
echo "Installed @zodiacs/engine@1.0.0 from the verified archive."
)
~~~

## References

- [Engine validation](https://zodiacs.org/developers/engine/)
- [Swiss Ephemeris licensing (Astrodienst)](https://www.astro.com/swisseph/swephinfo_e.htm)
- [sweph on npm](https://www.npmjs.com/package/sweph)
- [swisseph on npm](https://www.npmjs.com/package/swisseph)
- [astronomy-engine on npm](https://www.npmjs.com/package/astronomy-engine)
- [ephemeris on npm](https://www.npmjs.com/package/ephemeris)
