Search AllTrails for hiking, biking, running, climbing, backpacking, or paddling trails near a location with the full filter panel surface (activity, difficulty, length, elevation, route type, suitability, traffic, attributes, rating, sort). Returns structured JSON with name, location, coordinates, length/elevation, difficulty, rating, photos, description, dog/kid policy, and region-wide totals. Read-only.
Works with
AI-first code editor with Composer
Before installing skills in Cursor, ensure your development environment meets these requirements:
node --versionsearch-trailsExecute the skills CLI command in your project's root directory to begin installation:
Fetches search-trails from alltrails.com/search-trails-dsqvnx and configures it for Cursor.
The CLI shows a list of agents. Use arrow keys and space to select Cursor:
Confirm successful installation by checking the skill directory location:
Restart Cursor to activate search-trails. Access via /search-trails in your agent's command palette.
We perform automated surface-level scans (Gen AI Scanner, Socket, Snyk) during installation. These checks detect common vulnerabilities but do not guarantee complete security. Always review skill source code and verify the publisher's reputation before production use.
Skills execute code in your environment. Always review source, verify the publisher, and test in isolation before production.
Submit your Claude Code skill and start earning
Automate repetitive workflows and reduce manual effort
Example
Generate reports, summarize documents, draft communications
Save 3-5 hours per week on routine tasks
Learn new skills, understand complex topics, get expert guidance
Example
Explain concepts, provide examples, suggest learning resources
Accelerate learning and skill development by 2x
Enhance output quality through reviews, suggestions, and refinements
Example
Review drafts, suggest improvements, catch errors
Improve work quality by 30-40% with less effort
0
total installs
0
this week
0
upvotes
Run in your terminal
0
installs
0
this week
—
stars
| name | search-trails |
| title | AllTrails Trail Search |
| description | >- Search AllTrails for hiking, biking, running, climbing, backpacking, or paddling trails near a location with the full filter panel surface (activity, difficulty, length, elevation, route type, suitability, traffic, attributes, rating, sort). Returns structured JSON with name, location, coordinates, length/elevation, difficulty, rating, photos, description, dog/kid policy, and region-wide totals. Read-only. |
| website | alltrails.com |
| category | outdoors |
| tags | - outdoors - hiking - trails - search - algolia - datadome |
| source | 'browserbase: agent-runtime 2026-05-18' |
| updated | '2026-05-18' |
| recommended_method | api |
| alternative_methods | [] |
| verified | true |
| proxies | true |
Given a location (URL, intent string, place name, bounding box, lat/lng+radius, or trail slug/ID) and any combination of the AllTrails filter panel dimensions (activity, difficulty, length, elevation gain, route type, dog/kid/wheelchair/stroller suitability, traffic, attributes, season, rating, sort), return matching trails as structured JSON — name, location, coordinates, length, elevation, difficulty, route type, average rating + review count, primary + thumbnail photo URLs, description, trail attributes, dog policy, kid policy, canonical AllTrails URL, plus region-wide totals ("16,544 trails in California"). Read-only — never saves a trail, follows a list, logs a completion, or posts a review.
The AllTrails web UI is a Next.js front-end over a public Algolia search index (alltrails_primary_en-US) and a small REST API at /api/alltrails/v2/*. The search index is the same one the site itself queries — credentials are bootstrapped into every page's hydration JSON. Lead with the Algolia path; only fall back to a stealth Browserbase browser session when you need the trail-detail page (description, recent conditions, GPX, full photo carousel), since those endpoints are DataDome-protected and the Algolia hit already contains ~80% of the fields the skill needs.
| Service | App ID | API Key |
|---|---|---|
| Algolia primary | 9IOACG5NHE | a557051fc69f8a3e456db3084df4780e |
| Algolia maps (heatmap geo) | N4IFDT32SC | b44b73be8a8cd9c256e26c7e0a80fc34 |
AllTrails web (/api/alltrails/*) | n/a (key only) | 3p0t5s6b5g4g0e8k3c1j3w7y5c3m4t8i |
All three are public client-side keys (apiKey rather than adminApiKey). They are visible in unauthenticated page source today; re-extract from a fresh homepage fetch if a request starts 401-ing.
| Input shape | What to do |
|---|---|
Full alltrails.com/explore?b_tl_lat=…&b_tl_lng=…&b_br_lat=…&b_br_lng=… | Use the bounding box as insideBoundingBox=<bl_lat>,<bl_lng>,<tr_lat>,<tr_lng> |
alltrails.com/us/{state} or alltrails.com/{slug} | Search Algolia with filters=type:area AND slug:"…"; take the top hit's state_id / ID |
| Place name ("Yosemite National Park", "Marin County", "Joshua Tree") | Search Algolia with query=<name>&filters=type:area&hitsPerPage=1; record the hit's ID (use as associated_area_ids:<id> later), _geoloc.lat/lng, state_id, country_id |
| State name only ("California") | Look up the state's state_id once via query=<state>&filters=type:area, then use filters=type:trail AND state_id=<id> |
lat,lng + radius_m | Use aroundLatLng=<lat>,<lng>&aroundRadius=<meters> (use aroundRadius=all to disable cap) |
| Intent string ("easy hikes near Yosemite") | Parse out place name → resolve as above; treat the modifier ("easy", "dog-friendly") as filter clauses (step 2) |
Trail slug trail/us/california/grant-lakes | Search Algolia with filters=type:trail AND slug:"<slug>", hitsPerPage=1 |
Trail ID 10694011 | Search Algolia with filters=type:trail AND ID=<id>, hitsPerPage=1 |
GET https://9IOACG5NHE-dsn.algolia.net/1/indexes/alltrails_primary_en-US
?x-algolia-application-id=9IOACG5NHE
&x-algolia-api-key=a557051fc69f8a3e456db3084df4780e
&query=<free-text or empty>
&hitsPerPage=<1-100>
&page=<0-based>
&filters=<filter expression — see below>
&numericFilters=<numeric range — see below>
&aroundLatLng=<lat>,<lng> (when scoping by point + radius)
&aroundRadius=<meters | "all"> (default: 1500000m without "all")
&insideBoundingBox=<bl_lat>,<bl_lng>,<tr_lat>,<tr_lng> (when scoping by bbox)
&attributesToRetrieve=<comma-list> (slim down response — see "1MB cap" gotcha)
/1/indexes/{name} accepts the search params as GET query string (the /query sub-path is POST-only). Auth via URL params works — no headers, no auth cookie. Use --proxies with browse cloud fetch if your egress is from a sandbox/datacenter IP; Algolia itself doesn't bot-detect but a clean residential IP avoids any future tightening.
| Prompt filter | Algolia clause |
|---|---|
| Activity = Hiking / Mountain Biking / Trail Running / Walking / Backpacking / Bike Touring / Road Biking / Rock Climbing / Bird Watching / Camping / Cross-Country Skiing / Fishing / Horseback Riding / Off-Road Driving / Paddle Sports / Scenic Driving / Skiing / Snowshoeing / Via Ferrata | activities:hiking / activities:mountain-biking / activities:trail-running / activities:walking / activities:backpacking / activities:bike-touring / activities:road-biking / activities:rock-climbing / activities:birding / activities:camping / activities:cross-country-skiing / activities:fishing / activities:horseback-riding / activities:off-road-driving / activities:paddle-sports / activities:scenic-driving / activities:skiing / activities:snowshoeing / activities:via-ferrata |
| Difficulty = Easy / Moderate / Hard | difficulty:easy / difficulty:moderate / difficulty:hard (a small strenuous tier also exists — fold into hard for prompt-shape outputs unless surfacing the raw label) |
| Length min/max miles | numericFilters=length>=<min_m>,length<=<max_m> where length is stored in meters (multiply input miles × 1609.344) |
| Elevation gain min/max feet | numericFilters=elevation_gain>=<min_m>,elevation_gain<=<max_m> — also meters (input feet × 0.3048). Note: elevation_meters field exists too but is the trailhead's absolute elevation, not gain. |
| Route type = Loop / Out & Back / Point to Point | route_type:L / route_type:O / route_type:P |
| Suitability = Dog-friendly | (features:dogs OR features:dogs-leash) (off-leash or leashed) — note dogs-no is the explicit-deny tag |
| Suitability = Kid-friendly | features:kids |
| Suitability = Stroller-friendly | features:strollers |
| Suitability = Wheelchair-friendly | features:ada (the indexed enum value for wheelchair-accessible) |
| Trail traffic = Light / Moderate / Heavy | visitor_usage:1 / visitor_usage:2 / visitor_usage:3 (note: a 4 bucket exists in raw data — see gotcha — most popular trails are 3/4) |
| Trail attribute = Wildflowers / Wildlife / Waterfall / River / Lake / Forest / Beach / Cave / Hot springs / Historic site / Views | features:wild-flowers / features:wildlife / features:waterfall / features:river / features:lake / features:forest / features:beach / features:cave / features:hot-springs / features:historic-site / features:views |
| Trail attribute = Off-leash dogs | features:dogs |
| Trail attribute = Paved / Partially paved | features:paved / features:partially-paved |
| Trail attribute = Rocky / Muddy | Not indexed as facets. Surface from the per-trail recent reviews payload only (browser fallback). |
| Open year-round vs seasonal | Use filters=is_closed:false to exclude active closures; full season metadata is on the trail detail page only. |
| Min rating | numericFilters=avg_rating>=<n> (decimal stars, 0.0-5.0) |
| Park / area scope | associated_area_ids:<area_id> (a trail can belong to multiple parks) — preferred over area_id because it includes overlapping designations |
| State / country scope | state_id:<id> / country_id:<id> |
Combine clauses with AND / OR and group with parens, URL-encoded. Example:
type:trail
AND state_id=5
AND (features:dogs OR features:dogs-leash)
AND features:kids
AND difficulty:easy
alltrails_primary_en-US has a single replica (_replica) with identical customRanking: [desc(popularity)] — meaning Algolia ranking is the same on both indexes and AllTrails handles sort variants through the query shape, not a separate sort-index per option:
| Sort prompt value | How |
|---|---|
| Most popular (default) | Default index, empty query= — natural ranking |
| Best match | Default index with the user's free-text query=<term> — text relevance bubbles up |
| Closest | Default index + aroundLatLng=<lat>,<lng> (no aroundRadius cap, or aroundRadius=all) — Algolia's geo ranking criterion places nearest first |
| Highest rated | Sort client-side by avg_rating desc on the returned hits, OR add numericFilters=avg_rating>=4.5,num_reviews>=20 to pre-filter |
| Most reviewed | Sort client-side by num_reviews desc, OR numericFilters=num_reviews>=100 to pre-filter |
(See gotcha: there is no dedicated _avg_rating_desc or _num_reviews_desc replica index. Don't search for one — both _alpha and _local are internal staging mirrors, not sort variants.)
page=0 is the first page, hitsPerPage accepts 1-100 (defaults to 20). Returned nbHits is the true total even when capped; nbPages reflects what's paginable (Algolia caps page × hitsPerPage ≤ 1000 by default — beyond that you need bounding-box subdivision to reach trail 1001+).
Algolia returns each trail as a flat JSON object. Map fields like this:
| Output field | Algolia hit field | Notes |
|---|---|---|
trail_id | ID (also surfaces as objectID cast to string) | Integer |
name | name | |
slug | slug | Path segment, e.g. trail/us/california/grant-lakes |
canonical_url | construct: https://www.alltrails.com/{slug} | |
lat, lon | _geoloc.lat, _geoloc.lng | Trailhead, not centroid |
length_miles | length / 1609.344 | length is meters |
elevation_gain_ft | elevation_gain / 0.3048 | elevation_gain is meters |
difficulty | difficulty ∈ easy / moderate / hard / strenuous | |
route_type | route_type ∈ L / O / P | Map to Loop / Out & Back / Point to Point for output |
activity | activities[] | Array — pick primary by first element (usually hiking) |
attributes | features[] | Array (see gotcha for canonical-vs-display mapping) |
average_rating | avg_rating | Decimal 0.0-5.0 |
review_count | num_reviews | Integer (text + non-text combined). num_text_reviews is text-only subset. |
park_name | area_name + area_type | area_type:N = national park, S = state park, F = national forest, C = city park, W = wilderness, R = regional park, etc. |
city, state, country | city_name, state_name, country_name | |
location_label | location_label | Display string like "Yosemite National Park, California" |
traffic | visitor_usage | 1 light / 2 moderate / 3 heavy / 4 extreme — see gotcha |
dog_policy | derive from features[] | dogs → off-leash · dogs-leash → on-leash · dogs-no → not allowed · absent → unknown |
kid_policy | features:kids → kid-friendly; absent → unknown | |
is_closed, is_private_property | direct booleans | |
description | description | Plain text, sometimes empty — trail-detail page is the canonical source |
estimated_duration | estimated_time_to_complete.value + .unit | e.g. {"value":"6.5–7","unit":"hr"} |
distance_miles (from input pin) | derive client-side: haversine((_geoloc.lat,_geoloc.lng), (input.lat,input.lng)) | Algolia doesn't surface it in the body |
total_region_count | top-level nbHits | Capture this for the "1,234 trails in California" panel-total field |
Each trail hit carries profile_photo_data formatted as {photo_id}-{photo_hash} plus featured_photo_ids[] for additional carousel photos. Get a redirect to the actual image via:
GET https://www.alltrails.com/api/alltrails/v2/trails/{trail_id}/photos/{index}
?key=3p0t5s6b5g4g0e8k3c1j3w7y5c3m4t8i
&size=<sm|md|lg|xl|extra_large>
Response is a 302 to https://images.alltrails.com/<base64> where the base64 decodes to an imgix-style edits descriptor pointing at assets.alltrails.com/uploads/photo/image/{id}/{hash}.jpg. Only extra_large actually applies a resize (max 2048×2048 inside fit) — other sizes return the original. For the prompt's output shape:
primary_photo_url → ?index=0&size=extra_large redirect target.thumbnail_url → ?index=0&size=md.photo_urls[] → ?index=1...num_photos-1 (typically up to featured_photo_ids.length).You can also skip the redirect hop and build the imgix URL yourself when bandwidth matters (the JSON edits payload is deterministic; the bucket is always assets.alltrails.com and the key is uploads/photo/image/{photo_id}/{photo_hash}.jpg).
When the prompt mentions a state, country, or area scope, run a hitsPerPage=0 count-only query alongside the main search to populate the "X trails in California" headline number:
GET .../alltrails_primary_en-US?query=&hitsPerPage=0&filters=type:trail AND state_id=5&x-algolia-application-id=…&x-algolia-api-key=…
→ {"hits":[], "nbHits":16544, "nbPages":0, "page":0, ...}
nbHits is the answer (validated 2026-05-18: California = 16,544; Yosemite NP via associated_area_ids:10106838 = 32 easy + 130 surface-level matches).
The Algolia search index is intrinsically read-only — there's no POST /book, POST /save, or POST /complete to call by accident. The /api/alltrails/v2/trails/{id}/photos/{idx} endpoint is GET-only. You cannot accidentally save a trail, follow a list, log a completion, or post a review through this surface. When falling back to the browser path (next section), do not click Save / Add-to-list / Mark-completed / Submit-review buttons.
The trail-detail page, the user-reviews list, the recent-conditions feed, and the GPX download are gated by DataDome on www.alltrails.com. The fetch API path returns 403 with X-Datadome: protected for every /trail/*, /explore*, /parks/*, /us/*, and /api/alltrails/v2/trails/{id} (non-photo) endpoint — confirmed 2026-05-18 across 8 paths. To reach those, you need a Browserbase stealth + residential proxy session that lets DataDome complete its JS challenge:
SID=$(browse cloud sessions create --keep-alive --verified --proxies | jq -r .id)
browse open "https://www.alltrails.com/trail/us/california/grant-lakes" --remote -s "$SID"
browse wait load --remote -s "$SID"
browse wait timeout 4000 --remote -s "$SID" # DataDome JS challenge resolves async
browse get markdown body --remote -s "$SID"
The first navigation after a new session typically lands on a DataDome interstitial; the JS challenge solves within 2-4 s on a stealth+proxy session, after which subsequent same-session fetches return 200 (the datadome=<…> cookie persists for ~6 hours). Once on the trail-detail page, extract:
<section data-testid="trail-description"> text, cleaned of HTML.<section data-testid="recent-conditions">; each row has a date, condition tags (Rocky, Muddy, Snowy, Icy, Overgrown, Fallen tree), and a reviewer handle. This is where rocky and muddy live — they are surfaced as condition tags on reviews, not as Algolia features: facets.<div data-testid="trail-parking">.gpx_url: null, gpx_requires_auth: true, gpx_unlock: "alltrails-pro". When authed (Cookie containing a _at_session_id_v2 + Pro entitlement), the link is /trail/{slug}/gpx?key={web_api_key}.heatmap_available flag.Avoid browse snapshot on the explore/search-results pages — they're virtualized lists with 0 stable a11y refs; iterate via the Algolia path for the listings themselves and use the browser only for per-trail enrichment.
X-Datadome: protected, datadome.alltrails.com script tag, X-Dd-B: 3 header). The mitigation is the same shape — stealth + residential proxy + a few seconds for the JS challenge — but the cookie name (datadome=…) and the captcha host (geo.captcha-delivery.com) differ from Cloudflare's cf_clearance, so don't reuse Cloudflare-specific tooling/fixtures./ is the only AllTrails web path that returns 200 without solving DataDome. Every other URL — /explore, /trail/*, /parks/*, /us/{state}, /api/alltrails/v2/trails/{id} (non-photo) — is 403 on a cold session. The homepage is your "extract bootstrapped credentials, then leave" target if the in-bundle Algolia/web keys need re-discovery.elevation_meters is something else. length and elevation_gain are stored in meters regardless of the units: "i" flag on each hit (the units flag is a display preference for the user, not a data unit). elevation_meters is the trailhead's absolute elevation above sea level, not the gain — don't confuse them. Convert length / 1609.344 for miles, elevation_gain / 0.3048 for feet.route_type is one letter, not a word. L / O / P → Loop / Out & Back / Point to Point. The filter facet uses the letter too (route_type:L).difficulty has FOUR values, not three. The AllTrails filter panel shows three (Easy/Moderate/Hard), but the index also contains strenuous. Treat strenuous as a stricter superset of hard when mapping back to the panel's three-button UI.visitor_usage has FOUR levels, not three. Panel shows Light / Moderate / Heavy (1/2/3) but the data has a fourth bucket (4) that's not exposed in the filter UI but appears in ~94% of popular-trail hits in our 1,000-trail sample. Treat 4 as "Extreme / very crowded" and either fold into Heavy or surface as a separate enum if your output schema allows.browse cloud fetch. The Browserbase Fetch API rejects responses > 1MB. With default attributes, ~250 hits per response is the practical ceiling. For larger pulls, use attributesToRetrieve=[…] to slim each hit to the fields your output needs (a 1,000-hit slim response with 6 attrs comes in at ~825 KB — verified 2026-05-18).page × hitsPerPage caps at 1,000. Algolia returns trail-1 through trail-1000 across pages; trail-1001+ silently disappear even though nbHits is the true total. To exhaustively enumerate a state, subdivide by insideBoundingBox quadrants (lat halves × lng halves recurse until each cell's nbHits ≤ 1000).state_id / country_id, not name strings. state_name is in searchableAttributes (text-search index) but NOT in attributesForFaceting, so filters=state_name:"California" returns 0. Always resolve a state-name input to state_id first via a type:area query, then filter on the numeric ID. (Observed: California = 5, country US = 313.)area_id vs associated_area_ids. A trail's "home" park is area_id; associated_area_ids[] includes overlapping designations (e.g. a trail inside Yosemite NP that also touches Stanislaus National Forest will list both). Always use associated_area_ids:<id> for park-scoped filters — area_id:<id> will miss legitimate hits.views, wildlife, wild-flowers (not wildflowers), forest, river, waterfall, lake, beach, cave, hot-springs, historic-sitedogs (off-leash welcome), dogs-leash (not dogs-leashed), dogs-no (explicit "no dogs")kids (not kid-friendly), strollers, ada (the indexed value for wheelchair-accessible — NOT wheelchair)paved, partially-pavedcity-walk, rails-trails, pub-crawlrocky, muddy, snowy, icy, overgrown, fallen-tree — these are review-time condition tags (live on the trail detail page's Recent Conditions panel), not index facets. The AllTrails filter panel calls them "Trail conditions" and that filter is implemented client-side from the most-recent reviews, not from a server facet.hiking, walking, running (general), trail-running, mountain-biking, road-biking, bike-touring, backpacking, camping, birding (not bird-watching), fishing, horseback-riding, rock-climbing, snowshoeing, skiing, cross-country-skiing, paddle-sports, scenic-driving, off-road-driving, via-ferrata.alltrails_primary_en-US is [typo, geo, words, filters, proximity, attribute, exact, custom] with customRanking: [desc(popularity)]. There is no *_avg_rating_desc or *_num_reviews_desc replica — the only replica is alltrails_primary_en-US_replica with the same ranking. For "Highest rated" / "Most reviewed" sort, fetch the top-N by popularity (or by aroundLatLng for distance) then sort the returned hits client-side. Don't waste a request hunting for a non-existent sort replica.type:trail filter is mandatory. Without it, your search results will be polluted with type:area, type:city, type:park, type:poi, and type:user hits. Always include type:trail for trail-listing tasks; use type:area only when resolving a place-name input to an ID.profile_photo_data looks like a single string but is {photo_id}-{photo_hash}. The hyphen is the field separator. profile_photo_data: "50889290-02bd9fbfaf51cdd737bb00f0198e0fe4" → photo_id = 50889290, photo_hash = 02bd9fbfaf51cdd737bb00f0198e0fe4. featured_photo_ids[] is a parallel array of additional photo IDs for the carousel./api/alltrails/v2/trails/{id}/photos/{idx} is the one DataDome-allowlisted AllTrails-owned endpoint — it 302s straight through (verified 2026-05-18). All other /api/alltrails/v2/* paths are 403'd by DataDome from the same cold session. Don't waste time probing other /api/alltrails/ paths from a non-stealth fetch; they require the same browser-session unblocking as the page routes._alpha and _local Algolia indexes are internal mirrors, not regional variants. alltrails_primary_en-US_alpha is a staging index with ~83% the row count of production; alltrails_primary_en-US_local has 1,902 entries (test fixtures). Always use `alPrerequisites
Time Estimate
15-45 minutes depending on use case complexity
Steps
Common Pitfalls
✓ Do
✗ Don't
💡 Pro Tips
✓ Use when
Use when skill capabilities match your task, clear ROI on time saved, and you can validate outputs. Best for repetitive tasks, learning, and quality improvement.
✗ Avoid when
Avoid when task requires deep expertise you can't validate, involves sensitive decisions, or when learning process is more valuable than speed of completion.
kostja94/marketing-skills
aaaaqwq/claude-code-skills
agentbay-ai/agentbay-skills
glebis/claude-skills
shopmeskills/mcp
sundial-org/awesome-openclaw-skills
search-trails fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.
search-trails is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.
Solid pick for teams standardizing on skills: search-trails is focused, and the summary matches what you get after install.
Registry listing for search-trails matched our evaluation — installs cleanly and behaves as described in the markdown.
search-trails has been reliable in day-to-day use. Documentation quality is above average for community skills.
search-trails reduced setup friction for our internal harness; good balance of opinion and flexibility.
I recommend search-trails for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.
search-trails fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.
Useful defaults in search-trails — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
search-trails has been reliable in day-to-day use. Documentation quality is above average for community skills.
showing 1-10 of 43