Q Quikdin · Dinova Post Nav

v1.0.0Documentation

Dinova Post Nav For DIVI

A focused WordPress plugin that upgrades the three pieces of Divi's blog reading experience that haven't really moved in years — Read More, blog pagination, and the prev/next links at the end of a single post.

§ 01Requirements

  • WordPress 6.0 or newer (verified on 6.9.4).
  • PHP 7.4 or newer (verified on 8.5).
  • The Divi theme or the Divi Builder plugin (4.x or 5.x). Auto-detected — no setting required. Verified on Divi 4.27.6 and Divi 5.5.1.

The plugin auto-detects Divi via three independent symbols (ET_BUILDER_VERSION, ET_Builder_Element, et_setup_theme()). If none are present an admin notice appears and no frontend hooks register — the settings page stays usable so you can configure ahead of Divi activation.

§ 02Installation

  1. Download the dinova-post-nav-for-divi.zip from your account.
  2. In WordPress admin, go to Plugins → Add New → Upload Plugin and upload the zip.
  3. Click Activate.
  4. Open Dinova Post Nav from the WordPress sidebar (top-level menu, slotted just below Appearance).
Tip. The plugin works the moment you activate it — defaults are sensible (numbered pagination, prev/next cards with thumbnails, Read More rewrites enabled). Open the settings only when you want to change behavior.

§ 03Quick start

The settings live under Dinova Post Nav as a three-tab page:

  • Read More — controls the a.more-link anchor on Divi Blog modules and (optionally) on native WP archives.
  • Pagination — switches the pagination mode and exposes the per-mode options.
  • Single Post Nav — chooses one of three layouts for the prev/next block at the bottom of a single post.

Each tab can be enabled independently. Toggle the master switch off on any tab to restore stock Divi behavior for that feature only.

§ 04Settings — Read More

The Read More tab controls every a.more-link anchor Divi outputs from a Blog, Posts, or Blog Extras module, plus the optional injection on native WordPress archives that bypass those modules.

Available options

OptionTypeDefaultDescription
qdpn_readmore_enableboolonMaster switch for the whole feature.
qdpn_readmore_textstring"Continue Reading →"Global replacement text. Each post can override this individually.
qdpn_readmore_hidebooloffRemoves the anchor entirely — useful for tile/grid layouts.
qdpn_readmore_css_classstringExtra CSS class appended to the anchor (letters, numbers, hyphens, underscores).
qdpn_readmore_per_post_overrideboolonAdds a meta box on the Edit Post screen for per-post text.
qdpn_readmore_native_wp_enablebooloffAlso append Read More on excerpts on /blog/, categories, tags, search.
qdpn_readmore_bg_colorhex#7F77DDBackground colour for the styled anchor.
qdpn_readmore_text_colorhex#FFFFFFText colour.
qdpn_readmore_paddingstring8px 18pxCSS padding shorthand (px / em / rem / % / vw / vh).
qdpn_readmore_radiusint4Corner radius in pixels (0–60).

Per-post override

With Allow per-post override on, the Edit Post screen gains a sidebar meta box called Read More Override. Anything typed there replaces the global text on that post only. Leave it empty to fall back to the global setting.

Note. The meta box only shows on the classic post post type. Custom post types are not currently supported.

Hide entirely

Toggle Hide Read More entirely when your card design doesn't need a CTA — for example, a 3-column grid where the whole card is already clickable. Hidden anchors are removed in the DOM, not just CSS-hidden, so they don't show up in screen readers either.

§ 05Settings — Pagination

Picks one of five modes for every Divi Blog module render, every Theme Builder body-template archive render, and (optionally) native WordPress archives.

Modes at a glance

IDModeNotes
numberedNumberedStandard numeric pager. JSON-LD BreadcrumbList microdata included.
numbered_prev_nextNumbered + Prev/NextNumbers framed by directional controls. Keyboard left/right navigation.
prev_next_onlyPrev/Next onlyTwo-button nav with current/total summary in the middle.
load_moreLoad More (AJAX)One button fetches the next page over REST and appends.
infinite_scrollInfinite Scroll (AJAX)IntersectionObserver auto-loads with a manual fallback after N pages.

Common options (every mode)

OptionRangeDescription
qdpn_pagination_enableboolMaster switch.
qdpn_pagination_modeenumOne of the mode IDs above.
qdpn_pagination_accent_colorhexAccent colour for buttons + current-page state.
qdpn_pagination_btn_radius0–24Button corner radius in pixels.
qdpn_pagination_apply_to_native_wpboolAlso style stock paginate_links() and Divi's index.php pager.

Numbered modes (numbered, numbered + prev/next, prev/next only)

OptionRangeDescription
qdpn_pagination_mid_size1–5How many numbers around the current page.
qdpn_pagination_end_size0–3How many numbers at the edges before / after the dots.
qdpn_pagination_prev_textstringPrevious-button label (e.g. «).
qdpn_pagination_next_textstringNext-button label (e.g. »).
qdpn_pagination_show_jumpboolAdds an inline "Go to page" input.
qdpn_pagination_show_countboolAdds a "Page X of Y" line.

Load More options

OptionRangeDescription
qdpn_loadmore_button_textstringDefault button label.
qdpn_loadmore_loading_textstringLabel during fetch.
qdpn_loadmore_no_more_textstringEnd-of-list message.
qdpn_loadmore_show_progressboolShow "Showing X of Y posts" beneath the button.
qdpn_loadmore_progress_formatstringFormat string. Supports %current% and %total%.
qdpn_loadmore_scroll_to_newboolScroll to the first newly-loaded post after each load.
qdpn_loadmore_update_urlboolUpdate the URL via history.replaceState after each load.

Infinite Scroll options

OptionRangeDescription
qdpn_infinite_threshold_px100–2000Pixels from viewport bottom that trigger the next auto-load.
qdpn_infinite_max_auto_pages0–20Maximum consecutive auto-loads before the manual fallback button takes over.
qdpn_infinite_fallback_button_textstringLabel for the manual fallback button.
qdpn_infinite_end_messagestringEnd-of-list message.
qdpn_infinite_back_to_topboolShow a back-to-top button.
qdpn_infinite_update_urlboolUpdate URL via history.replaceState after each auto-load.
Why the auto-load cap matters. Setting max auto pages to 0 disables the cap. Doing so on a long archive means visitors can never reach your footer — the page keeps growing as they scroll. Leave the default of 5 or set it to a number that makes sense for your archive length.

§ 06Settings — Single Post Nav

Replaces the stock previous/next links at the bottom of a single post with one of three layouts. Auto-injected on stock single.php; rendered inline where a Divi Post Nav module exists.

Layouts

IDLayoutWhen to choose it
card_thumbCard with thumbnailYou have featured images on every post and want them as visual anchors.
card_minimalCard minimalSame card structure, no featured image — clean for editorial blogs without thumbs.
textText-only rowMinimal visual weight; just a left/right pair of text links.

Options

OptionRangeDescription
qdpn_single_enableboolMaster switch.
qdpn_single_modeenumOne of the three IDs above.
qdpn_single_auto_injectboolInject the cards at the end of the post content on stock single.php when no Post Nav module is present.
qdpn_single_show_categoryboolRender a category chip on each card.
qdpn_single_show_reading_timeboolRender an "N min read" line (200 wpm estimate).
qdpn_single_same_categoryboolRestrict adjacency to posts in the same category.
qdpn_single_title_length20–200Character cap on each post title.
qdpn_single_prev_labelstring"Previous Post" label.
qdpn_single_next_labelstring"Next Post" label.
qdpn_single_accent_colorhexAccent colour used for the arrow + label.

Deduplication

The auto-inject is smart about not doubling up. Before emitting the cards, it scans:

  1. The current post's post_content for [et_pb_post_nav (D4) or <!-- wp:divi/post-nav (D5).
  2. Every enabled Theme Builder layout (header, body, footer) for the same tokens.
  3. A request-scoped "already rendered" flag set by the filter replacement.

If any of those find a Post Nav module, the auto-inject skips and lets the filter replacement handle the swap at the module's actual position. Net result: exactly one prev/next block per single post page, regardless of how Theme Builder is configured.

§ 07Theme Builder

Everything works inside Theme Builder body, header, and footer templates the same way it works on the corresponding regular pages:

  • If your body template uses a Divi Blog module, the pagination mode you've chosen replaces the module's default pager.
  • If your body template uses a Divi Post Nav module on single posts, the layout you've chosen replaces the module's default output.
  • If your body template doesn't include a Post Nav module, the cards auto-inject after the post content the same as on stock single.php.

Divi 5 native blocks are detected via the divi_module_wrapper_render filter; nothing changes from the editor's perspective.

§ 08Native WP archives

Many Divi sites serve their /blog/, category, tag, author, and search archives directly from the theme's index.php — bypassing the Divi Blog module entirely. The plugin handles that path via two complementary entry points:

  1. navigation_markup_template — filters core's get_the_posts_navigation() markup template, so pages that use that helper see the styled pager.
  2. loop_end — emits a pager wrapper directly below the main loop on archives that don't go through the markup filter at all, and hides Divi's default .pagination.clearfix block via a tiny vanilla-JS sibling-scan.

The "Apply to native WP archives" master switch on the Pagination tab gates both paths.

§ 09AJAX endpoint

Load More and Infinite Scroll modes both ride a custom REST route.

EndpointGET /wp-json/qdpn/v1/posts
AuthPublic — verified by X-WP-Nonce against wp_rest
Rate limit60 requests per minute per hashed-IP. HTTP 429 with Retry-After: 60 when over.
Paramspage (integer, ≥ 1), context (UUID returned in the first page render)
ResponseJSON: html, has_more, current_page, total_pages, total_posts, posts_in_response

The context param is the only state the endpoint reads — it's a UUID for a request-time transient containing the originating module's attributes or the originating archive's whitelisted query vars. Transients expire after one hour. Without a valid context the endpoint returns HTTP 410.

The frontend JS retries on transient errors with exponential backoff, then degrades to a plain <a> link to the next page after three consecutive failures so the visitor is never stuck.

§ 10Translations

Two layers cover translation:

  1. Standard gettext — every UI string passes through __() / esc_html__() with text domain dinova-post-nav-for-divi. The translation template ships at languages/dinova-post-nav-for-divi.pot.
  2. WPML / Polylang — translator-facing option values (Read More text, Prev/Next labels, Load More button text, etc.) are registered with the WPML string registry on save and piped through wpml_translate_single_string on output. Polylang implements the same hook pair through its compat layer, so a single integration covers both plugins. With neither installed the filter is a pass-through and the raw option value is returned unchanged.

§ 11Filter hooks

Every public filter the plugin exposes is documented inline in the source with a proper docblock. The most commonly-used one is the module slug list:

qdpn_blog_module_slugs

Filters the list of Divi render slugs that emit <a class="more-link"> anchors. Add custom post-listing module slugs here to make the Read More rewriter target them too.

php · functions.php qdpn_blog_module_slugs
add_filter( 'qdpn_blog_module_slugs', function ( $slugs ) {
    $slugs[] = 'my_custom_blog_module';
    return $slugs;
} );

§ 12Option keys

All settings persist as standard WordPress options with the qdpn_ prefix. They are grouped into three option groups, one per settings tab, so saving on one tab cannot clobber the others. uninstall.php removes every key plus the per-post override meta and all AJAX transients.

For the full list of option keys and their defaults, see §4, §5, and §6 above.

§ 13Debug mode

Diagnostic logging is opt-in. Both of these need to be true in wp-config.php before the plugin will write anything to disk:

php · wp-config.php both required
define( 'WP_DEBUG', true );
define( 'QDPN_DEBUG_LOG', true );

With both set, the plugin writes to wp-content/qdpn-debug.log. Without QDPN_DEBUG_LOG the log function is a no-op even if WP_DEBUG is on, so staging sites that turn on WP_DEBUG globally don't accidentally accumulate plugin debug entries.

Logged-in administrators can also append ?qdpn_debug=1 to any frontend URL for an in-memory diagnostic mode (no file write, no telemetry). Anonymous visitors cannot trigger this — the URL flag is gated by current_user_can( 'manage_options' ).

§ 14Troubleshooting

The new pagination isn't appearing

  1. Confirm Pagination → Enable custom pagination is on.
  2. If you're on a stock WP archive (not a Divi Blog module page), confirm Apply to native WP archives is on too.
  3. Visit the page with ?qdpn_debug=1 as an admin. Inspect the page source for data-qdpn-pager attribute — its absence means the filter didn't fire; presence means the filter fired but CSS may be hiding the result.

Read More text didn't change

  1. Did Divi cache the page? Clear Divi's Static CSS & cache from Divi → Theme Options → Builder → Advanced.
  2. If using a per-post override, confirm the meta box exists on the Edit Post screen and that you've saved the post since editing the field.
  3. Confirm the module you're looking at is in the qdpn_blog_module_slugs list — a custom blog module needs the filter (see §11).

Infinite Scroll never advances past the first auto-load cap

That's the design. After qdpn_infinite_max_auto_pages consecutive auto-loads the observer disconnects and exposes a manual button. Set the cap to a higher number — or to 0 for unlimited — if your archive is long and your visitors will actually keep scrolling.

The cards show on a post that already has a Post Nav module

That should never happen because of the deduplication logic in §6. If it does, confirm the module's render slug is exactly et_pb_post_nav (D4) or divi/post-nav (D5). Custom-renamed modules won't be detected.

§ 15Frequently asked

Does the plugin work with caching plugins?

Yes — the server-rendered output is the source of truth, and cached HTML works exactly as it would without the plugin. Only the AJAX (Load More / Infinite Scroll) modes touch the server beyond first load, and those calls bypass page caches because they go through the REST API.

Does it work with custom post types?

Pagination and Single Post Nav: yes, on archives. Read More: only on the post post type's meta box. CPT support for the per-post override meta box is queued for v1.1.

Can I use my own templates?

v1.0 doesn't expose template overrides. The five pagination templates and three single-nav templates live in templates/ and you can fork the plugin to swap them, but there is no themes/your-theme/dinova-post-nav/ override path yet.

§ 16Changelog

1.0.0 — 2026-05-17

  • Initial release.
  • Smart Read More: global text override, per-post override meta box, hide-entirely mode, custom CSS class, button styling, opt-in native WP archive injection.
  • Blog pagination: five modes — Numbered, Numbered + Prev/Next, Prev/Next Only, Load More (AJAX), Infinite Scroll (AJAX).
  • Single Post navigation: three modes — Card with Thumbnail, Card Minimal, Text.
  • REST endpoint qdpn/v1/posts with nonce verification and per-IP rate limiting.
  • Divi 4 + Divi 5 render-path support.
  • Theme Builder body/header/footer compatibility.
  • WPML / Polylang integration.
  • Multisite-aware uninstall.
  • Translation template at languages/dinova-post-nav-for-divi.pot.

§ 17Support

Plugin bugs and feature requests go to quikdin.com/support. When reporting an issue, please include:

  • WordPress, PHP, and Divi versions.
  • Plugin settings (a screenshot of the Pagination tab is usually enough).
  • The URL where the problem occurs.
  • Any other navigation/cache/AJAX plugins active on the site.
  • If you can reproduce with ?qdpn_debug=1 appended (while logged in as an admin), the contents of wp-content/qdpn-debug.log after QDPN_DEBUG_LOG is set.