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
- Download the
dinova-post-nav-for-divi.zipfrom your account. - In WordPress admin, go to Plugins → Add New → Upload Plugin and upload the zip.
- Click Activate.
- Open Dinova Post Nav from the WordPress sidebar (top-level menu, slotted just below Appearance).
§ 03Quick start
The settings live under Dinova Post Nav as a three-tab page:
- Read More — controls the
a.more-linkanchor 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
| Option | Type | Default | Description |
|---|---|---|---|
| qdpn_readmore_enable | bool | on | Master switch for the whole feature. |
| qdpn_readmore_text | string | "Continue Reading →" | Global replacement text. Each post can override this individually. |
| qdpn_readmore_hide | bool | off | Removes the anchor entirely — useful for tile/grid layouts. |
| qdpn_readmore_css_class | string | — | Extra CSS class appended to the anchor (letters, numbers, hyphens, underscores). |
| qdpn_readmore_per_post_override | bool | on | Adds a meta box on the Edit Post screen for per-post text. |
| qdpn_readmore_native_wp_enable | bool | off | Also append Read More on excerpts on /blog/, categories, tags, search. |
| qdpn_readmore_bg_color | hex | #7F77DD | Background colour for the styled anchor. |
| qdpn_readmore_text_color | hex | #FFFFFF | Text colour. |
| qdpn_readmore_padding | string | 8px 18px | CSS padding shorthand (px / em / rem / % / vw / vh). |
| qdpn_readmore_radius | int | 4 | Corner 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.
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
| ID | Mode | Notes |
|---|---|---|
| numbered | Numbered | Standard numeric pager. JSON-LD BreadcrumbList microdata included. |
| numbered_prev_next | Numbered + Prev/Next | Numbers framed by directional controls. Keyboard left/right navigation. |
| prev_next_only | Prev/Next only | Two-button nav with current/total summary in the middle. |
| load_more | Load More (AJAX) | One button fetches the next page over REST and appends. |
| infinite_scroll | Infinite Scroll (AJAX) | IntersectionObserver auto-loads with a manual fallback after N pages. |
Common options (every mode)
| Option | Range | Description |
|---|---|---|
| qdpn_pagination_enable | bool | Master switch. |
| qdpn_pagination_mode | enum | One of the mode IDs above. |
| qdpn_pagination_accent_color | hex | Accent colour for buttons + current-page state. |
| qdpn_pagination_btn_radius | 0–24 | Button corner radius in pixels. |
| qdpn_pagination_apply_to_native_wp | bool | Also style stock paginate_links() and Divi's index.php pager. |
Numbered modes (numbered, numbered + prev/next, prev/next only)
| Option | Range | Description |
|---|---|---|
| qdpn_pagination_mid_size | 1–5 | How many numbers around the current page. |
| qdpn_pagination_end_size | 0–3 | How many numbers at the edges before / after the dots. |
| qdpn_pagination_prev_text | string | Previous-button label (e.g. «). |
| qdpn_pagination_next_text | string | Next-button label (e.g. »). |
| qdpn_pagination_show_jump | bool | Adds an inline "Go to page" input. |
| qdpn_pagination_show_count | bool | Adds a "Page X of Y" line. |
Load More options
| Option | Range | Description |
|---|---|---|
| qdpn_loadmore_button_text | string | Default button label. |
| qdpn_loadmore_loading_text | string | Label during fetch. |
| qdpn_loadmore_no_more_text | string | End-of-list message. |
| qdpn_loadmore_show_progress | bool | Show "Showing X of Y posts" beneath the button. |
| qdpn_loadmore_progress_format | string | Format string. Supports %current% and %total%. |
| qdpn_loadmore_scroll_to_new | bool | Scroll to the first newly-loaded post after each load. |
| qdpn_loadmore_update_url | bool | Update the URL via history.replaceState after each load. |
Infinite Scroll options
| Option | Range | Description |
|---|---|---|
| qdpn_infinite_threshold_px | 100–2000 | Pixels from viewport bottom that trigger the next auto-load. |
| qdpn_infinite_max_auto_pages | 0–20 | Maximum consecutive auto-loads before the manual fallback button takes over. |
| qdpn_infinite_fallback_button_text | string | Label for the manual fallback button. |
| qdpn_infinite_end_message | string | End-of-list message. |
| qdpn_infinite_back_to_top | bool | Show a back-to-top button. |
| qdpn_infinite_update_url | bool | Update URL via history.replaceState after each auto-load. |
§ 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
| ID | Layout | When to choose it |
|---|---|---|
| card_thumb | Card with thumbnail | You have featured images on every post and want them as visual anchors. |
| card_minimal | Card minimal | Same card structure, no featured image — clean for editorial blogs without thumbs. |
| text | Text-only row | Minimal visual weight; just a left/right pair of text links. |
Options
| Option | Range | Description |
|---|---|---|
| qdpn_single_enable | bool | Master switch. |
| qdpn_single_mode | enum | One of the three IDs above. |
| qdpn_single_auto_inject | bool | Inject the cards at the end of the post content on stock single.php when no Post Nav module is present. |
| qdpn_single_show_category | bool | Render a category chip on each card. |
| qdpn_single_show_reading_time | bool | Render an "N min read" line (200 wpm estimate). |
| qdpn_single_same_category | bool | Restrict adjacency to posts in the same category. |
| qdpn_single_title_length | 20–200 | Character cap on each post title. |
| qdpn_single_prev_label | string | "Previous Post" label. |
| qdpn_single_next_label | string | "Next Post" label. |
| qdpn_single_accent_color | hex | Accent colour used for the arrow + label. |
Deduplication
The auto-inject is smart about not doubling up. Before emitting the cards, it scans:
- The current post's
post_contentfor[et_pb_post_nav(D4) or<!-- wp:divi/post-nav(D5). - Every enabled Theme Builder layout (header, body, footer) for the same tokens.
- 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:
navigation_markup_template— filters core'sget_the_posts_navigation()markup template, so pages that use that helper see the styled pager.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.clearfixblock 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.
| Endpoint | GET /wp-json/qdpn/v1/posts |
| Auth | Public — verified by X-WP-Nonce against wp_rest |
| Rate limit | 60 requests per minute per hashed-IP. HTTP 429 with Retry-After: 60 when over. |
| Params | page (integer, ≥ 1), context (UUID returned in the first page render) |
| Response | JSON: 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:
- Standard gettext — every UI string passes through
__()/esc_html__()with text domaindinova-post-nav-for-divi. The translation template ships atlanguages/dinova-post-nav-for-divi.pot. - 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_stringon 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.
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:
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
- Confirm Pagination → Enable custom pagination is on.
- If you're on a stock WP archive (not a Divi Blog module page), confirm Apply to native WP archives is on too.
- Visit the page with
?qdpn_debug=1as an admin. Inspect the page source fordata-qdpn-pagerattribute — 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
- Did Divi cache the page? Clear Divi's Static CSS & cache from Divi → Theme Options → Builder → Advanced.
- 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.
- Confirm the module you're looking at is in the
qdpn_blog_module_slugslist — 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/postswith 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=1appended (while logged in as an admin), the contents ofwp-content/qdpn-debug.logafterQDPN_DEBUG_LOGis set.