@googleforcreators/elements package provides core APIs for registering and managing element types in Web Stories. It includes utilities for creating elements, pages, and working with the element type system.
Installation
npm install @googleforcreators/elements
Element Types
Web Stories supports several built-in element types:import { ELEMENT_TYPES } from '@googleforcreators/elements';
console.log(ELEMENT_TYPES.IMAGE); // 'image'
console.log(ELEMENT_TYPES.VIDEO); // 'video'
console.log(ELEMENT_TYPES.TEXT); // 'text'
console.log(ELEMENT_TYPES.SHAPE); // 'shape'
console.log(ELEMENT_TYPES.GIF); // 'gif'
console.log(ELEMENT_TYPES.STICKER); // 'sticker'
console.log(ELEMENT_TYPES.PRODUCT); // 'product'
console.log(ELEMENT_TYPES.AUDIO_STICKER); // 'audioSticker'
Media Element Types
import { MEDIA_ELEMENT_TYPES } from '@googleforcreators/elements';
// Returns: ['image', 'video', 'gif', 'product']
const isMediaElement = MEDIA_ELEMENT_TYPES.includes(element.type);
Element Registration
Registering Element Types
import { registerElementType } from '@googleforcreators/elements';
import type { ElementDefinition } from '@googleforcreators/elements';
const customElementDefinition: ElementDefinition = {
type: 'customElement',
defaultAttributes: {
width: 100,
height: 100,
},
// Additional properties
};
registerElementType(customElementDefinition);
Getting Registered Element Types
import { elementTypes } from '@googleforcreators/elements';
// Access all registered element types
const imageElementDef = elementTypes['image'];
const textElementDef = elementTypes['text'];
Utilities
Creating Elements
- createNewElement
- duplicateElement
import { createNewElement, ELEMENT_TYPES } from '@googleforcreators/elements';
// Create a new text element
const textElement = createNewElement(ELEMENT_TYPES.TEXT, {
x: 50,
y: 100,
width: 200,
height: 50,
content: 'Hello World',
font: {
family: 'Roboto',
},
});
// Create a new image element
const imageElement = createNewElement(ELEMENT_TYPES.IMAGE, {
x: 0,
y: 0,
width: 640,
height: 853,
resource: {
type: 'image',
src: 'https://example.com/image.jpg',
width: 1920,
height: 1080,
},
});
import { duplicateElement } from '@googleforcreators/elements';
const originalElement = {
id: '123',
type: 'text',
x: 50,
y: 100,
content: 'Original',
};
const duplicated = duplicateElement(originalElement);
// Returns new element with:
// - New unique ID
// - Same properties as original
// - Slightly offset position
Working with Pages
- createPage
- duplicatePage
import { createPage, ELEMENT_TYPES } from '@googleforcreators/elements';
// Create a new blank page
const page = createPage();
console.log(page);
// {
// id: 'unique-id',
// elements: [],
// backgroundColor: { color: { r: 255, g: 255, b: 255 } },
// // ... other default properties
// }
// Create page with custom properties
const customPage = createPage({
backgroundColor: {
type: 'solid',
color: { r: 0, g: 0, b: 0 },
},
});
import { duplicatePage } from '@googleforcreators/elements';
const originalPage = {
id: 'page-1',
elements: [
{ id: 'el-1', type: 'text', content: 'Hello' },
{ id: 'el-2', type: 'image', src: 'image.jpg' },
],
backgroundColor: { color: { r: 255, g: 255, b: 255 } },
};
const duplicated = duplicatePage(originalPage);
// Returns new page with:
// - New page ID
// - All elements duplicated with new IDs
// - Same styling and properties
Element Utilities
- getDefinitionForType
- elementIs
- getLayerName
import { getDefinitionForType, ELEMENT_TYPES } from '@googleforcreators/elements';
const imageDefinition = getDefinitionForType(ELEMENT_TYPES.IMAGE);
const textDefinition = getDefinitionForType(ELEMENT_TYPES.TEXT);
// Use definition to understand element capabilities
console.log(imageDefinition.defaultAttributes);
import { elementIs, ELEMENT_TYPES } from '@googleforcreators/elements';
const element = {
id: '123',
type: 'image',
// ... other properties
};
if (elementIs.image(element)) {
console.log('This is an image element');
// TypeScript now knows element is an ImageElement
}
if (elementIs.text(element)) {
// This won't execute
}
if (elementIs.media(element)) {
console.log('This is a media element (image, video, gif, or product)');
}
// Available checkers:
// elementIs.image(el)
// elementIs.video(el)
// elementIs.text(el)
// elementIs.shape(el)
// elementIs.gif(el)
// elementIs.sticker(el)
// elementIs.product(el)
// elementIs.media(el)
import { getLayerName, ELEMENT_TYPES } from '@googleforcreators/elements';
const element = {
id: '123',
type: ELEMENT_TYPES.IMAGE,
resource: {
alt: 'Product Photo',
},
};
const layerName = getLayerName(element);
console.log(layerName); // 'Product Photo' or default based on type
Position & Transform
- getOffsetCoordinates
- getTransformFlip
- isElementBelowLimit
import { getOffsetCoordinates } from '@googleforcreators/elements';
const element = {
x: 100,
y: 200,
width: 150,
height: 100,
rotationAngle: 45,
};
const offset = getOffsetCoordinates(element);
console.log(offset);
// Returns calculated offset based on rotation and position
import { getTransformFlip } from '@googleforcreators/elements';
const element = {
flip: {
horizontal: true,
vertical: false,
},
};
const transform = getTransformFlip(element);
console.log(transform);
// Returns CSS transform for flipping: 'scaleX(-1)'
import { isElementBelowLimit } from '@googleforcreators/elements';
const element = {
y: 100,
height: 50,
};
const pageHeight = 853;
const isBelowFold = isElementBelowLimit(element, pageHeight / 2);
if (isBelowFold) {
console.log('Element is below the halfway point');
}
Constants
Default Background Color
import { DEFAULT_PAGE_BACKGROUND_COLOR } from '@googleforcreators/elements';
const page = {
backgroundColor: DEFAULT_PAGE_BACKGROUND_COLOR,
// { type: 'solid', color: { r: 255, g: 255, b: 255 } }
};
Text Element Defaults
import {
TEXT_ELEMENT_DEFAULT_FONT,
BACKGROUND_TEXT_MODE
} from '@googleforcreators/elements';
// Default font for text elements
const textElement = {
type: 'text',
font: TEXT_ELEMENT_DEFAULT_FONT,
// {
// family: 'Roboto',
// weights: [100, 300, 400, 500, 700, 900],
// service: 'fonts.google.com',
// ... font metrics
// }
};
// Background text modes
const modes = {
none: BACKGROUND_TEXT_MODE.NONE, // 'NONE'
fill: BACKGROUND_TEXT_MODE.FILL, // 'FILL'
highlight: BACKGROUND_TEXT_MODE.HIGHLIGHT, // 'HIGHLIGHT'
};
Overlay Types
import { OverlayType } from '@googleforcreators/elements';
const overlays = {
none: OverlayType.NONE, // 'none'
solid: OverlayType.SOLID, // 'solid'
linear: OverlayType.LINEAR, // 'linear'
radial: OverlayType.RADIAL, // 'radial'
};
const element = {
type: 'image',
overlay: {
type: OverlayType.LINEAR,
// ... overlay properties
},
};
Multiple Value Constant
import { MULTIPLE_VALUE } from '@googleforcreators/elements';
// Used when multiple elements have different values for a property
const fontFamily = selectedElements.every(
el => el.font?.family === selectedElements[0].font?.family
) ? selectedElements[0].font?.family : MULTIPLE_VALUE;
if (fontFamily === MULTIPLE_VALUE) {
console.log('Multiple fonts selected');
}
TypeScript Types
The package exports comprehensive TypeScript types:import type {
Element,
Page,
Story,
ElementType,
ElementDefinition,
ImageElement,
TextElement,
VideoElement,
ShapeElement,
GifElement,
StickerElement,
ProductElement,
Media,
Resource,
Taxonomies,
} from '@googleforcreators/elements';
// Example usage
function processElement(element: Element) {
if (elementIs.image(element)) {
// TypeScript knows element is ImageElement
console.log(element.resource.src);
}
}
function processPage(page: Page) {
page.elements.forEach(element => {
console.log(`Element ${element.id} at (${element.x}, ${element.y})`);
});
}
interface StoryData extends Story {
customField?: string;
}
Complete Example
import {
createPage,
createNewElement,
duplicateElement,
ELEMENT_TYPES,
TEXT_ELEMENT_DEFAULT_FONT,
DEFAULT_PAGE_BACKGROUND_COLOR,
elementIs,
getLayerName,
} from '@googleforcreators/elements';
import type { Page, Element } from '@googleforcreators/elements';
// Create a new story page
const page: Page = createPage({
backgroundColor: DEFAULT_PAGE_BACKGROUND_COLOR,
});
// Add a text element
const headline = createNewElement(ELEMENT_TYPES.TEXT, {
x: 50,
y: 100,
width: 540,
height: 100,
content: '<span style="font-weight: 700">My Story Headline</span>',
font: TEXT_ELEMENT_DEFAULT_FONT,
fontSize: 48,
backgroundColor: { color: { r: 0, g: 0, b: 0, a: 0.5 } },
});
// Add an image element
const backgroundImage = createNewElement(ELEMENT_TYPES.IMAGE, {
x: 0,
y: 0,
width: 640,
height: 853,
resource: {
type: 'image',
mimeType: 'image/jpeg',
src: 'https://example.com/background.jpg',
width: 1920,
height: 1080,
alt: 'Background Image',
},
});
// Add elements to page
page.elements = [backgroundImage, headline];
// Duplicate an element
const duplicatedHeadline = duplicateElement(headline);
duplicatedHeadline.y = 250;
duplicatedHeadline.content = '<span style="font-weight: 400">Subheadline</span>';
page.elements.push(duplicatedHeadline);
// Work with elements
page.elements.forEach((element: Element) => {
const layerName = getLayerName(element);
console.log(`Layer: ${layerName}`);
if (elementIs.image(element)) {
console.log(`Image source: ${element.resource.src}`);
}
if (elementIs.text(element)) {
console.log(`Text content: ${element.content}`);
console.log(`Font: ${element.font.family}`);
}
});
// Create a complete story with multiple pages
const story = {
storyId: 1,
title: 'My Web Story',
pages: [
page,
duplicatePage(page), // Duplicate the entire page
],
};
console.log(`Created story with ${story.pages.length} pages`);