Join the Federation
TL;DR — Download starter pack (descriptor, sites.json, search.php, site search widget, auto-crawler, and .htaccess). Upload to your web root or a subfolder on your host, edit your domain and pages, then register below.
If you want to contribute to the Litter Layer Federation, you can register your site as a federated node. Approved nodes share search results when visitors turn on Federated search on the results page.
How federated search works
When a visitor has the federated toggle on, Litter Layer queries approved partner nodes once per search and merges their results with this hub's index. Network results can appear across multiple result pages when the visitor clicks “Load more results,” until that search's federated pool runs out. After that, later pages come from this hub's central index only.
This keeps the network efficient: nodes are not re-queried on every page. The hub caches the merged pool briefly (about 10 minutes) so load-more stays fast on shared hosting. If a visitor waits too long before loading more, federated results may expire and only hub results will appear.
Each node may return up to 10 results per search. Those results may spread across several pages — not only the first page. No changes are required on node operators' servers for multi-page pagination.
Who is this guide for? We want anyone to contribute — including people on cheap shared hosting with cPanel, Plesk, or similar. The steps below assume your website files live in a folder called public_html (sometimes named www or httpdocs). You upload files with File Manager or FTP. You can put the node files at the root of that folder or in a subfolder — both work.
If your setup is different — VPS, static host, your own app stack, or a node on your Mac or PC — you probably already know how to adapt these files. The rules are the same: publish a descriptor, answer search requests at /search, then register here. See Need help? at the end of this page for links to other setup guides.
What you will create
You do not need to install all of Litter Layer on your host. You need a small set of files:
- A descriptor that tells the network who you are (
.well-known/litterlayer.json). - A list of pages you want searchable (
sites.json— edit manually or let the auto-crawler fill it). - A search script that reads that list (
search.php). - An optional site search widget (
ll-widget.js) for searching your own site on your pages. - An optional auto-crawler (
crawl_tick.php) that discovers pages on your domain and merges them intosites.json. - A tiny .htaccess rule so
/searchand/crawl_tickreach your scripts (common on Apache hosts).
When someone searches on Litter Layer with federated search on, the hub sends your site a query; your script returns matching rows from sites.json. The widget uses the same index for on-site search on your domain.
All of these files can live at your domain root (e.g. https://yoursite.com/) or in a subfolder (e.g. https://yoursite.com/test/). If you use a subfolder, set base_url in your descriptor to that folder URL — not the domain root alone.
File tree (web root or subfolder)
When you are done, your files should sit together in one folder — either your host's web root (public_html, www, or httpdocs) or a subfolder inside it. Example at the root:
- public_html/
- .well-known/
- litterlayer.json
- sites.json
- search.php
- crawl_tick.php
- ll-widget.js
- lib/
- ll_node_common.php
- ll_node_crawler.php
- data/ (writable by PHP; stores crawl queue)
- .htaccess (add rewrite rules here)
- …your normal site files (index.html, WordPress, etc.)…
- .well-known/
Subfolder example: the same layout inside public_html/test/ — then base_url is https://yoursite.com/test and your descriptor URL is https://yoursite.com/test/.well-known/litterlayer.json.
In cPanel: open File Manager → public_html (or your subfolder). Create the .well-known folder if it does not exist (some hosts hide dot-folders — enable “Show Hidden Files” in Settings).
Step 1 — Create .well-known/litterlayer.json
This file describes your node. Replace your-domain.example with your real domain (no www is fine if that is your canonical host). Use https:// in base_url. If your node files are in a subfolder, base_url must include that path (e.g. https://yoursite.com/test).
Live example from this hub (root install): https://litterlayer.com/.well-known/litterlayer.json
node_id— pick a short unique name (often your domain).categories— required. Add one or more that fit your site:music,tech,art,writing,games.
{
"node_id": "your-domain.example",
"base_url": "https://your-domain.example",
"categories": ["writing", "tech"],
"capabilities": {
"search": true
},
"performance": {
"timeout_ms": 1200
}
}
Test: open your descriptor URL in a browser — root: https://your-domain.example/.well-known/litterlayer.json; subfolder: https://your-domain.example/test/.well-known/litterlayer.json. You should see JSON text, not a 404.
Step 2 — Create sites.json
List every page you want searchable. Copy the sample below into the same folder as search.php and edit the URLs, titles, and descriptions. Add more objects inside the [ ] array as you publish new pages.
[
{
"url": "https://your-domain.example/",
"title": "Home page",
"description": "Welcome to my site — essays, links, and projects.",
"score": 1.0
},
{
"url": "https://your-domain.example/blog/hello-world/",
"title": "Hello world",
"description": "My first post about joining the small web.",
"score": 0.9
},
{
"url": "https://your-domain.example/links/",
"title": "Links",
"description": "Friends, tools, and places I like on the internet.",
"score": 0.8
}
]
Tip: keep URLs on the same domain as your descriptor. Use the full https:// address for each entry.
Step 3 — Create search.php
Save this file next to sites.json in your node folder. It reads sites.json, matches the visitor’s keywords, and returns JSON. No database required.
<?php
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Only POST allowed']);
exit;
}
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
$query = is_array($data) ? trim((string)($data['query'] ?? '')) : '';
$limit = is_array($data) ? max(1, min(50, (int)($data['limit'] ?? 10))) : 10;
$path = __DIR__ . '/sites.json';
if (!is_file($path)) {
echo json_encode(['results' => []]);
exit;
}
$sites = json_decode(file_get_contents($path), true);
if (!is_array($sites)) {
echo json_encode(['results' => []]);
exit;
}
$terms = preg_split('/\s+/u', mb_strtolower($query), -1, PREG_SPLIT_NO_EMPTY);
$results = [];
foreach ($sites as $row) {
if (!is_array($row)) {
continue;
}
$haystack = mb_strtolower(
($row['title'] ?? '') . ' ' . ($row['description'] ?? '') . ' ' . ($row['url'] ?? '')
);
if ($terms) {
$match = true;
foreach ($terms as $term) {
if ($term !== '' && mb_strpos($haystack, $term) === false) {
$match = false;
break;
}
}
if (!$match) {
continue;
}
}
$results[] = [
'url' => (string)($row['url'] ?? ''),
'title' => (string)($row['title'] ?? ''),
'description' => (string)($row['description'] ?? ''),
'score' => (float)($row['score'] ?? 0.5),
];
}
usort($results, function ($a, $b) {
return ($b['score'] <=> $a['score']);
});
echo json_encode(
['results' => array_slice($results, 0, $limit)],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
Step 4 — Update .htaccess
Litter Layer calls {base_url}/search (not search.php) — e.g. https://yoursite.com/search at the root, or https://yoursite.com/test/search in a subfolder. On most shared Apache hosts, add this rewrite rule in the .htaccess in your node folder so /search runs your script:
# Add these lines to public_html/.htaccess
# (If you already have a .htaccess file, paste inside it — do not delete existing rules.)
RewriteEngine On
RewriteRule ^search$ search.php [L,QSA]
If your host uses Nginx or you cannot edit what you need to, ask your web hosting support how to route /search to search.php. Also, these instructions are mainly for non-developers. If you have web building experience you may set this up however works best for your environment.
Test the search endpoint: after uploading, you can use your host’s Terminal (if available) or any online HTTP client. A successful response looks like {"results":[...]}. From a terminal:
curl -sS -X POST https://your-domain.example/search \
-H "Content-Type: application/json" \
-d '{"query":"hello","limit":5}'
Step 5 — Register your node
Once the descriptor URL works and search returns JSON, submit your descriptor below. We fetch it to verify HTTPS and settings, then queue your node for approval. Use the full descriptor URL — including the subfolder path if your node is not at the domain root.
Step 6 — Heartbeats (optional)
After approval, you can occasionally ping this hub so we know your node is online. On shared hosting, add a once-daily cron job in cPanel if you like — it is optional.
curl -sS -X POST https://litterlayer.com/api/federation/heartbeat.php -H "Content-Type: application/json" -d '{"node_id":"your-domain.example"}'
Replace your-domain.example with the same node_id from your descriptor.
Step 7 — Site search widget and auto-crawler (optional)
The starter pack includes a search box you can embed on any page of your site. It inherits your page's colors and font-family, searches your website via your local sites.json (not Litter Layer's index), and works on the same domain as your node files.
Add this HTML where you want search to appear. Point src at wherever you uploaded ll-widget.js — the widget resolves /search and /crawl_tick relative to that script, so subfolder nodes work too.
<div id="ll-site-search"></div>
<script src="/ll-widget.js" defer></script>
Subfolder example: if your node files live under /test/, use src="/test/ll-widget.js".
When visitors load a page with the widget, a background crawl may run — similar to wp-cron, not on every visit. At most once every 5 minutes, the crawler processes up to 2 pages on your domain:
- First it looks for
/sitemap.xmlat your site root (e.g.https://yoursite.com/sitemap.xml) - If there is no sitemap, it crawls your homepage at
/for internal links - Discovered pages anywhere on your domain are merged into
sites.json
If your node files live in a subfolder (e.g. /test/) but your website is at the domain root, that is the default: base_url points at /test/ for search; the crawler seeds from /. To crawl a subfolder only, add optional crawl_root_url to your descriptor.
No cron job is required. Make sure the data/ folder is writable by PHP. Test the crawler with:
curl -sS https://your-domain.example/crawl_tick
If you already run an older starter pack, upload the new files (ll-widget.js, crawl_tick.php, lib/, data/) and add the new .htaccess rewrite line without replacing your edited descriptor or sites.json.
Example Federated Result at Litter Layer
litterlayer.com.Need help?
See the Help page for how federated search works for visitors.
Running a node on your own computer? See our guides for PC self-hosting(opens in new tab) and Mac self-hosting(opens in new tab).
Instructions for other environments can be found at our Github wiki(opens in new tab).
For any questions, please ask in our Github forum(opens in new tab).
Flag this result
Hide this result?
Are you sure? This will hide this search result from you while using your current web browser. This cannot be undone. Unless you visit with a different web browser.
Bookmark this result
Bookmarked
What would you like to do?