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!

Open beta

Start with one Capture

Join the open beta and create screenshots or videos without local browser setup.

Join Open Beta