@eeeooolll/graphiql
A Svelte 5 GraphiQL alternative. CodeMirror 6 under the hood, runes-only state, SSR-safe, zero build config for SvelteKit consumers.
Published on npm as @eeeooolll/graphiql.
Install
bun add @eeeooolll/graphiql
Requires Svelte 5. The package ships .svelte sources — your SvelteKit/Vite build compiles them against your own Svelte version.
Entry points
@eeeooolll/graphiql— utilities, fetchers, stores, types (TS-only)@eeeooolll/graphiql/component— theGraphiQLSvelte component (default export)@eeeooolll/graphiql/splitter— theSplitterSvelte component (default export)
Component entry points are separate from the TS API so SvelteKit/Vite can resolve .svelte SFCs through their dedicated bundler hooks.
Usage
<script lang="ts">
import { createHttpFetcher } from "@eeeooolll/graphiql";
import GraphiQL from "@eeeooolll/graphiql/component";
const fetcher = createHttpFetcher({ url: "/graphql" });
</script>
<GraphiQL {fetcher}/>
Full prop list:
| Prop | Type | Default |
|---|---|---|
fetcher |
Fetcher (required) |
— |
initialQuery |
string |
"" |
namespace |
string |
"eol-graphiql" |
resultFooter |
Snippet<[{ result: string }]> |
undefined |
storage |
Storage |
localStorage-based |
subscriptionMode |
"append" \| "replace" |
"append" |
tabExtras |
Snippet<[{ tab: Tab }]> |
undefined |
theme |
Extension (CodeMirror) |
oneDark |
toolbarExtras |
Snippet |
undefined |
Integration
The component only needs a Fetcher — a function that takes { query, variables, operationName, headers } and returns either a Promise<FetcherResult> (HTTP) or an AsyncIterable<FetcherResult> (SSE/WS). That's the full seam. Any GraphQL server that speaks HTTP JSON works out of the box via createHttpFetcher.
GraphQL Yoga
// server.ts
import { createYoga, createSchema } from "graphql-yoga";
export const yoga = createYoga({
schema: createSchema({
resolvers: { Query: { hello: () => "world" } },
typeDefs: /* GraphQL */ `type Query { hello: String }`
})
});
<!-- +page.svelte -->
<script lang="ts">
import { createHttpFetcher } from "@eeeooolll/graphiql";
import GraphiQL from "@eeeooolll/graphiql/component";
const fetcher = createHttpFetcher({ url: "/graphql" });
</script>
<GraphiQL {fetcher}/>
Yoga's /graphql endpoint speaks standard JSON; no adapter needed.
Apollo Server
// server.ts
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
const fetcher = createHttpFetcher({
headers: { "apollo-require-preflight": "true" },
url: "http://localhost:4000/"
});
The apollo-require-preflight header satisfies Apollo's CSRF mitigation for non-browser clients; drop it if you disable that check.
graphql-modules
graphql-modules builds a composed schema; you still expose it over HTTP via Yoga, Apollo, or raw graphql-http. The GraphiQL wiring is identical — point the fetcher at whatever endpoint you mounted.
// modules/app.ts
import { createApplication, createModule, gql } from "graphql-modules";
const userModule = createModule({
id: "user",
resolvers: { Query: { me: () => ({ id: "1", name: "Ada" }) } },
typeDefs: gql`type Query { me: User } type User { id: ID! name: String! }`
});
export const app = createApplication({ modules: [userModule] });
// server.ts (Yoga host)
import { createYoga } from "graphql-yoga";
import { app } from "./modules/app.ts";
export const yoga = createYoga({
plugins: [app.createSubscription()],
schema: app.createSchemaForApollo()
});
<script lang="ts">
import { createHttpFetcher } from "@eeeooolll/graphiql";
import GraphiQL from "@eeeooolll/graphiql/component";
const fetcher = createHttpFetcher({ url: "/graphql" });
</script>
<GraphiQL {fetcher}/>
Hono / Bun / Deno
// deno
import { createHttpFetcher } from "@eeeooolll/graphiql";
const fetcher = createHttpFetcher({
fetch: globalThis.fetch,
url: "https://countries.trevorblades.com/"
});
The injectable fetch is how you plug in undici, a mocked fetch for tests, or a custom one that attaches auth headers.
Custom headers (auth, tenancy)
Two places to set headers:
- Per-request, server-wide — pass
headerstocreateHttpFetcher. Applied to every request. - Per-tab, user-editable — use the Headers pane in the UI. Merged on top of fetcher-level headers.
const fetcher = createHttpFetcher({
headers: { authorization: `Bearer ${token}` },
url: "/graphql"
});
Subscriptions
SSE (graphql-sse protocol):
import { createSseFetcher, createHttpFetcher } from "@eeeooolll/graphiql";
const http = createHttpFetcher({ url: "/graphql" });
const sse = createSseFetcher({ url: "/graphql/stream" });
// Dispatch by operation type in a wrapper:
const fetcher: Fetcher = (req) => /subscription\s/.test(req.query) ? sse(req) : http(req);
WebSocket (graphql-ws protocol):
import { createWsFetcher } from "@eeeooolll/graphiql";
const ws = createWsFetcher({ url: "ws://localhost:4000/graphql" });
Either transport returns an AsyncIterable<FetcherResult>; the component handles streaming into the result pane per the subscriptionMode prop.
Custom fetcher
Anything that matches the Fetcher signature works. Useful for request batching or injecting trace headers:
import type { Fetcher } from "@eeeooolll/graphiql";
const traced: Fetcher = async (req) => {
const traceId = crypto.randomUUID();
const response = await fetch("/graphql", {
body: JSON.stringify(req),
headers: { "content-type": "application/json", "x-trace-id": traceId },
method: "POST"
});
return response.json();
};
Persisted queries (APQ)
createApqFetcher implements the Apollo Automatic Persisted Queries protocol — first request sends only the SHA-256 hash; on PersistedQueryNotFound the fetcher retries with the full query and the server caches the mapping. HTTP-only.
import { createApqFetcher } from "@eeeooolll/graphiql";
const fetcher = createApqFetcher({
url: "/graphql"
});
Pass disable: true to bypass the two-step dance (full query on every request) for debugging. Hashes are cached per-fetcher in memory; no disk persistence.
Keyboard shortcuts
| Shortcut | Action |
|---|---|
Cmd/Ctrl + Enter |
Run query |
Cmd/Ctrl + Shift + Enter |
New tab |
Cmd/Ctrl + Shift + W |
Close tab |
Cmd/Ctrl + Shift + F |
Format query |
Cmd/Ctrl + Alt + Right/Left |
Next / prev tab |
Cmd+T and Cmd+W aren't used because browsers reserve them; embedders running in Tauri/Electron can remap via matchShortcut (exported from the package).
Session export/import
import { validateSessionExport } from "@eeeooolll/graphiql";
const json = JSON.parse(rawText);
const result = validateSessionExport(json);
if ("error" in result)
console.error(result.error);
else
console.log(`${result.tabs.length} tabs ready to import`);
The History panel ships Export/Import buttons that round-trip through this validator. Import accepts version-1 exports, caps at 50 tabs, and rejects strings over 1 MB.
Theming
CSS custom properties drive the chrome:
--graphiql-accent--graphiql-bg--graphiql-border--graphiql-fg--graphiql-font--graphiql-muted--graphiql-panel
The editor theme is a separate CodeMirror Extension passed via the theme prop. Ships with oneDark (default) and lightTheme:
<script lang="ts">
import { lightTheme } from "@eeeooolll/graphiql";
import GraphiQL from "@eeeooolll/graphiql/component";
</script>
<GraphiQL {fetcher} theme={lightTheme}/>
License
MIT