Skip to article frontmatterSkip to article content

🐞 4 - Debugging: What to do when things go wrong?

💡 Introduction¶

You might find yourself spending a significant portion of your development time debugging an extension that is not building, or some functionality that is now broken. With the right tools and approach, debugging can be an experience that leaves you feeling accomplished rather than frustrated. In this section we will go over some common issues that you might encounter when working on extensions, and a few ways in which you can tackle them.


1ïžâƒŁ Understanding Error Messages¶

Knowing the kind of error you are dealing with is already a solid step forward.

🔍 Types of Errors¶

⏰ By when they occur¶

Build-time errors¶

Build-time errors happen when you’re compiling your TypeScript code or building your extension. These are often syntax errors, type mismatches or missing/unused imports that don’t allow you to run your code until they are fixed.

Common examples:¶

Build-time errors appear in your terminal during jlpm build.

Runtime errors¶

Runtime errors happen when your extension logic is executing, maybe a failed network request, or an undefined property that you’ve tried accessing.

Common examples:¶

Runtime errors show up in the browser console or jupyter lab terminal logs after your extension is loaded.

đŸ—ș By where they occur¶

With a full stack extension, which includes a server component, it is important to understand which side, server-side or client-side, is causing the error.

Python server errors¶

Python errors will show up in the terminal from which you launched your JupyterLab instance (the server terminal).

Example:

ValueError: Invalid endpoint configuration
  File "jupytercon2025_extension_workshop/routes.py", line 32, in get
Javascript frontend errors¶

JavaScript error details will be available in the browser console.

Example:

Uncaught TypeError: widget.render is not a function
    at activate (index.ts:23)

📚 Reading Stack Traces¶

2ïžâƒŁ Terminal¶

📋 Terminal Logs¶

Where to find JupyterLab server logs

In the terminal where you run jupyter lab, you’ll find a great deal of information.

During build time, webpack will tell you about compilation errors, missing dependencies, or TypeScript type errors. And while the messages can be verbose, you can scroll up to the first error and work through any subsequent errors.

3ïžâƒŁ Browser Developer Tools¶

This is where the real magic happens! Head to your browser and open the developer tools. Let’s take a look at some of the most useful tabs.

💬 Console Tab¶

🌐 The Network Tab: Following the data trail¶

If your extension makes API calls to a server (JupyterLab or otherwise), the Network tab can provide you plenty of the details you might need. You can start by checking that the extension resources have been loaded.

Open the Network tab and interact with your extension. You’ll see a list of all the HTTP requests made, and click on any request to see:

⏞ The Sources Tab: Let’s Pause¶

This is where you can actually step through your code line by line, verifying the logic and behavior.

First, make sure you’re running JupyterLab in an environment were your extension is installed and source maps are enabled. Source maps are enabled by default if you’re working from the extension-template.

Then:

  1. Navigate to the Sources tab

  2. Find your extension’s TypeScript files in the file tree (look for a folder with your extension’s name)

  3. Click on a line number to set a breakpoint

  4. Now, interact with your extension in a way that will execute that line

  5. When the code hits your breakpoint, the code execution will pause. You’ll see a visual indicator of this in your browser session.

🎹 The Elements/Inspector Tab: Examining the DOM¶

When something looks wrong visually, either a button is in the wrong place, content is missing or styles aren’t being applied as expected, you want to visit the Elements tab.

You can right-click on any element in the page and select “Inspect” to jump directly to that element in the Elements tab.

From there, you can:


4ïžâƒŁ Best Practices¶

🎯 Debugging Workflow¶

đŸ›Ąïž Prevention is better¶

Of course, even better than debugging, would be to have working code that behaves as you expect. A few practices that can save you time debugging are:


5ïžâƒŁ Hands-on Exercise¶

Work through the issues systematically, using the appropriate debugging tool for each problem. Remember: start simple, use console logs, then progress to more advanced tools as needed.

Instructions¶

  1. Make sure all your work up to now is saved and/or committed.

  2. Navigate to the src directory where your widget.ts file is located

  3. Copy the following modified widget.ts file contents and paste them into your local src/widget.ts file

src/widget.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import { Widget } from '@lumino/widgets';
import { MainAreaWidget, ToolbarButton } from '@jupyterlab/apputils';
import { imageIcon, refreshIcon } from '@jupyterlab/ui-components';

import { requestAPI } from './request';

class ImageCaptionWidget extends Widget {
  // Initialization
  constructor() {
    super();

    // Create and append an HTML <p> (paragraph) tag to our widget's node in
    // the HTML document
    const hello = document.createElement('p');
    hello.innerHTML = 'Hello, world!';
    this.node.appendChild(hello);

    const center = document.createElement('center');
    this.node.appendChild(center);

    // Put an <img> tag into the <center> tag, and also save it as a class
    // attribute so we can update it later.
    this.img = document.createElement('img');
    center.appendChild(this.img);

    // Do the same for a caption!
    this.caption = document.createElement('p');
    center.appendChild(this.caption);

    // BUG #1: Typo in method name - TypeScript will catch this at build time
    // Initialize the image from the server extension
    this.load_imge();
  }

  // Fetch data from the server extension and save the results to img and
  // caption class attributes
  load_image(): void {
    // BUG #3: Wrong endpoint name - will cause 404 error
    requestAPI<any>('random-image-captions')
      .then(data => {
        console.log(data);
        this.img.src = `data:image/jpeg;base64, ${data.b64_bytes}`;
        this.caption.innerHTML = data.caption;
      })
      .catch(reason => {
        console.error(`Error fetching image data.\n${reason}`);
      });
  }

  // Information about class attributes for the type checker
  img: HTMLImageElement;
  caption: HTMLParagraphElement;
}

export class ImageCaptionMainAreaWidget extends MainAreaWidget<ImageCaptionWidget> {
  constructor() {
    const widget = new ImageCaptionWidget();
    super({ content: widget });

    this.title.label = 'Random image with caption';
    this.title.caption = this.title.label;
    this.title.icon = imageIcon;

    // Add a refresh button to the toolbar
    const refreshButton = new ToolbarButton({
      icon: refreshIcon,
      tooltip: 'Refresh image',
      onClick: () => {
        // BUG #2: Wrong method name - using 'as any' bypasses TypeScript checking
        // This will cause a runtime "is not a function" error when clicked
        // Should be load_image(), not refresh()
        (widget as any).refresh();
      }
    });
    this.toolbar.addItem('refresh', refreshButton);
  }
}

Key Debugging Tools¶

  1. Terminal/Compiler - TypeScript catches errors at build time

  2. Browser Console - Runtime errors and logging

  3. Network Tab - API calls and HTTP status codes

The 3 Bugs¶

Bug #1: Build-Time Error (Terminal)¶

If you try building the extension with this modified widget.ts file, you should see a compilation error in the terminal.

Expected error:

src/widget.ts:32:10 - error TS2551: Property 'load_imge' does not exist on type 'ImageCaptionWidget'. Did you mean 'load_image'?

32     this.load_imge();
            ~~~~~~~~~

  src/widget.ts:37:3
    37   load_image(): void {
         ~~~~~~~~~~
    'load_image' is declared here.

Found 1 error in src/widget.ts:32

Bug #2: Runtime Error (Browser Console)¶

Expected error:

961.29c067b15a524e556eed.js?v=29c067b15a524e556eed:2 Uncaught TypeError: widget.refresh is not a function
    at Object.onClick (widget.ts:78:25)
    at Ws.o (jlab_core.e595af6ce37775e8a915.js?v=e595af6ce37775e8a915:1:1764774)
    at [... internal JupyterLab code ...]

Bug #3: Network Error (Browser Network Tab)¶

What you’ll see in Network tab:

FieldValue
Request URLhttp://localhost:8890/jupytercon2025-extension-workshop/random-image-captions?1761852662635
Request MethodGET
Status Code404 Not Found
Remote Address[::1]:8890
Referrer Policystrict-origin-when-cross-origin

🔧 Common Debugging Scenarios¶

Let’s walk through some common problems when working on developing extensions, and how to approach debugging them.

❌ “My extension is not loading!”¶

First, check the terminal where you’re running the build. Did the build complete successfully? Next, make sure you actually installed the extension and refresh the page. Run jupyter labextension list in your terminal to verify it’s installed.

Finally, check the browser console for any red errors.

đŸ–Œïž “My UI isn’t updating!”¶

This one can be tricky, as there are a number of possible root causes, especially once you begin using libraries like React, creating more complex and reactive UIs. For the purposes of our tutorial today, we’ll only focus on potential CSS specificity conflicts. Sometimes an element is updating but we can’t see it because it is being hidden by CSS, or pushed off-screen. Use the elements tab to verify the DOM is changing.

🌐 “My API call isn’t working!”¶

Open the Network tab and interact with your extension to trigger the API call. Then look at:

  1. Is the request being made? If not, maybe your code isn’t actually calling the API.

  2. What’s the URL? Check for any typos, incorrect endpoint or ports, or missing path segments.

  3. What’s the status code? Where 500 means there’s a server error, 404 means the request endpoint doesn’t exist and 403 means your request is unauthorized.

  4. What’s the response body? Even if you do get a 200 status code, the data structure might not be what you expect.

📚 Additional Resources¶