---
title: "Mapping Page Data: Mapping Recipes"
description: Every construct in the mapping format, including resolvers, URL reads, regex shaping, combined values, tags and aliases, each with a snippet and its output.
---

[Skip to content](https://docs.toggl.com/mapping-page-data-mapping-recipes#main-content)

English

Show submenu for translations

[![logo-dark](https://docs.toggl.com/hs-fs/hubfs/logo-dark.png?width=223&height=85&name=logo-dark.png)](https://docs.toggl.com/?hsLang=en)

Open main navigation

Close main navigation

- English
  
  Show submenu for translations
- [Go to Toggl](https://focus.toggl.com/)

[Go to Toggl](https://focus.toggl.com/)

 How can we help you?

- There are no suggestions because the search field is empty.

1. [Toggl 2.0 Knowledge Base](https://docs.toggl.com/?hsLang=en)
2. [Toggl Apps](https://docs.toggl.com/toggl-apps?hsLang=en)
3. [Browser Extension](https://docs.toggl.com/toggl-apps?hsLang=en#browser-extension)

# Mapping Page Data: Mapping Recipes

This guide is part of [Mapping Page Data into Toggl](https://docs.toggl.com/mapping-page-data-into-toggl?hsLang=en).

Every construct the mapping format supports, with a snippet and what it produces.

### Reading a value off the page

A resolver starts somewhere, then travels. Pick **one** starting point and **one** way to travel — the editor rejects a resolver that names two of either.

### Starting points

| Key | Starts at | Use when |
| --- | --- | --- |
| `root` | The whole document | One button on the page, value lives anywhere |
| `closest` | Nearest matching ancestor of the button | One button per row or card — keeps each button reading its own row |
| `parent` | N levels up from the button | The value sits in a wrapper with no useful class or test id |

⚠️ On a list or board, `root` is almost always wrong. Every button would read the same value — whichever row happens to be first. Use `closest` scoped to the row.

### Ways to travel

```
"title":  { "closest": "@card", "query": "h3.title" }  "after":  { "closest": ".me", "nextSibling": 1 }  "before": { "closest": ".me", "previousSibling": 1 } 
```

`query` is a CSS selector. The sibling forms step to the element beside your starting point instead — useful when a value has no selector of its own but sits predictably next to something that does.

### Read an attribute instead of the text

```
"ticketId": { "closest": ".card", "textAttribute": "data-id" } 
```

Produces `AB-9` from `<div class="card" data-id="AB-9">`. This is how the Zendesk integration gets the ticket number — it is in an attribute, never shown on screen.

### Strip markdown

```
"clean": { "query": ".body", "unmarkdown": true } 
```

`**bold** text` becomes `bold text`.

---

### Reading from the URL

Often more reliable than the page, because URLs change far less often than markup.

```
"repo": { "url": "path",  "regex": "^/([^/]+/[^/]+)", "replace": "$1" } "qid":  { "url": "query", "regex": "id=([0-9]+)" } 
```

`url` accepts `path`, `query`, `hash` or `href`.

---

### Shaping the value with regex

| Goal | Write | Result |
| --- | --- | --- |
| Keep only part of it | `"regex": "#([0-9]+)"` | `23450` from `#23450` |
| Add a prefix | `"regex": "^(.*)$", "replace": "State:$1"` | `State:Open` |
| Drop a prefix | `"regex": "^.*/(.*)$", "replace": "$1"` | `Sanjin` from `Support/Sanjin` |
| Fixed text on a match | `"regex": "/issues/", "replace": "Kind:Issue"` | `Kind:Issue` |
| Two pieces | `"regex": "^/([^/]+)/([^/]+)", "replace": "$1 :: $2"` | `vitejs :: vite` |

Omit `replace` and it defaults to `$1`. If the regex matches nothing you get no value at all — never a half-formed one.

---

### Combining values

These apply to `description`, `task` and `project`. Tags behave differently — see the next section.

#### Glue several pieces together

```
"description": { "resolvers": ["key", " ", "title"] } 
```

Produces `AB-9 Fix the thing`. Any name that is **not** a resolver is treated as literal text — that is how the `" "` becomes a space, and how `"#"` or `": "` become separators.

#### Join every match with a separator

```
"description": { "join": ", ", "resolvers": "label" } 
```

With two labels on the page, produces `bug, urgent`.

#### Try one, then another

```
"project": [   { "resolvers": ["breadcrumbProject"] },   { "resolvers": ["boardHeaderProject"] } ] 
```

A list of locators is a **fallback chain**: the first one that produces something wins, the rest are skipped. Jira uses exactly this, because the project name sits in one of two places depending on whether the issue is a full page or a modal over a board.

---

### Tags

Tags are the exception to everything above, in two ways.

#### They union, they do not fall back

```
"tags": ["labelTag", "stateTag", "authorTag"] 
```

All three contribute. Every other field would stop at the first. This is what lets one button collect tags from several unrelated places on the page.

#### One resolver can produce many tags

A resolver matching three labels produces three tags, and `regex`/`replace` is applied to each one:

```
"labelTag": { "query": ".label", "regex": "^(.*)$", "replace": "Label:$1" } 
```

Gives `Label:bug` and `Label:urgent`, not one merged value.

#### `field:value` needs regex, not literals

🚨 Writing `["Status:", "status"]` under `tags` produces **two tags** — `Status:` and `To Do` — because every part becomes its own tag. `join` does not help; it is ignored for tags.

Build the label into the value with `regex` and `replace`, as in the example above.

Duplicates are removed automatically, case-insensitively, so a value that appears twice on the page still produces one tag. A start sends at most 10 tags; anything beyond that is dropped.

---

### Naming things

#### Selector aliases

Define a selector once and reuse it with `@name`:

```
"selectors": { "card": "[data-testid='board-card']" }, "resolvers": {   "title": { "closest": "@card", "query": ".title" } } 
```

#### A stable identity for the item

```
"taskResolver": { "keyPattern": "^[A-Za-z][A-Za-z0-9]+-[0-9]+$" } 
```

A regex tried against the **first word** of the description. A match becomes the item's identity, which is what keeps the button attached to its running timer when someone renames the ticket.

---

### The trap that costs the most time

🕵️ **A misspelled resolver name becomes literal text, and it wins.**

In a fallback chain, `[{ "resolvers": ["titel"] }, { "resolvers": ["title"] }]` produces the literal string `titel`. The first candidate "succeeded", so the real one is never tried.

The editor does not catch this: a bare word is valid JSON. The button does. With the side panel open, its `data-toggl-debug-issues` attribute names the unknown resolver, so check it before assuming the selector is wrong.

- [Getting started](https://docs.toggl.com/getting-started?hsLang=en#main-content)

    - [Start here](https://docs.toggl.com/getting-started?hsLang=en#start-here)
    - [Quickstart Guides](https://docs.toggl.com/getting-started?hsLang=en#quickstart-guides)
    - [Set up your Workspace](https://docs.toggl.com/getting-started?hsLang=en#set-up-your-workspace)
    - [Toggl Guides for Toggl Track and Toggl Plan users](https://docs.toggl.com/getting-started?hsLang=en#toggl-guides-for-toggl-track-and-toggl-plan-users)
- [Using Toggl, page by page](https://docs.toggl.com/using-toggl-page-by-page?hsLang=en#main-content)

    - [Timer](https://docs.toggl.com/using-toggl-page-by-page?hsLang=en#timer)
    - [Reports](https://docs.toggl.com/using-toggl-page-by-page?hsLang=en#reports)
    - [Projects](https://docs.toggl.com/using-toggl-page-by-page?hsLang=en#projects)
    - [Tasks](https://docs.toggl.com/using-toggl-page-by-page?hsLang=en#tasks)
    - [Timeline](https://docs.toggl.com/using-toggl-page-by-page?hsLang=en#timeline)
    - [Members](https://docs.toggl.com/using-toggl-page-by-page?hsLang=en#members)
    - [Approvals](https://docs.toggl.com/using-toggl-page-by-page?hsLang=en#approvals)
    - [Time Off](https://docs.toggl.com/using-toggl-page-by-page?hsLang=en#time-off)
    - [Profile Settings](https://docs.toggl.com/using-toggl-page-by-page?hsLang=en#profile-settings)
- [Admin Settings & Security](https://docs.toggl.com/admin-settings-security?hsLang=en#main-content)

    - [Workspace Setup](https://docs.toggl.com/admin-settings-security?hsLang=en#workspace-setup)
    - [Tracking & Rates](https://docs.toggl.com/admin-settings-security?hsLang=en#tracking-rates)
    - [Single Sign-on](https://docs.toggl.com/admin-settings-security?hsLang=en#single-sign-on)
- [Subscription & Billing](https://docs.toggl.com/subscription-billing?hsLang=en)
- [Toggl Apps](https://docs.toggl.com/toggl-apps?hsLang=en#main-content)

    - [Mobile](https://docs.toggl.com/toggl-apps?hsLang=en#mobile)
    - [Desktop](https://docs.toggl.com/toggl-apps?hsLang=en#desktop)
    - [Browser Extension](https://docs.toggl.com/toggl-apps?hsLang=en#browser-extension)
- [API & Integrations](https://docs.toggl.com/api-integrations?hsLang=en)
- [Troubleshooting & FAQs](https://docs.toggl.com/troubleshooting-faqs?hsLang=en#main-content)

    - [My Toggl Acount](https://docs.toggl.com/troubleshooting-faqs?hsLang=en#my-toggl-acount)
    - [Common Questions](https://docs.toggl.com/troubleshooting-faqs?hsLang=en#common-questions)

<https://github.com/toggl/> <https://www.linkedin.com/company/toggl/> <https://community.toggl.com/>

Copyright © 2026. Toggl. [Privacy Policy](https://toggl.com/legal/focus/privacy/)