Publishing packages
You can publish most JavaScript and TypeScript code written using ESM modules as a JSR package. JSR packages are published to jsr.io, and can be imported from Deno, Node.js, and other tools. Learn more about using packages.
Both code written originally to use package.json
, and code written originally
for Deno can be published as a JSR package. JSR supports and encourages
publishing TypeScript source code rather than pairs of .js
+ .d.ts
files.
This allows JSR to provide more helpful auto-generated documentation, and helps
provide improved auto-completion in editors.
JSR package rules
All packages uploaded to JSR are automatically processed and verified during publishing to ensure that all code hosted on JSR abides by a consistent set of rules. These rules are designed to enable portability across environments. Your code must follow these rules to be able to be published to JSR.
- ESM modules only: JSR packages are published as ESM modules. This means
that you can only publish modules that use the
import
andexport
keywords. You cannot publish CommonJS modules. - npm packages are supported: You can depend on npm packages by either
specifying them in the
dependencies
of yourpackage.json
, or by referencing them in code usingnpm:
specifiers such asimport { cloneDeep } from "npm:lodash@4";
. - jsr packages are supported: You can depend on JSR packages by specifying
them in the
dependencies
of yourpackage.json
, or by referencing them in code usingjsr:
specifiers such asimport { encodeBase64 } from "jsr:@std/encoding@1/base64";
node:
built-ins are supported: You can import Node.js built-ins using thenode:
scheme. For example, you can import thefs
module usingimport { readFile } from "node:fs";
. If your package has apackage.json
, you can also import Node.js built-ins with bare specifiers (without thenode:
prefix).- Simple file names: File names must be Windows and Unix compatible. This
means that file names cannot contain characters like
*
,:
, or?
. You may also not have multiple files with the same name, but different casing. - Preferably, no TypeScript “slow types”: To speed up type checking, support documentation generation, and Node.js compatibility, JSR packages should not use certain TypeScript types in exported functions, classes, or variables. This is enforced by default, but can be opted out of. Learn more about “slow types.”
- Valid cross file imports: All of the relative imports between modules in
your package must resolve at publish time. The format of supported specifiers
depends on whether a
package.json
is in use, and is elaborated below.
Writing the code
ESM only
To publish a JSR package, you must first write the code for your package. JSR packages are written in JavaScript or TypeScript, and are published as ESM modules.
// greet.ts
/**
* Greet a person.
* @param name The name of the person to greet.
*/
export function greet(name: string) {
console.log(`Hello, ${name}!`);
}
Relative imports
A package can consist of multiple modules. You can reference other modules in
your package using relative imports. You should use the correct extensions in
the imports – ./greet.ts
to import the greet.ts
, rather than ./greet
or
./greet.js
.
When a package.json
is present in your package, modules may use “sloppy
imports”. With “sloppy imports”, you can import files without extensions, or
with a .js
extension even if the underlying file is .ts
. You can also use
directory imports with index.js
resolution.
// mod.ts
/**
* A module providing a function to greet people.
*
* @example
* ```ts
* import { greet } from "@luca/greet";
*
* greet("Luca");
* ```
*
* @module
*/
export * from "./greet.ts";
Importing npm packages
You may import npm packages specified in the "dependencies"
of a
package.json
, ones specified in an import map or deno.json
, or ones
specified in source code using npm:
specifiers.
// package.json
{
"dependencies": {
"chalk": "5"
}
}
// mod.ts
import * as chalk from "chalk";
import * as express from "npm:express@4";
Importing JSR packages
You may import JSR packages specified in the "dependencies"
of a
package.json
, ones specified in an import map or deno.json
, or ones
specified in source code using jsr:
specifiers.
Learn more about using packages.
// package.json
{
"dependencies": {
"@std/encoding": "npm:@jsr/std__encoding@1"
}
}
// mod.ts
import * as encoding from "@std/encoding";
import { camelCase } from "jsr:@luca/cases@1";
Importing Node.js built-ins
You may import Node.js built-ins using the node:
scheme. If a package.json
is present in your package, you may also omit the node:
scheme.
// mod.ts
import { readFileSync } from "node:fs";
export function readJsonFile(path: string) {
return JSON.parse(readFileSync(path, "utf8"));
}
Dependency manifest
You may use a dependency manifest like a package.json
, or an
import map (like the
deno.json
file) to simplify your imports. During publishing, jsr publish
/
deno publish
will automatically rewrite the specifiers in your source code to
fully qualified specifiers that do not require an import map / package.json
anymore.
// import_map.json / deno.json
{
"imports": {
"@luca/greet": "jsr:@luca/greet@1",
"@std/path": "jsr:@std/path@1"
}
}
// mod.ts
export { greet } from "@luca/greet";
export { join } from "@std/path";
Preventing slow types
When writing TypeScript, you should ensure that your code does not use “slow types” that prevent JSR from generating documentation, generating type declarations for the npm compatibility layer, and speeding up type checking for consumers of your package. Learn more about “slow types.”
You may temporarily bypass this restriction by publishing with the
--allow-slow-types
flag. This will cause type checking to be significantly slower for all of your users. Additionally, documentation generation and node compatibility will suffer. Consider fixing the slow types to avoid these drawbacks rather than using this flag.
Package config file
After you have written your code, you must add a config file to your package.
This file contains package metadata like the name, version, and entrypoint(s).
This file should be named jsr.json
. Deno users can also include the required
JSR properties in their deno.json
to avoid having to create another file.
// jsr.json / deno.json
{
"name": "@luca/greet",
"version": "1.0.0",
"exports": "./mod.ts"
}
Read more about the configuring JSR packages.
Creating a scope and package
JSR is organized into scopes. A scope is a collection of packages. Scopes are
similar to npm organizations. Scopes are prefixed with an @
symbol, and are
followed by a name. For example, @luca
is a scope.
You can create a scope at jsr.io/new. Scopes names must be between 2 and 32 characters long, and can only contain lowercase letters, numbers, and hyphens. You can only create a scope if the name is not already taken. Scope names that are very similar to existing scope names – for example ones that only differ by a hyphen – are prohibited. Learn more about scopes.
After you have created a scope, you can create a package in that scope. You can create a package at jsr.io/new. Package names must be between 2 and 20 characters long, and can only contain lowercase letters, numbers, and hyphens. You can only create a package if the name is not already taken. Package names that are very similar to existing package names – for example ones that only differ by a hyphen – are prohibited. Learn more about packages.
Verifying your package
To publish packages, including performing a dry run to confirm your package
meets all JSR rules, involves using jsr publish
or deno publish
. The syntax
for both commands is broadly identical. Depending on your tool, you can invoke
the publish command as follows.
# deno
deno publish
# npm
npx jsr publish
# yarn
yarn dlx jsr publish
# pnpm
pnpm dlx jsr publish
You can run jsr publish
with the --dry-run
flag to perform all publish
verification that would happen during a real publish. This will print out a list
of files that will be published, but stop short of actually publishing to the
registry.
# deno
$ deno publish --dry-run
# npm
$ npx jsr publish --dry-run
# yarn
yarn dlx jsr publish --dry-run
# pnpm
pnpm dlx jsr publish --dry-run
Publishing from your local machine
You can publish packages from your local machine using either jsr publish
or
deno publish
command.
Authentication will happen via your browser, so you do not need to provide any credentials to the CLI.
Enter the root directory of your package (containing the jsr.json
/
deno.json
file), and run jsr publish
.
# deno
$ deno publish
# npm
$ npx jsr publish
# yarn
yarn dlx jsr publish
# pnpm
pnpm dlx jsr publish
When you run jsr publish
, the CLI will open your browser to approve
publishing. You will be prompted to log in with your JSR account if you are not
already logged in. After you have logged in, you will be prompted to grant the
CLI access to publish the specific package you are trying to publish. Click
“Allow” to grant the CLI access.
The CLI will now upload your package to the JSR registry. After the upload is complete, the CLI will output the URL of where you can view the package on the JSR site.
During publishing, both the JSR CLI and the JSR server will run many checks against your package to ensure that it is valid. If any of these checks fail, the CLI will output an error message. You must fix these errors before you can attempt publishing again. Learn more about troubleshooting publishing errors.
Publishing from GitHub Actions
JSR has first class support for publishing packages from GitHub Actions. This allows you to publish packages automatically from CI without you having to configure any secrets or authentication.
To publish from GitHub Actions, you must first link your package to your GitHub repository from your package settings in JSR. To do this, go to the settings tab of your package on jsr.io, enter your GitHub repository name, and click “Link”.
After you have linked your package to your GitHub repository, you can publish
from GitHub Actions. To do this, create a workflow file in your repository, for
example at .github/workflows/publish.yml
. In this workflow file, you can
create a job that publishes your package using the jsr publish
command.
# .github/workflows/publish.yml
name: Publish
on:
push:
branches:
- main
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # The OIDC ID token is used for authentication with JSR.
steps:
- uses: actions/checkout@v4
- run: npx jsr publish
This workflow will run every time you push to the main
branch of your
repository. It will publish your package to JSR, and will automatically use the
correct version number based on the version in your jsr.json
file.
jsr publish
will not attempt to publish if the version specified in your
jsr.json
file is already published to JSR.
Filtering files
jsr publish
will ignore files that are listed in a .gitignore
file in the
root of your package. Additionally, you can specify the include
and exclude
fields in your jsr.json
/ deno.json
file to include, ignore, or un-gitignore
specific files.
For example, to only selectively include certain files, you can specify a glob
that matches all files by using the include
option:
// jsr.json
{
"name": "@luca/greet",
"version": "1.0.0",
"exports": "./src/mod.ts",
// note: this will be collapsed down to just include in the future
"publish": {
"include": [
"LICENSE",
"README.md",
"src/**/*.ts"
]
}
}
You may also exclude certain files via the exclude
option:
// jsr.json
{
"name": "@luca/greet",
"version": "1.0.0",
"exports": "./src/mod.ts",
"publish": {
"include": [
"LICENSE",
"README.md",
"src/**/*.ts"
],
"exclude": [
"src/tests"
]
}
}
When using Deno, the include
and exclude
options in deno.json
are used for
many other Deno subcommands as well, such as deno test
and deno bundle
. You
can use publish.include
and publish.exclude
in your deno.json
file to
specify options that only apply to deno publish
.
// deno.json
{
"name": "@luca/greet",
"version": "1.0.0",
"exports": "./dist/mod.ts",
"publish": {
"include": ["src"],
"exclude": ["src/tests"]
}
}
Un-gitignoring files when not using “include”
You may have a package that has a .gitignore
file with the following contents:
.env
dist/
In this case any files in the dist/
directory, and any files named .env
will
be ignored when publishing.
This may however be inconvenient if you want to publish the dist/
directory,
because you have "exports"
pointing to it (or a subdirectory of it). In this
case, you can un-ignore the dist/
directory by using a negation in the
exclude
field in your jsr.json
/ deno.json
file.
// jsr.json
{
"name": "@luca/greet",
"version": "1.0.0",
"exports": "./dist/mod.ts",
"publish": {
"exclude": ["!dist"]
}
}
In this case, the dist/
directory will be included when publishing, even
though it is listed in the .gitignore
file.