Building and Using Astro Integrations
How Astro integrations work under the hood: using official integrations, configuring MDX and Tailwind, and building your own custom integration with lifecycle hooks.
What you'll learn
- ✓How the Astro integration API works with lifecycle hooks
- ✓How to configure official integrations: MDX, Tailwind, Sitemap
- ✓How to build a custom integration from scratch
- ✓How to modify the Vite config and inject scripts through integrations
- ✓Best practices for publishing and sharing integrations
Prerequisites
- •Astro project setup
- •Basic understanding of build tools and Vite
What Integrations Actually Do
An Astro integration is a JavaScript object with a name and a set of lifecycle hooks. When Astro builds your site, it calls these hooks at specific points: before the build starts, when the Vite config is being assembled, after pages are rendered, when the dev server starts. Integrations use these hooks to add functionality: MDX support, CSS frameworks, image optimization, analytics scripts.
astro:config:setup → astro:config:done → astro:server:setup → astro:build:start → astro:build:done
(modify config) (config finalized) (dev server) (build begins) (build complete) Using Official Integrations
Adding MDX Support
npx astro add mdx
This installs @astrojs/mdx and adds it to your config. MDX lets you use components inside Markdown:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
export default defineConfig({
integrations: [
mdx({
// Customize remark/rehype plugins
remarkPlugins: [],
rehypePlugins: [],
// Enable GitHub Flavored Markdown
gfm: true,
}),
],
});
Now .mdx files in your content collections can import and use components:
---
title: "My Post"
---
import Callout from '@/components/Callout.astro';
## Introduction
Here is a callout:
<Callout type="warning">
This is important information.
</Callout>
Adding Tailwind CSS
npx astro add tailwind
// astro.config.mjs
import tailwind from '@astrojs/tailwind';
export default defineConfig({
integrations: [
tailwind({
// Apply Tailwind's base styles
applyBaseStyles: true,
// Custom config path
configFile: './tailwind.config.mjs',
}),
],
});
// tailwind.config.mjs
export default {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
extend: {
colors: {
brand: {
50: '#eff6ff',
500: '#3b82f6',
900: '#1e3a5f',
},
},
},
},
};
Adding a Sitemap
npx astro add sitemap
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://example.com',
integrations: [
sitemap({
filter: (page) => !page.includes('/admin/'),
changefreq: 'weekly',
lastmod: new Date(),
}),
],
});
Adding React (or Vue, Svelte, Solid)
npx astro add react
import react from '@astrojs/react';
export default defineConfig({
integrations: [
react({
// Include specific file patterns
include: ['**/react/*'],
}),
],
});
Building a Custom Integration
The Integration Shape
Every integration is an object with a name and hooks:
// src/integrations/my-integration.ts
import type { AstroIntegration } from 'astro';
export default function myIntegration(options?: { debug?: boolean }): AstroIntegration {
return {
name: 'my-integration',
hooks: {
'astro:config:setup': ({ config, updateConfig, addMiddleware, logger }) => {
logger.info('Setting up my integration');
},
'astro:config:done': ({ config }) => {
// Config is finalized, read-only
},
'astro:build:done': ({ dir, pages, logger }) => {
logger.info(`Built ${pages.length} pages to ${dir}`);
},
},
};
}
Use it in your config:
import myIntegration from './src/integrations/my-integration';
export default defineConfig({
integrations: [myIntegration({ debug: true })],
});
Example: Auto-Import Global Styles
// src/integrations/global-styles.ts
import type { AstroIntegration } from 'astro';
export default function globalStyles(stylePaths: string[]): AstroIntegration {
return {
name: 'global-styles',
hooks: {
'astro:config:setup': ({ injectScript }) => {
const imports = stylePaths
.map((path) => `import '${path}';`)
.join('\n');
injectScript('page-ssr', imports);
},
},
};
}
// astro.config.mjs
import globalStyles from './src/integrations/global-styles';
export default defineConfig({
integrations: [
globalStyles([
'./src/styles/reset.css',
'./src/styles/typography.css',
]),
],
});
Example: Build-Time Analytics
Generate a build report showing page counts, sizes, and build time:
// src/integrations/build-report.ts
import type { AstroIntegration } from 'astro';
import fs from 'node:fs';
import path from 'node:path';
export default function buildReport(): AstroIntegration {
let buildStart: number;
return {
name: 'build-report',
hooks: {
'astro:build:start': ({ logger }) => {
buildStart = performance.now();
logger.info('Build started');
},
'astro:build:done': async ({ dir, pages, logger }) => {
const duration = Math.round(performance.now() - buildStart);
const outputDir = dir.pathname;
// Calculate total output size
let totalSize = 0;
const walkDir = (dirPath: string) => {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
walkDir(fullPath);
} else {
totalSize += fs.statSync(fullPath).size;
}
}
};
walkDir(outputDir);
const report = {
pages: pages.length,
totalSizeKB: Math.round(totalSize / 1024),
buildTimeMs: duration,
timestamp: new Date().toISOString(),
};
// Write report to dist
fs.writeFileSync(
path.join(outputDir, 'build-report.json'),
JSON.stringify(report, null, 2),
);
logger.info(`Build complete: ${report.pages} pages, ${report.totalSizeKB}KB, ${duration}ms`);
},
},
};
}
Example: Inject Analytics Script
// src/integrations/analytics.ts
import type { AstroIntegration } from 'astro';
export default function analytics(trackingId: string): AstroIntegration {
return {
name: 'analytics',
hooks: {
'astro:config:setup': ({ injectScript }) => {
injectScript(
'page',
`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${trackingId}');
`,
);
},
},
};
}
Example: Modify Vite Config
// src/integrations/custom-aliases.ts
import type { AstroIntegration } from 'astro';
import path from 'node:path';
export default function customAliases(): AstroIntegration {
return {
name: 'custom-aliases',
hooks: {
'astro:config:setup': ({ updateConfig }) => {
updateConfig({
vite: {
resolve: {
alias: {
'@components': path.resolve('./src/components'),
'@layouts': path.resolve('./src/layouts'),
'@utils': path.resolve('./src/lib'),
},
},
},
});
},
},
};
}
Example: Add Middleware from an Integration
// src/integrations/security-headers.ts
import type { AstroIntegration } from 'astro';
export default function securityHeaders(): AstroIntegration {
return {
name: 'security-headers',
hooks: {
'astro:config:setup': ({ addMiddleware }) => {
addMiddleware({
entrypoint: './src/integrations/security-headers-middleware.ts',
order: 'pre',
});
},
},
};
}
// src/integrations/security-headers-middleware.ts
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware(async (context, next) => {
const response = await next();
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set(
'Permissions-Policy',
'camera=(), microphone=(), geolocation=()',
);
return response;
});
The Complete Hook Reference
| Hook | When | Common uses |
|---|---|---|
astro:config:setup | Config is being assembled | Modify config, add renderers, inject scripts, add middleware |
astro:config:done | Config is finalized | Read final config, validate settings |
astro:server:setup | Dev server is created | Add custom middleware to the dev server |
astro:server:start | Dev server has started | Log the dev URL, run setup tasks |
astro:server:done | Dev server is closing | Cleanup resources |
astro:build:start | Build begins | Start timers, validate pre-conditions |
astro:build:setup | Build is being configured | Modify the Vite build config |
astro:build:generated | Static routes are generated | Post-process static HTML |
astro:build:done | Build is complete | Generate sitemaps, reports, clean up |
Integration Ordering
Integrations run in the order they appear in the integrations array. This matters when one integration depends on another:
export default defineConfig({
integrations: [
// Tailwind first — it modifies PostCSS config
tailwind(),
// MDX second — it processes .mdx files
mdx(),
// React third — it adds the React renderer
react(),
// Sitemap last — it reads the final page list
sitemap(),
// Custom integrations after official ones
buildReport(),
],
});
Publishing an Integration
If you want to share your integration as an npm package:
{
"name": "astro-my-integration",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"keywords": ["astro-integration", "withastro"],
"peerDependencies": {
"astro": "^5.0.0"
}
}
The astro-integration and withastro keywords make your package discoverable on the Astro integrations page.
Summary
Integrations are Astro’s extension mechanism. Official integrations add MDX, Tailwind, React, sitemaps, and more with a single command. Custom integrations use lifecycle hooks to modify the build pipeline: inject scripts, add middleware, modify Vite config, generate reports, and post-process output. The API is small — a name and a handful of hooks — but it covers every point where you might want to customize how Astro builds your site.
Related articles
- Astro Advanced Content Collections in Astro
Go beyond basic content collections: schema references between collections, computed fields, custom loaders, advanced querying patterns, and full type safety across your Astro project.
- Astro Mastering View Transitions in Astro
Deep dive into Astro View Transitions: custom animations, transition groups, lifecycle events, fallback behavior, and production patterns for smooth page navigation.
- Astro Astro Integrations and Adapters: An Overview
Understand the difference between Astro integrations and adapters, when to reach for each, and how they shape your deployment pipeline.
- Tailwind Tailwind CSS Plugins and Presets
Learn to write custom Tailwind plugins, use presets for shared configurations, and extend Tailwind's utility system with your own design primitives.