This is a Next.js project bootstrapped with webflow cloud init.
Copy .env.example to .env and fill in the values. See comments in .env.example for details.
First, run the development server:
npm run devOpen http://localhost:3000 with your browser to see the result.
You can start editing the page by modifying app/page.tsx. The page auto-updates as you edit the file.
You can deploy your app by running webflow cloud deploy.
Base path in production: /app (see next.config.ts).
POST /app/api/submit-contact-formGET /app/api/get-pipedrive-user?userId=123GET /app/api/calendar?to=123GET /app/api/get-job-offers?company=kaikoPOST /app/api/sync-jobs— sync Teamtailor jobs into the Webflow Jobs collection (live items). Headerx-sync-secret: value ofSYNC_JOBS_SECRET. QueryskipArchive=1to keep Webflow items whose slug is no longer in Teamtailor (default: archive them).
Defaults in src/lib/webflow-jobs-sync.ts match this shape (field slug = Webflow "API name"):
| Webflow field | Slug (typical) | Sync source |
|---|---|---|
| Name | name |
Job title |
| Slug | slug |
TeamTailor job id (e.g. 7466842) |
| Department | department |
Department name (from job included or departments list) |
| Locations label | locations-label |
City / name per location from included |
| Remote status | remote-status-2 |
remote / hybrid / none (plain text) |
| Apply URL | apply-url |
Link URL |
| TeamTailor ID | teamtailor-id |
Same as job id |
| Description | description |
Job body HTML from Teamtailor (falls back to pitch) |
| Min / Max salary | min-salary, max-salary |
Numbers |
| Currency | currency |
Plain text |
Override or extend with WEBFLOW_JOBS_FIELD_MAP if your API names differ.
Webflow token: Data API with CMS:read and CMS:write. Data API docs.
Example:
curl -X POST 'https://<host>/app/api/sync-jobs' \
-H "x-sync-secret: $SYNC_JOBS_SECRET"GET /api/get-job-offers returns:
{
"count": 0,
"hasOffers": false,
"offers": []
}When no company query is provided, it returns all offers.
When testing the real API response, you can filter jobs by company:
GET https://<your-cloud-domain>/app/api/get-job-offers?company=kaiko
Notes:
companymatches the optional Teamtailor custom field value (TEAMTAILOR_COMPANY_CUSTOM_FIELD_API_ID)- if
companyis omitted, the API returns every offer from Teamtailor (no company filter)
public/job-offers.js supports CMS-first careers pages and a legacy Teamtailor JSON mode.
-
Custom attribute
data-job-offers-cmson a parent section. -
Wrapper around the Collection list:
data-job-offers-list(or classjob_offers_list). -
Inside each collection item, on the element that shows the row number only:
data-job-position. Do not CMS-bind that element if the script should own the value. -
On the "Can't find an offer?" number element:
data-find-offer-index(value optional). -
Optional department tabs:
data-job-department-idanddata-job-department-nameon each row, plus.all_positions_wrapper+.all_positions_buttonin the Designer.
CMS mode does not call the API or hide the list.
Use this only when data-job-offers-cms is not set. Requires [data-job-offers-list] and [data-job-offer-template].
Expected markup:
<div data-job-offers-wrapper data-company="kaiko" style="display:none;">
<template data-job-offer-template>
<a data-job-offer-item data-job-link href="#">
<span data-job-position></span>
<span data-job-title></span>
<span data-job-salary></span>
</a>
</template>
<div data-job-offers-list></div>
</div>
<div data-job-offers-empty style="display:none;">
No open roles right now.
</div>Then include:
<script src="https://<your-cloud-domain>/job-offers.js"></script>