# Customizing `perspective.worker()`
`perspective.worker()` creates a `Client` that connects to a Perspective data
engine. By default it spins up a dedicated `Worker` running the built-in
WebAssembly engine, but you can pass an argument to change this behavior:
- A **`Worker`**, **`SharedWorker`**, or **`ServiceWorker`** — runs the
built-in engine in a different worker context.
- A **`MessagePort`** from `createMessageHandler()` — connects to a
[Virtual Server](virtual_server/custom.md) instead of the built-in engine.
## Built-in engine with a custom Worker
Pass a `Worker`, `SharedWorker`, or `ServiceWorker` that loads the worker script
distributed at
`"@perspective-dev/client/dist/cdn/perspective-server.worker.js"`.
`SharedWorker` and `ServiceWorker` have more complicated
behavior compared to a dedicated `Worker`, and will need special consideration
to integrate (or debug).
### Dedicated `Worker`
```javascript
const worker = await perspective.worker(new Worker(url));
```
### `SharedWorker`
```javascript
const worker = await perspective.worker(new SharedWorker(url));
```
### `ServiceWorker`
```javascript
const registration = await navigator.serviceWorker.register(url, {
scope: "", // Your scope here
});
const worker = await perspective.worker(registration.active);
```
## Virtual Server
Instead of the built-in WebAssembly engine, `perspective.worker()` can connect
to a Virtual Server — an adapter that translates Perspective queries into
operations on an external data source such as
[DuckDB](virtual_server/duckdb.md) or
[ClickHouse](virtual_server/clickhouse.md).
Use `perspective.createMessageHandler()` with a `VirtualServerHandler` to create
a `MessagePort`, then pass it to `worker()`:
```javascript
import perspective from "@perspective-dev/client";
const handler = {
/* VirtualServerHandler implementation */
};
const server = perspective.createMessageHandler(handler);
const client = await perspective.worker(server);
const table = await client.open_table("my_table");
```
The returned `Client` works identically to one backed by the built-in engine —
you can pass it to `.load()`, call `open_table()`, etc. The
difference is that queries are fulfilled by your handler rather than the WASM
engine.
For the full `VirtualServerHandler` interface and a worked example, see
[Implementing a custom Virtual Server](virtual_server/custom.md).