Better Disqus Comments has a stable extension surface — PHP actions and filters, JavaScript slots in the settings screen, REST endpoints, and an option schema that hasn't changed since version 10. Everything on this page is public API.
PHP examples can go in your theme's functions.php or a small plugin.
The plugin's slug, text domain, option names and hook prefix (dcl_) date from its old name, Disqus Conditional Load, and are kept for compatibility. The PHP namespace is FoxeLabs\DCL.
FoxeLabs\DCL\├── Core Boot orchestrator — wires every module, fires dcl_running├── Settings dcl_gnrl_options: defaults, access, sanitising, REST schema├── Account dcl_disqus_account: shortname and API credentials├── Front\│ ├── Detector "Can Disqus load here, and how?" — one answer per request│ ├── Renderer Thread markup, button, shortcode│ ├── TemplateReplacer Swaps the classic comments template│ ├── BlockReplacer Swaps the core/comments block│ ├── Assets Front-end script and inline styles│ ├── Count Comment-count markers│ └── LinkRewriter Points comment links at #disqus_thread├── Embed\Config Identifier, URL, title and embed config per post├── Sync\{Webhook, Manager,│ Mapper, Exporter,│ ApiService, Log} Comment sync, manual sync and export├── Addons\{Addons, Catalog,│ Bundle} Freemius wiring, addon catalogue, Premium Bundle license├── Admin\… Menu, settings page, notices, toolbar menu├── Api\… REST controllers under dcl/v1└── Compat\Manager Official Disqus plugin detection, WooCommerce reviews
Front-end classes only load outside the admin. The rest loads on every request.
The recommended way to extend the plugin:
Hook into dcl_running so your code runs after the plugin has booted.
Use the documented filters and actions.
For settings UI, add a panel through the dcl.settings.panels JavaScript filter and save your own show_in_rest option.
Filter. Whether the Disqus embed loads on this request. Computed once per request.
The built-in rules, in order: a shortname is set; not a feed; dsq_can_load doesn't return false; a singular view of a post; comments open; the post isn't a draft, pending, scheduled, auto-draft or trashed; the post type isn't excluded; and the visitor isn't a bot, unless caching support is on.
php
// Never load Disqus on posts in the "announcements" category.add_filter( 'dcl_can_load', function ( $can_load ) { return is_singular( 'post' ) && has_category( 'announcements' ) ? false : $can_load;} );
Parameter
Type
Description
$can_load
bool
Result of the built-in rules.
$post
WP_Post|null
The post passed to the check. Usually null — use get_post().
When it returns false, the theme's normal WordPress comments show instead.
Filter. Default true: WooCommerce products keep their own reviews template and are excluded from Disqus. Return false to let Disqus replace product reviews. The Comments for WooCommerce addon uses this.
Filter kept from the official Disqus plugin, so existing snippets keep working. Called with 'embed' for the thread and 'count' for comment counts; returning exactly false blocks that part.
Filter. The resolved loading method for this visitor, after the mobile method and the fallback for inactive addons: scroll, click, normal or an addon's method.
php
// Button on long reads only.add_filter( 'dcl_load_method', function ( $method ) { $words = str_word_count( wp_strip_all_tags( get_post_field( 'post_content' ) ) ); return $words > 3000 ? 'click' : $method;} );
Filter. The methods offered in the settings, as slug => [ 'label' => …, 'description' => … ]. A stored method missing from this list falls back to scroll. See Adding a load method.
Filter. The Disqus shortname. Useful on multisite or staging:
php
// Keep staging comments out of the live forum.add_filter( 'dcl_shortname', function ( $shortname ) { return 'staging' === wp_get_environment_type() ? 'example-staging' : $shortname;} );
Filter. Registered button styles, as class => label. Empty in the free plugin; the Advanced Buttons addon registers its six here. A stored style only takes effect while it's registered.
The thread is printed from templates/disqus-comments.php; a blanked comments area uses templates/empty-comments.php. Themes can't override these files — use the hooks above, or filter comments_template at a priority above 100 (the plugin's own swap runs at 100).
[dcl-comments] shows the Disqus thread where you put it, and blanks the theme's comments area further down the page so the thread shows once. [js-disqus] is an older alias.
It shows nothing where Disqus can't load — archives, closed comments, excluded post types.
Filter. Return true to skip the shortcode's own checks. The shortcode then always prints the comments template — the Disqus thread where dcl_can_load allows it, and the theme's own comments where it doesn't.
Block themes
In block themes the shortcode doesn't hide the Comments block. Remove that block from the template if you place the thread with the shortcode.
Filter. The per-post embed configuration handed to the front-end script.
php
// Force the Disqus interface language.add_filter( 'dcl_embed_vars', function ( $vars, $post ) { $vars['disqusConfig']['language'] = 'de'; return $vars;}, 10, 2 );
Key
Description
disqusShortname
The shortname
disqusIdentifier
The thread identifier
disqusUrl
The post URL
disqusTitle
The post title
disqusConfig
integration and an optional language
postId
The post ID
Changing the identifier
Changing disqusIdentifier detaches existing threads, and comment counts and sync keep using the original identifier. Don't change it on an existing site.
A window.disqus_config function your site defines is kept. The plugin sets the page URL, identifier, title and language, then calls your function, so your values win. Define it before the footer:
html
<script>window.disqus_config = function () { this.callbacks.onNewComment = [ function ( comment ) { console.log( 'New comment', comment.id ); } ];};</script>
Filter. Whether comment-count markers and Disqus's count.js are output on this request. Default: a shortname is set, counts are on, not a feed, and dsq_can_load allows 'count'.
The plugin wraps the output of WordPress's comments_number in <span class="dsq-postid" data-dsqidentifier="…">; the script turns links around those markers into Disqus count links.
Trigger it. For a method it doesn't know, the front-end script loads nothing and exposes window.dclEmbed = { method, load }. Enqueue a script that depends on the dcl-comments handle and call load():
Enqueue it on wp_footer at an early priority, so it also works when the shortcode enqueued the main script late. load() is safe to call more than once.
The settings screen is a React app. Addons extend it through @wordpress/hooks filters. Register them in a script that loads on the settings screen (hook suffix toplevel_page_dcl-settings) before the app mounts.
Adds a tab to the settings page, as key => { label, component, footer?, wide? }. footer: true shows the Save Changes button; wide: true uses the wide layout. Resolved once, when the page loads.
The settings page and every dcl/v1 REST route need the manage_options capability. Change it with the DCL_ACCESS constant, in wp-config.php:
php
define( 'DCL_ACCESS', 'edit_others_posts' );
or with the dcl_capability filter. dcl_has_access filters the final check.
General settings still need manage_options
The general settings save through WordPress's own settings endpoint, which always requires manage_options. Lower DCL_ACCESS and other roles can open the page and manage the Disqus account and sync, but not save the other settings.
Disqus sends comments here. The routes use the official plugin's namespace so existing subscriptions keep working, and are only registered while the official plugin is inactive.
Requests must carry an X-Hub-Signature header — sha512= followed by the HMAC-SHA512 of the request body, keyed with the sync token (at least 32 characters). Anything else gets 401. Handled verbs are verify (the subscription handshake), create, update and force_sync; others get 204.
Deleting the plugin removes dcl_gnrl_options, dcl_disqus_account, dcl_db_version, dcl_sso_notice and dcl_sync_last_message. Synced comments, their meta and the official Disqus plugin's own settings are left in place.
Developer Docs
Better Disqus Comments has a stable extension surface — PHP actions and filters, JavaScript slots in the settings screen, REST endpoints, and an option schema that hasn't changed since version 10. Everything on this page is public API.
PHP examples can go in your theme's
functions.phpor a small plugin.Architecture overview
The plugin's slug, text domain, option names and hook prefix (
dcl_) date from its old name, Disqus Conditional Load, and are kept for compatibility. The PHP namespace isFoxeLabs\DCL.Front-end classes only load outside the admin. The rest loads on every request.
The recommended way to extend the plugin:
dcl_runningso your code runs after the plugin has booted.dcl.settings.panelsJavaScript filter and save your ownshow_in_restoption.The
dcl()helper dcl()returns theCoreinstance, with accessors for the main services:dcl()->settings()Settings—all(),get( $key, $fallback ),set( $key, $value ),update( $values ),defaults()dcl()->account()Account—shortname(),is_connected(),has_api_credentials(),get( $key )dcl()->compat()Compat\Manager—is_disqus_active(),sync_allowed(),woocommerce_review_support()dcl()->front()Front\Controller— front-end requests onlyLifecycle hooks
dcl_runningAction. Fires on
plugins_loadedonce every module is wired. The hook every addon boots from.$coreFoxeLabs\DCL\Coredcl().dcl_deactivatedAction. Fires when the plugin is deactivated.
Loading and detection
The plugin answers one question per request — can Disqus load here, and how? — and every part of it uses the same answer.
dcl_can_loadFilter. Whether the Disqus embed loads on this request. Computed once per request.
The built-in rules, in order: a shortname is set; not a feed;
dsq_can_loaddoesn't returnfalse; a singular view of a post; comments open; the post isn't a draft, pending, scheduled, auto-draft or trashed; the post type isn't excluded; and the visitor isn't a bot, unless caching support is on.$can_loadbool$postWP_Post|nullnull— useget_post().When it returns
false, the theme's normal WordPress comments show instead.dcl_excluded_cptsFilter. Post types that never show Disqus: the Exclude post types setting, plus
productwhile WooCommerce review support is on.dcl_woocommerce_review_supportFilter. Default
true: WooCommerce products keep their own reviews template and are excluded from Disqus. Returnfalseto let Disqus replace product reviews. The Comments for WooCommerce addon uses this.dsq_can_loadFilter kept from the official Disqus plugin, so existing snippets keep working. Called with
'embed'for the thread and'count'for comment counts; returning exactlyfalseblocks that part.dcl_load_methodFilter. The resolved loading method for this visitor, after the mobile method and the fallback for inactive addons:
scroll,click,normalor an addon's method.dcl_is_mobileFilter. Whether the visitor gets the mobile method. Default:
wp_is_mobile().dcl_is_lazyFilter. Whether the resolved method is lazy. Parameters:
bool $is_lazy,string $method.dcl_load_method_optionsFilter. The methods offered in the settings, as
slug => [ 'label' => …, 'description' => … ]. A stored method missing from this list falls back toscroll. See Adding a load method.dcl_lazy_load_methodsFilter. Slugs that count as lazy. Default
[ 'scroll', 'click' ].dcl_shortnameFilter. The Disqus shortname. Useful on multisite or staging:
Rendering
The comments area becomes:
The button container only appears for the click method.
dcl_inside_disqus_threadAction. Fires inside
#disqus_thread, before the button. Anything printed here is replaced when Disqus loads — a good place for a placeholder.dcl_button_textFilter. The button label. Escaped on output — plain text only.
dcl_button_classFilter. The button's CSS classes: the Button CSS classes setting plus the selected style.
dcl_button_stylesFilter. Registered button styles, as
class => label. Empty in the free plugin; the Advanced Buttons addon registers its six here. A stored style only takes effect while it's registered.dcl_empty_commentsAction. Fires where the theme's comments area was blanked — when the shortcode or the Comments Widget shows the thread elsewhere.
Templates
The thread is printed from
templates/disqus-comments.php; a blanked comments area usestemplates/empty-comments.php. Themes can't override these files — use the hooks above, or filtercomments_templateat a priority above100(the plugin's own swap runs at100).Shortcodes
[dcl-comments]shows the Disqus thread where you put it, and blanks the theme's comments area further down the page so the thread shows once.[js-disqus]is an older alias.It shows nothing where Disqus can't load — archives, closed comments, excluded post types.
dcl_force_shortcodeFilter. Return
trueto skip the shortcode's own checks. The shortcode then always prints the comments template — the Disqus thread wheredcl_can_loadallows it, and the theme's own comments where it doesn't.Block themes
In block themes the shortcode doesn't hide the Comments block. Remove that block from the template if you place the thread with the shortcode.
Embed configuration
For each post the plugin builds the same values the official Disqus plugin used, so threads stay attached to their posts:
"<post ID> <guid>", e.g.42 https://example.com/?p=42get_permalink()dcl_embed_varsFilter. The per-post embed configuration handed to the front-end script.
disqusShortnamedisqusIdentifierdisqusUrldisqusTitledisqusConfigintegrationand an optionallanguagepostIdChanging the identifier
Changing
disqusIdentifierdetaches existing threads, and comment counts and sync keep using the original identifier. Don't change it on an existing site.disqus_configA
window.disqus_configfunction your site defines is kept. The plugin sets the page URL, identifier, title and language, then calls your function, so your values win. Define it before the footer:dcl_localized_dataFilter. The whole
window.dclDataobject passed to the front-end script:Comment counts
dcl_can_countFilter. Whether comment-count markers and Disqus's
count.jsare output on this request. Default: a shortname is set, counts are on, not a feed, anddsq_can_loadallows'count'.The plugin wraps the output of WordPress's
comments_numberin<span class="dsq-postid" data-dsqidentifier="…">; the script turns links around those markers into Disqus count links.Adding a load method
Addons can add a loading method in three steps — this is how Scroll Load works.
Offer it in the settings:
Mark it lazy, so the plugin holds the embed back:
Trigger it. For a method it doesn't know, the front-end script loads nothing and exposes
window.dclEmbed = { method, load }. Enqueue a script that depends on thedcl-commentshandle and callload():Enqueue it on
wp_footerat an early priority, so it also works when the shortcode enqueued the main script late.load()is safe to call more than once.Sync
dcl_comment_syncedAction. Fires after Disqus sends a comment and it's saved in WordPress.
$comment_idint0if an update changed nothing.$dataarray$verbstringcreate,updateorforce_sync.It doesn't fire for Sync past comments.
dcl_export_post_typesFilter. Post types whose comments are exported to Disqus. Default: public post types that support comments.
Settings screen
The settings screen is a React app. Addons extend it through
@wordpress/hooksfilters. Register them in a script that loads on the settings screen (hook suffixtoplevel_page_dcl-settings) before the app mounts.dcl.settings.panelsAdds a panel to the Settings tab.
iddisqus,loading,display,sync,advanced— replaces that panel.Componentafteradvanced.Register your option with
register_setting()andshow_in_rest— the Save Changes button then saves it along with everything else.dcl.settings.loading.fieldsAdds fields to the Comment loading panel. Each entry is
{ id, Component }; the component receives:getSetting( key, fallback )dcl_gnrl_optionsvalue.setSetting( key, value )usesButtontruewhile desktop or mobile uses the click method.dcl.admin.tabsAdds a tab to the settings page, as
key => { label, component, footer?, wide? }.footer: trueshows the Save Changes button;wide: trueuses the wide layout. Resolved once, when the page loads.dcl_admin_script_varsPHP filter. The
window.dclobject passed to the settings screen.Admin
Capability
The settings page and every
dcl/v1REST route need themanage_optionscapability. Change it with theDCL_ACCESSconstant, inwp-config.php:or with the
dcl_capabilityfilter.dcl_has_accessfilters the final check.General settings still need manage_options
The general settings save through WordPress's own settings endpoint, which always requires
manage_options. LowerDCL_ACCESSand other roles can open the page and manage the Disqus account and sync, but not save the other settings.dcl_show_admin_barFilter. Whether the toolbar Disqus menu shows. Default:
current_user_can( 'moderate_comments' ).dcl_admin_menuAction. Fires after the Disqus menu page is added, with the page's hook suffix.
dcl_disqus_conflict_alert_textFilter. The HTML of the warning shown while the official Disqus plugin is active.
dcl_disqus_fileFilter. The plugin file used to detect the official Disqus plugin. Default
disqus-comment-system/disqus.php.dcl_default_settingsFilter. The default settings. Keys added here are also kept when settings are saved.
Addons
dcl_register_addonFilter. How addon plugins register with the plugin's licensing. Add it at file load, not on a hook:
Premium addons are licensed by the Premium Bundle automatically.
dcl_addons_catalogFilter. The addon rows shown on the Addons tab.
dcl_addons_bundleFilter. The Premium Bundle banner's data. Return an empty array to hide the banner.
REST API
Admin routes
All need the plugin's capability and a REST nonce.
dcl/v1/accountGEThas_secret_key,has_access_token,has_sync_tokenflags. Secrets are never returned.dcl/v1/accountPOSTshortname,public_key,secret_key,access_token,sync_token. An empty string keeps a stored secret;nullclears it.dcl/v1/syncGETconfigured,allowed,subscribed,enabled,requires_update,webhook_url,last.dcl/v1/sync/enablePOSTdcl/v1/sync/disablePOSTdcl/v1/sync/manualPOSTstart,end(date or timestamp),cursor. Returnssynced,failed,next.dcl/v1/exportPOSTpage. Returnslog,page,total_pages.dcl/v1/addonsGETdcl/v1/addons/refreshPOSTdcl/v1/addons/<id>/licensePOST,DELETEkey) or deactivate an addon's license.dcl/v1/addons/bundle/licensePOST,DELETEkey) or deactivate the Premium Bundle.General settings use WordPress's settings endpoint,
/wp/v2/settings, under thedcl_gnrl_optionskey.Webhook
disqus/v1/sync/webhookPOSTdisqus/v1/sync/commentPOST(alias)Disqus sends comments here. The routes use the official plugin's namespace so existing subscriptions keep working, and are only registered while the official plugin is inactive.
Requests must carry an
X-Hub-Signatureheader —sha512=followed by the HMAC-SHA512 of the request body, keyed with the sync token (at least 32 characters). Anything else gets401. Handled verbs areverify(the subscription handshake),create,updateandforce_sync; others get204.Stored data
Options
dcl_gnrl_optionsdcl_disqus_accountshortname,public_key,secret_key,access_token,sync_token. Not exposed through/wp/v2/settings.dcl_db_versiondcl_sso_noticedcl_sync_last_messagedcl_bundle_licensedcl_gnrl_optionskeys:dcl_typescrolldcl_type_mob''dcl_btn_txtLoad Commentsdcl_btn_class''dcl_messageLoading...dcl_btn_style''dcl_btn_count0dcl_count_disable11means ondcl_cpt_exclude''dcl_div_width''dcl_div_width_typepxdcl_caching0dcl_cfasync0dcl_render_inline0Switches are stored as
0or1.Meta
dsq_post_iddsq_thread_idSynced comments also have the user agent
Disqus Sync Host. The keys match the official Disqus plugin's.Uninstalling
Deleting the plugin removes
dcl_gnrl_options,dcl_disqus_account,dcl_db_version,dcl_sso_noticeanddcl_sync_last_message. Synced comments, their meta and the official Disqus plugin's own settings are left in place.