# Capture a page behind a sign-in

Pages that require an account cannot be reached by a browser that has just started. This guide saves a signed-in session once, then reuses it for every screenshot that needs it.

A session is what a signed-in browser carries: its cookies and per-origin local storage. shotlist stores it in a JSON file. You never write that file by hand.

## Step 1: declare the sessions

Name each session in your configuration, and say where its file should live:

shotlist.config.yaml

```
site:
  sessions:
    admin: .shotlist/admin.json
    reader:
      path: .shotlist/reader.json
      verify: '[data-signed-in]'
```

Sessions are named rather than singular because the same page is often worth photographing more than once: as an administrator, and as somebody with no rights at all.

`verify` is a selector that only a signed-in page contains, such as an avatar, an admin bar or a sign-out link. Set it. Without it, an expired session does not fail: the site redirects to its sign-in form, every screenshot in the run becomes a picture of that form, and `--install` publishes them.

## Step 2: sign in once

```
npx shotlist --login admin
```

This opens a browser at `site.url` and waits while you sign in by hand. When you are done, it writes the cookies the browser came back with to the path you declared, creating the folder if needed. Your password never reaches shotlist.

What it writes is this site's cookies and local storage, and nothing else. Signing in through Google, Discord or any other provider leaves that provider's session in the browser too, and none of it is saved — those cookies are your account rather than a screenshot credential. `--login` names what it left out.

Before writing, it loads what remains into a fresh browser and checks the session's `verify` selector against it. A session that no longer signs in is refused with the hosts it dropped, and nothing is written — see [when the sign-in needs a provider's cookies](#provider).

## Step 3: point a recipe at the session

Add one line to any recipe that should be taken as that account:

screenshots/recipes/dashboard.yaml

```
name: dashboard
install: guide
session: admin

clip: { css: '.panel' }
```

Leave it out and the browser is a stranger to the site, which is what you want for screenshots of signed-out pages.

## Sign in without a person at the keyboard

In continuous integration there is nobody to type a password. Write the sign-in as a macro and pass it to `--using`, which runs it in a headless browser instead of waiting:

```
npx shotlist --login admin --using sign-in
```

screenshots/macros/sign-in.yaml

```
steps:
  - fill: { label: Email }
    value: admin@example.com
  - fill: { label: Password }
    value: ${env.ADMIN_PASSWORD}
  - click: { role: button, name: Sign in }
```

`${env.ADMIN_PASSWORD}` resolves only for names the person running the command allowed, so the run must name it:

```
npx shotlist --login admin --using sign-in --allow-env ADMIN_PASSWORD
```

`--allow-env` is repeatable. A CI system that already exports its secrets can list the names in `SHOTLIST_ENV` instead; the two add up. An allowed name that is empty stays unresolved and the run reports which one it was, rather than typing nothing into a password field and reporting a failed sign-in.

If your project always signs in the same way, name the variables once in the configuration instead of repeating the flag on every run:

shotlist.config.yaml

```
allowEnv:
  - ADMIN_PASSWORD
```

`allowEnv` holds names, never values — the values stay in the environment. It adds to whatever the command line and `SHOTLIST_ENV` allowed, and an `--untrusted` run ignores it, the same way it ignores `site.allow`. A machine that sets `SHOTLIST_ENV_DENY` can put a name beyond all three. See [the configuration reference](/docs/reference/configuration/#allowenv).

## Keeping the password in a .env file

You can, and it works with everything on this page — but load it yourself. shotlist never reads a `.env` file. That name is on the list of paths it refuses to open in every mode, with no flag to turn it off, so a configuration cannot aim anything at one either.

Write the file as usual:

.env

```
ADMIN_PASSWORD=hunter2
SHOTLIST_ENV=ADMIN_PASSWORD
```

Then put it into the environment before shotlist starts. Node can do it:

```
node --env-file=.env node_modules/.bin/shotlist --all --install
```

Or the shell can:

```
set -a; . ./.env; set +a
npx shotlist --all --install
```

`--env-file` arrived in Node 20.6, and shotlist supports Node 20 and later, so on 20.0 to 20.5 it fails with an unknown option. The `set -a` form works everywhere.

Once the file is loaded there is nothing special about it. shotlist reads the process environment, so `SHOTLIST_ENV`, `SHOTLIST_ENV_DENY`, `SHOTLIST_ALLOW`, `SHOTLIST_WORK_LIMITS`, `SHOTLIST_DENY` and `SHOTLIST_UNTRUSTED` can all live in `.env` alongside the secrets themselves. For why shotlist will not load it for you, see [What a configuration can do](/docs/explanation/security-model/#dotenv).

Without a terminal to wait in and without `--using`, the run reports that it cannot sign in rather than hanging.

## When the sign-in needs a provider's cookies

Almost no application does: its own session is a cookie or a token under its own origin, and that is what gets kept. Where one genuinely needs the provider's, approve its destination and name the credential host on the session:

shotlist.config.yaml

```
site:
  allow:
    - accounts.google.com
  sessions:
    admin:
      path: .shotlist/admin.json
      verify: '[data-signed-in]'
      keep: [accounts.google.com]
```

`site.allow` lets the browser reach the provider; `keep` retains that provider's cookies. The file then signs in as that Google account as well as yours, and `--login` says so every time it writes one. Treat it as the credential it is: what is on disk is no longer only a screenshot session.

## Keep the session file out of version control

The file holds live cookies. Anyone who has it is signed in as whoever created it. shotlist writes it `0600`, so no other account on the machine can read it — version control is the gap that leaves. Add the folder to your `.gitignore`:

.gitignore

```
.shotlist/
```

An `--untrusted` run loads no session at all, so a configuration you did not write cannot ask to be somebody.

## Related

- [Command line reference](/docs/reference/cli/)
- [Configuration reference: `sessions`](/docs/reference/configuration/#sessions)
- [What a configuration can do](/docs/explanation/security-model/)
- [Annotate an existing image](/docs/how-to/annotate-an-image/), for a sign-in that cannot be automated at all

Written by Nicola Mustone · Applies to shotlist 0.6.0 · Maintained by Nicola Mustone

Published date unavailable · Updated date unavailable · [View source](https://github.com/SirDarcanos/shotlist.dev/blob/main/src/pages/docs/how-to/sign-in.astro) · [Propose a correction](https://github.com/SirDarcanos/shotlist.dev/edit/main/src/pages/docs/how-to/sign-in.astro)
