From content strategy to code, JoyConf brings the Storyblok community together - Register Now!

Bulk update AI-generated meta fields

Storyblok is the first headless CMS that works for developers & marketers alike.

Storyblok's AI SEO App uses LLMs to generate the content of HTML and Open Graph (OG) <meta> tags to speed up the editorial process.

An invaluable SEO tool, the app scans a story's contents and helps editors generate matching meta fields with one click. While this works great for one-time tasks, there's currently no easy way to generate content programmatically or in bulk for all relevant stories.

The Node.js script presented in this tutorial solves this challenge using the Management API (MAPI).

hint:

You can find the code for this tutorial in a dedicated GitHub repository.

Once you configure and run the script, it will generate or update the predefined set of tags supported by the app using your preferred AI provider/model. This saves content editors even more time; they only need to check the output and revise where needed.

Setup and usage

The script processes all stories of a specific content type block that contain an AI SEO field. It uses a custom AI API request to generate optimized metadata, so make sure you have access to OpenAI, Anthropic, or another provider's API.

First, clone the accompanying repository and install the dependencies:

npm install

Then, rename .env.example to .env and provide the required credentials and variables:

.env
# Storyblok details
SB_MANAGEMENT_API_TOKEN=your_token_here
SB_SPACE_ID=12345

# Storyblok schema
SB_CONTENT_TYPE_BLOCK=page
SB_AI_SEO_FIELD=sb_ai_seo

# AI API credentials
AI_API_URL=https://api.example.com
AI_API_TOKEN=your_ai_token_here
AI_CUSTOM_PROMPT="Your_prompt_here"

hint:

Where to find the required details in Storyblok:

  • To generate a personal access token, open Spaces and select My account > Account settings. Select Personal access token and then Generate new token.
  • To get your space ID, select the space you work on and open Settings > Space.
  • To find the content type block, open the Block library, select the relevant block, select Config, and copy the Technical name.
  • To find the AI SEO field, repeat these steps, select the relevant field, and copy the Field name.

Finally, run a test with the --dry-run flag, then generate or update the SEO metadata of all relevant stories, and verify that all stories were updated successfully:

# Test (recommended first)
npm run update -- --dry-run

# Perform the update
npm run update

Command line arguments

You can override some values set as environment variables:

  • --space-id=VALUE: override the pre-configured space ID
  • --content-type-block=VALUE: override the pre-configured content type block
  • --ai-seo-field=VALUE: override the pre-configured AI SEO field name
# Example command with overrides
npm run update -- --space-id=12345 --content-type-block=blog_post --ai-seo-field=seo_metadata
warn:

It's recommended to use a testing space with a sample set of stories to ensure everything works as expected before running the script in production.

What the tool does

The bulk update tool comprises two files: index.js, which sets up the configuration, and bulk-update-ai-seo.js, which contains the API calls. Together, these perform the following actions:

  • Fetch all stories with a specified content type block using pagination
  • Generate AI SEO meta tags for all matching stories in the space
  • Skip system fields (starting with _), such as _uid and _editable, before updating
  • Preserve existing non-AI SEO field values
  • Rate limit with a 1-second delay between requests to avoid API throttling
  • Provide detailed progress and completion reports, and complete operation and error logs
hint:

Right now, the script updates all matching fields. Reach out to let us know if you would like an option to generate only empty fields.

The script generates content for specific tags while preserving existing values (or setting defaults) of non-AI fields:

Changed tags

Ignored tags

title

author

description

og:url

keywords

og:image

og:title

twitter:card

og:description

twitter:image

twitter:title

advanced:robots

twitter:description

advanced:charset


advanced:refresh


advanced:viewport


advanced:alternate


advanced:canonical


advanced:content-type

Customize the script

You can adapt various aspects of the script to match your needs:

Define an AI prompt

In .env, provide a value for AI_CUSTOM_PROMPT and instruct the LLM what to consider when it generates the SEO content.

Adjust the MAPI's parameters

The script uses a single MAPI client (this.client) for all operations. You can customize the following parameters:

  • Region settings: uncomment and set the region parameter
  • Content filtering: modify the filter_query in fetchStoriesByContentType
  • Pagination size: adjust the per_page parameter for different batch sizes
  • Filter system fields: modify the cleanContent method to set different field filtering rules
bulk-update-ai-seo.js
// ...

// 1. Management API client for all operations
this.client = new StoryblokClient({
	oauthToken: settings.token,
	// region: 'us'
});


// ...

/**
* 2. Fetch stories with the specified content type block that have the AI SEO field
*/
async fetchStoriesByContentType(page = 1, per_page = 25) {
	try {
		const response = await this.client.get(
			`spaces/${this.space_id}/stories`,
				{
					filter_query: {
						component: { in: this.content_type_block },
					},
					page,
					per_page,
					story_only: true,
				},
		);

// ...

/**
* 3. Clean the content object by removing specific fields
*/
cleanContent(content) {
	if (!content || typeof content !== 'object') {
		return content;
	}
	const cleanedContent = {};
	for (const [key, value] of Object.entries(content)) {
		// Skip fields that start with an underscore (such as _uid, _editable, etc.)
		if (key.startsWith('_')) {
			continue;
		}
		// Include all other fields
		cleanedContent[key] = value;
	}
	return cleanedContent;
}

// ...

Customize the AI API integration

Depending on the AI API you use, you may need to adapt the API call in the generateAiSeoContent method:

  • Request format: adjust the request body structure
  • Response parsing: modify how the AI response is parsed
  • Error handling: adapt error handling for your API's response format
bulk-update-ai-seo.js
// ...

// Make a request to the AI API. This is a generic structure for Anthropic models that you can adapt to other AI APIs
const aiResponse = await (
	await fetch(this.ai_api_url, {
		body: JSON.stringify({
			max_tokens: 1000,
			messages: [{ content: prompt, role: 'user' }],
			model: 'claude-opus-4-20250514',
		}),
		headers: {
			'anthropic-version': '2023-06-01',
			'Content-Type': 'application/json',
			'x-api-key': this.ai_api_token,
		},
		method: 'POST',
	})
).json();
if (!aiResponse) {
	throw new Error(
		`AI API request failed: ${aiResponse.status} ${aiResponse.statusText}`,
	);
}

// ...

Tweak the API calls as needed, and check the logs; the tool provides extensive reporting to inform and alert you to potential issues.

Takeaways

The Management API allows you to tap into the inner workings of Storyblok’s UI. And since this tool is a platform-agnostic Node.js script, you can adapt it to the frontend framework of your choice.

Experiment with different prompts and models, adjust the API calls, and modify the script to fit the requirments of your project and content team.

hint:

Alba Silvente Fuentes, Senior Frontend Engineer, created the tool as part of the Storyblok MAPI helpers. Discover more useful utilities in the MAPI helpers GitHub repository.

If you’ve already used the AI SEO App you know how much time it saves; now you can boost your content team’s joy and productivity, letting them focus on the stories they want to tell.