Skip to content

Stream post types

Loveshack registers three custom post types alongside the standard WordPress post. Together they make up the activity stream on the homepage.

Post type Name constant Default archive slug Admin label
Status update LOVESHACK_CPT_STATUS (cp_status) /status/ Status Updates
Image post LOVESHACK_CPT_IMAGE (cp_image) /image/ Image Posts
Video post LOVESHACK_CPT_VIDEO (cp_video) /video/ Video Posts

Each is public, queryable, block-editor enabled (show_in_rest), has its own archive, and shares the category and post_tag taxonomies with regular posts — so a tag archive lists every kind of stream item together.

All three support the same feature set:

title, editor, author, thumbnail, excerpt, comments, trackbacks, custom-fields, sticky, page-attributes

Sticky support means any stream item can be pinned to the top of the stream, not just classic posts.

Writing in Markdown

The Text field on the stream editor is Markdown. The source is stored as you wrote it, so reopening a post shows your Markdown rather than generated HTML, and it is converted to HTML when the post is rendered.

Syntax Result
**bold**, __bold__ bold
*italic*, _italic_ italic
~~struck~~ struck through
`code` inline code
```php … ``` fenced code block, optionally language-tagged
# Heading … ###### Heading headings, one to six levels; the space after the # is required
> quoted blockquote
- item, * item, + item bulleted list, indent to nest
1. item numbered list, indent to nest
[label](https://example.com) link
![alt](https://example.com/x.png) image
---, ***, ___ horizontal rule

Underscores only mark emphasis at word boundaries, so snake_case_names and 2*3*4 are left alone. Bare URLs are linked automatically.

A # glued to the word after it is a hashtag, not a heading: #release tags the post, # Release starts a section.

Markdown applies to the three stream post types, in both the Text and the Teaser Text field. Regular blog posts use the block editor and are unaffected.

What is deliberately not supported

This is a focused subset rather than full CommonMark. Reference-style links ([label][ref]), tables, footnotes, definition lists, and setext headings (underlining a line with ===) are not parsed and will show as literal text.

Existing posts and raw HTML

HTML already in a post is passed through untouched, so posts written before Markdown was enabled render exactly as they did. You can also mix HTML into Markdown where the syntax does not reach far enough.

Rendered output always passes through wp_kses_post(), so a post can never emit markup WordPress would not otherwise allow.

Angle brackets and user capability

Markdown source is stored verbatim for users with the unfiltered_html capability — administrators and editors on a single site — which is the same rule WordPress applies to post content everywhere else. For users without it, WordPress entity-encodes stray angle brackets, so > blockquote markers and < inside fenced code will not survive. In practice everyone who can reach the stream editor has the capability.

Previewing

The editor has a Preview button below the Text field. It renders through the same code the frontend uses, so what you see is what the post will look like, and it updates as you type while it is open.

Teasers and summaries

Stream cards and subscriber-only teasers render the same HTML as the single post, cut to the configured teaser length with the markup kept intact — a heading or list looks the same in the stream as on the detail page. Only the visible text counts towards the length, and the cut never splits a word or an element. Leave a Teaser text length setting empty for no maximum — the full text is rendered in the stream — or set it to 0 to hide the text on cards altogether. When a post has a Teaser Text, that Markdown is rendered in place of the Text. Headings inside a card are capped at the card's body text size so a # line reads as a bold lead rather than a page title.

Anywhere a post is reduced to plain text — digest emails, generated excerpts, the ActivityPub summary — the Markdown markers are stripped so readers never see raw **bold**. A link reads as its label, an image as its alt text.

Enabling and disabling types

Each post type is registered only when its setting is enabled, so a site that never posts video can switch that type off entirely and keep the admin clean.

Setting Default
enabled_status Enabled
enabled_image Enabled
enabled_video Enabled

Disabling a type unregisters it. Existing content is not deleted — it becomes inaccessible on the frontend until the type is re-enabled.

Changing archive slugs

The archive slug for each type is configurable:

Setting Default slug
status_slug status
image_slug image
video_slug video

Slugs are sanitized with sanitize_title(), and an empty value falls back to the default. Loveshack flushes the rewrite rules automatically when these settings change, so a manual permalinks save is not needed after editing them.

Archives use with_front => false

Stream archives sit at the site root — /status/, not /blog/status/ — regardless of the permalink structure's front base.

Stream items always use a flat permalink — /image/my-post/ — regardless of whether the item is part of a thread.

Threaded items, such as those the Twitter importer creates from a reply chain, are linked by their parent relationship but do not nest in the URL. Thread children are also excluded from the stream itself, which queries only top-level items.

Nested URLs from before 1.1.11

Until 1.1.11 these post types were hierarchical, so a threaded item's permalink contained the slug of every ancestor: /image/first-tweet/second-tweet/third-tweet/. Because a thread can mix post types, most of those URLs did not resolve at all and returned a 404.

Loveshack now redirects any such URL to the flat permalink of the deepest item it can still find, so old links in feeds, sitemaps, and inbound traffic keep working.

The stream query

The homepage stream queries post plus every enabled custom type. In code, the set is returned by:

lscpt_get_stream_post_types();  // array( 'post', 'cp_status', 'cp_image', 'cp_video' )
lscpt_get_enabled_post_types(); // just the enabled Loveshack types

Use these rather than hardcoding the type names — they respect the site's enabled settings.

Video posts

Video posts have one type-specific setting:

Setting Default Effect
video_hide_text_default Disabled New video posts hide their body text in the stream by default, showing the video alone

This is a default for new posts, not a global lock — it can be overridden per post.

Loveshack renders link previews for URLs in stream items. The preview fetcher is selectable:

Setting Default
link_preview_proxy jina

Update releases

When the Update API plugin is active on the same site, its published releases appear in the stream as Release items. Each card shows the product name and version, the Markdown teaser entered on the release in Update API, and a download button with the package file name and size. The card links to a release page at /release/<name-version>/ that shows the full Markdown text. When no teaser is set, the card shows the start of the text instead. Visitors whose subscription tier is allowed for the product, and administrators, get a direct download; everyone else is sent to the subscription page.

When at least one live release is downloadable by the current visitor, a sticky Latest Releases item at the top of the home stream lists the newest release per product as download links with file size and release date, dated with the most recent release. Visitors without access to any product do not see it.

Release dates there are relative while they are recent — Just now, then minutes, hours, and days for the first week — and fall back to the site's date format from one week on.

The stream keeps earlier releases as history: publishing a new release marks the previous one as archived rather than hiding it. Turn the items off with Update releases in the stream under Loveshack → Custom Post Types.

Release items are full stream items. They show the author who published the release, next to the Release and Latest labels, and follow the All Posts header and timestamp settings in the Customizer. The release page accepts comments whenever the site's default comment status under Settings → Discussion is open, and releases take reactions according to the All Posts reactions setting.

The Update API plugin adds releases through the theme's stream post type API, which is how any plugin or child theme can add a post type to the stream, feeds, and digest emails. Since Loveshack 1.2.25 the release templates and styles ship with the plugin, and the stream checkbox for releases sits with the other plugin-added types under Loveshack → Custom Post Types.

Blog posts in the stream

Regular blog posts are part of the stream, feeds, and digest emails by default. The Blog Posts (post) checkbox under Loveshack → Custom Post Types takes them out of the stream without touching the posts themselves; they stay reachable at their own URLs and in the blog archives. When every checkbox is off, the stream falls back to blog posts.