I am trying to display this captcha thing on my UI but the container is null. It seems like the container element with the ID hcaptcha-container is not found in my component's template and I don't know why. I am a newbie on LWC and probably I don't see the issue. Any advice? Thanks

js file

import { LightningElement, wire, track} from 'lwc';
import getCaptchaCredentials from "@salesforce/apex/ShipperCaptchaController.getCaptchaCredentials";
import { loadScript } from 'lightning/platformResourceLoader';
import api from '@salesforce/resourceUrl/api';

export default class ShipperCaptcha extends LightningElement {
    @track siteKey;
    @track secret;

connectedCallback() {
        console.log('before loading');
        Promise.all([loadScript(this, api)])
            .then(() => {
                console.log('after loading');
                console.log('calling loadCaptchaCredentials');
            .catch((error) => {
                console.error('Error connect callback:', error);

    loadCaptchaCredentials() {
            .then((result) => {
                this.siteKey = result.siteKey;
                this.secret = result.secret;
                console.log('sitekey ', this.siteKey);
                console.log('secret ', this.secret);
            .catch((error) => {
                console.error('Error loading captcha credentials:', error);

    renderCaptcha() {
        const container = this.template.querySelector('#hcaptcha-container');
        console.log('container: ' , container);
        console.log('site key renderCaptcha: ' , this.siteKey);
        if (container) {
          hcaptcha.render(container, {
                sitekey: this.siteKey
                // callback: (response) => {
                //     this.token = response;
                // }
        } else {
          console.error('Container element not found');

html file

    <div id = "hcaptcha-container"></div>

2 Answers 2


In Lightning Web Components (LWC), the use of ids in the template with the intent to select elements using querySelector or similar methods is not recommended and will not work as expected. This is due to the concept of the Shadow DOM in LWC.

LWC uses the Shadow DOM to encapsulate and isolate component styles and behavior. This means that the HTML structure and elements within an LWC component are not directly accessible from the outside, including elements with ids. The ids defined within the component's template are scoped to the component itself and cannot be selected using querySelector from outside the component.

To interact with elements within an LWC component, it is recommended to use directives like data- attributes or classes to identify and manipulate the elements. You can use event handling and data binding to communicate with the component and update the relevant elements based on component state or user interactions.

If you need to manipulate the DOM or access specific elements outside of the component, you can consider using imperative JavaScript, such as using the this.template.querySelector method within the component's JavaScript file. This method allows you to select elements within the component's template by using the CSS selector syntax.

// Assuming you have an element with class 'my-element' within the template

const element = this.template.querySelector('.my-element');
if (element) {
  // Do something with the element
  element.textContent = 'Updated content';

Hope it helps!!

  • Thanks a lot for you help!!! In the html file I used the class like this <div class = "hcaptcha-container" data-sitekey = {siteKey}></div>, instead of the id, and in the js file I put like this const container = this.template.querySelector('.hcaptcha-container'); and it worked.
    – LauraB
    Commented May 18, 2023 at 11:08
  • @LauraB I am glad, it helped. Can you mark any of the answers as accepted so that it can help other users with the same issue?
    – Gopal
    Commented May 19, 2023 at 7:50

You have two main problems. First, you can't query by id, as the runtime changes it to be globally unique at runtime. Second, you need to allow direct DOM access for the third-party logic. Third, since this this is a CAPTCHA library, you may also need to allow access to CORS resources (Setup | CORS) and CSP settings (Setup | CSP).

So, you'll need to fix your template to read:

  <div lwc:dom="manual"></div>

Because of the Shadow DOM, we don't have to worry about querying by ID or any other attribute, since we're guaranteed to just get the div within the template.

const container = this.template.querySelector('div');
const { sitekey } = this;
const callback = (result) => { /* ... */ };
hcaptcha.render(container, { sitekey, callback });
  • Thanks a lot for you help!!! I used Gopal's idea with the class for the querySelector and it worked. Also it helped that you mentioned CSP. I had to create CSPs for hcaptcha.com and https://*.hcaptcha.com because I had an error "fused to frame 'newassets.hcaptcha.com' because it violates the following Content Security Policy" and after creating the CPSs with context "All" it worked.
    – LauraB
    Commented May 18, 2023 at 11:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .