Building components

How to create a new OP web component using the shared store pattern

Last modified 2026-05-01

Table of content
  1. The pattern
  2. Quick start
  3. Available endpoints
  4. The store
  5. Standard props
  6. Testing
  7. Troubleshooting

This development pattern is accurate to the system in the standard OP server profile provided by the Octothorpes project. Different OP servers and apps may have different approaches, or decline to host web components at all.

The pattern

Each component is a Svelte custom element compiled to a standalone JS bundle. The build target is a plain .js file that anyone can load with a <script> tag — no framework, no npm, no build step on their end.

The store (octo-store.js) is shared across all components. It owns the fetch lifecycle: building the query URL, setting loading/error state, and exposing results as reactive Svelte stores. Components only declare their props and render the data. Adding a new component mostly means picking an endpoint and writing a template.

At compile time, each component and its share of the store get bundled together:

YourComponent.svelte  ← props, template, styles
    ↓ imports
octo-store.js         ← fetch, loading state, error handling
    ↓ queries
/get/[what]/[by]      ← OP API endpoint
    ↓ npm run build:components
static/components/your-component.js  ← self-contained bundle

Quick start

1. Copy the template

cp src/lib/web-components/shared/COMPONENT_TEMPLATE.svelte \
   src/lib/web-components/your-component/YourComponent.svelte

2. Set the component name and endpoint

Change the customElement name at the top of the file:

<svelte:options customElement="octo-webring" />

Change the query endpoint to match the data you want:

const query = createOctoQuery('domains', 'in-webring');

See available endpoints below. Optionally, customize the rendering template below the CUSTOMIZE comment in the file.

3. Add to the build config

// vite.config.components.js
entry: {
  'octo-thorpe': resolve(__dirname, 'src/lib/web-components/octo-thorpe/OctoThorpe.svelte'),
  'your-component': resolve(__dirname, 'src/lib/web-components/your-component/YourComponent.svelte')
}

4. Build

npm run build:components

The output lands at static/components/your-component.js and is served automatically by SvelteKit.

Available endpoints

Pass these as createOctoQuery(what, by):

what by Returns
pages thorped Pages tagged with octothorpes
pages linked Pages linking to specific URLs
pages backlinked Pages backlinking to URLs
pages bookmarked Pages bookmarking URLs
pages posted Pages from specific domains
pages in-webring Pages in a webring
domains in-webring Domains in a webring
domains posted Domains that have posted
thorpes used Octothorpe terms that have been used
everything thorped All items tagged with octothorpes
everything posted All items from specific domains

The store

createOctoQuery returns a Svelte store with three pieces of reactive state:

import { createOctoQuery } from '../shared/octo-store.js';

const query = createOctoQuery('pages', 'thorped');

await query.fetch({ server: 'https://your-op-server.example.com', o: 'demo', limit: 10 });

$query.results  // Array of result objects
$query.loading  // Boolean
$query.error    // String or null
$query.count    // Number of results

query.fetch() accepts the same parameters as the HTML attributes: s, o, nots, noto, match, limit, offset, when, server.

Standard props

Copy these into every component — web components require individual exports for HTML attributes.

export let server = new URL(import.meta.url).origin;
export let s = '';
export let o = '';
export let nots = '';
export let noto = '';
export let match = '';
export let limit = '10';
export let offset = '0';
export let when = '';
export let autoload = false;
export let render = 'list';

$: params = { server, s, o, nots, noto, match, limit, offset, when };

Testing

Create a demo HTML file in static/components/:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Your Component</title></head>
<body>
  <your-component o="test" autoload></your-component>
  <script type="module" src="/components/your-component.js"></script>
</body>
</html>

Then run npm run dev and open http://localhost:5173/components/your-component-demo.html.

Troubleshooting

Component doesn't appear

Attributes not working

Styles not applying