Contributing

PreviousNext

Want to contribute to Slate? That would be awesome!

Reporting Bugs

If you run into any weird behavior while using Slate, feel free to open a new issue in this repository! Please run a search before opening a new issue, to make sure that someone else hasn't already reported or solved the bug you've found.

Any issue you open must include:

  • A JSFiddle that reproduces the bug with a minimal setup.
  • A GIF showing the issue in action. (Using something like RecordIt.)
  • A clear explanation of what the issue is.

Here's a JSFiddle template for Slate to get you started:

Asking Questions

ℹ️ If you're looking for help with the Slate CRM platform, that's an unrelated project to us.

We've also got a Slate Slack team where you can ask questions and get answers from other people using Slate:

Please use the Slack instead of asking questions in issues, since we want to reserve issues for keeping track of bugs and features. We close questions in issues so that maintaining the project isn't overwhelming.

Submitting Pull Requests

All pull requests are super welcomed and greatly appreciated! Issues in need of a solution are marked with a ♥ help label if you're looking for somewhere to start.

Please include tests and docs with every pull request!

Repository Setup

The Slate repository is a Bun workspace orchestrated with Turbo. Unlike more traditional repositories, this means that the repository must be built in order for tests, linting, or other common development activities to function as expected.

To run the build, you need to have the Slate repository cloned to your computer. After that, you need to cd into the directory where you cloned it, and install the dependencies with Bun and build the monorepo:

bun install
bun run build
bun install
bun run build

Running Examples

To run the examples, start by building the monorepo as described in the Repository Setup section.

Then you can start the examples server with:

bun dev
bun dev

Running Tests

To run the tests, start by building the monorepo as described in the Repository Setup section.

The fast local repository gate is:

bun check
bun check

It runs lint, typecheck, and tests. Playwright integration coverage lives in the fuller gates:

bun check:ci
bun check:full
bun check:ci
bun check:full

Package typechecking pulls the package build prerequisites through Turbo instead of front-loading a full root build. For purely mechanical formatting fixes, run:

bun lint:fix
bun lint:fix

If you only want to rerun the tests, use:

bun run test
bun run test

That runs the full package test graph:

  • bun test for Bun-owned lanes
  • bun test:vitest for the @platejs/slate-react DOM lane

If you only want the Bun-owned package tests, use:

bun test
bun test

If you only want the @platejs/slate-react Vitest lane, use:

bun test:vitest
bun test:vitest

If you need to debug something, you can add a debugger line to the source, and then run bun test:inspect.

If you only want to run a specific Bun-owned test or tests, use Bun's test-name filtering:

bun test ./packages/slate/test/index.spec.ts --test-name-pattern "Editor\\.above"
bun test ./packages/slate/test/index.spec.ts --test-name-pattern "Editor\\.above"

If you only want to run a specific @platejs/slate-react Vitest test or tests, run Vitest directly:

cd ./packages/slate-react && bun test:vitest -- test/react-editor.test.tsx -t "should not trigger onValueChange"
cd ./packages/slate-react && bun test:vitest -- test/react-editor.test.tsx -t "should not trigger onValueChange"

If you only want the lint gate, use:

bun lint
bun lint

Running integration tests

To run integrations with Playwright, either:

  • run bun dev and then bun run playwright in a separate session
  • or run bun test:integration-local

The local integration command manages its own exported-site server, so it does not need the normal dev server to already be running.

For a broad local sweep that should not block your current terminal or agent thread, start it asynchronously:

bun test:integration-local:async
bun test:integration-local:async

The async command accepts Playwright targets and flags when you want a narrower background run:

bun test:integration-local:async playwright/integration/examples/richtext.test.ts --project=chromium
bun test:integration-local:async playwright/integration/examples/richtext.test.ts --project=chromium

Then inspect the latest run later:

bun test:integration-local:pickup
bun test:integration-local:status
bun test:integration-local:failures
bun test:integration-local:pickup
bun test:integration-local:status
bun test:integration-local:failures

Async runs write ignored artifacts under .tmp/integration-runs/<run-id>/, including status.json, pickup.md, raw.log, report.json, and failures.md. Only one async integration run starts at a time. Start from pickup.md or bun test:integration-local:pickup when resuming a previous run.

The async command is shared across local agents. If the same command is already running for the current source stamp, bun test:integration-local:async reuses that run and prints its pickup/status commands instead of starting another Playwright sweep. If the same command already completed for the current source stamp, it prints the cached pickup. A different command while another run is active is rejected clearly; use pickup first, then start the different sweep after the active one finishes.

Running integration tests in Docker

If tests fail on CI but pass locally (often due to OS differences), you can run tests in a Docker container that replicates the same environment as CI.

Prerequisites: The project must be built first (same as running tests locally).

bun test:integration-docker
bun test:integration-docker

The script will automatically:

  1. Start the development server (if not already running)
  2. Run tests inside a Docker container
  3. Stop the server when tests complete

You can also pass additional arguments to the test runner. For example, to run a specific test file:

bun test:integration-docker playwright/integration/slate-react/selection.test.ts
bun test:integration-docker playwright/integration/slate-react/selection.test.ts

Or run a specific browser project:

bun test:integration-docker --project=chromium
bun test:integration-docker --project=chromium

You can combine arguments as well:

bun test:integration-docker playwright/integration/examples/richtext.test.ts --project chromium
bun test:integration-docker playwright/integration/examples/richtext.test.ts --project chromium

Testing Input Methods

Here's a helpful page detailing how to test various input scenarios on Windows, Mac and Linux.

Android tests

When making changes that affect Android or IME behavior, test against /examples/android-tests.

That page links to the placeholder, inline-edge, and void-edge examples.

Publishing Releases

Publishing Normal @latest Release

Slate uses Changesets for publishing, so run:

bun release:latest
bun release:latest

This automatically runs the prerelease script first so the build, test, and lint gate is green before publishing.

Publishing @next Release

If we are unsure as to the stability of a release because there are significant changes and/or particularly complex changes, release with the @next tag.

bun release:next
bun release:next

Publishing @experimental Release

If you need to create an experimental release to see how a published package will behave during an actual publish, release with the @experimental tag. End users should have no expectation that an @experimental release will be usable.

bun release:experimental
bun release:experimental

Running Prerelease Script

If we want to make sure that Slate code follows the preparations for a release but without actually publishing, run:

bun prerelease
bun prerelease

Which will build, test and lint Slate code.