Degoog Themes
Override the look, layout, and result markup.
Default theme reference
The default theme lives at
src/public/themes/degoog-theme/. You should use it as your starting point.
Getting started
Create a folder inside data/themes/ or whatever you set
DEGOOG_THEMES_DIR to. The folder name will be your
theme ID. Add a theme.json file with at least a
name property.
| Tier | What you provide | What you control |
|---|---|---|
| CSS only | theme.json plus your stylesheet |
Colors, fonts, and spacing using CSS variables |
| CSS and templates |
theme.json plus your stylesheet and template
files
|
Everything above plus the HTML structure of any section |
| Layout override |
theme.json plus your stylesheet and a custom
layout.html
|
Everything above plus the page title, meta tags, favicons, and scripts |
| Full HTML override |
theme.json plus your stylesheet and a custom
index.html or search.html
|
Total control that bypasses the layout entirely |
Only include the templates you actually want to change because everything else will naturally fall back to the default theme. You can apply your new theme by navigating to Settings and then Themes.
Layout override
To customize the page title, meta description, favicons, or anything
inside the <head>, provide a
layout.html in your theme and reference it under
html.layout. You can copy the
default layout
and edit it. You must keep these exact
placeholders:
-
__PAGE_CONTENT__is where the skeleton HTML gets inserted __BODY_CLASS__is the body class attribute-
__THEME_TEMPLATES__is where your templates are injected -
__THEME_CSS__,__PLUGIN_ASSETS__,__CUSTOM_CSS__, and__APP_VERSION__
Page architecture
Every page is built with three layers:
-
Layout
(
layout.html) is the shared HTML shell. Change this at your own risk. -
Skeleton
(
index.htmlorsearch.html) provides empty<div>elements with required IDs that our JavaScript relies on. - Templates are individual HTML files rendered directly into the skeleton. These are what your themes will override.
theme.json
Check out the default theme.json for a complete working example.
| Key | Required | Description |
|---|---|---|
name |
yes | The display name shown in Settings then Themes |
author, description,
version
|
no | Details shown on the theme card |
css |
no |
Your stylesheet path which also supports
.scss files. It is served at
/theme/style.css
|
templates |
no |
An object mapping your template keys to their file paths.
These are injected automatically as
<template> elements
|
html |
no |
An object containing layout, index,
and search. Fragments are composed into the
layout while full HTML documents starting with
<!doctype> bypass the layout entirely
|
settingsSchema |
no |
Exposes a Configure button directly on the theme card. These
values are stored under theme-<id>
|
Your custom HTML can utilize these placeholders:
__THEME_CSS__, __THEME_ATTRS__,
__PLUGIN_ASSETS__, __CUSTOM_CSS__,
__APP_VERSION__, __THEME_TEMPLATES__,
__PAGE_CONTENT__, and __BODY_CLASS__.
Static assets
Any file placed inside your theme folder is served securely at
/themes/<theme-id>/<path>. We support JS,
CSS, HTML, JSON, SVG, PNG, JPG, GIF, WebP, TTF, WOFF, and WOFF2
files.
You can use this to load your custom fonts, images, or scripts directly from your theme CSS or HTML:
@font-face {
font-family: "My Font";
src: url("/themes/my-theme/fonts/my-font.woff2") format("woff2");
font-display: swap;
}
Note that theme.json is never served to the client.
Example
{
"name": "My Theme",
"css": "style.css",
"templates": {
"result": "templates/result.html"
}
}
Templates
Templates control the HTML structure of every page section. You list
them under
templates inside your theme.json file. The
keys are automatically prefixed with degoog- to form
the element ID. The last template with a specific ID wins, meaning
your overrides will always take priority over the defaults.
Search page templates
| Key | Description | Source |
|---|---|---|
search-header |
Logo plus search bar plus settings gear | header.html |
search-tabs |
Tab bar plus options dropdown | tabs.html |
search-media-preview |
Image and video preview panel | media-preview.html |
search-lightbox |
Full screen image lightbox | lightbox.html |
result |
Web and news result items | result.html |
image-card |
Image grid cards | image-card.html |
video-card |
Video grid cards | video-card.html |
at-a-glance |
At a glance content box | at-a-glance.html |
Home page templates
| Key | Description | Source |
|---|---|---|
home-header |
Top header navigation bar | header.html |
home-logo |
Main logo and branding | logo.html |
home-search |
The primary search form | search.html |
home-footer |
Page footer | footer.html |
Skeleton IDs
Skeleton elements are simply empty <div> tags
that only contain an id. Our JavaScript injects the
templates right into them.
Please do not remove or rename these IDs.
Home page
| ID | Receives |
|---|---|
header |
degoog-home-header |
main-home |
Main content area where plugins might inject |
home-logo |
degoog-home-logo |
home-search |
degoog-home-search |
home-footer |
degoog-home-footer |
Search page
| ID | Receives |
|---|---|
results-page |
Top level page wrapper |
results-header |
degoog-search-header |
results-tabs |
degoog-search-tabs |
results-meta |
Engine timing and stats display |
results-layout |
Layout wrapper covering main, sidebar, and previews |
results-main |
Main results column |
at-a-glance |
degoog-at-a-glance |
results-list |
The actual search results |
pagination |
Page navigation controls |
sidebar-col |
Sidebar column container |
results-sidebar |
Sidebar content area |
media-preview-panel |
degoog-search-media-preview |
img-lightbox |
degoog-search-lightbox |
slot-above-results |
Plugin slot area |
slot-below-results |
Plugin slot area |
slot-above-sidebar |
Plugin slot area |
slot-below-sidebar |
Plugin slot area |
Required IDs inside templates
Some templates contain inner elements that our JavaScript hooks into
directly. Keep these IDs intact when you override files otherwise
the feature will break. Any templates not listed here like
degoog-home-header, degoog-home-logo,
degoog-home-footer, degoog-image-card,
degoog-video-card, and
degoog-at-a-glance can be restructured freely.
degoog-home-search
search-inputhandles the text input-
ac-dropdown-homehandles the autocomplete dropdown -
search-bar-actions-homehandles the action buttons -
btn-luckyandlucky-slot-innerhandle the lucky animation
degoog-search-header
results-search-inputhandles the search inputresults-search-btnhandles the submit button-
ac-dropdown-resultshandles the autocomplete dropdown -
search-bar-actions-resultshandles the action buttons -
.results-logois the required class for the logo link
degoog-search-tabs
-
.results-tab[data-type]is the class and attribute for the tab buttons tools-baris the options wrappertools-toggleis the dropdown toggletools-dropdownis the dropdown menu-
tools-submenu-timeandtools-submenu-langhandle the filter submenus -
tools-time-valandtools-lang-valhandle the active filter labels -
tools-lang-filterandtools-lang-listhandle the language filter -
tools-date-from,tools-date-to, andtools-date-applyhandle the custom date range
degoog-result
The IDs inside this template are suffixed with the
{{ index }} placeholder. This represents the starting
position of the result from zero and ensures IDs stay unique across
the page. Make sure to keep the prefix intact when you override
this.
-
result-favicon-{N}is the favicon image. It must also carrydata-favicon-host="{{ favicon_host }}"so JavaScript can attach the fallback chain. During the final fallback phase the<img>is smoothly replaced with a<span class="result-favicon-fallback">element. -
result-actions-{N}is the wrapper for per result actions like block, replace, and score. This only renders whenshow_actionsis true. -
result-actions-toggle-{N}is the button that opens the menu. Itsaria-expandedstate is toggled by JavaScript. -
result-actions-menu-{N}is the popover containing the action buttons. It is toggled using thehiddenattribute. -
result-action-block-{N},result-action-replace-{N}, andresult-action-score-{N}are the individual menu buttons. Each one only shows up if its matchingaction_block,action_replace, oraction_scoreplaceholder is active.
degoog-search-media-preview
-
media-preview-close,media-preview-prev, andmedia-preview-nextare the navigation buttons media-preview-imgis the main preview imagemedia-preview-infois the sidebar info panel
degoog-search-lightbox
img-lightbox-bgis the dark backdropimg-lightbox-closeis the close button-
img-lightbox-wrapis the container for the image -
img-lightbox-imgis the full size image element
Placeholder syntax
Inside your templates you can use double curly braces for dynamic values. Every value gets HTML escaped automatically for security.
| Syntax | Description |
|---|---|
{{ name }} |
Outputs a value |
{{#if name}} content {{/if name}} |
Conditional block that renders when the value is truthy |
{{#each name}} content {{/each name}} |
Looping block where {{ . }} is the item and
{{ @index }} is the index
|
Your {{#if}} blocks must contain balanced HTML.
Placeholders work perfectly in regular text and quoted attribute
values like href="{{ url }}"
but you cannot use them as HTML tag names.
Placeholders per template
degoog-result
| Key | Type | Description |
|---|---|---|
index |
number | The index starting at zero for this result. This is used to suffix the required IDs in the template. |
title |
string | The result title |
url |
string | The result URL |
cite_url |
string | The display URL showing the hostname and path |
snippet |
string | The text description |
favicon_url |
string | The proxied favicon. This is just the first candidate before the fallback chain runs in JavaScript. |
favicon_host |
string | The hostname used by the favicon fallback chain |
thumbnail_url |
string | The proxied thumbnail which is empty if none exists |
sources |
array | The engine names |
duration |
string | The video duration which is empty for non video results |
link_target |
string | Either _blank or _self |
link_rel |
string | Either noopener or completely empty |
show_actions |
boolean | Returns true when the per result block, replace, or score menu should render. This requires being authenticated on a private instance with at least one action enabled in settings. |
action_block |
boolean | Shows the block domain menu item |
action_replace |
boolean | Shows the replace domain menu item |
action_score |
boolean | Shows the score domain menu item |
degoog-image-card
| Key | Type | Description |
|---|---|---|
title |
string | The alt text |
url |
string | The source page URL |
thumbnail_url |
string | The proxied thumbnail |
hostname |
string | The source hostname |
sources |
array | The engine names |
degoog-video-card
| Key | Type | Description |
|---|---|---|
title |
string | The video title |
url |
string | The video page URL |
thumbnail_url |
string | The proxied thumbnail |
hostname |
string | The source hostname |
duration |
string | The video duration which might be empty |
sources |
array | The engine names |
degoog-at-a-glance
| Key | Type | Description |
|---|---|---|
title |
string | The title |
url |
string | The URL |
snippet |
string | The snippet text |
sources |
array | The engine names |
sources_text |
string | The engine names separated by commas |
Quick example
Here is how you can override just the result template while keeping everything else as the default:
templates/result.html:
<div class="my-result">
<a href="{{ url }}" target="{{ link_target }}">{{ title }}</a>
<p>{{ snippet }}</p>
<small>{{ cite_url }}</small>
{{#if thumbnail_url}}
<img src="{{ thumbnail_url }}" loading="lazy">
{{/if thumbnail_url}}
{{#each sources}}
<span class="tag">{{ . }}</span>
{{/each sources}}
</div>
theme.json:
{
"name": "My Theme",
"css": "style.css",
"templates": {
"result": "templates/result.html"
}
}