Scrnify blog

A Guide for Script Injection in Puppeteer

Updated 1/29/2025

Hey there! Laura and Heidi here from SCRNIFY. Today, we're diving deep into script injection with Puppeteer. After spending countless hours working with browsers (and building a screenshot service along the way), we've learned a thing or two about injecting scripts effectively. Let's share what we've discovered!

If your Puppeteer setup exists mainly to capture pages, Scrnify is a website screenshot API that handles remote browser infrastructure for you.

The Basics: Your Script Injection Toolkit

Puppeteer gives us three main ways to inject scripts:

  • By URL (loading from external sources)
  • By file path (loading from your local system)
  • Raw content (directly injecting JavaScript)

Let's explore each method with real, working examples.

Method 1: External Scripts via URL

const puppeteer = require('puppeteer')

async function injectExternalScript() {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()

    try {
        await page.goto('https://example.com')

        // Inject jQuery from CDN
        await page.addScriptTag({
            url: 'https://code.jquery.com/jquery-3.7.1.min.js',
        })

        // Verify jQuery is working
        const jQueryVersion = await page.evaluate(() => {
            return jQuery.fn.jquery
        })

        console.log(`jQuery version ${jQueryVersion} injected successfully!`)
    } finally {
        await browser.close()
    }
}

injectExternalScript().catch(console.error)

Console output:

jQuery version 3.7.1 injected successfully!

Method 2: Local File Injection

const puppeteer = require('puppeteer')
const path = require('path')

async function injectLocalScript() {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()

    try {
        await page.goto('https://example.com')

        // Inject local script
        await page.addScriptTag({
            path: path.join(__dirname, 'custom-script.js'),
        })

        // Verify our custom function exists
        const result = await page.evaluate(() => {
            return window.myCustomFunction()
        })

        console.log('Custom script result:', result)
    } finally {
        await browser.close()
    }
}

// custom-script.js content:
/*
window.myCustomFunction = function() {
    return 'Hello from custom script!';
};
*/

injectLocalScript().catch(console.error)

Console output:

Custom script result: Hello from custom script!

Method 3: Raw Script Injection

const puppeteer = require('puppeteer')

async function injectRawScript() {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()

    try {
        await page.goto('https://example.com')

        // Inject raw JavaScript
        await page.addScriptTag({
            content: `
                window.modifyPage = function() {
                    document.body.style.backgroundColor = '#f0f0f0';
                    document.querySelector('h1').textContent = 'Modified!';
                    return 'Page modified successfully!';
                };
            `,
        })

        // Execute our injected function
        const result = await page.evaluate(() => {
            return window.modifyPage()
        })

        console.log('Script execution result:', result)
    } finally {
        await browser.close()
    }
}

injectRawScript().catch(console.error)

Console output:

Script execution result: Page modified successfully!

Advanced Scenarios: Async Scripts and Security

Handling Async Scripts

const puppeteer = require('puppeteer')

async function handleAsyncScript() {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()

    try {
        await page.goto('https://example.com')

        // Inject async script
        await page.addScriptTag({
            content: `
                window.asyncOperation = async function() {
                    return new Promise(resolve => {
                        setTimeout(() => {
                            resolve('Async operation completed!');
                        }, 1000);
                    });
                };
            `,
        })

        // Wait for async operation
        const result = await page.evaluate(async () => {
            return await window.asyncOperation()
        })

        console.log('Async result:', result)
    } finally {
        await browser.close()
    }
}

handleAsyncScript().catch(console.error)

Security Considerations

When injecting scripts, keep these high-level security practices in mind:

const puppeteer = require('puppeteer')

async function secureScriptInjection() {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()

    try {
        // Use content security policy
        await page.setExtraHTTPHeaders({
            'Content-Security-Policy': "script-src 'self' 'unsafe-inline' https://trusted-cdn.com",
        })

        await page.goto('https://example.com')

        // Only inject from trusted sources
        await page.addScriptTag({
            url: 'https://trusted-cdn.com/script.js',
        })
    } finally {
        await browser.close()
    }
}

Comparing Methods: evaluate() vs addScriptTag()

Let's look at the key differences:

const puppeteer = require('puppeteer')

async function compareMethods() {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()

    try {
        await page.goto('https://example.com')

        // Using evaluate()
        const evaluateResult = await page.evaluate(() => {
            document.body.style.backgroundColor = 'red'
            return 'Modified with evaluate()'
        })
        console.log('Evaluate result:', evaluateResult)

        // Using addScriptTag()
        await page.addScriptTag({
            content: `
                document.body.style.backgroundColor = 'blue';
                console.log('Modified with addScriptTag()');
            `,
        })
    } finally {
        await browser.close()
    }
}

compareMethods().catch(console.error)
Method Execution Best Used For Behavior
evaluate() Once One-off operations Executes and returns result
addScriptTag() Persistent Reusable functions Remains in page context

Performance Tips

Keep these general performance considerations in mind:

const puppeteer = require('puppeteer')

async function performanceOptimizedInjection() {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()

    try {
        // Combine multiple scripts into one
        await page.addScriptTag({
            content: `
                // All your functions in one injection
                window.function1 = () => {};
                window.function2 = () => {};
                window.function3 = () => {};
            `,
        })

        // Use async/defer for external scripts
        await page.addScriptTag({
            url: 'https://example.com/script.js',
            type: 'text/javascript',
            async: true,
        })
    } finally {
        await browser.close()
    }
}

Wrapping Up

Script injection in Puppeteer is powerful but requires careful consideration of your use case. Whether you're loading external libraries, injecting custom code, or handling async operations, always remember:

  • Choose the right injection method for your needs
  • Consider security implications
  • Keep performance in mind
  • Use error handling appropriate for your use case

We hope this guide helps you master script injection in Puppeteer! If you're looking for a ready-to-use solution for web captures, check out SCRNIFY - we've already handled all these complexities for you! 😉

Cheers, Laura & Heidi 🇦🇹

P.S. Got questions about Puppeteer or web automation? We'd love to hear from you!

Capture API

Start with one Capture

Create screenshots or videos without local browser setup.

Start capturing