Screenshot pages behind a login without scripting the sign-in
To screenshot pages behind a login, the browser has to carry a session, and that session has to stay out of any URL. Dashboards for client reports, invoices for billing emails, admin panels for audits and staging sites behind basic auth all live behind authentication. The Screenshot API forwards headers on the request itself, so the page loads as your logged-in user.
A screenshot of an authenticated page shows the login form
A fresh headless browser has no cookies, so an authenticated URL redirects to the login page and the capture shows a sign-in form. The request succeeds, the image is delivered, and the report you emailed contains a password field instead of a chart.
Automating the login flow is the obvious fix and the worst one. Filling the form on every capture is slow, breaks whenever the markup changes, and is exactly what multi-factor authentication and bot protection are designed to stop. It also means storing a real password next to your screenshot job.
Microlink lets you forward any header to the target: send it as an x-api-header-* HTTP header on your request and it reaches the page as the original header. A session cookie or an authorization token is enough for the browser to load the page as that user, and the headers reference explains why secrets belong there and not in the query string.
How to screenshot a page behind a login
Two paths exist: the headers query parameter for public values such as a language, and x-api-header-* request headers for cookies and tokens. Use the second for anything secret. The private pages guide covers both.
import createClient from 'microlink.io'
const microlink = createClient({
apiKey: process.env.MICROLINK_API_KEY
})
const { url } = await microlink.screenshot('https://app.example.com/dashboard', {
headers: {
'x-api-header-cookie': `session=${process.env.SESSION_COOKIE}`
}
})The SDK sends headers as real HTTP request headers. Microlink strips the x-api-header- prefix and forwards cookie to the target, so the dashboard renders as the session owner and you get back the hosted image URL.
import createClient from 'microlink.io'
const microlink = createClient({
apiKey: process.env.MICROLINK_API_KEY
})
const { url } = await microlink.screenshot('https://app.example.com/reports/42', {
headers: {
'x-api-header-authorization': `Bearer ${process.env.APP_TOKEN}`
},
waitForSelector: '#report'
})Any header works the same way, Basic and Bearer authorization included. waitForSelector holds the capture until the authenticated content exists, which also proves the session was accepted.
curl -G https://pro.microlink.io \
-d url=https://app.example.com/dashboard \
-d screenshot=true \
-d meta=false \
-H 'x-api-key: $MICROLINK_API_KEY' \
-H 'x-api-header-cookie: session=abc123'Credentials travel as HTTP headers on the Microlink request, never in the query string. x-api-key authenticates you against pro.microlink.io, and the x-api-header-cookie value is what the target receives.
- headers Non-sensitive headers as a public query parameter; secrets go in x-api-header-* request headers. Pro plans.
- waitForSelector Waits for an element that only exists once the user is authenticated.
- screenshot.element Crops the capture to the widget or table you need from the dashboard.
- styles Injects CSS to hide navigation, avatars or other app chrome before the capture.
- cacheKey Appends a custom identifier, such as a user id, to the cache key. Pro plans.
- proxy Add it when the authenticated site also sits behind antibot protection. Pro plans.
Forwarding headers requires a Pro plan. Sending x-api-key to the free endpoint fails with EPRO, so point authenticated requests at pro.microlink.io, as the authentication docs describe.
Why forwarding headers beats scripting the login for screenshots
A session is the smallest thing that makes a page render for a user, and forwarding it keeps the sensitive part off the wire you do not control.
The cache key is derived from the URL and the query parameters. When several users capture the same URL, add a cacheKey per user so their captures live in separate cache entries.
Keep these requests on your backend. The custom headers feature shows how the values ride the HTTP layer, and every capture runs in its own isolated browser that is destroyed afterwards, so a forwarded session is never shared between requests.
When not to: if the page renders user data you are not authorized to store, or the session belongs to a third party, do not forward it. For documents rather than images, the same header pattern drives PDF invoices from authenticated pages.
FAQ
How do I pass a cookie to the page being screenshotted?
Can I put the session cookie in the headers query parameter for a screenshot?
Can I screenshot a page behind a login on the free plan?
Why does my screenshot still show the login form?
Can I screenshot a page protected by HTTP basic auth?
Solve the next problem with the same API
Screenshot a single element
Screenshots of JavaScript-rendered pages
Screenshot blocked websites
Screenshots without cookie banners or ads
Screenshots under traffic spikes
PDF invoices from authenticated pages
Ready to capture private pages?
Forward the session, keep the secret off the URL. Get a Pro key and screenshot the dashboards only your users can see.