Loot Table Modification

onEvent('block.loot_tables', event => {
  event.addSimpleBlock('minecraft:dirt', 'minecraft:red_sand')
})
onEvent('block.loot_tables', event => {
  event.addSimpleBlock('minecraft:dirt') // To drop itself (fix broken blocks)
  event.addSimpleBlock(/minecraft:.*_ore/, 'minecraft:red_sand') // To drop a different item
})
onEvent('block.loot_tables', event => {
  event.addBlock('minecraft:dirt', table => { // Build loot table manually
    table.addPool(pool => {
      pool.rolls = 1 // fixed
      // pool.rolls = [4, 6] // or {min: 4, max: 6} // uniform
      // pool.rolls = {n: 4, p: 0.3} // binominal
      pool.survivesExplosion()
      pool.addItem('minecraft:dirt')
      pool.addItem('minecraft:dirt', 40) // 40 = weight
      pool.addItem('minecraft:dirt', 40, [4, 8]) // [4-8] = count modifier, uses same syntax as rolls
      // pool.addCondition({json condition, see vanilla wiki})
      // pool.addEntry({json entry, see vanilla wiki for non-items})
    })
  })
})

Example from Factorial: (adds 1-3 leaves dropped from all Leaves blocks, 4-8 logs from all log and wood blocks and 4-8 stone from Stone, Cobblestone, Andesite, Diorite and Granite)

onEvent('block.loot_tables', event => {
	event.addBlock(/minecraft:.*_leaves/, table => {
		table.addPool(pool => {
			pool.survivesExplosion()
			pool.addItem('factorial:leaf', 1, [1, 3])
		})
	})

	event.addBlock(/minecraft:.*_(log|wood)/, table => {
		table.addPool(pool => {
			pool.survivesExplosion()
			pool.addItem('factorial:wood', 1, [4, 8])
		})
	})

	event.addBlock([
		'minecraft:stone',
		'minecraft:cobblestone',
		'minecraft:andesite',
		'minecraft:diorite',
		'minecraft:granite'
	], table => {
		table.addPool(pool => {
			pool.rolls = [4, 8] // Roll the pool instead of individual items
			pool.survivesExplosion()
			pool.addItem('factorial:stone', 1)
		})
	})
})

You can also modify existing loot tables to add items to them:

onEvent('block.loot_tables', event => {
  // all dirt blocks have a 50% chance to drop an enchanted diamond sword named "test"
  event.modifyBlock(/^minecraft:.*dirt/, table => {
    table.addPool(pool => {
      pool.addItem('minecraft:diamond_sword').randomChance(0.5).enchantWithLevels(1, true).name(Text.of('Test').blue())
    })
  })
})

Other loot table types work too:

onEvent('entity.loot_tables', event => {
  // Override zombie loot table that will drop 5 of either carrot (25% chance) or apple (75% chance)
  event.addEntity('minecraft:zombie', table => {
    table.addPool(pool => {
      pool.rolls = 5
      pool.addItem('minecraft:carrot', 1)
      pool.addItem('minecraft:apple', 3)
    })
  })
  
  event.modifyEntity('minecraft:pig', table => {
    table.addPool(pool => {
      // Modify pig loot table to *also* drop dirt on top of its regular drops
      pool.addItem('minecraft:dirt')
    })
  })
})

Supported table types:

Event ID Override method name Modify method name
generic.loot_tables addGeneric modify
block.loot_tables addBlock modifyBlock
entity.loot_tables addEntity modifyEntity
gift.loot_tables addGift modify
fishing.loot_tables addFishing modify
chest.loot_tables addChest modify