Themes on Peep consist of HTML files, a config file and CSS. Within the HTML files, you can use Handlebars tags to render specific information. Specifically:
The config.json file defines theme metadata and feature support. It must be valid JSON.
name
Type: String
Description: Display name for the theme
Required: No (defaults to existing theme name)
Example: "Minimal Dark"
supportsFont
Type: Boolean
Description: Whether the theme supports custom font selection by users
Required: No (defaults to true)
Example: true or false
supportsSecondaryFont
Type: Boolean
Description: Whether the theme supports a secondary font selection by users
Required: No (defaults to true)
Example: true or false
supportsColors
Type: Boolean
Description: Whether the theme supports custom color selection by users
Required: No (defaults to true)
Example: true or false
isPublic
Type: Boolean
Description: Whether the theme is publicly available for others to use
Required: No (defaults to false)
Example: true or false
The theme.scss file contains your theme's styles written in SCSS (Sass). SCSS is a CSS preprocessor that extends CSS with features like variables, nesting, mixins, and more. The compiled CSS is automatically injected into the page when your theme is active. Any syntax errors will prevent compilation and show an error message in the theme editor.
The post.html template is used to render individual posts. Available tags:
{{id}}
Type: Integer
Description: Unique post ID
Example: 123
{{user_id}}
Type: Integer
Description: ID of the user who created the post
Example: 45
{{blurb}}
Type: String (HTML)
Description: Formatted post content. For short posts, this includes formatted hashtags and links. For long posts, this is HTML content.
Usage: Use {{{blurb}}} for HTML output, {{blurb}} for escaped text
Example: "This is a post with #hashtag and https://example.com"
{{raw_blurb}}
Type: String (Plain Text)
Description: Raw, unformatted post content (plain text). Only available for short posts. Empty for long posts.
Usage: Use {{raw_blurb}} for plain text
Example: "This is a post with #hashtag"
{{title}}
Type: String
Description: Post title (only for long posts, empty for short posts)
Usage: Use {{#if is_long}}{{title}}{{/if}} to conditionally show title
Example: "My Blog Post Title"
{{slug}}
Type: String
Description: Unique URL slug for the post
Usage: Use for creating permalinks
Example: "abc123xyz"
{{image}}
Type: String (URL) or null
Description: URL to the post's image, if present
Usage: Use {{#if image}}<img src="{{image}}">{{/if}}
Example: "https://example.com/image.jpg" or null
{{created_at}}
Type: String (Formatted Date)
Description: Formatted creation date
Usage: Display as-is
Example: "Oct 15, 2024"
{{edited}}
Type: Integer (0 or 1)
Description: Whether the post has been edited
Usage: Use {{#if edited}}Edited{{/if}}
Example: 1 or 0
{{pinned}}
Type: Integer (0 or 1)
Description: Whether the post is pinned
Usage: Use {{#if pinned}}Pinned{{/if}}
Example: 1 or 0
{{type}}
Type: String
Description: Post type - either "short" or "long"
Usage: Use {{#if is_long}}...{{/if}} or {{#if is_short}}...{{/if}}
Example: "short" or "long"
{{is_long}}
Type: Boolean
Description: True if post type is "long"
Usage: Use {{#if is_long}}...{{/if}}
Example: true or false
{{is_short}}
Type: Boolean
Description: True if post type is "short"
Usage: Use {{#if is_short}}...{{/if}}
Example: true or false
{{url}}
Type: String
Description: Username/URL of the post author
Usage: Use for creating profile links
Example: "username"
{{reply_to}}
Type: Integer or null
Description: ID of the post this is replying to, if it's a reply
Usage: Use {{#if reply_to}}This is a reply{{/if}}
Example: 123 or null
{{reply_slug}}
Type: String or null
Description: Slug of the post being replied to
Usage: Use for creating links to the original post
Example: "xyz789abc" or null
{{reply_url}}
Type: String or null
Description: Username of the author of the post being replied to
Usage: Use for creating links to the original post author
Example: "originaluser" or null
{{allow_replies}}
Type: Integer (0 or 1)
Description: Whether replies are allowed on this post
Usage: Use {{#if allow_replies}}Replies allowed{{/if}}
Example: 1 or 0
{{reply_count}}
Type: Integer
Description: Number of replies to this post
Usage: Display reply count
Example: 5
{{custom_domain}}
Type: String or null
Description: Custom domain for the post author (if active)
Usage: Use for creating profile links
Example: "example.com" or null
{{reply_custom_domain}}
Type: String or null
Description: Custom domain for the original post author (if replying)
Usage: Use for creating links to original post author
Example: "example.com" or null
{{hashtags}}
Type: Array of Objects
Description: Array of hashtag objects associated with the post
Structure: [{tag: "hashtag", color: "FF0000"}, ...]
Usage: Use {{#each hashtags}}...{{/each}}
Example: [{"tag": "example", "color": "FF0000"}]
{{hashtags.[].tag}}
Type: String
Description: Hashtag name (within hashtags array)
Usage: Use inside {{#each hashtags}}{{tag}}{{/each}}
Example: "example"
{{hashtags.[].color}}
Type: String (Hex color without #)
Description: Hashtag color (within hashtags array)
Usage: Use inside {{#each hashtags}}<span style="color: #{{color}}">{{tag}}</span>{{/each}}
Example: "FF0000"
{{control_strip}}
Type: String (HTML)
Description: Pre-rendered HTML for post controls (edit, delete, pin, bookmark, reply buttons)
Usage: Use {{{control_strip}}} to output HTML
Note: This is automatically generated and includes all interactive controls
{{isReplyList}}
Type: Boolean
Description: True if this post is being rendered in the replies list
Usage: Use {{#if isReplyList}}...{{/if}} to style replies differently
Example: true or false (only present in replies)
The profile_html template is used to render the user profile page. Available tags:
{{posts}}
Type: String (HTML)
Description: Rendered HTML of all posts (each post rendered using post_html template)
Usage: Use {{{posts}}} to output the HTML
Note: This is pre-rendered, so you can't iterate over individual posts here
{{profile_title}}
Type: String (HTML)
Description: Rendered profile title (includes contenteditable div if viewing own profile)
Usage: Use {{{profile_title}}} to output the HTML
Example: "<div id="user-title">My Profile</div>"
{{profile_title_raw}}
Type: String (Plain Text)
Description: Raw profile title text (no HTML)
Usage: Use {{profile_title_raw}} for plain text
Example: "My Profile"
{{profile_blurb_raw}}
Type: String (Plain Text)
Description: Raw profile blurb text (no HTML)
Usage: Use {{profile_blurb_raw}} for plain text
Example: "My bio"
{{profile_blurb}}
Type: String (HTML)
Description: Rendered profile blurb/description (includes contenteditable div if viewing own profile)
Usage: Use {{{profile_blurb}}} to output the HTML
Example: "<div id="user-blurb">My bio</div>"
{{user_id}}
Type: Integer
Description: ID of the profile owner
Usage: Use for profile-specific logic
Example: 45
{{replies}}
Type: String (HTML)
Description: Rendered HTML of all reply posts (each reply rendered using post.html template)
Usage: Use {{{replies}}} to output the HTML
Note: Only available when viewing a single post page, not profile pages
The single_html template is used to render individual post pages. Available tags:
{{posts}}
Type: String (HTML)
Description: Rendered HTML of the main post (rendered using post_html template)
Usage: Use {{{posts}}} to output the HTML
{{replies}}
Type: String (HTML)
Description: Rendered HTML of all reply posts (each reply rendered using post_html template)
Usage: Use {{{replies}}} to output the HTML
{{profile_title}}
Type: String (HTML)
Description: Rendered profile title
Usage: Use {{{profile_title}}} to output the HTML
{{profile_title_raw}}
Type: String (Plain Text)
Description: Raw profile title text
Usage: Use {{profile_title_raw}} for plain text
{{profile_blurb_raw}}
Type: String (Plain Text)
Description: Raw profile blurb text
Usage: Use {{profile_blurb_raw}} for plain text
{{profile_blurb}}
Type: String (HTML)
Description: Rendered profile blurb/description
Usage: Use {{{profile_blurb}}} to output the HTML
Example config.json:
{
"name": "My Custom Theme",
"supportsFont": true,
"supportsSecondaryFont": true,
"supportsColors": true,
"isPublic": false
}
Post template:
<article>
{{#if is_long}}
<h2>{{title}}</h2>
{{/if}}
<div class="content">{{{blurb}}}</div>
{{#if image}}
<img src="{{image}}" alt="">
{{/if}}
<div class="meta">
<span>{{created_at}}</span>
{{{control_strip}}}
</div>
</article>
Profile template:
<header>
{{{profile_title}}}
{{{profile_blurb}}}
</header>
<main>
{{{posts}}}
</main>
Single template:
<article>
{{{posts}}}
</article>
<section class="replies">
{{{replies}}}
</section>
{{{triple braces}}} for HTML content, {{double braces}} for plain textcontrol_strip includes all interactive buttons (edit, delete, pin, bookmark, reply)font-family:var(--secondary-font); in your theme.scss to use the secondary font, if your theme supports it.