Add Small Pics image CDN and transforms to EmDash.
npm install emdash-smallpicsRegister the integration in astro.config.mjs.
A source baseUrl is required.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
baseUrl |
String | When sources is omitted |
None | Delivery URL for the shorthand source named default. |
sources |
Record of source configurations | No | Derived from baseUrl |
Named origins. When supplied, must be nonempty and replaces the shorthand. |
defaultSource |
String | For relative paths with multiple sources unless an image selects source |
Only configured source | Source name for relative paths. Absolute URLs use prefix matching. |
defaultParams |
Transform options | No | {} |
Global defaults applied before source and image parameters. |
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
baseUrl |
String | Yes | None | Absolute HTTP or HTTPS Small Pics delivery URL, optionally including a directory. |
originUrl |
String | No | None | Original URL prefix to match and remove before generating a delivery URL. |
secret |
String | When the Small Pics origin requires signing | None | Server-side signing secret. |
defaultParams |
Transform options | No | {} |
Source defaults applied after global defaults. |
transformSvgs |
Boolean | No | false |
If false, SVG requests receive passthrough=1, overriding a per-image false value. |
For signing or SVG settings, use an entry in sources, even with a single origin. secret and transformSvgs are source options, not top-level options.
import { defineConfig } from 'astro/config';
import smallpics from 'emdash-smallpics';
export default defineConfig({
integrations: [
smallpics({
sources: {
default: {
baseUrl: 'https://my-source.smallpics.io',
secret: process.env.SMALLPICS_SECRET,
transformSvgs: false,
},
},
defaultParams: { q: 65 },
}),
],
});Use source labels to select the source setup for an image. The label is only used by the integration and is not added to generated URLs.
See an example of selecting a source in Select a Source below.
import { defineConfig } from 'astro/config';
import smallpics from 'emdash-smallpics';
export default defineConfig({
integrations: [
smallpics({
defaultSource: 'productImages',
sources: {
productImages: {
baseUrl: 'https://product-images.smallpics.io',
secret: process.env.SMALLPICS_PRODUCTS_SECRET,
transformSvgs: false,
defaultParams: { q: 80 },
},
editorialImages: {
baseUrl: 'https://editorial-images.smallpics.io',
secret: process.env.SMALLPICS_EDITORIAL_SECRET,
transformSvgs: false,
},
},
}),
],
});Later values override earlier values.
- Global
defaultParams, sourcedefaultParams, native image properties, then per-imagesmallpicsparameters. - Native image dimensions take precedence so responsive descriptors match their generated variants.
transformSvgs: falseaddspassthrough=1for SVGs, overridingpassthrough: false.
Keep the site's existing EmDash database, storage, React integration, and runtime adapter. Add Small Pics after EmDash and set images: false in EmDash to disable its image endpoint wrapper.
This excerpt shows a Cloudflare site with existing D1 and R2 bindings:
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
import react from '@astrojs/react';
import emdash from 'emdash/astro';
import { d1, r2 } from '@emdash-cms/cloudflare';
import smallpics, { mediaStorageOptions } from 'emdash-smallpics';
const baseUrl = 'https://media.smallpics.io';
export default defineConfig({
output: 'server',
adapter: cloudflare(),
integrations: [
react(),
emdash({
database: d1({ binding: 'DB' }),
storage: r2(mediaStorageOptions(baseUrl, 'MEDIA')),
images: false,
}),
smallpics({ baseUrl }),
],
});Configure the matching image origin in Small Pics to read the same bucket and object paths that EmDash uploads. Setting publicUrl only changes generated URLs; it does not configure the origin or move files. Existing assets must remain available at their current paths. Use a separate delivery source for a different bucket.
The integration configures Astro's image service. Native Image, Picture, and getImage() calls generate Small Pics URLs.
The original must exist in the configured Small Pics origin. Imported local Astro assets are not uploaded automatically. Match existing origin URLs with a source's originUrl; unknown absolute URLs are rejected.
Register Small Pics after EmDash and set images: false in EmDash. This disables EmDash's image endpoint wrapper. Use the supplied image and rich-text components to forward saved focal points to Small Pics.
Native image properties are translated to Small Pics parameters:
| Image property | Small Pics parameter |
|---|---|
width |
w |
height |
h |
quality |
q |
format |
fm |
fit |
fit; cover becomes crop, fill becomes stretch, inside becomes max, outside becomes crop |
position |
Percentage coordinates in fp |
background |
bg |
The URL examples below assume the source has no default transform parameters.
---
import Image from 'emdash-smallpics/Image';
const { post } = Astro.props;
---
<Image
image={post.data.featured_image}
width={800}
height={600}
fit="cover"
quality={80}
alt="Bird"
/>The image's src uses the configured delivery domain:
https://my-source.smallpics.io/bird.jpg?fit=crop&h=600&q=80&w=800Select a source with source.
---
import Image from 'emdash-smallpics/Image';
const { post } = Astro.props;
---
<Image
image={post.data.featured_image}
source="editorialImages"
width={1200}
height={900}
alt="Bird"
/>With the multiple-source configuration above, this uses https://editorial-images.smallpics.io.
For format selection, see the note in Transform Options.1
---
import Image from 'emdash-smallpics/Image';
const { post } = Astro.props;
---
<Image
image={post.data.featured_image}
width={1200}
height={900}
format="avif"
alt="Bird"
/>Pass widths to generate responsive image variants.
---
import Image from 'emdash-smallpics/Image';
const { post } = Astro.props;
---
<Image
image={post.data.featured_image}
width={800}
height={600}
widths={[400, 800]}
fit="cover"
alt="Bird"
/>The generated srcset contains:
https://my-source.smallpics.io/bird.jpg?fit=crop&h=300&w=400 400w,
https://my-source.smallpics.io/bird.jpg?fit=crop&h=600&w=800 800wThe component also supplies a fallback src.
---
import PortableText from 'emdash-smallpics/PortableText';
const { post } = Astro.props;
---
<PortableText value={post.data.content} />The wrapper supplies image and gallery renderers. Custom components.type entries override them. Editors continue uploading and selecting images in EmDash.
All the transform options supported by the Small Pics transform API are supported by this integration. Take a look at the Small Pics docs for more detailed information about each parameter.
Transform options can use either the Small Pics URL param key or the option name used by smallpics-js. For example, q and quality are equivalent.
Pass these options through smallpics on an image or through global and source defaultParams.
Use a single value for options that accept a single argument:
({
w: 800,
q: 80,
});Use an array for options that accept multiple arguments:
({
crop: [400, 300, 10, 20],
ar: [16, 9],
border: [8, 'ffffff', 'expand'],
fit: 'crop',
fp: '50w:0h',
});| Query parameter | Plugin option name | Accepted values | Example |
|---|---|---|---|
or |
orientation |
0, 90, 180, 270, or auto |
or: 'auto' |
flip |
flip |
v, h, or both |
flip: 'h' |
crop |
crop |
Named anchor, entropy, face[,fallback], facesarea[,fallback], or [width, height, x, y] |
crop: [400, 300, 10, 20] |
w |
width |
Integer or decimal pixels, or relative dimensions | w: '65p' |
h |
height |
Integer or decimal pixels, or relative dimensions | h: '50w' |
ar |
aspectRatio |
width:height, decimal ratio, or [dividend, divisor] |
ar: '16:9' |
fit |
fit |
contain, max, fill, fill-max, stretch, or crop |
fit: 'crop' |
dpr |
devicePixelRatio |
Integer or decimal | dpr: 1.5 |
bri |
brightness |
Integer brightness | bri: 10 |
con |
contrast |
Integer contrast | con: 15 |
gam |
gamma |
Float gamma | gam: 1.2 |
sharp |
sharpen |
Integer sharpen amount | sharp: 20 |
blur |
blur |
Integer blur amount | blur: 5 |
pixel |
pixelate |
Integer pixelate amount | pixel: 8 |
filt |
filter |
grayscale or sepia |
filt: 'grayscale' |
mark |
watermarkPath |
Watermark image path | mark: '/watermark.png' |
markorigin |
watermarkOrigin |
Watermark origin name | markorigin: 'default' |
markw |
watermarkWidth |
Integer, decimal, or relative width | markw: '20w' |
markh |
watermarkHeight |
Integer, decimal, or relative height | markh: '20h' |
markfit |
watermarkFit |
contain, max, fill, fill-max, stretch, or crop |
markfit: 'contain' |
markfp |
watermarkFocalPoint |
Pixels, relative values, or x:y within the watermark |
markfp: '20p:20p' |
markzoom |
watermarkZoom |
Numeric zoom from 1 to 100 |
markzoom: 2 |
markpad |
watermarkPadding |
Pixels, relative values, or x:y |
markpad: '10:20' |
markpos |
watermarkPosition |
Named anchor, numeric coordinate, or pixel/relative x:y string |
markpos: '25p:50p' |
markalpha |
watermarkAlpha |
Integer alpha | markalpha: 80 |
bg |
background |
Background color | bg: 'ffffff' |
border |
border |
[width, color, method]; method is overlay, shrink, or expand |
border: [8, 'ffffff', 'expand'] |
trim |
trim |
auto, color, alpha, or either |
trim: 'color' |
trim-color |
trimColor |
Color to remove | 'trim-color': 'ffffff' |
trim-alpha |
trimAlpha |
Alpha threshold, 0 to 255 | 'trim-alpha': 8 |
trim-tol |
trimTolerance |
Tolerance, 0 to 255 | 'trim-tol': 8 |
trim-pad |
trimPadding |
Padding in pixels or relative values2 | 'trim-pad': 8 |
trim-md |
trimMeanDifference |
Auto mean difference, 0 to 255 | 'trim-md': 8 |
trim-sd |
trimStandardDeviation |
Auto standard deviation, 0 to 255 | 'trim-sd': 8 |
q |
quality |
Integer quality | q: 80 |
fm |
format |
strict, auto, jpg, jpeg, pjpg, png, gif, webp, avif, or jxl 1 |
fm: 'avif' |
interlace |
interlaced |
Boolean | interlace: true |
fp |
focalPoint |
Pixels or relative x/y | fp: '25w:75h' |
zoom |
zoom |
Numeric, face, facesarea, optional numeric fallback |
zoom: 'face,2.5' |
zoompad |
zoomPadding |
Pixels or relative x/y | zoompad: '10:20' |
face |
face |
One-based face index | face: 1 |
debug |
debug |
Boolean | debug: true |
passthrough |
passthrough |
Boolean; false removes the flag unless transformSvgs is false |
passthrough: true |
Dimensions accept decimal pixels and p, w, or h units.2 Paired values accept serialized x:y strings.
---
import { Image } from 'astro:assets';
---
<Image
src="bird.jpg"
width={800}
height={600}
alt="Bird"
smallpics={{
fit: 'crop',
crop: 'face,top',
zoom: 'face,2.5',
zoompad: '5p:10p',
}}
/>Use getImage() in Astro server code to access generated URLs and responsive variants.
import { getImage } from 'astro:assets';
const image = await getImage({
src: 'bird.jpg',
width: 800,
height: 600,
fit: 'cover',
widths: [400, 800],
});
const url = image.src;
const srcset = image.srcSet.attribute;getImage() returns the image URL, HTML attributes, and responsive variants.
const url = image.src;
const attributes = image.attributes;
const srcset = image.srcSet.attribute;
const variants = image.srcSet.values;| Property | Type / accepted values | Default | Description |
|---|---|---|---|
image |
Media value, string, null, undefined | Required | Field value or origin path. Null and undefined render nothing. |
width, height |
Positive numbers | Media dimensions | Override display dimensions. One override preserves the media aspect ratio. Both dimensions must resolve. |
widths |
Positive integer array | Generated candidates | Defaults include 320, 640, 960, 1280, 1600, 1920 plus display width and cap, capped at the smaller of twice display width and original width. |
sizes |
String | Display width above its breakpoint, otherwise 100vw |
Browser responsive-image sizing. |
source |
String | Integration source resolution | Selects a named delivery source. |
fit |
cover, contain, fill, inside, outside |
Inherited source/global fit | Maps to crop, contain, stretch, max, crop respectively. |
format |
avif, webp, jpg, png, gif |
Inherited format | Output format. Other native formats can be passed in smallpics. |
quality |
Number 0–100 | Inherited quality | Output quality. |
smallpics |
Transform options | Saved focal point when present | Explicit values override saved focal-point metadata and other transform defaults. |
alt |
String | Media alt or empty string | Explicit empty strings are preserved. |
darkVariant |
Media value, string, null | Media's dark variant | Alternate image for dark mode. Null falls back to the media value. |
priority |
Boolean | false |
Defaults to eager loading and high fetch priority when true. |
loading |
lazy, eager |
lazy, or eager with priority |
Explicit value overrides the priority default. |
decoding |
async, sync, auto |
async |
HTML decoding attribute. |
fetchpriority |
high, low, auto |
high with priority, otherwise omitted |
Explicit value overrides the priority default. |
id, class, other HTML attributes |
HTML attribute values | Omitted | Applied to each variant. Dark variant IDs receive a --dark suffix. |
| Field | Type | Resolution behavior |
|---|---|---|
meta.storageKey |
String | First choice for the source path. |
src |
String | Used when the storage key is absent or empty. |
url |
String | Used when both storage key and src are absent or empty. |
id |
String | Identifier only; never treated as a storage path. |
provider |
String | Absent or local supported; other providers rejected. |
width, height |
Numbers | Original dimensions used for sizing and responsive limits. |
alt |
String | Default alternative text. |
focalX, focalY |
Numbers 0–1 or null | Both coordinates must be present and valid, or both absent. Convert to percentage fp. |
darkVariant |
Media value or string | Optional alternate image. |
dominantColor |
String | Accepted in media values but not rendered by this adapter. |
| Field | Type / default | Behavior |
|---|---|---|
PortableText value |
EmDash rich-text value | Passed to EmDash PortableText. |
PortableText components.type |
Component map | Caller entries override the adapter's image and gallery renderers. Other PortableText props pass through. |
Gallery node.images |
Image-block array, required | Image blocks rendered in order. |
Gallery node.columns |
Integer 1–12, default 3 | Desktop columns; two columns at widths up to 640px. |
Gallery sizes |
String, optional | Defaults to 50vw below 640px, otherwise 100 / columns vw. |
Image block asset |
Media value with optional _ref |
Must contain a storage key or URL; _ref alone is insufficient. |
Image block alt, width, height, focalX, focalY |
Media metadata | Block values override asset metadata when present. |
Image block displayWidth, displayHeight |
Numbers, optional | Override rendered dimensions. |
Image block caption, title |
Strings, optional | Caption appears in figcaption; title passes to the image. |
Image block alignment |
left, center, right, wide, full |
Controls figure alignment. |
Image block link |
String or { href, blank? } |
Supports HTTP(S), root-relative paths, and fragments. Other protocols are omitted. |
| Export | Behavior |
|---|---|
| Default export | Reuses the Astro Small Pics integration and its source configuration |
resolveImage(image, options?) |
Resolves a field to a path, dimensions, alt text, and transform metadata |
imageBlockValue(node) |
Converts a rich-text reference or seeded media value into a field value |
focalPoint(x, y) |
Converts normalized coordinates to a Small Pics focal-point value |
mediaStorageOptions(baseUrl, binding?) |
Returns R2 storage configuration with a delivery URL; binding defaults to MEDIA |
The supplied components support EmDash's storage-backed media. External media-provider entries are rejected; retain the provider's component for those assets. Admin asset editing and inline editing use EmDash's own UI. The adapter does not rewrite all admin thumbnails, replace media storage, or purge Small Pics caches when an object is replaced. Prefer new object keys for changed originals or use the existing Small Pics cache invalidation workflow.
This version preserves alt text and captions but does not render EmDash blurhash placeholders. SVGs use the Astro integration's passthrough behavior.
transformSvgs defaults to false. When false, and the image is an SVG, Small Pics will proxy the SVG to the client without applying any transforms, so that it can be cached the same as other transformed images.
This setting takes precedence over passthrough: false in transform parameters.
const config = {
sources: {
default: {
baseUrl: 'https://images.example.com',
transformSvgs: false, // Default: adds passthrough=1.
},
},
};Footnotes
-
Format selection. Omitting
fmorformatusesstrict. Withfm=strict, a missing or catch-allAcceptheader (*/*orimage/*) preserves the source format. A specificAcceptheader selects the output format.fm=autoalways selects a format fromAccept, falling back to AVIF. GIF inputs use WebP in auto mode, which supports animation. ↩ ↩2 -
Relative values. Use a percentage from 0 to 100 followed by
wfor width,hfor height, orpfor the relevant axis. For example,5wmeans 5% of the base image's width, and35hmeans 35% of its height. ↩ ↩2