Import your LinkedIn data export into Astro content collections and render your profile, resume, CV, experience, and posts with drop-in components. Stop maintaining your developer portfolio in two places.
One-way: LinkedIn → Astro. LinkedIn stays the source of truth; your Astro site rebuilds from the exported data.
- Astro integration — auto-runs the sync when you start
astro dev/astro build. - CLI —
astro-linkedin-sync sync ./export.zipfor manual or CI runs. - Pre-built Astro components —
<Profile />,<Experience />,<Skills />,<Posts />, etc. - Zod schemas — for typed access via
getEntry()/getCollection(). - Smart re-sync — manual edits are detected and preserved;
locked: trueopts a file out forever.
npm install astro-linkedin-sync
npx astro-linkedin-sync initAdd the integration to astro.config.mjs:
import { defineConfig } from "astro/config";
import linkedinSync from "astro-linkedin-sync/integration";
export default defineConfig({
integrations: [linkedinSync()],
});Drop your LinkedIn data export ZIP into ./exports/, then:
npm run dev
# linkedin-sync: created: 9, unchanged: 2The components in src/pages/cv.astro (scaffolded by init) now render the synced data.
linkedin.com → Settings & Privacy → Data Privacy → "Get a copy of your data" → check Profile / Connections / Articles / Shares / Skills → wait 10–30 min for the email → save the ZIP into ./exports/.
Optional PDF supplement (used only to fill blanks the ZIP truncates):
linkedin.com/in/your-handle → More → Save to PDF → pass via pdfPath.
| LinkedIn file | Astro destination |
|---|---|
Profile.csv |
src/content/linkedin/profile.json |
Positions.csv |
src/content/linkedin/positions.json |
Education.csv |
src/content/linkedin/education.json |
Skills.csv |
src/content/linkedin/skills.json |
Certifications.csv |
src/content/linkedin/certifications.json |
Projects.csv |
src/content/linkedin/projects.json |
Languages.csv |
src/content/linkedin/languages.json |
Publications.csv |
src/content/linkedin/publications.json |
Honors.csv |
src/content/linkedin/honors.json |
Volunteering.csv |
src/content/linkedin/volunteer.json |
Shares.csv |
src/content/posts/*.md |
Articles/*.html |
src/content/articles/*.md |
All components live under astro-linkedin-sync/components/*.astro. Each auto-fetches its data from the default collection but can also receive data via prop.
---
import Profile from "astro-linkedin-sync/components/Profile.astro";
import Experience from "astro-linkedin-sync/components/Experience.astro";
import Education from "astro-linkedin-sync/components/Education.astro";
import Skills from "astro-linkedin-sync/components/Skills.astro";
import Certifications from "astro-linkedin-sync/components/Certifications.astro";
import Posts from "astro-linkedin-sync/components/Posts.astro";
import Articles from "astro-linkedin-sync/components/Articles.astro";
---
<Profile />
<Experience />
<Education />
<Skills limit={20} />
<Certifications />
<Posts limit={5} />
<Articles limit={3} hrefPrefix="/blog/" />Slot overrides — every section has a named slot for full markup control:
<Profile>
<header slot="header">
<h1>{firstName} – Senior Engineer</h1>
</header>
</Profile>
<Experience>
<Fragment slot="position" let:position>
<article class="my-card">
<strong>{position.title}</strong> at {position.company}
</article>
</Fragment>
</Experience>Pass data explicitly — bypass auto-fetch when you want full control:
---
import { getEntry } from "astro:content";
import Profile from "astro-linkedin-sync/components/Profile.astro";
const profile = (await getEntry("linkedin", "profile"))!.data;
---
<Profile data={profile} />Styling — components emit semantic class names (als-profile, als-experience__title, als-skills__tag, etc.) with zero opinionated CSS. Style them with Tailwind, your own CSS, or whatever you use.
linkedinSync({
zipPath: "./Basic_LinkedInDataExport.zip", // explicit path (overrides exportsDir)
exportsDir: "./exports", // scanned for newest .zip
pdfPath: "./Profile.pdf", // optional, fills blanks
outDir: "src/content/linkedin",
force: false, // overwrite manual edits + locked files
verbose: false, // log every file action
silentIfMissing: true // don't warn on production builds when ZIP is gitignored
})Each output file carries a _sync block (JSON) or sync frontmatter (Markdown):
source: linkedin
syncedAt: 2026-06-11T19:30:00.000Z
hash: 712b7e60f6a50144
locked: falseEach re-sync run lands every file in one of five states:
| State | When | Action |
|---|---|---|
created |
New file, nothing on disk yet | Write |
unchanged |
LinkedIn data didn't change since last sync | Skip |
updated |
LinkedIn data changed, no manual edit on disk | Overwrite |
skipped (locked) |
File has locked: true |
Skip |
skipped (manual edit) |
LinkedIn changed AND you edited the file too | Skip + warn |
So the workflow is:
- Edit on LinkedIn → next
astro dev/astro buildsyncs it. - Want to tweak a synced post or rewrite your summary? Set
locked: truein the file. It will never be overwritten, even on--force. - Got a "skipped (manual edit)" warning? Either delete the file (re-sync recreates from LinkedIn) or commit your edits + flip
locked: true.
astro-linkedin-sync init [--out src/content/linkedin]
Scaffold content config, example page, and exports directory.
astro-linkedin-sync sync <export.zip> [--pdf profile.pdf] [--out DIR] [--dry-run] [--force]
Parse and write/update content files manually.
astro-linkedin-sync status [--out DIR]
List every synced file with its lock state and last sync timestamp.
The integration is preferred for local dev; the CLI is preferred for CI.
import { sync, parseLinkedInExport } from "astro-linkedin-sync";
const report = await sync({
zipPath: "./export.zip",
outDir: "src/content/linkedin",
});
// Just parse, do something else with the data:
const parsed = await parseLinkedInExport({ zipPath: "./export.zip" });- Astro → LinkedIn (publishing) — LinkedIn API gates this behind Marketing Developer Platform partner approval.
- Company Pages — the data export is personal-only.
- Real-time sync — no public webhook from LinkedIn. Re-download the ZIP periodically (the integration picks up the newest automatically).
MIT