Sync Are.na channels into Sanity.
This monorepo provides:
- arena-sanity-core - framework-agnostic sync engine (Are.na to Sanity)
- arena-sanity-adapter-nuxt - Nuxt 3 API route for triggering syncs
- sanity-plugin-arena-sync - Sanity Studio dashboard plugin
The low-level sync engine.
- Syncs Are.na blocks into Sanity documents
- Handles retries, image uploads, drift-fix, idempotency
- Use directly in Node, serverless functions, or custom cron jobs
Nuxt 3 adapter.
- Exposes a
POST /api/syncendpoint - Reads config from
.envandruntimeConfig - Useful for cron jobs or manual triggers
Sanity Studio plugin.
- Adds an "Are.na Sync" tool with channel picker and block browser
- Bundles and auto-registers all schemas (
areNaBlock,arenaSyncConfig,arenaChannelSettings) - Structure builder for organized desk hierarchy (by type, by channel, orphans)
- Schemas are extensible — add your own custom fields
- Real-time config updates, manual sync trigger
npm install arena-sanity-core @sanity/client are.naExample Nuxt API route (server/api/sync.post.ts):
import {
syncArenaChannels,
createSanityClient,
createArenaClient,
} from "arena-sanity-core";
export default defineEventHandler(async (event) => {
const cfg = useRuntimeConfig();
const sanity = createSanityClient({
projectId: cfg.sanityProjectId,
dataset: cfg.sanityDataset,
token: cfg.sanityApiToken,
});
const arena = createArenaClient({
accessToken: cfg.arenaAccessToken,
});
const result = await syncArenaChannels({
arena,
sanity,
options: {
channels: process.env.ARENA_CHANNELS?.split(",") ?? [],
},
});
return result;
});SANITY_PROJECT_ID=xxx
SANITY_DATASET=production
SANITY_API_TOKEN=...
ARENA_ACCESS_TOKEN=...
ARENA_CHANNELS=my-channel-1,my-channel-2
SYNC_CRON_SECRET=optional-secret
curl -X POST "https://your-app.com/api/sync" \
-H "Authorization: Bearer $SYNC_CRON_SECRET"You can also run syncs directly from the command line using npx:
npx arena-sanity-core --channels my-channel| Option | Description |
|---|---|
-c, --channels <slugs> |
Comma-separated channel slugs (required) |
-i, --image-upload <mode> |
Image upload mode: off, auto, on (default: auto) |
-d, --dry-run |
Print what would happen without making changes |
-v, --verbose |
Show detailed progress logs |
-h, --help |
Show help message |
--version |
Show version |
# Sync a single channel
npx arena-sanity-core --channels my-channel
# Sync multiple channels without uploading images
npx arena-sanity-core -c channel-1,channel-2 -i off
# Verbose output to see detailed progress
npx arena-sanity-core -c my-channel -v
# Dry run to preview what would be synced
npx arena-sanity-core -c my-channel --dry-runARENA_ACCESS_TOKEN # Are.na API access token
SANITY_PROJECT_ID # Sanity project ID
SANITY_DATASET # Sanity dataset name
SANITY_TOKEN # Sanity API token with write access
If you use the Studio plugin (recommended), schemas are bundled and auto-registered:
import { arenaSyncPlugin } from "sanity-plugin-arena-sync";
plugins: [arenaSyncPlugin()]; // registers all three schemasTo extend schemas with custom fields, set schemas: false and spread the exports. See the plugin README.
Standalone schemas are also available in schemas/arena/ for projects not using the plugin:
arenaBlock.jsx— document schema for Are.na blocksarenaChannelSettings.js— per-channel settings (visibility)arenaSyncConfig.js— sync configuration singleton
The sync engine only updates fields prefixed with arena* and respects lockAll/lockImage flags.
| Mode | Behavior |
|---|---|
| off | Never uploads images (stores Are.na URLs only) |
| auto | Uploads if missing in Sanity (default) |
| on | Always uploads if changed |
Uploading to Sanity counts toward asset storage costs.
- Are.na API: ~60 requests/minute (retries and backoff are built in)
- Use
timeBudgetMsoption to set a soft timeout for long syncs
arena-sanity-sync/
├── packages/
│ ├── core/ # arena-sanity-core
│ ├── adapter-nuxt/ # arena-sanity-adapter-nuxt
│ └── sanity-plugin-arena-sync/ # Sanity Studio plugin
├── schemas/arena/ # Example Sanity schemas
├── examples/ # Example configurations
└── README.md
MIT - Bartek Pierscinski