/

Enhanced Notion Blog Creator with Auto Database Setup

Copy script

Copied

Copy script

Copied

Enhanced Notion Blog Creator with Auto Database Setup

Create professional blog posts in Notion with zero setup required! This enhanced script automatically finds or creates a complete blog database with all necessary properties, then creates rich, formatted blog posts with cover images, SEO optimization, and markdown support. Perfect for content creators, marketers, and bloggers who want to streamline their Notion workflow without manual database configuration.

Created by

andrew van beek - Keyboard Team

Requirements

notion.com logo

KEYBOARD_NOTION_BLOG_INTERNAL_SECRET

Script

Copy script

Copied

Copy script

Copied

const axios = require('axios');

// Configuration
const NOTION_API_URL = 'https://api.notion.com/v1';
const NOTION_TOKEN = process.env.KEYBOARD_NOTION_BLOG_INTERNAL_SECRET;

console.log('šŸš€ Enhanced Notion Blog Creator - Auto Database Setup + Blog Post Creation');
console.log('='.repeat(70));

// High-quality cover images
const randomCovers = [
  'https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d?w=1500&q=80', // Laptop on desk
  'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=1500&q=80', // Code on screen
  'https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=1500&q=80', // Programming setup
  'https://images.unsplash.com/photo-1498050108023-c5249f4df085?w=1500&q=80', // Computer and coffee
  'https://images.unsplash.com/photo-1504639725590-34d0984388bd?w=1500&q=80', // Minimal workspace
  'https://images.unsplash.com/photo-1611224923853-80b023f02d71?w=1500&q=80', // Notion-style workspace
  'https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=1500&q=80', // Technology abstract
  'https://images.unsplash.com/photo-1517077304055-6e89abbf09b0?w=1500&q=80', // Modern office
  'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=1500&q=80', // Creative workspace
  'https://images.unsplash.com/photo-1434030216411-0b793f4b4173?w=1500&q=80', // Writing and planning
  'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=1500&q=80', // Data visualization
  'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=1500&q=80', // Analytics dashboard
  'https://images.unsplash.com/photo-1555421689-491a97ff2040?w=1500&q=80', // Mobile and laptop
  'https://images.unsplash.com/photo-1581291518857-4e27b48ff24e?w=1500&q=80', // Clean desk setup
  'https://images.unsplash.com/photo-1531297484001-80022131f5a1?w=1500&q=80'  // Tech workspace
];

// Function to get root page ID for database creation
async function getRootPageId() {
  try {
    console.log('šŸ” Finding accessible workspace page for database creation...');
    
    const searchResponse = await axios({
      method: 'POST',
      url: `${NOTION_API_URL}/search`,
      headers: {
        'Authorization': `Bearer ${NOTION_TOKEN}`,
        'Notion-Version': '2022-06-28',
        'Content-Type': 'application/json'
      },
      data: {
        filter: {
          value: 'page',
          property: 'object'
        },
        page_size: 1
      }
    });

    if (searchResponse.data.results.length > 0) {
      const rootPage = searchResponse.data.results[0];
      console.log(`āœ… Found root page for database creation`);
      return rootPage.id;
    }

    throw new Error('No accessible pages found. Please ensure your Notion integration has access to at least one page in your workspace.');

  } catch (error) {
    console.error('āŒ Error getting root page:', error.response?.data || error.message);
    throw error;
  }
}

// Function to create a complete blog database with all properties
async function createBlogDatabase(parentPageId) {
  try {
    console.log('šŸ“ Creating new Blog Posts database with complete schema...');
    
    const databaseData = {
      parent: {
        type: 'page_id',
        page_id: parentPageId
      },
      title: [
        {
          type: 'text',
          text: {
            content: 'Blog Posts'
          }
        }
      ],
      properties: {
        'Title': {
          title: {}
        },
        'Status': {
          status: {
            options: [
              { name: 'Draft', color: 'gray' },
              { name: 'In Review', color: 'yellow' },
              { name: 'Published', color: 'green' }
            ]
          }
        },
        'Category': {
          select: {
            options: [
              { name: 'Technology', color: 'blue' },
              { name: 'Health & Wellness', color: 'green' },
              { name: 'Business', color: 'orange' },
              { name: 'Lifestyle', color: 'purple' },
              { name: 'Travel', color: 'red' },
              { name: 'Personal Development', color: 'yellow' },
              { name: 'Food & Recipes', color: 'brown' },
              { name: 'Education', color: 'pink' }
            ]
          }
        },
        'Tags': {
          multi_select: {
            options: [
              { name: 'Technology', color: 'blue' },
              { name: 'Personal', color: 'green' },
              { name: 'Tutorial', color: 'orange' },
              { name: 'News', color: 'red' },
              { name: 'Opinion', color: 'purple' },
              { name: 'Review', color: 'yellow' },
              { name: 'Travel', color: 'pink' },
              { name: 'Health', color: 'gray' }
            ]
          }
        },
        'Publish Date': {
          date: {}
        },
        'Author': {
          people: {}
        },
        'Framer Slug': {
          rich_text: {}
        },
        'SEO Description': {
          rich_text: {}
        },
        'Content': {
          rich_text: {}
        },
        'Featured Image': {
          url: {}
        }
      }
    };

    const response = await axios({
      method: 'POST',
      url: `${NOTION_API_URL}/databases`,
      headers: {
        'Authorization': `Bearer ${NOTION_TOKEN}`,
        'Notion-Version': '2022-06-28',
        'Content-Type': 'application/json'
      },
      data: databaseData
    });

    console.log(`āœ… Created new blog database: ${response.data.title[0].plain_text} (ID: ${response.data.id})`);
    return response.data;

  } catch (error) {
    console.error('āŒ Error creating blog database:', error.response?.data || error.message);
    throw error;
  }
}

// Function to find existing blog database or create new one
async function findOrCreateBlogDatabase() {
  try {
    console.log('šŸ” Looking for existing blog database...');
    
    const response = await axios({
      method: 'POST',
      url: `${NOTION_API_URL}/search`,
      headers: {
        'Authorization': `Bearer ${NOTION_TOKEN}`,
        'Notion-Version': '2022-06-28',
        'Content-Type': 'application/json'
      },
      data: {
        filter: {
          value: 'database',
          property: 'object'
        }
      }
    });

    // Find the first database with "Blog" in the title
    const blogDb = response.data.results.find(db => 
      db.title?.[0]?.plain_text?.toLowerCase().includes('blog')
    );

    if (blogDb) {
      console.log(`āœ… Found existing blog database: ${blogDb.title[0].plain_text} (ID: ${blogDb.id})`);
      return blogDb;
    } else {
      console.log('šŸ“ No blog database found, creating new one...');
      const rootPageId = await getRootPageId();
      return await createBlogDatabase(rootPageId);
    }

  } catch (error) {
    console.error('āŒ Error finding/creating blog database:', error.response?.data || error.message);
    throw error;
  }
}

// Enhanced function to parse markdown content into Notion blocks
function parseMarkdownToNotionBlocks(content) {
  const blocks = [];
  const lines = content.split('\n');
  
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    
    // Handle headers (H1-H3)
    const headerMatch = line.match(/^(#{1,3})\s+(.+)$/);
    if (headerMatch) {
      const level = headerMatch[1].length;
      const headerText = headerMatch[2];
      const headerType = level === 1 ? 'heading_1' : level === 2 ? 'heading_2' : 'heading_3';
      
      blocks.push({
        object: 'block',
        type: headerType,
        [headerType]: {
          rich_text: [{ text: { content: headerText } }]
        }
      });
      continue;
    }
    
    // Handle horizontal rules
    if (line.match(/^(-{3,}|\*{3,}|_{3,})$/)) {
      blocks.push({
        object: 'block',
        type: 'divider',
        divider: {}
      });
      continue;
    }
    
    // Handle blockquotes
    if (line.startsWith('> ')) {
      const quoteText = line.substring(2);
      blocks.push({
        object: 'block',
        type: 'quote',
        quote: {
          rich_text: [{ text: { content: quoteText } }]
        }
      });
      continue;
    }
    
    // Handle bulleted lists
    if (line.match(/^[\*\-\+]\s+/)) {
      const listItemText = line.replace(/^[\*\-\+]\s+/, '');
      blocks.push({
        object: 'block',
        type: 'bulleted_list_item',
        bulleted_list_item: {
          rich_text: [{ text: { content: listItemText } }]
        }
      });
      continue;
    }
    
    // Handle numbered lists  
    if (line.match(/^\d+\.\s+/)) {
      const listItemText = line.replace(/^\d+\.\s+/, '');
      blocks.push({
        object: 'block',
        type: 'numbered_list_item',
        numbered_list_item: {
          rich_text: [{ text: { content: listItemText } }]
        }
      });
      continue;
    }
    
    // Handle regular paragraphs
    if (line.trim()) {
      blocks.push({
        object: 'block',
        type: 'paragraph',
        paragraph: {
          rich_text: [{ text: { content: line } }]
        }
      });
    }
  }
  
  return blocks;
}

// Function to create blog post with enhanced content
async function createBlogPost(databaseId, blogData) {
  try {
    console.log('✨ Creating blog post with enhanced content...');
    
    // Select random cover image if requested
    const coverImage = blogData.useRandomCover 
      ? randomCovers[Math.floor(Math.random() * randomCovers.length)]
      : null;

    if (coverImage) {
      console.log('šŸ–¼ļø Selected random cover image');
    }

    // Generate SEO description if not provided
    const seoDescription = blogData.seoDescription || 
      `Discover insights about ${blogData.title.toLowerCase()}. ${blogData.content.substring(0, 100)}...`;

    // Generate slug from title
    const slug = blogData.title.toLowerCase()
      .replace(/[^a-z0-9\s-]/g, '')
      .replace(/\s+/g, '-')
      .replace(/-+/g, '-')
      .trim('-');

    // Parse content into rich Notion blocks
    const contentBlocks = parseMarkdownToNotionBlocks(blogData.content);
    console.log(`šŸ“„ Parsed content into ${contentBlocks.length} Notion blocks`);

    // Build page children with rich content
    const pageChildren = [
      {
        object: 'block',
        type: 'callout',
        callout: {
          rich_text: [{
            text: { content: `šŸš€ ${blogData.content.substring(0, 150)}...` },
            annotations: { bold: true }
          }],
          icon: { emoji: '✨' }
        }
      },
      {
        object: 'block',
        type: 'divider',
        divider: {}
      }
    ];

    // Add parsed content blocks (limit to 90 to stay under 100 block limit)
    const maxContentBlocks = Math.min(90, contentBlocks.length);
    pageChildren.push(...contentBlocks.slice(0, maxContentBlocks));

    if (contentBlocks.length > maxContentBlocks) {
      console.log(`āš ļø Content limited to ${maxContentBlocks} blocks due to Notion's limits`);
    }

    // Add footer
    pageChildren.push(
      {
        object: 'block',
        type: 'divider',
        divider: {}
      },
      {
        object: 'block',
        type: 'callout',
        callout: {
          rich_text: [{
            text: { content: 'šŸŽ‰ Created with Enhanced Notion Blog Creator - Auto Database Setup!' }
          }],
          icon: { emoji: 'šŸ¤–' }
        }
      }
    );

    // Create the page data
    const pageData = {
      parent: {
        database_id: databaseId
      },
      properties: {
        'Title': {
          title: [{ text: { content: blogData.title } }]
        },
        'Status': {
          status: { name: blogData.status }
        },
        'Category': {
          select: { name: blogData.category }
        },
        'Tags': {
          multi_select: Array.isArray(blogData.tags) 
            ? blogData.tags.map(tag => ({ name: tag.trim() }))
            : blogData.tags.split(',').map(tag => ({ name: tag.trim() }))
        },
        'Publish Date': {
          date: { start: new Date().toISOString().split('T')[0] }
        },
        'Framer Slug': {
          rich_text: [{ text: { content: slug } }]
        },
        'SEO Description': {
          rich_text: [{ text: { content: seoDescription } }]
        },
        'Content': {
          rich_text: [{ text: { content: blogData.content } }]
        }
      },
      children: pageChildren
    };

    // Add cover image if requested
    if (coverImage) {
      pageData.cover = {
        type: 'external',
        external: {
          url: coverImage
        }
      };
    }

    const response = await axios({
      method: 'POST',
      url: `${NOTION_API_URL}/pages`,
      headers: {
        'Authorization': `Bearer ${NOTION_TOKEN}`,
        'Notion-Version': '2022-06-28',
        'Content-Type': 'application/json'
      },
      data: pageData
    });

    return response.data;

  } catch (error) {
    console.error('āŒ Error creating blog post:', error.response?.data || error.message);
    throw error;
  }
}

// Main execution
async function main() {
  try {
    // Parse input data
    const blogData = {
      title: '{{title}}',
      status: '{{status}}',
      category: '{{category}}',
      tags: {{tags}},
      author: '{{author}}',
      content: '{{content}}',
      seoDescription: '{{seoDescription}}' || null,
      useRandomCover: {{useRandomCover}}
    };

    console.log(`šŸ“ Creating blog post: "${blogData.title}"`);
    console.log(`šŸ“Š Content: ${blogData.content.length} characters`);
    console.log(`šŸ·ļø Category: ${blogData.category} | Status: ${blogData.status}`);

    // Find existing blog database or create new one automatically
    const blogDb = await findOrCreateBlogDatabase();

    // Create the blog post with enhanced features
    const newPost = await createBlogPost(blogDb.id, blogData);

    // Verify creation by fetching the post
    const verifyResponse = await axios({
      method: 'GET',
      url: `${NOTION_API_URL}/pages/${newPost.id}`,
      headers: {
        'Authorization': `Bearer ${NOTION_TOKEN}`,
        'Notion-Version': '2022-06-28'
      }
    });

    console.log('\nāœ… SUCCESS! Enhanced blog post created!');
    console.log('šŸ“‹ Results:');
    console.log(`   šŸ“ Title: ${verifyResponse.data.properties.Title?.title?.[0]?.text?.content}`);
    console.log(`   šŸ“Š Status: ${verifyResponse.data.properties.Status?.status?.name}`);
    console.log(`   šŸ·ļø Category: ${verifyResponse.data.properties.Category?.select?.name}`);
    console.log(`   šŸ·ļø Tags: ${verifyResponse.data.properties.Tags?.multi_select?.map(tag => tag.name).join(', ')}`);
    console.log(`   šŸ–¼ļø Cover: ${verifyResponse.data.cover ? 'Professional cover image applied' : 'No cover'}`);
    console.log(`   šŸ”— Page URL: ${verifyResponse.data.url}`);
    console.log(`   šŸ“„ Page ID: ${newPost.id}`);
    console.log(`   šŸ—„ļø Database ID: ${blogDb.id}`);

    console.log('\nšŸŽ‰ Enhanced Features Applied:');
    console.log('   āœ… Automatic database detection/creation');
    console.log('   āœ… Complete database schema with all properties'); 
    console.log('   āœ… Markdown content parsing to rich blocks');
    console.log('   āœ… Professional cover image selection');
    console.log('   āœ… SEO optimization (slug, description)');
    console.log('   āœ… Rich content formatting and structure');
    console.log('   āœ… Full verification workflow');

  } catch (error) {
    console.error('āŒ Enhanced script failed:', error.message);
  }
}

// Execute the enhanced script
main();