diff --git a/README.md b/README.md
index a33e9026d..fbb9b6abf 100644
--- a/README.md
+++ b/README.md
@@ -90,18 +90,24 @@ Thank you to all the people who contributed to Lucide!
-### Hero backers 🦸
+[//]: <> (Sponsor tiers start)
-
+### Hero Backer 🦸
+
+
+
### Awesome backers 🍺
-
-
+
+
-### Backers ☕
+### Backer ☕️
-
+
+
+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/docs/.vitepress/theme/components/overrides/VPFooter.vue b/docs/.vitepress/theme/components/overrides/VPFooter.vue
index cbdee3b5f..d102c151b 100644
--- a/docs/.vitepress/theme/components/overrides/VPFooter.vue
+++ b/docs/.vitepress/theme/components/overrides/VPFooter.vue
@@ -54,16 +54,6 @@ const links = computed(() => [
width="200"
/>
-
-
-
-

-### Hero backers 🦸
-

+[//]: <> (Sponsor tiers start)
+
+### Hero Backer 🦸
+
+

+

### Awesome backers 🍺
-

-

+

+

-### Backers ☕
+### Backer ☕️
-

+

+

+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/packages/astro/README.md b/packages/astro/README.md
index 8898336f1..9c1a4f7f6 100644
--- a/packages/astro/README.md
+++ b/packages/astro/README.md
@@ -68,20 +68,26 @@ Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/licen

-

-### Hero backers 🦸
-

+[//]: <> (Sponsor tiers start)
+
+### Hero Backer 🦸
+
+

+

### Awesome backers 🍺
-

-

+

+

-### Backers ☕
+### Backer ☕️
-

+

+

+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/packages/icons/README.md b/packages/icons/README.md
index 1cd8df31d..6f336bc6e 100644
--- a/packages/icons/README.md
+++ b/packages/icons/README.md
@@ -76,20 +76,26 @@ Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/licen

-

-### Hero backers 🦸
-

+[//]: <> (Sponsor tiers start)
+
+### Hero Backer 🦸
+
+

+

### Awesome backers 🍺
-

-

+

+

-### Backers ☕
+### Backer ☕️
-

+

+

+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/packages/lucide-preact/README.md b/packages/lucide-preact/README.md
index fda48ad2b..0ba6fc6b4 100644
--- a/packages/lucide-preact/README.md
+++ b/packages/lucide-preact/README.md
@@ -67,20 +67,26 @@ Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/licen

-

-### Hero backers 🦸
-

+[//]: <> (Sponsor tiers start)
+
+### Hero Backer 🦸
+
+

+

### Awesome backers 🍺
-

-

+

+

-### Backers ☕
+### Backer ☕️
-

+

+

+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/packages/lucide-react-native/README.md b/packages/lucide-react-native/README.md
index c5a9034cb..a4acea999 100644
--- a/packages/lucide-react-native/README.md
+++ b/packages/lucide-react-native/README.md
@@ -67,20 +67,26 @@ Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/licen

-

-### Hero backers 🦸
-

+[//]: <> (Sponsor tiers start)
+
+### Hero Backer 🦸
+
+

+

### Awesome backers 🍺
-

-

+

+

-### Backers ☕
+### Backer ☕️
-

+

+

+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/packages/lucide-react/README.md b/packages/lucide-react/README.md
index a034bcc0f..09410e96f 100644
--- a/packages/lucide-react/README.md
+++ b/packages/lucide-react/README.md
@@ -67,20 +67,26 @@ Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/licen

-

-### Hero backers 🦸
-

+[//]: <> (Sponsor tiers start)
+
+### Hero Backer 🦸
+
+

+

### Awesome backers 🍺
-

-

+

+

-### Backers ☕
+### Backer ☕️
-

+

+

+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/packages/lucide-solid/README.md b/packages/lucide-solid/README.md
index b006549df..5ced5f68e 100644
--- a/packages/lucide-solid/README.md
+++ b/packages/lucide-solid/README.md
@@ -67,20 +67,26 @@ Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/licen

-

-### Hero backers 🦸
-

+[//]: <> (Sponsor tiers start)
+
+### Hero Backer 🦸
+
+

+

### Awesome backers 🍺
-

-

+

+

-### Backers ☕
+### Backer ☕️
-

+

+

+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/packages/lucide-static/README.md b/packages/lucide-static/README.md
index a13c57f3e..47f3233ce 100644
--- a/packages/lucide-static/README.md
+++ b/packages/lucide-static/README.md
@@ -81,20 +81,26 @@ Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/licen

-

-### Hero backers 🦸
-

+[//]: <> (Sponsor tiers start)
+
+### Hero Backer 🦸
+
+

+

### Awesome backers 🍺
-

-

+

+

-### Backers ☕
+### Backer ☕️
-

+

+

+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/packages/lucide/README.md b/packages/lucide/README.md
index 0e3bb9433..ca38e4352 100644
--- a/packages/lucide/README.md
+++ b/packages/lucide/README.md
@@ -77,20 +77,26 @@ Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/licen

-

-### Hero backers 🦸
-

+[//]: <> (Sponsor tiers start)
+
+### Hero Backer 🦸
+
+

+

### Awesome backers 🍺
-

-

+

+

-### Backers ☕
+### Backer ☕️
-

+

+

+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/packages/solid/README.md b/packages/solid/README.md
index 242f974b8..3094db0d9 100644
--- a/packages/solid/README.md
+++ b/packages/solid/README.md
@@ -69,20 +69,26 @@ Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/licen

-

-### Hero backers 🦸
-

+[//]: <> (Sponsor tiers start)
+
+### Hero Backer 🦸
+
+

+

### Awesome backers 🍺
-

-

+

+

-### Backers ☕
+### Backer ☕️
-

+

+

+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/packages/svelte/README.md b/packages/svelte/README.md
index 316ee6efc..7eb810548 100644
--- a/packages/svelte/README.md
+++ b/packages/svelte/README.md
@@ -69,20 +69,26 @@ Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/licen

-

-### Hero backers 🦸
-

+[//]: <> (Sponsor tiers start)
+
+### Hero Backer 🦸
+
+

+

### Awesome backers 🍺
-

-

+

+

-### Backers ☕
+### Backer ☕️
-

+

+

+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/packages/vue/README.md b/packages/vue/README.md
index fa5f6c610..13ca60186 100644
--- a/packages/vue/README.md
+++ b/packages/vue/README.md
@@ -67,20 +67,26 @@ Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/licen

-

-### Hero backers 🦸
-

+[//]: <> (Sponsor tiers start)
+
+### Hero Backer 🦸
+
+

+

### Awesome backers 🍺
-

-

+

+

-### Backers ☕
+### Backer ☕️
-

+

+

+
+[//]: <> (Sponsor tiers end)
### Other contributors 💸
diff --git a/scripts/updateSponsors.mts b/scripts/updateSponsors.mts
index 816fa109c..7cdb59fe7 100644
--- a/scripts/updateSponsors.mts
+++ b/scripts/updateSponsors.mts
@@ -1,10 +1,193 @@
-import { readdirSync } from 'node:fs';
+import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
-import { existsSync, readFileSync, writeFileSync } from 'fs';
-function updateSponsors() {
- const packagesPath = join(import.meta.dirname, '../packages');
- const mainReadme = readFileSync(join(import.meta.dirname, '../README.md'), 'utf8');
+const COLLECTIVE_SLUG = 'lucide-icons';
+const OPEN_COLLECTIVE_API = 'https://api.opencollective.com/graphql/v2';
+
+const rootPath = join(import.meta.dirname, '..');
+const mainReadmePath = join(rootPath, 'README.md');
+const sponsorLogosPath = join(rootPath, 'docs/public/sponsors');
+
+const tiersStartMarker = '[//]: <> (Sponsor tiers start)';
+const tiersEndMarker = '[//]: <> (Sponsor tiers end)';
+
+/**
+ * Headings for the Open Collective tiers, keyed by tier slug.
+ * Tiers that are not listed here fall back to their Open Collective name.
+ */
+const TIER_TITLES: Record
= {
+'hero-backer': 'Hero Backer 🦸',
+ 'awesome-backer': 'Awesome backers 🍺',
+ backer: 'Backer ☕️',
+};
+
+interface Account {
+ slug: string;
+ name: string;
+ website: string | null;
+ imageUrl: string | null;
+ description: string | null;
+}
+
+interface Tier {
+ slug: string;
+ name: string;
+ amount: { valueInCents: number | null } | null;
+}
+
+interface Order {
+ tier: Pick | null;
+ fromAccount: Account;
+}
+
+interface CollectiveResponse {
+ data?: {
+ account: {
+ tiers: { nodes: Tier[] };
+ orders: { nodes: Order[] };
+ } | null;
+ };
+ errors?: { message: string }[];
+}
+
+const query = /* GraphQL */ `
+ query Sponsors($slug: String!) {
+ account(slug: $slug) {
+ ... on AccountWithContributions {
+ tiers(limit: 100) {
+ nodes {
+ slug
+ name
+ amount {
+ valueInCents
+ }
+ }
+ }
+ }
+ orders(filter: INCOMING, status: [ACTIVE], limit: 1000) {
+ nodes {
+ tier {
+ slug
+ }
+ fromAccount {
+ slug
+ name
+ website
+ imageUrl
+ description
+ }
+ }
+ }
+ }
+ }
+`;
+
+async function fetchCollective() {
+ const headers: Record = { 'Content-Type': 'application/json' };
+ if (process.env.OPENCOLLECTIVE_TOKEN) {
+ headers['Personal-Token'] = process.env.OPENCOLLECTIVE_TOKEN;
+ }
+
+ const response = await fetch(OPEN_COLLECTIVE_API, {
+ method: 'POST',
+ headers,
+ body: JSON.stringify({ query, variables: { slug: COLLECTIVE_SLUG } }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Open Collective API responded with ${response.status} ${response.statusText}`);
+ }
+
+ const { data, errors } = (await response.json()) as CollectiveResponse;
+ if (errors?.length) {
+ throw new Error(`Open Collective API errors: ${errors.map(({ message }) => message).join(', ')}`);
+ }
+ if (!data?.account) {
+ throw new Error(`Could not find Open Collective account "${COLLECTIVE_SLUG}"`);
+ }
+
+ return data.account;
+}
+
+const escapeHtml = (value: string) =>
+ value
+ .replaceAll('&', '&')
+ .replaceAll('"', '"')
+ .replaceAll('<', '<')
+ .replaceAll('>', '>');
+
+function getLogo({ slug, imageUrl }: Account) {
+ const localLogo = ['svg', 'png'].find((extension) =>
+ existsSync(join(sponsorLogosPath, `${slug}.${extension}`)),
+ );
+ if (localLogo) return `docs/public/sponsors/${slug}.${localLogo}`;
+
+ return imageUrl ?? `https://images.opencollective.com/${slug}/logo/256.png`;
+}
+
+function renderSponsor(account: Account) {
+ const href = account.website || `https://opencollective.com/${account.slug}`;
+ const alt = account.description ? `${account.name} – ${account.description}` : account.name;
+
+ return `
`;
+}
+
+function renderTiers({ tiers, orders }: Awaited>) {
+ const sponsorsByTier = new Map>();
+
+ orders.nodes.forEach(({ tier, fromAccount }) => {
+ if (!tier) return;
+ if (!sponsorsByTier.has(tier.slug)) sponsorsByTier.set(tier.slug, new Map());
+ sponsorsByTier.get(tier.slug)!.set(fromAccount.slug, fromAccount);
+ });
+
+ return [...tiers.nodes]
+ .sort((a, b) => (b.amount?.valueInCents ?? 0) - (a.amount?.valueInCents ?? 0))
+ .flatMap((tier) => {
+ const sponsors = [...(sponsorsByTier.get(tier.slug)?.values() ?? [])];
+ if (!sponsors.length) return [];
+
+ if (!TIER_TITLES[tier.slug]) {
+ console.warn(`No title configured for tier "${tier.slug}", using "${tier.name}"`);
+ }
+
+ const sponsorLinks = sponsors
+ .sort((a, b) => a.name.localeCompare(b.name))
+ .map(renderSponsor)
+ .join('\n');
+
+ return `### ${TIER_TITLES[tier.slug] ?? tier.name}\n\n${sponsorLinks}`;
+ })
+ .join('\n\n');
+}
+
+async function updateReadmeTiers() {
+ const mainReadme = readFileSync(mainReadmePath, 'utf8');
+ const startIndex = mainReadme.indexOf(tiersStartMarker);
+ const endIndex = mainReadme.indexOf(tiersEndMarker);
+
+ if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
+ throw new Error(`Could not find "${tiersStartMarker}" and "${tiersEndMarker}" in README.md`);
+ }
+
+ const tiersMarkup = renderTiers(await fetchCollective());
+
+ writeFileSync(
+ mainReadmePath,
+ [
+ mainReadme.slice(0, startIndex + tiersStartMarker.length),
+ tiersMarkup,
+ mainReadme.slice(endIndex),
+ ]
+ .filter(Boolean)
+ .join('\n\n'),
+ );
+ console.log(`Updated sponsor tiers in ${mainReadmePath}`);
+}
+
+function updatePackageSponsors() {
+ const packagesPath = join(rootPath, 'packages');
+ const mainReadme = readFileSync(mainReadmePath, 'utf8');
const sponsorsRegex = /(\[\/\/]: <> \(Sponsors\).+)$/s;
const sponsorsBlock = sponsorsRegex.exec(mainReadme);
if (sponsorsBlock?.[1]) {
@@ -31,4 +214,7 @@ function updateSponsors() {
}
}
-updateSponsors();
+if (!process.argv.includes('--skip-fetch')) {
+ await updateReadmeTiers();
+}
+updatePackageSponsors();