Overview
Given a qualified prospect list and a single source-of-truth site profile, drive each target's submission form to completion, capture evidence, and emit an auditable log. This skill is execution-only — it assumes prospects are already vetted upstream and assumes anchor / description copy is either pre-supplied or fetched via the agent's main loop.
Outcome: a submission-log.json with one row per attempt (status, evidence, live URL once verified) plus screenshots under ./out/backlinks/. No fabricated successes, no fire-and-forget, no link-farm targets ever.
Mental model
A submission run is a pipeline: profile → per-target field mapping → form drive → evidence capture → verification follow-up. Most failures are not bugs — they are signals: captcha walls, login-required, paid-only, dead form, off-topic category. The skill's job is to detect, classify, and log these honestly so the user gets a reliable picture of what shipped.
Three immovable invariants:
- Never submit to a target the prospect list did not qualify. If a target looks shady mid-run (hidden links, casino/pharma neighbours, "1000 directories for $5" footer), skip and log
skipped:quality.
- One identity per run. Reuse the same email, brand name, contact, anchor pool — directories that detect inconsistency flag listings. The profile is the source of truth.
- Evidence or it didn't happen. Every
submitted row needs a post-submit screenshot and either a confirmation URL or a confirmation-email subject line.
Rules
The site profile (input contract)
The user (or an upstream step) supplies one JSON file. If any required field is missing, stop and ask — do not invent values.
| Field |
Required |
Notes |
name |
yes |
Brand / site / product name as it should appear |
url |
yes |
Canonical URL, no UTM, https |
tagline |
yes |
≤ 80 chars |
description_short |
yes |
≤ 160 chars |
description_long |
yes |
300–600 chars, neutral tone |
description_alt |
recommended |
A second long variant — rotate to avoid duplicate-content footprint |
category_primary |
yes |
Map to each directory's taxonomy |
category_alt |
recommended |
Fallback when primary doesn't exist on a target |
tags |
yes |
5–10 keywords, lowercase |
logo_path |
yes if any target requires image |
Local file; PNG ≥ 256 px square preferred |
screenshot_path |
recommended |
For directories asking for a product preview |
contact_email |
yes |
Real, monitored — verification mail lands here |
contact_name |
yes |
Real human; some directories reject "Admin" |
pricing |
recommended |
"free" / "freemium" / "paid" — startup directories ask |
founded_year, location, team_size |
recommended |
Business directories ask |
social.twitter, social.linkedin, social.github |
recommended |
Many forms require ≥ 1 social URL |
anchors |
yes |
Array of 3–6 anchor texts: brand, brand+keyword, naked URL, generic ("learn more"). Rotate; never reuse same anchor more than 30% of the run. |
Target classification (per prospect, before driving the form)
| Target type |
Treat as |
Notes |
| Free general directory (BOTW free tier, niche aggregators) |
submit |
Verify it's still live and indexed |
| Business directory (GMB, Yelp, BBB, Apple Maps) |
submit, ownership-verify |
Often needs phone/postcard verification — log as pending:ownership-verify |
| Startup / SaaS directory (BetaList, F6S, AlternativeTo, SaaSHub, Tools Directory style) |
submit |
Usually needs account + email verify |
| Web2.0 property (Medium, Tumblr, WordPress.com, Notion public, Bear) |
publish, not submit |
Different flow — author a short on-topic post linking once, naturally |
| Niche industry list (curated GitHub awesome lists, vertical aggregators) |
PR / issue, not form |
Reroute: open issue / PR — out of this skill's scope, log skipped:not-a-form |
| Q&A / forum (Quora, Reddit, Stack Exchange) |
NOT this skill |
Persona / on-topic answers are out of scope — log skipped:not-a-form |
| Paid-only ($/listing) |
skip unless user pre-approved budget |
Log skipped:paid with the price |
| Link farm / "submit to 500 directories" / spammy ad-laden / hidden links / unrelated niche |
skip, hard |
Log skipped:quality with the reason |
| Captcha-walled (hCaptcha, reCAPTCHA v2, Cloudflare Turnstile) |
stop and escalate |
Do not attempt to bypass. Log skipped:captcha; the user solves manually if they want |
| Login-required and we have no account |
escalate |
Either user supplies creds, or log skipped:auth |
Field-mapping rules
name → "Site Name", "Title", "Project", "Tool name", "Company". Match what the form asks; never invent prefixes/suffixes.
url → "URL", "Website", "Homepage". Always canonical https; never with tracking params.
description_short → "Tagline", "Short description", "Pitch", fields with ≤ 160 char limit.
description_long / description_alt → "Description", "About", "Details" (300+ chars). Rotate variants across the run; don't paste the identical paragraph 50 times.
category → match to directory taxonomy by exact label first, then nearest synonym. If nothing fits, pick the closest and log category_mapping.
tags → split into the form's expected separator (comma, space, enter-per-tag). Trim to the form's max count.
anchors → only used when the target lets you choose anchor text on the link. Rotate per-target from anchors[]; track usage to keep brand-anchor share ≥ 50%.
logo / screenshot → upload via setInputFiles on the file input; verify the upload preview appears before submit.
Drive protocol (per target)
- Pre-flight — open URL, confirm it's the submission form (not 404, not paywall, not "submissions closed"). If wrong, log and move on.
- Account check — if the form requires an account: look up cached creds for that domain in this session's profile vault; if absent, ask user once, then proceed. Never store creds to disk.
- Captcha check — if a captcha is on the submit page, stop. Do not iterate.
- Field walk — accessibility-tree first; fill in the order the form lays them out; verify each value sticks (re-read the input).
- Category mapping — if a select / radio is required, pick by label match; if no match, pick the nearest sibling and record the mapping in the log row.
- Upload check — for image fields, confirm the preview thumbnail rendered before submit.
- Submit — click the actual submit button; wait for a success state: redirect, success message, confirmation page selector, or verification email.
- Evidence — screenshot the post-submit state (full page) to
./out/backlinks/<domain>-<UTC>.png; capture the confirmation URL or message text.
- Verification — if the target sends a confirmation email, poll the user's mailbox via the supplied flow (out of scope of this skill — escalate if no flow available); record the verified URL when it goes live.
- Log — append one row to
./out/backlinks/submission-log.json.
Throttling and identity hygiene
- Spread across time: never submit to more than 5 targets in a row from the same IP without a 60-second cool-off.
- Pace per day: cap free-directory submissions at 20 per day per identity; business directories at 5; web2.0 publishes at 2.
- Rotate anchor variants — track use counts in-memory; never let one anchor exceed 30% of the run.
- Rotate description variants — alternate
description_long and description_alt per target.
- One profile, one email, one contact name across the entire run. Inconsistency is a flag.
Submission log schema
./out/backlinks/submission-log.json is an array of rows. Append-only.
{
"target_url": "https://example-directory.com/submit",
"target_domain": "example-directory.com",
"target_type": "saas-directory",
"submitted_at": "2026-05-10T18:32:11Z",
"status": "submitted | pending:email | pending:ownership-verify | live | rejected | skipped:captcha | skipped:paid | skipped:quality | skipped:auth | skipped:not-a-form | failed:selector | failed:timeout",
"anchor_used": "the brand name",
"description_variant": "long | alt",
"category_mapping": { "requested": "Developer Tools", "selected": "Tools / Dev" },
"live_url": null,
"screenshot_path": "./out/backlinks/example-directory-com-20260510T183211Z.png",
"confirmation_url": null,
"confirmation_email_subject": null,
"notes": "Selector for category was a custom dropdown; mapped Tools → Tools / Dev."
}
Out-of-scope handling
- Need fresh, on-topic descriptions per niche → stop; copy authoring is an upstream concern, do NOT write copy here.
- Need to qualify fresh prospects mid-run → stop. This skill does not vet new targets on the fly; prospect vetting is an upstream concern.
- Q&A / forum / community work → out of scope. Persona / on-topic answers are not this skill's job.
Examples
Example 1: Bad → Good (description handling)
❌ Bad:
For all 50 directories, paste description_long verbatim. Reuse "TheBrand" anchor every time.
✅ Good:
Alternate description_long and description_alt across submissions.
Rotate anchors from profile.anchors[]; track use, cap brand-anchor share at 50%, naked-URL at 20%, generic at 30%.
What changed: identical copy + identical anchor across many targets is the duplicate-footprint signal Google uses to discount or penalize.
Example 2: Edge case — category not in taxonomy
The site is a Developer Tools SaaS but the directory only offers Software / Other, Business / Productivity, Lifestyle. Pick Software / Other, record category_mapping: { requested: "Developer Tools", selected: "Software / Other" }, do not pick Business / Productivity because it's slightly broader — closer is better than larger.
Example 3: Edge case — captcha on submit
Form filled cleanly, "Submit" button reveals an hCaptcha challenge. Stop. Screenshot. Log skipped:captcha. Do not refresh and retry; do not attempt an OCR/solver. Move to the next target.
Checklist
Composition / References
- Pairs with
browser-form-filling-patterns (selector resilience, file uploads, multi-step forms) — base layer.
- Runs only after an upstream prospect-vetting step has produced a qualified list — this skill does not discover or qualify prospects.
- Copy generation (descriptions, anchor variants) is an upstream concern — do not author copy inside this skill.
- Google Search Essentials — Link spam
- Google: Qualifying outbound links to your site
1---2name: browser-backlink-submission3description: Operational playbook for EXECUTING backlink submissions via a headless browser — driving directory/listing/Web2.0 forms with Playwright, mapping a single site profile to per-target fields, handling signup + email verification + captchas, throttling to avoid spam flags, and producing an auditable submission log. Activate ONLY when the user has a qualified prospect list and wants the browser to actually submit. Prospect discovery, qualification, and anchor/description copy are upstream concerns — this skill is execution-only.4license: Apache-2.05---67## Overview89Given a qualified prospect list and a single source-of-truth site profile, drive each target's submission form to completion, capture evidence, and emit an auditable log. This skill is execution-only — it assumes prospects are already vetted upstream and assumes anchor / description copy is either pre-supplied or fetched via the agent's main loop.1011Outcome: a `submission-log.json` with one row per attempt (status, evidence, live URL once verified) plus screenshots under `./out/backlinks/`. No fabricated successes, no fire-and-forget, no link-farm targets ever.1213## Mental model1415A submission run is a pipeline: profile → per-target field mapping → form drive → evidence capture → verification follow-up. Most failures are not bugs — they are signals: captcha walls, login-required, paid-only, dead form, off-topic category. The skill's job is to detect, classify, and log these honestly so the user gets a reliable picture of what shipped.1617Three immovable invariants:181. Never submit to a target the prospect list did not qualify. If a target looks shady mid-run (hidden links, casino/pharma neighbours, "1000 directories for $5" footer), skip and log `skipped:quality`.192. One identity per run. Reuse the same email, brand name, contact, anchor pool — directories that detect inconsistency flag listings. The profile is the source of truth.203. Evidence or it didn't happen. Every `submitted` row needs a post-submit screenshot and either a confirmation URL or a confirmation-email subject line.2122## Rules2324### The site profile (input contract)2526The user (or an upstream step) supplies one JSON file. If any required field is missing, stop and ask — do not invent values.2728| Field | Required | Notes |29|---|---|---|30| `name` | yes | Brand / site / product name as it should appear |31| `url` | yes | Canonical URL, no UTM, https |32| `tagline` | yes | ≤ 80 chars |33| `description_short` | yes | ≤ 160 chars |34| `description_long` | yes | 300–600 chars, neutral tone |35| `description_alt` | recommended | A second long variant — rotate to avoid duplicate-content footprint |36| `category_primary` | yes | Map to each directory's taxonomy |37| `category_alt` | recommended | Fallback when primary doesn't exist on a target |38| `tags` | yes | 5–10 keywords, lowercase |39| `logo_path` | yes if any target requires image | Local file; PNG ≥ 256 px square preferred |40| `screenshot_path` | recommended | For directories asking for a product preview |41| `contact_email` | yes | Real, monitored — verification mail lands here |42| `contact_name` | yes | Real human; some directories reject "Admin" |43| `pricing` | recommended | "free" / "freemium" / "paid" — startup directories ask |44| `founded_year`, `location`, `team_size` | recommended | Business directories ask |45| `social.twitter`, `social.linkedin`, `social.github` | recommended | Many forms require ≥ 1 social URL |46| `anchors` | yes | Array of 3–6 anchor texts: brand, brand+keyword, naked URL, generic ("learn more"). Rotate; never reuse same anchor more than 30% of the run. |4748### Target classification (per prospect, before driving the form)4950| Target type | Treat as | Notes |51|---|---|---|52| Free general directory (BOTW free tier, niche aggregators) | submit | Verify it's still live and indexed |53| Business directory (GMB, Yelp, BBB, Apple Maps) | submit, ownership-verify | Often needs phone/postcard verification — log as `pending:ownership-verify` |54| Startup / SaaS directory (BetaList, F6S, AlternativeTo, SaaSHub, Tools Directory style) | submit | Usually needs account + email verify |55| Web2.0 property (Medium, Tumblr, WordPress.com, Notion public, Bear) | publish, not submit | Different flow — author a short on-topic post linking once, naturally |56| Niche industry list (curated GitHub awesome lists, vertical aggregators) | PR / issue, not form | Reroute: open issue / PR — out of this skill's scope, log `skipped:not-a-form` |57| Q&A / forum (Quora, Reddit, Stack Exchange) | NOT this skill | Persona / on-topic answers are out of scope — log `skipped:not-a-form` |58| Paid-only ($/listing) | skip unless user pre-approved budget | Log `skipped:paid` with the price |59| Link farm / "submit to 500 directories" / spammy ad-laden / hidden links / unrelated niche | skip, hard | Log `skipped:quality` with the reason |60| Captcha-walled (hCaptcha, reCAPTCHA v2, Cloudflare Turnstile) | stop and escalate | Do not attempt to bypass. Log `skipped:captcha`; the user solves manually if they want |61| Login-required and we have no account | escalate | Either user supplies creds, or log `skipped:auth` |6263### Field-mapping rules6465- `name` → "Site Name", "Title", "Project", "Tool name", "Company". Match what the form asks; never invent prefixes/suffixes.66- `url` → "URL", "Website", "Homepage". Always canonical https; never with tracking params.67- `description_short` → "Tagline", "Short description", "Pitch", fields with ≤ 160 char limit.68- `description_long` / `description_alt` → "Description", "About", "Details" (300+ chars). Rotate variants across the run; don't paste the identical paragraph 50 times.69- `category` → match to directory taxonomy by exact label first, then nearest synonym. If nothing fits, pick the closest and log `category_mapping`.70- `tags` → split into the form's expected separator (comma, space, enter-per-tag). Trim to the form's max count.71- `anchors` → only used when the target lets you choose anchor text on the link. Rotate per-target from `anchors[]`; track usage to keep brand-anchor share ≥ 50%.72- `logo` / `screenshot` → upload via `setInputFiles` on the file input; verify the upload preview appears before submit.7374### Drive protocol (per target)75761. Pre-flight — open URL, confirm it's the submission form (not 404, not paywall, not "submissions closed"). If wrong, log and move on.772. Account check — if the form requires an account: look up cached creds for that domain in this session's profile vault; if absent, ask user once, then proceed. Never store creds to disk.783. Captcha check — if a captcha is on the submit page, stop. Do not iterate.794. Field walk — accessibility-tree first; fill in the order the form lays them out; verify each value sticks (re-read the input).805. Category mapping — if a select / radio is required, pick by label match; if no match, pick the nearest sibling and record the mapping in the log row.816. Upload check — for image fields, confirm the preview thumbnail rendered before submit.827. Submit — click the actual submit button; wait for a success state: redirect, success message, confirmation page selector, or verification email.838. Evidence — screenshot the post-submit state (full page) to `./out/backlinks/<domain>-<UTC>.png`; capture the confirmation URL or message text.849. Verification — if the target sends a confirmation email, poll the user's mailbox via the supplied flow (out of scope of this skill — escalate if no flow available); record the verified URL when it goes live.8510. Log — append one row to `./out/backlinks/submission-log.json`.8687### Throttling and identity hygiene8889- Spread across time: never submit to more than 5 targets in a row from the same IP without a 60-second cool-off.90- Pace per day: cap free-directory submissions at 20 per day per identity; business directories at 5; web2.0 publishes at 2.91- Rotate anchor variants — track use counts in-memory; never let one anchor exceed 30% of the run.92- Rotate description variants — alternate `description_long` and `description_alt` per target.93- One profile, one email, one contact name across the entire run. Inconsistency is a flag.9495### Submission log schema9697`./out/backlinks/submission-log.json` is an array of rows. Append-only.9899```100{101 "target_url": "https://example-directory.com/submit",102 "target_domain": "example-directory.com",103 "target_type": "saas-directory",104 "submitted_at": "2026-05-10T18:32:11Z",105 "status": "submitted | pending:email | pending:ownership-verify | live | rejected | skipped:captcha | skipped:paid | skipped:quality | skipped:auth | skipped:not-a-form | failed:selector | failed:timeout",106 "anchor_used": "the brand name",107 "description_variant": "long | alt",108 "category_mapping": { "requested": "Developer Tools", "selected": "Tools / Dev" },109 "live_url": null,110 "screenshot_path": "./out/backlinks/example-directory-com-20260510T183211Z.png",111 "confirmation_url": null,112 "confirmation_email_subject": null,113 "notes": "Selector for category was a custom dropdown; mapped Tools → Tools / Dev."114}115```116117### Out-of-scope handling118119- Need fresh, on-topic descriptions per niche → stop; copy authoring is an upstream concern, do NOT write copy here.120- Need to qualify fresh prospects mid-run → stop. This skill does not vet new targets on the fly; prospect vetting is an upstream concern.121- Q&A / forum / community work → out of scope. Persona / on-topic answers are not this skill's job.122123## Examples124125### Example 1: Bad → Good (description handling)126127❌ Bad:128```129For all 50 directories, paste description_long verbatim. Reuse "TheBrand" anchor every time.130```131132✅ Good:133```134Alternate description_long and description_alt across submissions.135Rotate anchors from profile.anchors[]; track use, cap brand-anchor share at 50%, naked-URL at 20%, generic at 30%.136```137138What changed: identical copy + identical anchor across many targets is the duplicate-footprint signal Google uses to discount or penalize.139140### Example 2: Edge case — category not in taxonomy141142The site is a `Developer Tools` SaaS but the directory only offers `Software / Other`, `Business / Productivity`, `Lifestyle`. Pick `Software / Other`, record `category_mapping: { requested: "Developer Tools", selected: "Software / Other" }`, do not pick `Business / Productivity` because it's slightly broader — closer is better than larger.143144### Example 3: Edge case — captcha on submit145146Form filled cleanly, "Submit" button reveals an hCaptcha challenge. Stop. Screenshot. Log `skipped:captcha`. Do not refresh and retry; do not attempt an OCR/solver. Move to the next target.147148## Checklist149150- [ ] Site profile JSON validated — all required fields present, anchors[] has ≥ 3 entries, logo file exists if any target needs it151- [ ] Prospect list classified — each target has a `target_type`; link-farm / unrelated targets pre-filtered out152- [ ] Output directory `./out/backlinks/` exists; previous `submission-log.json` either rotated or appended to deliberately153- [ ] Per submission: post-submit screenshot saved, log row appended, status reflects reality (no "submitted" without evidence)154- [ ] Anchor and description rotation is enforced — no anchor or description variant exceeds its share cap155- [ ] Throttling respected — pace caps and cool-offs honored156- [ ] Captcha / paid / login-required targets skipped, not faked157- [ ] No credentials persisted to disk after the run158- [ ] Final report summarizes: total attempted, submitted, pending verification, skipped (by reason), failed (by reason)159160## Composition / References161162- Pairs with `browser-form-filling-patterns` (selector resilience, file uploads, multi-step forms) — base layer.163- Runs only after an upstream prospect-vetting step has produced a qualified list — this skill does not discover or qualify prospects.164- Copy generation (descriptions, anchor variants) is an upstream concern — do not author copy inside this skill.165- [Google Search Essentials — Link spam](https://developers.google.com/search/docs/essentials/spam-policies#link-spam)166- [Google: Qualifying outbound links to your site](https://developers.google.com/search/docs/essentials/spam-policies#user-generated-spam)